update to support power management discovery for virtual box environment
diff --git a/automation/Dockerfile b/automation/Dockerfile
index d1b6318..ad78874 100644
--- a/automation/Dockerfile
+++ b/automation/Dockerfile
@@ -1,6 +1,6 @@
FROM golang:alpine
-RUN apk --update add git
+RUN apk --update add openssh-client git
WORKDIR /go
RUN go get github.com/tools/godep
@@ -13,4 +13,7 @@
RUN go install github.com/ciena/cord-maas-automation
+RUN mkdir -p /root/.ssh
+COPY ssh-config /root/.ssh/config
+
ENTRYPOINT ["/go/bin/cord-maas-automation"]
diff --git a/automation/Godeps/Godeps.json b/automation/Godeps/Godeps.json
index cd9aa7e..a267c3c 100644
--- a/automation/Godeps/Godeps.json
+++ b/automation/Godeps/Godeps.json
@@ -24,6 +24,11 @@
"ImportPath": "github.com/hashicorp/consul",
"Comment": "v0.6.4-341-g22938ab",
"Rev": "22938ab8b3ca069e490a4897a8ec13308ac61605"
- }
+ },
+ {
+ "ImportPath": "github.com/kelseyhightower/envconfig",
+ "Comment": "1.1.0-17-g91921eb",
+ "Rev": "91921eb4cf999321cdbeebdba5a03555800d493b"
+ }
]
}
diff --git a/automation/maas-flow.go b/automation/maas-flow.go
index 1f501b9..84337aa 100644
--- a/automation/maas-flow.go
+++ b/automation/maas-flow.go
@@ -3,6 +3,7 @@
import (
"encoding/json"
"flag"
+ "github.com/kelseyhightower/envconfig"
"log"
"net/url"
"os"
@@ -26,11 +27,16 @@
}
}`
defaultMapping = "{}"
- PROVISION_URL = "PROVISION_URL"
- PROVISION_TTL = "PROVISION_TTL"
- DEFAULT_TTL = "30m"
)
+type Config struct {
+ PowerHelperUser string `default:"cord" envconfig:"POWER_HELPER_USER"`
+ PowerHelperHost string `default:"127.0.0.1" envconfig:"POWER_HELPER_HOST"`
+ PowerHelperScript string `default:"" envconfig:"POWER_HELPER_SCRIPT"`
+ ProvisionUrl string `default:"" envconfig:"PROVISION_URL"`
+ ProvisionTtl string `default:"30m" envconfig:"PROVISION_TTL"`
+}
+
var apiKey = flag.String("apikey", "", "key with which to access MAAS server")
var maasURL = flag.String("maas", "http://localhost/MAAS", "url over which to access MAAS")
var apiVersion = flag.String("apiVersion", "1.0", "version of the API to access")
@@ -90,28 +96,23 @@
func main() {
flag.Parse()
+ config := Config{}
+ err := envconfig.Process("AUTOMATION", &config)
+ checkError(err, "[error] unable to parse environment options : %s", err)
options := ProcessingOptions{
- Preview: *preview,
- Verbose: *verbose,
- AlwaysRename: *always,
- ProvTracker: NewTracker(),
- ProvisionURL: os.Getenv(PROVISION_URL),
+ Preview: *preview,
+ Verbose: *verbose,
+ AlwaysRename: *always,
+ ProvTracker: NewTracker(),
+ ProvisionURL: config.ProvisionUrl,
+ PowerHelper: config.PowerHelperScript,
+ PowerHelperUser: config.PowerHelperUser,
+ PowerHelperHost: config.PowerHelperHost,
}
- var ttl string
- if ttl = os.Getenv(PROVISION_TTL); ttl == "" {
- ttl = "30m"
- }
-
- var err error
- options.ProvisionTTL, err = time.ParseDuration(ttl)
- if err != nil {
- log.Printf("[warn] unable to parse specified duration of '%s', defaulting to '%s'",
- ttl, DEFAULT_TTL)
- options.ProvisionTTL, err = time.ParseDuration("30m")
- checkError(err, "[error] unable to parse default TTL duration of '30m' : %s", err)
- }
+ options.ProvisionTTL, err = time.ParseDuration(config.ProvisionTtl)
+ checkError(err, "[error] unable to parse specified duration of '%s' : %s", err)
// Determine the filter, this can either be specified on the the command
// line as a value or a file reference. If none is specified the default
@@ -161,14 +162,19 @@
MAAS URL: %s
MAAS API Version: %s
MAAS Query Interval: %s
- Node Filtter: %s
+ Node Filter: %s
Node Name Mappings: %s
Preview: %v
Verbose: %v
Always Rename: %v
- Provision URL: %s `,
+ Provision URL: %s
+ Provision TTL: %s
+ Power Helper: %s
+ Power Helper User: %s
+ Power Helper Host: %s`,
*maasURL, *apiVersion, *queryPeriod, *filterSpec, *mappings, options.Preview,
- options.Verbose, options.AlwaysRename, options.ProvisionURL)
+ options.Verbose, options.AlwaysRename, options.ProvisionURL, options.ProvisionTTL,
+ options.PowerHelper, options.PowerHelperUser, options.PowerHelperHost)
authClient, err := maas.NewAuthenticatedClient(*maasURL, *apiKey, *apiVersion)
if err != nil {
diff --git a/automation/node.go b/automation/node.go
index fe61add..a414820 100644
--- a/automation/node.go
+++ b/automation/node.go
@@ -2,8 +2,10 @@
import (
"fmt"
+ "log"
maas "github.com/juju/gomaasapi"
+ "net/url"
)
// MaasNodeStatus MAAS lifecycle status for nodes
@@ -69,11 +71,28 @@
return id
}
+func (n *MaasNode) PowerType() string {
+ ptype, _ := n.GetString("power_type")
+ return ptype
+}
+
func (n *MaasNode) PowerState() string {
state, _ := n.GetString("power_state")
return state
}
+func (n *MaasNode) UpdatePowerParameters(ptype string, params map[string]string) {
+ values := url.Values{}
+ values.Add("power_type", ptype)
+ for k, v := range params {
+ values.Add("power_parameters_"+k, v)
+ }
+ _, err := n.Update(values)
+ if err != nil {
+ log.Printf("[error] error updating power settings : %s", err.Error())
+ }
+}
+
// Hostname get the hostname
func (n *MaasNode) Hostname() string {
hn, _ := n.GetString("hostname")
diff --git a/automation/ssh-config b/automation/ssh-config
new file mode 100644
index 0000000..990a43d
--- /dev/null
+++ b/automation/ssh-config
@@ -0,0 +1,3 @@
+Host *
+ StrictHostKeyChecking no
+ UserKnownHostsFile=/dev/null
diff --git a/automation/state.go b/automation/state.go
index 4e0dd1f..04626ed 100644
--- a/automation/state.go
+++ b/automation/state.go
@@ -26,6 +26,13 @@
Using Action
}
+type Power struct {
+ Name string `json:"name"`
+ MacAddress string `json:"mac_address"`
+ PowerPassword string `json:"power_password"`
+ PowerAddress string `json:"power_address"`
+}
+
// ProcessingOptions used to determine on what hosts to operate
type ProcessingOptions struct {
Filter struct {
@@ -38,13 +45,16 @@
Exclude []string
}
}
- Mappings map[string]interface{}
- Verbose bool
- Preview bool
- AlwaysRename bool
- ProvTracker Tracker
- ProvisionURL string
- ProvisionTTL time.Duration
+ Mappings map[string]interface{}
+ Verbose bool
+ Preview bool
+ AlwaysRename bool
+ ProvTracker Tracker
+ ProvisionURL string
+ ProvisionTTL time.Duration
+ PowerHelper string
+ PowerHelperUser string
+ PowerHelperHost string
}
// Transitions the actual map
@@ -496,7 +506,40 @@
break
default:
// We are in a state from which we can't move forward.
- log.Printf("ERROR: %s has invalid power state '%s'", node.Hostname(), state)
+ log.Printf("[warn]: %s has invalid power state '%s'", node.Hostname(), state)
+
+ // If a power helper script is set, we have an unknown power state, and
+ // we have not power type then attempt to use the helper script to discover
+ // and set the power settings
+ if options.PowerHelper != "" && node.PowerType() == "" {
+ cmd := exec.Command(options.PowerHelper,
+ append([]string{options.PowerHelperUser, options.PowerHelperHost},
+ node.MACs()...)...)
+ stdout, err := cmd.Output()
+ if err != nil {
+ log.Printf("[error] Failed while executing power helper script '%s' : %s",
+ options.PowerHelper, err)
+ return err
+ }
+ power := Power{}
+ err = json.Unmarshal(stdout, &power)
+ if err != nil {
+ log.Printf("[error] Failed to parse output of power helper script '%s' : %s",
+ options.PowerHelper, err)
+ return err
+ }
+ switch power.Name {
+ case "amt":
+ params := map[string]string{
+ "mac_address": power.MacAddress,
+ "power_pass": power.PowerPassword,
+ "power_address": power.PowerAddress,
+ }
+ node.UpdatePowerParameters(power.Name, params)
+ default:
+ log.Printf("[warn] Unsupported power type discovered '%s'", power.Name)
+ }
+ }
break
}
return nil
@@ -543,7 +586,8 @@
var err error
for _, action := range actions {
if err = action(client, node, options); err != nil {
- log.Printf("[error] Error while processing action for node '%s' : %s", node.Hostname, err)
+ log.Printf("[error] Error while processing action for node '%s' : %s",
+ node.Hostname(), err)
break
}
}