From 3fd65e7994f72c671233642ec21d78499d6e0c71 Mon Sep 17 00:00:00 2001 From: Kendrick Coleman Date: Tue, 19 Apr 2016 17:22:09 -0400 Subject: [PATCH] v0.1.0 This commit fixes issues with noop-obm settings with VirtualBox used for testing. This fix will work for both noop and ipmi OBM settings Signed-off-by: Kendrick Coleman --- README.md | 12 ++- rackhd.go | 269 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 222 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index e29e852..a9dfabd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Docker Machine Driver for RackHD -Use [Docker Machine](https://github.com/docker/machine) to create Docker hosts with [RackHD](https://github.com/RackHD/RackHD). +Use [Docker Machine](https://github.com/docker/machine) to create Docker hosts with [RackHD](https://github.com/RackHD/RackHD). This is the first solution that enables bare-metal provisioning of [Docker](https://github.com/docker/docker) and [Docker Swarm](https://github.com/docker/swarm). ## Installation **Linux & Mac OSX**: ``` -curl -L https://github.com/emccode/docker-machine-rackhd/releases/download/v0.0.1/docker-machine-driver-rackhd.`uname -s`-`uname -m` >/usr/local/bin/docker-machine-driver-rackhd && chmod +x /usr/local/bin/docker-machine-driver-rackhd +curl -L https://github.com/emccode/docker-machine-rackhd/releases/download/v0.1.0/docker-machine-driver-rackhd.`uname -s`-`uname -m` >/usr/local/bin/docker-machine-driver-rackhd && chmod +x /usr/local/bin/docker-machine-driver-rackhd ``` ## Using the driver @@ -41,7 +41,7 @@ Options: | --rackhd-ssh-password | RACKHD_SSH_PASSWORD | root | SSH Password for the node | N | | --rackhd-ssh-port | RACKHD_SSH_PORT | 22 | SSH Port for the node | N | -This initial version of the driver uses explicit creation instructions. The user must specify the Node ID from RackHD. The NodeID is characterized as a `compute` instance. Do not use `enclosure`. +This initial version of the driver uses explicit instructions. The user must specify the Node ID from RackHD. The NodeID is characterized as a `compute` instance. Do not use `enclosure`. Create a Docker host using the following example. This will function as expected if Docker Machine has access to the DHCP network of RackHD. @@ -49,7 +49,7 @@ Create a Docker host using the following example. This will function as expected $ docker-machine create -d rackhd --rackhd-node-id 56c61189f21f01b608b3e594 rackhdtest Running pre-create checks... (rackhdtest) Testing accessibility of endpoint: localhost:8080 -(rackhdtest) Test Passed. localhost:8080 is accessbile and installation will begin +(rackhdtest) Test Passed. localhost:8080 Monorail and Redfish API's are accessible and installation will begin Creating machine... (rackhdtest) Connection succeeded on: 172.31.128.16:22 (rackhdtest) Creating SSH key... @@ -69,6 +69,10 @@ To see how to connect your Docker Client to the Docker Engine running on this vi Check out the [RackHD Vagrant + Docker Machine Example](https://github.com/emccode/machine/tree/master/rackhd) to view a complete in-depth configuration and walk-through. +## Other Machine Functions + +The other functions for **Start**, **Stop**, **Restart**, **Kill**, and **Remove** require the use of IPMI. This must be configured in RackHD. + # Licensing Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/rackhd.go b/rackhd.go index a62519c..9e8f87b 100644 --- a/rackhd.go +++ b/rackhd.go @@ -8,8 +8,12 @@ import ( "strconv" "strings" - apiclient "github.com/emccode/gorackhd/client" + apiclientRedfish "github.com/emccode/gorackhd-redfish/client" + "github.com/emccode/gorackhd-redfish/client/redfish_v1" + modelsRedfish "github.com/emccode/gorackhd-redfish/models" + apiclientMonorail "github.com/emccode/gorackhd/client" "github.com/emccode/gorackhd/client/lookups" + "github.com/emccode/gorackhd/client/nodes" httptransport "github.com/go-swagger/go-swagger/httpkit/client" "github.com/go-swagger/go-swagger/strfmt" @@ -25,14 +29,15 @@ import ( type Driver struct { *drivers.BaseDriver - Endpoint string - NodeID string - SSHUser string - SSHPassword string - SSHPort int - SSHKey string - Transport string - client *apiclient.Monorail + Endpoint string + NodeID string + SSHUser string + SSHPassword string + SSHPort int + SSHKey string + Transport string + clientMonorail *apiclientMonorail.Monorail + clientRedfish *apiclientRedfish.Redfish } const ( @@ -148,21 +153,27 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { func (d *Driver) PreCreateCheck() error { log.Infof("Testing accessibility of endpoint: %v", d.Endpoint) //Generate the client - client := d.getClient() - + clientMonorail := d.getClientMonorail() //do a test to see if the server is available. 2nd Nil is authentication params - // that need to be determined in v2.0 of API - _, err := client.Config.GetConfig(nil, nil) + _, err := clientMonorail.Config.GetConfig(nil, nil) if err != nil { - return fmt.Errorf("The Endpoint is not accessible. Error: %s", err) + return fmt.Errorf("The Monorail API Endpoint is not accessible. Error: %s", err) } - log.Infof("Test Passed. %v is accessbile and installation will begin", d.Endpoint) + + clientRedfish := d.getClientRedfish() + + _, err2 := clientRedfish.RedfishV1.GetRoles(nil) + if err2 != nil { + return fmt.Errorf("The Redfish API Endpoint is not accessible. Error: %s", err2) + } + + log.Infof("Test Passed. %v Monorail and Redfish API's are accessible and installation will begin", d.Endpoint) return nil } func (d *Driver) Create() error { //Generate the client - client := d.getClient() + client := d.getClientMonorail() // do a lookup on the ID to retrieve IP information resp, err := client.Lookups.GetLookups(&lookups.GetLookupsParams{Q: d.NodeID}, nil) @@ -279,71 +290,219 @@ func (d *Driver) GetIP() (string, error) { } func (d *Driver) GetState() (state.State, error) { - /* - TODO: THIS REQUIRES THE REDFISH API WHICH IS STILL IN DEVELOPMENT - switch instance.State { - case "online": + + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return state.None, errObm + } + + //If there is no obm (such as Vagrant), send back as Running + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + return state.Running, nil + default: + //Generate the client + clientRedfish := d.getClientRedfish() + + // do a lookup on the Node ID to retrieve Power information + resp, err := clientRedfish.RedfishV1.GetSystem(&redfish_v1.GetSystemParams{Identifier: d.NodeID}) + if err != nil { + return state.None, nil + } + switch resp.Payload.PowerState { + case "Online", "online", "Up", "up", "On", "on": return state.Running, nil - case "offline": + case "Offline", "offline", "Down", "down", "Off", "off": return state.Stopped, nil + case "Unknown", "unknown": + return state.None, nil + default: + return state.Running, nil } - return state.None, nil - */ - return state.Running, nil + } } func (d *Driver) Start() error { - /* - TODO: THIS REQUIRES THE REDFISH API WHICH IS STILL IN DEVELOPMENT - REMOTELY POWER ON A SERVER VIA IPMI - */ - return nil + + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return errObm + } + + //If there is no obm (such as Vagrant), nil + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + return fmt.Errorf("OBM %#v Type Not Supported For Starting: %#v", "noop-obm-service", d.NodeID) + default: + log.Debugf("Attempting Turn On: %#v", d.NodeID) + action := &modelsRedfish.RackHDResetAction{ + ResetType: "On", + } + + clientRedfish := d.getClientRedfish() + + _, err := clientRedfish.RedfishV1.DoReset(&redfish_v1.DoResetParams{Identifier: d.NodeID, Payload: action}) + if err != nil { + return fmt.Errorf("There was an issue Powering On the Server. Error: %s", err) + } + + log.Debugf("Node has succussfully been powered on: %#v", d.NodeID) + return nil + } } func (d *Driver) Stop() error { - /* - TODO: THIS REQUIRES THE REDFISH API WHICH IS STILL IN DEVELOPMENT - SEND A SIGKILL TO THE OS. OR USE THE API TO GRACEFULLY SHUTDOWN THE HOST - */ - return nil + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return errObm + } + + //If there is no obm (such as Vagrant), nil + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + return fmt.Errorf("OBM %#v Type Not Supported For Stopping: %#v", "noop-obm-service", d.NodeID) + default: + log.Debugf("Attempting Graceful Shutdown of: %#v", d.NodeID) + action := &modelsRedfish.RackHDResetAction{ + ResetType: "GracefulShutdown", + } + + clientRedfish := d.getClientRedfish() + + _, err := clientRedfish.RedfishV1.DoReset(&redfish_v1.DoResetParams{Identifier: d.NodeID, Payload: action}) + if err != nil { + return fmt.Errorf("There was an issue Shutting Down the Server. Error: %s", err) + } + log.Debugf("Node has succussfully been shutdown: %#v", d.NodeID) + return nil + } } func (d *Driver) Remove() error { - /* - TODO: DECIDE WHETHER TO UNINSTALL DOCKER OR - 1. ADD A GENERIC WORKFLOW - 2. REBOOT THE HOST - 3. HOPE THAT GENERIC WORKFLOW WILL RESET THE HOST BACK TO A BLANK SLATE - */ + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return errObm + } + + //If there is no obm (such as Vagrant), nil + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + log.Debugf("OBM %#v Type Not Supported For Shutdown: %#v", "noop-obm-service", d.NodeID) + default: + log.Debugf("Attempting Graceful Shutdown of: %#v", d.NodeID) + action := &modelsRedfish.RackHDResetAction{ + ResetType: "GracefulShutdown", + } + + clientRedfish := d.getClientRedfish() + + _, err := clientRedfish.RedfishV1.DoReset(&redfish_v1.DoResetParams{Identifier: d.NodeID, Payload: action}) + if err != nil { + log.Infof("There was an issue Shutting Down the Server. Error: %s", err) + //return fmt.Errorf("There was an issue Shutting Down the Server. Error: %s", err) + } else { + log.Debugf("Node has succussfully been shutdown: %#v", d.NodeID) + } + } + + //Remove the Node from RackHD Inventory + log.Debugf("Removing Node From RackHD: %#v", d.NodeID) + _, err2 := clientMonorail.Nodes.DeleteNodesIdentifier(&nodes.DeleteNodesIdentifierParams{Identifier: d.NodeID}, nil) + if err2 != nil { + return err2 + } + log.Debugf("Successfully Removed Node From RackHD: %#v", d.NodeID) + return nil } func (d *Driver) Restart() error { - /* - TODO: THIS REQUIRES THE REDFISH API WHICH IS STILL IN DEVELOPMENT - REMOTELY RESET OFF A SERVER VIA IPMI - */ - return nil + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return errObm + } + + //If there is no obm (such as Vagrant), nil + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + return fmt.Errorf("OBM Type Not Supported: %#v, %#v", "noop-obm-service", d.NodeID) + default: + log.Debugf("Attempting Restart of: %#v", d.NodeID) + action := &modelsRedfish.RackHDResetAction{ + ResetType: "GracefulRestart", + } + + clientRedfish := d.getClientRedfish() + + _, err := clientRedfish.RedfishV1.DoReset(&redfish_v1.DoResetParams{Identifier: d.NodeID, Payload: action}) + if err != nil { + return fmt.Errorf("There was an issue Shutting Down the Server. Error: %s", err) + } + log.Debugf("Successfully restarted node: %#v", d.NodeID) + return nil + } } func (d *Driver) Kill() error { - /* - TODO: THIS REQUIRES THE REDFISH API WHICH IS STILL IN DEVELOPMENT - POWER OFF THE HOST VIA IMPI - */ - return nil + //Get the Out of Band Management Type + clientMonorail := d.getClientMonorail() + respObm, errObm := clientMonorail.Nodes.GetNodesIdentifierObm(&nodes.GetNodesIdentifierObmParams{Identifier: d.NodeID}, nil) + if errObm != nil { + return errObm + } + + //If there is no obm (such as Vagrant), nil + switch respObm.Payload.([]interface{})[0].(map[string]interface{})["service"] { + case "noop-obm-service": + return fmt.Errorf("OBM Type Not Supported: %#v, %#v", "noop-obm-service", d.NodeID) + default: + log.Debugf("Attempting Force Off of: %#v", d.NodeID) + action := &modelsRedfish.RackHDResetAction{ + ResetType: "ForceOff", + } + + clientRedfish := d.getClientRedfish() + + _, err := clientRedfish.RedfishV1.DoReset(&redfish_v1.DoResetParams{Identifier: d.NodeID, Payload: action}) + if err != nil { + return fmt.Errorf("There was an issue Shutting Down the Server. Error: %s", err) + } + log.Debugf("Successfully turned off node: %#v", d.NodeID) + return nil + } } -func (d *Driver) getClient() *apiclient.Monorail { - log.Debugf("Getting RackHD Client") - if d.client == nil { +func (d *Driver) getClientMonorail() *apiclientMonorail.Monorail { + log.Debugf("Getting RackHD Monorail Client") + if d.clientMonorail == nil { // create the transport /** Will Need to determine changes for v 2.0 API **/ transport := httptransport.New(d.Endpoint, "/api/1.1", []string{d.Transport}) // create the API client, with the transport - d.client = apiclient.New(transport, strfmt.Default) + d.clientMonorail = apiclientMonorail.New(transport, strfmt.Default) + } + return d.clientMonorail +} + +func (d *Driver) getClientRedfish() *apiclientRedfish.Redfish { + log.Debugf("Getting RackHD Redfish Client") + if d.clientRedfish == nil { + // create the transport + transport := httptransport.New(d.Endpoint, "/redfish/v1", []string{d.Transport}) + // create the API client, with the transport + d.clientRedfish = apiclientRedfish.New(transport, strfmt.Default) } - return d.client + return d.clientRedfish } func (d *Driver) publicSSHKeyPath() string {