updated to add persistence to provisioning and roll that through the rest of the services
Change-Id: Ia0d5a49dc0d88dbe6780c76483fd2247ad631bdf
diff --git a/automation/state.go b/automation/state.go
index 5890b31..7fe1760 100644
--- a/automation/state.go
+++ b/automation/state.go
@@ -1,11 +1,9 @@
package main
import (
- "bytes"
"encoding/json"
"fmt"
"log"
- "net/http"
"net/url"
"os/exec"
"regexp"
@@ -49,7 +47,7 @@
Verbose bool
Preview bool
AlwaysRename bool
- ProvTracker Tracker
+ Provisioner Provisioner
ProvisionURL string
ProvisionTTL time.Duration
PowerHelper string
@@ -153,7 +151,7 @@
updateNodeName(client, node, options)
}
- options.ProvTracker.Clear(node.ID())
+ options.Provisioner.Clear(node.ID())
return nil
}
@@ -168,186 +166,52 @@
updateNodeName(client, node, options)
}
- record, err := options.ProvTracker.Get(node.ID())
+ record, err := options.Provisioner.Get(node.ID())
if err != nil {
log.Printf("[warn] unable to retrieve provisioning state of node '%s' : %s", node.Hostname(), err)
- } else if record.State == Unprovisioned || record.State == ProvisionError {
+ } else if record == nil || record.Status == Failed {
+ var label string
+ if record == nil {
+ label = "NotFound"
+ } else {
+ label = record.Status.String()
+ }
if options.Verbose {
- log.Printf("[info] Current state of node '%s' is '%s'", node.Hostname(), record.State.String())
+ log.Printf("[info] Current state of node '%s' is '%s'", node.Hostname(), label)
}
- var err error = nil
- var callout *url.URL
- log.Printf("PROVISION '%s'", node.Hostname())
- if len(options.ProvisionURL) > 0 {
- if options.Verbose {
- log.Printf("[info] Provisioning callout to '%s'", options.ProvisionURL)
- }
- callout, err = url.Parse(options.ProvisionURL)
- if err != nil {
- log.Printf("[error] Failed to parse provisioning URL '%s' : %s", options.ProvisionURL, err)
- } else {
- ips := node.IPs()
- ip := ""
- if len(ips) > 0 {
- ip = ips[0]
- }
- macs := node.MACs()
- mac := ""
- if len(macs) > 0 {
- mac = macs[0]
- }
- switch callout.Scheme {
- // If the scheme is a file, then we will execute the refereced file
- case "", "file":
- if options.Verbose {
- log.Printf("[info] executing local script file '%s'", callout.Path)
- }
- record.State = Provisioning
- record.Timestamp = time.Now().Unix()
- options.ProvTracker.Set(node.ID(), record)
- err = exec.Command(callout.Path, node.ID(), node.Hostname(), ip, mac).Run()
- if err != nil {
- log.Printf("[error] Failed to execute '%s' : %s", options.ProvisionURL, err)
- } else {
- if options.Verbose {
- log.Printf("[info] Marking node '%s' with ID '%s' as provisioned",
- node.Hostname(), node.ID())
- }
- record.State = Provisioned
- options.ProvTracker.Set(node.ID(), record)
- }
-
- default:
- if options.Verbose {
- log.Printf("[info] POSTing to '%s'", options.ProvisionURL)
- }
- data := map[string]string{
- "id": node.ID(),
- "name": node.Hostname(),
- "ip": ip,
- "mac": mac,
- }
- hc := http.Client{}
- var b []byte
- b, err = json.Marshal(data)
- if err != nil {
- log.Printf("[error] Unable to marshal node data : %s", err)
- } else {
- var req *http.Request
- var resp *http.Response
- if options.Verbose {
- log.Printf("[debug] POSTing data '%s'", string(b))
- }
- req, err = http.NewRequest("POST", options.ProvisionURL, bytes.NewReader(b))
- if err != nil {
- log.Printf("[error] Unable to construct POST request to provisioner : %s",
- err)
- } else {
- req.Header.Add("Content-Type", "application/json")
- resp, err = hc.Do(req)
- if err != nil {
- log.Printf("[error] Unable to process POST request : %s",
- err)
- } else {
- defer resp.Body.Close()
- if resp.StatusCode == http.StatusAccepted {
- record.State = Provisioning
- } else {
- record.State = ProvisionError
- }
- record.Timestamp = time.Now().Unix()
- options.ProvTracker.Set(node.ID(), record)
- }
- }
- }
- }
- }
+ ips := node.IPs()
+ ip := ""
+ if len(ips) > 0 {
+ ip = ips[0]
}
+ macs := node.MACs()
+ mac := ""
+ if len(macs) > 0 {
+ mac = macs[0]
+ }
+ if options.Verbose {
+ log.Printf("[info] POSTing '%s' (%s) to '%s'", node.Hostname(),
+ node.ID(), options.ProvisionURL)
+ }
+ err = options.Provisioner.Provision(&ProvisionRequest{
+ Id: node.ID(),
+ Name: node.Hostname(),
+ Ip: ip,
+ Mac: mac,
+ })
if err != nil {
- if options.Verbose {
- log.Printf("[warn] Not marking node '%s' with ID '%s' as provisioned, because of error '%s'",
- node.Hostname(), node.ID(), err)
- record.State = ProvisionError
- options.ProvTracker.Set(node.ID(), record)
- }
+ log.Printf("[error] unable to provision '%s' (%s) : %s", node.ID, node.Hostname(), err)
}
- } else if record.State == Provisioning && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
+
+ } else if options.ProvisionTTL > 0 &&
+ record.Status == Running && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
log.Printf("[error] Provisioning of node '%s' has passed provisioning TTL of '%v'",
node.Hostname(), options.ProvisionTTL)
- record.State = ProvisionError
- options.ProvTracker.Set(node.ID(), record)
- } else if record.State == Provisioning {
- callout, err := url.Parse(options.ProvisionURL)
- if err != nil {
- log.Printf("[error] Unable to parse provisioning URL '%s' : %s", options.ProvisionURL, err)
- } else if callout.Scheme != "file" {
- var req *http.Request
- var resp *http.Response
- if options.Verbose {
- log.Printf("[info] Fetching provisioning state for node '%s'", node.Hostname())
- }
- req, err = http.NewRequest("GET", options.ProvisionURL+"/"+node.ID(), nil)
- if err != nil {
- log.Printf("[error] Unable to construct GET request to provisioner : %s", err)
- } else {
- hc := http.Client{}
- resp, err = hc.Do(req)
- if err != nil {
- log.Printf("[error] Failed to quest provision state for node '%s' : %s",
- node.Hostname(), err)
- } else {
- defer resp.Body.Close()
- if options.Verbose {
- log.Printf("[debug] Got status '%s' for node '%s'", resp.Status, node.Hostname())
- }
- switch resp.StatusCode {
- case http.StatusOK: // provisioning completed or failed
- decoder := json.NewDecoder(resp.Body)
- var raw interface{}
- err = decoder.Decode(&raw)
- if err != nil {
- log.Printf("[error] Unable to unmarshal response from provisioner for '%s': %s",
- node.Hostname(), err)
- }
- status := raw.(map[string]interface{})
- switch int(status["status"].(float64)) {
- case 0, 1: // PENDING, RUNNING ... should never really get here
- // noop, already in this state
- case 2: // COMPLETE
- if options.Verbose {
- log.Printf("[info] Marking node '%s' with ID '%s' as provisioned",
- node.Hostname(), node.ID())
- }
- record.State = Provisioned
- options.ProvTracker.Set(node.ID(), record)
- case 3: // FAILED
- if options.Verbose {
- log.Printf("[info] Marking node '%s' with ID '%s' as failed provisioning",
- node.Hostname(), node.ID())
- }
- record.State = ProvisionError
- options.ProvTracker.Set(node.ID(), record)
- default:
- log.Printf("[error] unknown status state for node '%s' : %d",
- node.Hostname(), int(status["status"].(float64)))
- }
- case http.StatusAccepted: // in the provisioning state
- // Noop, presumably alread in this state
- case http.StatusNotFound:
- // Noop, but not an error
- default: // Consider anything else an erorr
- log.Printf("[warn] Node '%s' with ID '%s' failed provisioning, will retry",
- node.Hostname(), node.ID())
- record.State = ProvisionError
- options.ProvTracker.Set(node.ID(), record)
- }
- }
- }
- }
+ options.Provisioner.Clear(node.ID())
} else if options.Verbose {
log.Printf("[info] Not invoking provisioning for '%s', current state is '%s'", node.Hostname(),
- record.State.String())
+ record.Status.String())
}
return nil