diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go new file mode 100644 index 0000000..4329aa5 --- /dev/null +++ b/drivers/openstack/client.go @@ -0,0 +1,419 @@ +package openstack + +import ( + log "github.com/Sirupsen/logrus" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop" + "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" + "github.com/rackspace/gophercloud/openstack/compute/v2/images" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/openstack/networking/v2/ports" + "github.com/rackspace/gophercloud/pagination" +) + +type Client interface { + Authenticate(d *Driver) error + InitComputeClient(d *Driver) error + InitNetworkClient(d *Driver) error + + CreateInstance(d *Driver) (string, error) + GetInstanceState(d *Driver) (string, error) + StartInstance(d *Driver) error + StopInstance(d *Driver) error + RestartInstance(d *Driver) error + DeleteInstance(d *Driver) error + WaitForInstanceStatus(d *Driver, status string, timeout int) error + GetInstanceIpAddresses(d *Driver) ([]IpAddress, error) + CreateKeyPair(d *Driver, name string, publicKey string) error + DeleteKeyPair(d *Driver, name string) error + GetNetworkId(d *Driver) (string, error) + GetFlavorId(d *Driver) (string, error) + GetImageId(d *Driver) (string, error) + AssignFloatingIP(d *Driver, floatingIp *FloatingIp, portId string) error + GetFloatingIPs(d *Driver) ([]FloatingIp, error) + GetFloatingIpPoolId(d *Driver) (string, error) + GetInstancePortId(d *Driver) (string, error) +} + +type GenericClient struct { + Provider *gophercloud.ProviderClient + Compute *gophercloud.ServiceClient + Network *gophercloud.ServiceClient +} + +func (c *GenericClient) CreateInstance(d *Driver) (string, error) { + serverOpts := servers.CreateOpts{ + Name: d.MachineName, + FlavorRef: d.FlavorId, + ImageRef: d.ImageId, + SecurityGroups: d.SecurityGroups, + } + if d.NetworkId != "" { + serverOpts.Networks = []servers.Network{ + { + UUID: d.NetworkId, + }, + } + } + + log.WithFields(log.Fields{ + "Name": d.MachineName, + }).Info("Creating server...") + + server, err := servers.Create(c.Compute, keypairs.CreateOptsExt{ + serverOpts, + d.KeyPairName, + }).Extract() + if err != nil { + return "", err + } + return server.ID, nil +} + +const ( + Floating string = "floating" + Fixed string = "fixed" +) + +type IpAddress struct { + Network string + AddressType string + Address string + Mac string +} + +type FloatingIp struct { + Id string + Ip string + NetworkId string + PortId string +} + +func (c *GenericClient) GetInstanceState(d *Driver) (string, error) { + server, err := c.GetServerDetail(d) + if err != nil { + return "", err + } + return server.Status, nil +} + +func (c *GenericClient) StartInstance(d *Driver) error { + if result := startstop.Start(c.Compute, d.MachineId); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) StopInstance(d *Driver) error { + if result := startstop.Stop(c.Compute, d.MachineId); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) RestartInstance(d *Driver) error { + if result := servers.Reboot(c.Compute, d.MachineId, servers.SoftReboot); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) DeleteInstance(d *Driver) error { + if result := servers.Delete(c.Compute, d.MachineId); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) WaitForInstanceStatus(d *Driver, status string, timeout int) error { + if err := servers.WaitForStatus(c.Compute, d.MachineId, status, timeout); err != nil { + return err + } + return nil +} + +func (c *GenericClient) GetInstanceIpAddresses(d *Driver) ([]IpAddress, error) { + server, err := c.GetServerDetail(d) + if err != nil { + return nil, err + } + addresses := []IpAddress{} + for network, networkAddresses := range server.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + + addr := IpAddress{ + Network: network, + Address: address["addr"].(string), + } + + if tp, ok := address["OS-EXT-IPS:type"]; ok { + addr.AddressType = tp.(string) + } + if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { + addr.Mac = mac.(string) + } + + addresses = append(addresses, addr) + } + } + return addresses, nil +} + +func (c *GenericClient) GetNetworkId(d *Driver) (string, error) { + return c.getNetworkId(d, d.NetworkName) +} + +func (c *GenericClient) GetFloatingIpPoolId(d *Driver) (string, error) { + return c.getNetworkId(d, d.FloatingIpPool) +} + +func (c *GenericClient) getNetworkId(d *Driver, networkName string) (string, error) { + opts := networks.ListOpts{Name: networkName} + pager := networks.List(c.Network, opts) + networkId := "" + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.Name == networkName { + networkId = n.ID + return false, nil + } + } + + return true, nil + }) + + return networkId, err +} + +func (c *GenericClient) GetFlavorId(d *Driver) (string, error) { + pager := flavors.ListDetail(c.Compute, nil) + flavorId := "" + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + flavorList, err := flavors.ExtractFlavors(page) + if err != nil { + return false, err + } + + for _, f := range flavorList { + if f.Name == d.FlavorName { + flavorId = f.ID + return false, nil + } + } + + return true, nil + }) + + return flavorId, err +} + +func (c *GenericClient) GetImageId(d *Driver) (string, error) { + opts := images.ListOpts{Name: d.ImageName} + pager := images.ListDetail(c.Compute, opts) + imageId := "" + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + imageList, err := images.ExtractImages(page) + if err != nil { + return false, err + } + + for _, i := range imageList { + if i.Name == d.ImageName { + imageId = i.ID + return false, nil + } + } + + return true, nil + }) + + return imageId, err +} + +func (c *GenericClient) CreateKeyPair(d *Driver, name string, publicKey string) error { + opts := keypairs.CreateOpts{ + Name: name, + PublicKey: publicKey, + } + if result := keypairs.Create(c.Compute, opts); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) DeleteKeyPair(d *Driver, name string) error { + if result := keypairs.Delete(c.Compute, name); result.Err != nil { + return result.Err + } + return nil +} + +func (c *GenericClient) GetServerDetail(d *Driver) (*servers.Server, error) { + server, err := servers.Get(c.Compute, d.MachineId).Extract() + if err != nil { + return nil, err + } + return server, nil +} + +func (c *GenericClient) AssignFloatingIP(d *Driver, floatingIp *FloatingIp, portId string) error { + if floatingIp.Id == "" { + f, err := floatingips.Create(c.Network, floatingips.CreateOpts{ + FloatingNetworkID: d.FloatingIpPoolId, + PortID: portId, + }).Extract() + if err != nil { + return err + } + floatingIp.Id = f.ID + floatingIp.Ip = f.FloatingIP + floatingIp.NetworkId = f.FloatingNetworkID + floatingIp.PortId = f.PortID + return nil + } + _, err := floatingips.Update(c.Network, floatingIp.Id, floatingips.UpdateOpts{ + PortID: portId, + }).Extract() + if err != nil { + return err + } + return nil +} + +func (c *GenericClient) GetFloatingIPs(d *Driver) ([]FloatingIp, error) { + pager := floatingips.List(c.Network, floatingips.ListOpts{ + FloatingNetworkID: d.FloatingIpPoolId, + }) + + ips := []FloatingIp{} + err := pager.EachPage(func(page pagination.Page) (bool, error) { + floatingipList, err := floatingips.ExtractFloatingIPs(page) + if err != nil { + return false, err + } + for _, f := range floatingipList { + ips = append(ips, FloatingIp{ + Id: f.ID, + Ip: f.FloatingIP, + NetworkId: f.FloatingNetworkID, + PortId: f.PortID, + }) + } + return true, nil + }) + + if err != nil { + return nil, err + } + return ips, nil +} + +func (c *GenericClient) GetInstancePortId(d *Driver) (string, error) { + pager := ports.List(c.Network, ports.ListOpts{ + DeviceID: d.MachineId, + NetworkID: d.NetworkId, + }) + + var portId string + err := pager.EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + for _, port := range portList { + portId = port.ID + return false, nil + } + return true, nil + }) + + if err != nil { + return "", err + } + return portId, nil +} + +func (c *GenericClient) InitComputeClient(d *Driver) error { + if c.Compute != nil { + return nil + } + + compute, err := openstack.NewComputeV2(c.Provider, gophercloud.EndpointOpts{ + Region: d.Region, + Availability: c.getEndpointType(d), + }) + if err != nil { + return err + } + c.Compute = compute + return nil +} + +func (c *GenericClient) InitNetworkClient(d *Driver) error { + if c.Network != nil { + return nil + } + + network, err := openstack.NewNetworkV2(c.Provider, gophercloud.EndpointOpts{ + Region: d.Region, + Availability: c.getEndpointType(d), + }) + if err != nil { + return err + } + c.Network = network + return nil +} + +func (c *GenericClient) getEndpointType(d *Driver) gophercloud.Availability { + switch d.EndpointType { + case "internalURL": + return gophercloud.AvailabilityInternal + case "adminURL": + return gophercloud.AvailabilityAdmin + } + return gophercloud.AvailabilityPublic +} + +func (c *GenericClient) Authenticate(d *Driver) error { + if c.Provider != nil { + return nil + } + + log.WithFields(log.Fields{ + "AuthUrl": d.AuthUrl, + "Username": d.Username, + "TenantName": d.TenantName, + "TenantID": d.TenantId, + }).Debug("Authenticating...") + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: d.AuthUrl, + Username: d.Username, + Password: d.Password, + TenantName: d.TenantName, + TenantID: d.TenantId, + AllowReauth: true, + } + + provider, err := openstack.AuthenticatedClient(opts) + if err != nil { + return err + } + c.Provider = provider + + return nil +} diff --git a/drivers/openstack/openstack.go b/drivers/openstack/openstack.go new file mode 100644 index 0000000..49e502f --- /dev/null +++ b/drivers/openstack/openstack.go @@ -0,0 +1,748 @@ +package openstack + +import ( + "fmt" + "io/ioutil" + "os/exec" + "path" + "strconv" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/docker/docker/utils" + "github.com/docker/machine/drivers" + "github.com/docker/machine/ssh" + "github.com/docker/machine/state" +) + +type Driver struct { + AuthUrl string + Username string + Password string + TenantName string + TenantId string + Region string + EndpointType string + MachineName string + MachineId string + FlavorName string + FlavorId string + ImageName string + ImageId string + KeyPairName string + NetworkName string + NetworkId string + SecurityGroups []string + FloatingIpPool string + FloatingIpPoolId string + SSHUser string + SSHPort int + Ip string + EnableDockerInstall bool + storePath string + client Client +} + +type CreateFlags struct { + AuthUrl *string + Username *string + Password *string + TenantName *string + TenantId *string + Region *string + EndpointType *string + FlavorName *string + FlavorId *string + ImageName *string + ImageId *string + NetworkName *string + NetworkId *string + SecurityGroups *string + FloatingIpPool *string + SSHUser *string + SSHPort *int +} + +func init() { + drivers.Register("openstack", &drivers.RegisteredDriver{ + New: NewDriver, + GetCreateFlags: GetCreateFlags, + }) +} + +func GetCreateFlags() []cli.Flag { + return []cli.Flag{ + cli.StringFlag{ + EnvVar: "OS_AUTH_URL", + Name: "openstack-auth-url", + Usage: "OpenStack authentication URL", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_USERNAME", + Name: "openstack-username", + Usage: "OpenStack username", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_PASSWORD", + Name: "openstack-password", + Usage: "OpenStack password", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_TENANT_NAME", + Name: "openstack-tenant-name", + Usage: "OpenStack tenant name", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_TENANT_ID", + Name: "openstack-tenant-id", + Usage: "OpenStack tenant id", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_REGION_NAME", + Name: "openstack-region", + Usage: "OpenStack region name", + Value: "", + }, + cli.StringFlag{ + EnvVar: "OS_ENDPOINT_TYPE", + Name: "openstack-endpoint-type", + Usage: "OpenStack endpoint type (adminURL, internalURL or publicURL)", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-flavor-id", + Usage: "OpenStack flavor id to use for the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-flavor-name", + Usage: "OpenStack flavor name to use for the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-image-id", + Usage: "OpenStack image id to use for the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-image-name", + Usage: "OpenStack image name to use for the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-net-id", + Usage: "OpenStack image name to use for the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-net-name", + Usage: "OpenStack network name the machine will be connected on", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-sec-groups", + Usage: "OpenStack comma separated security groups for the machine", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-floatingip-pool", + Usage: "OpenStack floating IP pool to get an IP from to assign to the instance", + Value: "", + }, + cli.StringFlag{ + Name: "openstack-ssh-user", + Usage: "OpenStack SSH user", + Value: "root", + }, + cli.IntFlag{ + Name: "openstack-ssh-port", + Usage: "OpenStack SSH port", + Value: 22, + }, + // Using a StringFlag rather than a BoolFlag because + // the BoolFlag default value is always false + cli.StringFlag{ + Name: "openstack-docker-install", + Usage: "Set if docker have to be installed on the machine", + Value: "true", + }, + } +} + +func NewDriver(machineName string, storePath string) (drivers.Driver, error) { + log.WithFields(log.Fields{ + "machineName": machineName, + "storePath": storePath, + }).Debug("Instantiating OpenStack driver...") + + return NewDerivedDriver(machineName, storePath, &GenericClient{}) +} + +func NewDerivedDriver(machineName string, storePath string, client Client) (*Driver, error) { + return &Driver{ + MachineName: machineName, + storePath: storePath, + client: client, + }, nil +} + +func (d *Driver) DriverName() string { + return "openstack" +} + +func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { + d.AuthUrl = flags.String("openstack-auth-url") + d.Username = flags.String("openstack-username") + d.Password = flags.String("openstack-password") + d.TenantName = flags.String("openstack-tenant-name") + d.TenantId = flags.String("openstack-tenant-id") + d.Region = flags.String("openstack-region") + d.EndpointType = flags.String("openstack-endpoint-type") + d.FlavorId = flags.String("openstack-flavor-id") + d.FlavorName = flags.String("openstack-flavor-name") + d.ImageId = flags.String("openstack-image-id") + d.ImageName = flags.String("openstack-image-name") + d.NetworkId = flags.String("openstack-net-id") + d.NetworkName = flags.String("openstack-net-name") + if flags.String("openstack-sec-groups") != "" { + d.SecurityGroups = strings.Split(flags.String("openstack-sec-groups"), ",") + } + d.FloatingIpPool = flags.String("openstack-floatingip-pool") + d.SSHUser = flags.String("openstack-ssh-user") + d.SSHPort = flags.Int("openstack-ssh-port") + + installDocker, err := strconv.ParseBool(flags.String("openstack-docker-install")) + if err != nil { + return err + } + d.EnableDockerInstall = installDocker + return d.checkConfig() +} + +func (d *Driver) GetURL() (string, error) { + ip, err := d.GetIP() + if err != nil { + return "", err + } + if ip == "" { + return "", nil + } + return fmt.Sprintf("tcp://%s:2376", ip), nil +} + +func (d *Driver) GetIP() (string, error) { + if d.Ip != "" { + return d.Ip, nil + } + + log.WithField("MachineId", d.MachineId).Debug("Looking for the IP address...") + + if err := d.initCompute(); err != nil { + return "", err + } + + addressType := Fixed + if d.FloatingIpPool != "" { + addressType = Floating + } + + // Looking for the IP address in a retry loop to deal with OpenStack latency + for retryCount := 0; retryCount < 200; retryCount++ { + addresses, err := d.client.GetInstanceIpAddresses(d) + if err != nil { + return "", err + } + for _, a := range addresses { + if a.AddressType == addressType { + return a.Address, nil + } + } + time.Sleep(2 * time.Second) + } + return "", fmt.Errorf("No IP found for the machine") +} + +func (d *Driver) GetState() (state.State, error) { + log.WithField("MachineId", d.MachineId).Debug("Get status for OpenStack instance...") + if err := d.initCompute(); err != nil { + return state.None, err + } + + s, err := d.client.GetInstanceState(d) + if err != nil { + return state.None, err + } + + log.WithFields(log.Fields{ + "MachineId": d.MachineId, + "State": s, + }).Debug("State for OpenStack instance") + + switch s { + case "ACTIVE": + return state.Running, nil + case "PAUSED": + return state.Paused, nil + case "SUSPENDED": + return state.Saved, nil + case "SHUTOFF": + return state.Stopped, nil + case "BUILDING": + return state.Starting, nil + case "ERROR": + return state.Error, nil + } + return state.None, nil +} + +func (d *Driver) Create() error { + d.KeyPairName = fmt.Sprintf("%s-%s", d.MachineName, utils.GenerateRandomID()) + + if err := d.resolveIds(); err != nil { + return err + } + if err := d.createSSHKey(); err != nil { + return err + } + if err := d.createMachine(); err != nil { + return err + } + if err := d.waitForInstanceActive(); err != nil { + return err + } + if d.FloatingIpPool != "" { + if err := d.assignFloatingIp(); err != nil { + return err + } + } + if err := d.lookForIpAddress(); err != nil { + return err + } + if err := d.waitForSSHServer(); err != nil { + return err + } + if d.EnableDockerInstall { + if err := d.installDocker(); err != nil { + return err + } + } + return nil +} + +func (d *Driver) Start() error { + log.WithField("MachineId", d.MachineId).Info("Starting OpenStack instance...") + if err := d.initCompute(); err != nil { + return err + } + if err := d.client.StartInstance(d); err != nil { + return err + } + return d.waitForInstanceToStart() +} + +func (d *Driver) Stop() error { + log.WithField("MachineId", d.MachineId).Info("Stopping OpenStack instance...") + if err := d.initCompute(); err != nil { + return err + } + if err := d.client.StopInstance(d); err != nil { + return err + } + + log.WithField("MachineId", d.MachineId).Info("Waiting for the OpenStack instance to stop...") + if err := d.client.WaitForInstanceStatus(d, "SHUTOFF", 200); err != nil { + return err + } + return nil +} + +func (d *Driver) Remove() error { + log.WithField("MachineId", d.MachineId).Info("Deleting OpenStack instance...") + if err := d.initCompute(); err != nil { + return err + } + if err := d.client.DeleteInstance(d); err != nil { + return err + } + log.WithField("Name", d.KeyPairName).Info("Deleting Key Pair...") + if err := d.client.DeleteKeyPair(d, d.KeyPairName); err != nil { + return err + } + return nil +} + +func (d *Driver) Restart() error { + log.WithField("MachineId", d.MachineId).Info("Restarting OpenStack instance...") + if err := d.initCompute(); err != nil { + return err + } + if err := d.client.RestartInstance(d); err != nil { + return err + } + return d.waitForInstanceToStart() +} + +func (d *Driver) Kill() error { + return d.Stop() +} + +func (d *Driver) Upgrade() error { + return fmt.Errorf("unable to upgrade as we are using the custom docker binary with identity auth") +} + +func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { + ip, err := d.GetIP() + if err != nil { + return nil, err + } + + if len(args) != 0 && d.SSHUser != "root" { + cmd := strings.Replace(strings.Join(args, " "), "'", "\\'", -1) + args = []string{"sudo", "sh", "-c", fmt.Sprintf("'%s'", cmd)} + } + + log.WithField("MachineId", d.MachineId).Debug("Command: %s", args) + return ssh.GetSSHCommand(ip, d.SSHPort, d.SSHUser, d.sshKeyPath(), args...), nil +} + +const ( + errorMandatoryEnvOrOption string = "%s must be specified either using the environment variable %s or the CLI option %s" + errorMandatoryOption string = "%s must be specified using the CLI option %s" + errorExclusiveOptions string = "Either %s or %s must be specified, not both" + errorMandatoryTenantNameOrId string = "Tenant id or name must be provided either using one of the environment variables OS_TENANT_ID and OS_TENANT_NAME or one of the CLI options --openstack-tenant-id and --openstack-tenant-name" + errorWrongEndpointType string = "Endpoint type must be 'publicURL', 'adminURL' or 'internalURL'" + errorUnknownFlavorName string = "Unable to find flavor named %s" + errorUnknownImageName string = "Unable to find image named %s" + errorUnknownNetworkName string = "Unable to find network named %s" +) + +func (d *Driver) checkConfig() error { + if d.AuthUrl == "" { + return fmt.Errorf(errorMandatoryEnvOrOption, "Autentication URL", "OS_AUTH_URL", "--openstack-auth-url") + } + if d.Username == "" { + return fmt.Errorf(errorMandatoryEnvOrOption, "Username", "OS_USERNAME", "--openstack-username") + } + if d.Password == "" { + return fmt.Errorf(errorMandatoryEnvOrOption, "Password", "OS_PASSWORD", "--openstack-password") + } + if d.TenantName == "" && d.TenantId == "" { + return fmt.Errorf(errorMandatoryTenantNameOrId) + } + + if d.FlavorName == "" && d.FlavorId == "" { + return fmt.Errorf(errorMandatoryOption, "Flavor name or Flavor id", "--openstack-flavor-name or --openstack-flavor-id") + } + if d.FlavorName != "" && d.FlavorId != "" { + return fmt.Errorf(errorExclusiveOptions, "Flavor name", "Flavor id") + } + + if d.ImageName == "" && d.ImageId == "" { + return fmt.Errorf(errorMandatoryOption, "Image name or Image id", "--openstack-image-name or --openstack-image-id") + } + if d.ImageName != "" && d.ImageId != "" { + return fmt.Errorf(errorExclusiveOptions, "Image name", "Image id") + } + + if d.NetworkName != "" && d.NetworkId != "" { + return fmt.Errorf(errorExclusiveOptions, "Network name", "Network id") + } + if d.EndpointType != "" && (d.EndpointType != "publicURL" && d.EndpointType != "adminURL" && d.EndpointType != "internalURL") { + return fmt.Errorf(errorWrongEndpointType) + } + return nil +} + +func (d *Driver) resolveIds() error { + if d.NetworkName != "" { + if err := d.initNetwork(); err != nil { + return err + } + + networkId, err := d.client.GetNetworkId(d) + + if err != nil { + return err + } + + if networkId == "" { + return fmt.Errorf(errorUnknownNetworkName, d.NetworkName) + } + + d.NetworkId = networkId + log.WithFields(log.Fields{ + "Name": d.NetworkName, + "ID": d.NetworkId, + }).Debug("Found network id using its name") + } + + if d.FlavorName != "" { + if err := d.initCompute(); err != nil { + return err + } + flavorId, err := d.client.GetFlavorId(d) + + if err != nil { + return err + } + + if flavorId == "" { + return fmt.Errorf(errorUnknownFlavorName, d.FlavorName) + } + + d.FlavorId = flavorId + log.WithFields(log.Fields{ + "Name": d.FlavorName, + "ID": d.FlavorId, + }).Debug("Found flavor id using its name") + } + + if d.ImageName != "" { + if err := d.initCompute(); err != nil { + return err + } + imageId, err := d.client.GetImageId(d) + + if err != nil { + return err + } + + if imageId == "" { + return fmt.Errorf(errorUnknownImageName, d.ImageName) + } + + d.ImageId = imageId + log.WithFields(log.Fields{ + "Name": d.ImageName, + "ID": d.ImageId, + }).Debug("Found image id using its name") + } + + if d.FloatingIpPool != "" { + if err := d.initNetwork(); err != nil { + return err + } + f, err := d.client.GetFloatingIpPoolId(d) + + if err != nil { + return err + } + + if f == "" { + return fmt.Errorf(errorUnknownNetworkName, d.FloatingIpPool) + } + + d.FloatingIpPoolId = f + log.WithFields(log.Fields{ + "Name": d.FloatingIpPool, + "ID": d.FloatingIpPoolId, + }).Debug("Found floating IP pool id using its name") + } + + return nil +} + +func (d *Driver) initCompute() error { + if err := d.client.Authenticate(d); err != nil { + return err + } + if err := d.client.InitComputeClient(d); err != nil { + return err + } + return nil +} + +func (d *Driver) initNetwork() error { + if err := d.client.Authenticate(d); err != nil { + return err + } + if err := d.client.InitNetworkClient(d); err != nil { + return err + } + return nil +} + +func (d *Driver) createSSHKey() error { + log.WithField("Name", d.KeyPairName).Debug("Creating Key Pair...") + if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil { + return err + } + publicKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + + if err := d.initCompute(); err != nil { + return err + } + if err := d.client.CreateKeyPair(d, d.KeyPairName, string(publicKey)); err != nil { + return err + } + return nil +} + +func (d *Driver) createMachine() error { + log.WithFields(log.Fields{ + "FlavorId": d.FlavorId, + "ImageId": d.ImageId, + }).Debug("Creating OpenStack instance...") + + if err := d.initCompute(); err != nil { + return err + } + instanceId, err := d.client.CreateInstance(d) + if err != nil { + return err + } + d.MachineId = instanceId + return nil +} + +func (d *Driver) assignFloatingIp() error { + + if err := d.initNetwork(); err != nil { + return err + } + + portId, err := d.client.GetInstancePortId(d) + if err != nil { + return err + } + + ips, err := d.client.GetFloatingIPs(d) + if err != nil { + return err + } + + var floatingIp *FloatingIp + + log.WithFields(log.Fields{ + "MachineId": d.MachineId, + "Pool": d.FloatingIpPool, + }).Debugf("Looking for an available floating IP") + + for _, ip := range ips { + if ip.PortId == "" { + log.WithFields(log.Fields{ + "MachineId": d.MachineId, + "IP": ip.Ip, + }).Debugf("Available floating IP found") + floatingIp = &ip + break + } + } + + if floatingIp == nil { + floatingIp = &FloatingIp{} + log.WithField("MachineId", d.MachineId).Debugf("No available floating IP found. Allocating a new one...") + } else { + log.WithField("MachineId", d.MachineId).Debugf("Assigning floating IP to the instance") + } + + if err := d.client.AssignFloatingIP(d, floatingIp, portId); err != nil { + return err + } + d.Ip = floatingIp.Ip + return nil +} + +func (d *Driver) waitForInstanceActive() error { + log.WithField("MachineId", d.MachineId).Debug("Waiting for the OpenStack instance to be ACTIVE...") + if err := d.client.WaitForInstanceStatus(d, "ACTIVE", 200); err != nil { + return err + } + return nil +} + +func (d *Driver) lookForIpAddress() error { + ip, err := d.GetIP() + if err != nil { + return err + } + d.Ip = ip + log.WithFields(log.Fields{ + "IP": ip, + "MachineId": d.MachineId, + }).Debug("IP address found") + return nil +} + +func (d *Driver) waitForSSHServer() error { + ip, err := d.GetIP() + if err != nil { + return err + } + log.WithFields(log.Fields{ + "MachineId": d.MachineId, + "IP": ip, + }).Debug("Waiting for the SSH server to be started...") + return ssh.WaitForTCP(fmt.Sprintf("%s:%d", ip, d.SSHPort)) +} + +func (d *Driver) waitForInstanceToStart() error { + if err := d.waitForInstanceActive(); err != nil { + return err + } + return d.waitForSSHServer() +} + +func (d *Driver) installDocker() error { + log.WithField("MachineId", d.MachineId).Debug("Adding key to authorized-keys.d...") + + if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/.docker/authorized-keys.d"); err != nil { + return err + } + + log.WithField("MachineId", d.MachineId).Debug("Installing docker daemon on the machine") + + if err := d.sshExec([]string{ + `apt-get install -y curl`, + `curl -sSL https://get.docker.com | /bin/sh >/var/log/docker-install.log 2>&1`, + `service docker stop`, + `curl -sSL https://ehazlett.s3.amazonaws.com/public/docker/linux/docker-1.4.1-136b351e-identity -o /usr/bin/docker`, + `echo "export DOCKER_OPTS=\"--auth=identity --host=tcp://0.0.0.0:2376\"" >> /etc/default/docker`, + `service docker start`, + }); err != nil { + log.Error("The docker installation failed.") + log.Error( + "The driver assumes that your instance is running Ubuntu. If this is not the case, you should ", + "use the option --openstack-docker-install=false (or --{provider}-docker-install=false) when ", + "creating a machine, and then install and configure docker manually.", + ) + log.Error( + `Also, you can use "machine ssh" to manually configure docker on this host.`, + ) + + // Don't return this ssh error so that host creation succeeds and "machine ssh" and "machine rm" + // are usable. + } + return nil +} + +func (d *Driver) sshExec(commands []string) error { + for _, command := range commands { + sshCmd, err := d.GetSSHCommand(command) + if err != nil { + return err + } + if err := sshCmd.Run(); err != nil { + return err + } + } + return nil +} + +func (d *Driver) sshKeyPath() string { + return path.Join(d.storePath, "id_rsa") +} + +func (d *Driver) publicSSHKeyPath() string { + return d.sshKeyPath() + ".pub" +}