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
 		}
 	}
diff --git a/build.gradle b/build.gradle
index f5120d3..3f515a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,6 +28,8 @@
     deployConfig = project.hasProperty('deployConfig') ? project.getProperty('deployConfig') : './config/default.yml'
 
     dockerPath = project.hasProperty('dockerPath') ? project.getProperty('dockerPath') : '/usr/bin'
+
+    vboxUser = project.hasProperty('vboxUser') ? project.getProperty('vboxUser') : 'cord'
 }
 
 task buildBootstrapImage(type: Exec) {
@@ -219,6 +221,10 @@
 	    .p(config.seedServer.external_ip, "external_ip")
     }
 
+    if (vboxUser != "") {
+        extraVars = extraVars.p(vboxUser, "power_helper_user")
+    }
+
     if (config.otherServers) {
         extraVars = extraVars.p(config.otherServers.location, "prov_location")
         .p(config.otherServers.rolesPath, "prov_role_path")
diff --git a/roles/maas/files/amt.template b/roles/maas/files/amt.template
index 2f2df8c..22c0ccc 100755
--- a/roles/maas/files/amt.template
+++ b/roles/maas/files/amt.template
@@ -9,11 +9,11 @@
 
 get_uuid () {
     local DATA=$(echo -n "$1" | sed -e 's/://g')
-    echo $(ssh $POWER_PASS@$POWER_ADDRESS vboxmanage list vms | grep "$DATA" | awk '{print $NF}' | sed -e 's/[{}]//g')
+    echo $(ssh $POWER_PASS@$POWER_ADDRESS vboxmanage list vms 2> /tmp/power_err.last | grep "$DATA" | awk '{print $NF}' | sed -e 's/[{}]//g')
 }
 
 query_state () {
-    local state=$(ssh $POWER_PASS@$POWER_ADDRESS vboxmanage showvminfo $1 | grep "^State" | grep -i running | wc -l)
+    local state=$(ssh $POWER_PASS@$POWER_ADDRESS vboxmanage showvminfo $1 2> /tmp/power_err.last | grep "^State" | grep -i running | wc -l)
     if [ $state -eq 1 ]; then
         echo 'on'
     else
diff --git a/roles/maas/files/power_discovery b/roles/maas/files/power_discovery
new file mode 100755
index 0000000..b691010
--- /dev/null
+++ b/roles/maas/files/power_discovery
@@ -0,0 +1,62 @@
+#!/bin/ash
+
+USERNAME=$1; shift
+HOSTIP=$1; shift
+SSH_KEY="-i /etc/maas/virtualbox/id_rsa"
+
+LIST=$(echo $* | awk '{print toupper($0)}')
+MATCH=
+
+best() {
+	local i
+	local BEST=
+	local BEST_CNT=0
+	for i in $MATCH; do
+		local CNT=$(echo $i | cut -d- -f2)
+		if [ $CNT > $BEST_CNT ]; then
+			BEST=$(echo $i | cut -d- -f1)
+			BEST_CNT=$CNT
+		fi
+	done
+	echo $BEST
+}
+
+inc() {
+	FOUND=$(echo $MATCH | grep "$1" |  wc -l)
+	if [ $FOUND -eq 0 ]; then
+		MATCH="$MATCH $1-1"
+	else
+		NEXT=
+		local i=
+		for  i in $MATCH; do
+			FOUND=$(echo $i | grep "$1" | wc -l)
+			if [ $FOUND -eq 1 ]; then
+				COUNT=$(echo $i | cut -d- -f2)
+				COUNT=$(expr $COUNT + 1)
+				NEXT="$NEXT $1-$COUNT"
+			else
+				NEXT="$NEXT $i"
+			fi
+		done
+		MATCH=$NEXT
+	fi
+}
+
+for i in $(ssh $SSH_KEY $USERNAME@$HOSTIP /usr/local/bin/vboxmanage list vms | awk '{print $NF}' | sed -e 's/[{}]//g'); do
+	for m in $(ssh $SSH_KEY $USERNAME@$HOSTIP /usr/local/bin/vboxmanage showvminfo --machinereadable $i | grep -i macaddress | cut -d= -f2 | sed -e 's/"//g' -e 's/\(..\)/\1:/g' -e 's/:$//g'); do
+		p=$(echo $i | cut -d- -f5)
+		for t in $LIST; do
+			if [ "$t" == "$m" ]; then
+				inc $p
+			fi
+		done
+	done
+done
+
+BEST=$(best)
+if [ "$BEST x" == " x" ]; then
+	echo "{}"
+else
+	MAC=$(echo $BEST | cut -d- -f5 | sed -e 's/"//g' -e 's/\(..\)/\1:/g' -e 's/:$//g')
+        echo "{\"name\":\"amt\",\"mac_address\":\"$MAC\",\"power_password\":\"$USERNAME\",\"power_address\":\"$HOSTIP\"}"
+fi
diff --git a/roles/maas/files/ssh_config b/roles/maas/files/ssh_config
index f30d239..990a43d 100644
--- a/roles/maas/files/ssh_config
+++ b/roles/maas/files/ssh_config
@@ -1,2 +1,3 @@
 Host *
-    StrictHostKeyChecking no
+   StrictHostKeyChecking no
+   UserKnownHostsFile=/dev/null
diff --git a/roles/maas/tasks/main.yml b/roles/maas/tasks/main.yml
index 5e7e349..c78cc4e 100644
--- a/roles/maas/tasks/main.yml
+++ b/roles/maas/tasks/main.yml
@@ -292,6 +292,21 @@
   tags:
     - interface_config
 
+- name: Default VirtualBox Host
+  become: no
+  set_fact: 
+    virtualbox_host: "{{ virtualbox.power_helper_host }}"
+  when: virtualbox_support is defined
+  changed_when: false
+
+- name: Override VirtualBox Host
+  become: no
+  set_fact:
+    virtualbox_host: "{{ discovered_vbox_host.stdout }}"
+  when: virtualbox_support is defined and virtualbox_host == ''
+  changed_when: false
+  
+
 - name: Custom Automation Compose Configurations
   become: yes
   template:
diff --git a/roles/maas/tasks/virtualbox.yml b/roles/maas/tasks/virtualbox.yml
index b263886..88dcba7 100644
--- a/roles/maas/tasks/virtualbox.yml
+++ b/roles/maas/tasks/virtualbox.yml
@@ -14,7 +14,34 @@
     group: maas
     mode: 0755
 
-- name: Ensure SSH Directory
+- name: Ensure SSH Discovery Directory
+  become: yes
+  file:
+    path: /etc/maas/virtualbox
+    state: directory
+    owner: maas
+    group: maas
+    mode: 0755
+
+- name: VirtualBox Power Discovery Support
+  become: yes
+  copy:
+    src: files/cord_id_rsa
+    dest: /etc/maas/virtualbox/id_rsa
+    owner: root
+    group: root
+    mode: 0600
+
+- name: VirtualBox Power Discovery Script
+  become: yes
+  copy:
+    src: files/power_discovery
+    dest: /etc/maas/virtualbox/power_discovery
+    owner: maas
+    group: maas
+    mode: 0755
+
+- name: Ensure SSH Power Script Directory
   become: yes
   file:
     path: /var/lib/maas/.ssh
@@ -23,7 +50,7 @@
     group: maas
     mode: 0700
 
-- name: VirtualBox SSH Support
+- name: VirtualBox Power Script Support
   become: yes
   copy:
     src: files/{{ item.src }}
@@ -36,3 +63,9 @@
     - { src: cord_id_rsa.pub, dest: id_rsa.pub }
     - { src: ssh_config, dest: config }
 
+- name: Discover VirtualBox Host
+  become: yes
+  shell: netstat -rn | grep "^0.0.0.0 " | cut -d " " -f10
+  register: discovered_vbox_host
+  changed_when: false
+
diff --git a/roles/maas/templates/automation-compose.yml.j2 b/roles/maas/templates/automation-compose.yml.j2
index d014bec..241214c 100644
--- a/roles/maas/templates/automation-compose.yml.j2
+++ b/roles/maas/templates/automation-compose.yml.j2
@@ -42,8 +42,12 @@
   environment:
     # need to explicitly set the resolver, else go will skip the /etc/hosts file
     - "GODEBUG=netdns=go"
-    - "PROVISION_URL=http://provisioner:4243/provision/"
-    - "PROVISION_TTL=30m"
+    - "AUTOMATION_PROVISION_URL=http://provisioner:4243/provision/"
+    - "AUTOMATION_PROVISION_TTL=30m"
+    - "AUTOMATION_POWER_HELPER_SCRIPT=/etc/maas/virtualbox/power_discovery"
+    - "AUTOMATION_POWER_HELPER_USER={{ virtualbox.power_helper_user }}"
+    - "AUTOMATION_POWER_HELPER_HOST={{ virtualbox_host }}"
   volumes:
     - "/etc/maas:/mappings"
+    - "/etc/maas/virtualbox:/etc/maas/virtualbox"
   command: [ "-apiVersion", "1.0", "-apikey", "{{ apikey.stdout }}", "-maas", "http://{{ mgmt_ip_address.stdout }}/MAAS", "-period", "30s", "-mappings", "@/mappings/mappings.json", "-always-rename" ]
diff --git a/roles/maas/vars/main.yml b/roles/maas/vars/main.yml
index b7f5a64..f881137 100644
--- a/roles/maas/vars/main.yml
+++ b/roles/maas/vars/main.yml
@@ -1,6 +1,15 @@
 accton_as5712_54x: 'http://178.33.61.6/putstorage/DownloadFileHash/AA90EC2D3A5A4A5QQWE3332069EWQS/onie-installer-x86_64-accton_as6712_32x-r0'
 accton_as6712_32x: 'http://178.33.61.6/putstorage/DownloadFileHash/AA90EC2D3A5A4A5QQWE3332069EWQS/onie-installer-x86_64-accton_as6712_32x-r0'
 
+virtualbox:
+    # CHANGE:
+    #   'power_helper_user' specifies the user to use when SSHing to the host
+    #                       that is running virtualbox
+    #   'power_helper_host' specifies the IP address of host that is running
+    #                       virtualbox. will be dynamically set if empty string
+    power_helper_user: "{{ power_helper_user | default('cord') }}"
+    power_helper_host: "{{ power_helper_host | default('') }}"
+
 maas:
     admin_email: "{{ admin_email | default('admin@cord.lab') }}"
     user: "{{ maas_user | default('cord') }}"