diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go index 4a50d14dad..0c4c96a2fd 100644 --- a/drivers/openstack/client.go +++ b/drivers/openstack/client.go @@ -30,9 +30,13 @@ type Client interface { GetInstanceIpAddresses(d *Driver) ([]IpAddress, error) CreateKeyPair(d *Driver, name string, publicKey string) error DeleteKeyPair(d *Driver, name string) error - GetNetworkId(d *Driver, name string) (string, error) - GetFlavorId(d *Driver, name string) (string, error) - GetImageId(d *Driver, name string) (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 { @@ -77,16 +81,18 @@ type IpAddress struct { 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 } - - c.getFloatingIPs(d) - - c.getPorts(d) - return server.Status, nil } @@ -153,8 +159,16 @@ func (c *GenericClient) GetInstanceIpAddresses(d *Driver) ([]IpAddress, error) { return addresses, nil } -func (c *GenericClient) GetNetworkId(d *Driver, networkName string) (string, error) { - opts := networks.ListOpts{Name: d.NetworkName} +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 := "" @@ -165,7 +179,7 @@ func (c *GenericClient) GetNetworkId(d *Driver, networkName string) (string, err } for _, n := range networkList { - if n.Name == d.NetworkName { + if n.Name == networkName { networkId = n.ID return false, nil } @@ -177,7 +191,7 @@ func (c *GenericClient) GetNetworkId(d *Driver, networkName string) (string, err return networkId, err } -func (c *GenericClient) GetFlavorId(d *Driver, flavorName string) (string, error) { +func (c *GenericClient) GetFlavorId(d *Driver) (string, error) { pager := flavors.ListDetail(c.Compute, nil) flavorId := "" @@ -200,8 +214,8 @@ func (c *GenericClient) GetFlavorId(d *Driver, flavorName string) (string, error return flavorId, err } -func (c *GenericClient) GetImageId(d *Driver, imageName string) (string, error) { - opts := images.ListOpts{Name: imageName} +func (c *GenericClient) GetImageId(d *Driver) (string, error) { + opts := images.ListOpts{Name: d.ImageName} pager := images.ListDetail(c.Compute, opts) imageId := "" @@ -250,16 +264,48 @@ func (c *GenericClient) GetServerDetail(d *Driver) (*servers.Server, error) { return server, nil } -func (c *GenericClient) getFloatingIPs(d *Driver) ([]string, error) { - pager := floatingips.List(c.Network, floatingips.ListOpts{}) +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 { - log.Debug("### FloatingIP => %s", f) + ips = append(ips, FloatingIp{ + Id: f.ID, + Ip: f.FloatingIP, + NetworkId: f.FloatingNetworkID, + PortId: f.PortID, + }) } return true, nil }) @@ -267,29 +313,32 @@ func (c *GenericClient) getFloatingIPs(d *Driver) ([]string, error) { if err != nil { return nil, err } - return nil, nil + return ips, nil } -func (c *GenericClient) getPorts(d *Driver) ([]string, error) { +func (c *GenericClient) GetInstancePortId(d *Driver) (string, error) { pager := ports.List(c.Network, ports.ListOpts{ - DeviceID: d.MachineId, + 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 { - log.Debug("### Port => %s", port) + portId = port.ID + return false, nil } return true, nil }) if err != nil { - return nil, err + return "", err } - return nil, nil + return portId, nil } func (c *GenericClient) InitComputeClient(d *Driver) error { diff --git a/drivers/openstack/openstack.go b/drivers/openstack/openstack.go index 468e5c76dc..797a6a854e 100644 --- a/drivers/openstack/openstack.go +++ b/drivers/openstack/openstack.go @@ -6,6 +6,7 @@ import ( "os/exec" "path" "strings" + "time" log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" @@ -16,28 +17,30 @@ import ( ) 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 - SSHUser string - SSHPort int - storePath string - client Client + 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 + storePath string + client Client } type CreateFlags struct { @@ -218,44 +221,34 @@ func (d *Driver) GetURL() (string, error) { } 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 } - addresses, err := d.client.GetInstanceIpAddresses(d) - if err != nil { - return "", err + addressType := Fixed + if d.FloatingIpPool != "" { + addressType = Floating } - floating := []string{} - fixed := []string{} - - for _, address := range addresses { - if address.AddressType == Floating { - floating = append(floating, address.Address) - continue + // 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 } - if address.AddressType == Fixed { - fixed = append(fixed, address.Address) - continue + for _, a := range addresses { + if a.AddressType == addressType { + return a.Address, nil + } } - log.Warnf("Unknown IP address type : %s", address) + time.Sleep(2 * time.Second) } - - if len(floating) == 1 { - return d.foundIP(floating[0]), nil - } else if len(floating) > 1 { - log.Warnf("Multiple floating IP found. Take the first one of %s", floating) - return d.foundIP(floating[0]), nil - } - - if len(fixed) == 1 { - return d.foundIP(fixed[0]), nil - } else if len(fixed) > 1 { - log.Warnf("Multiple fixed IP found. Take the first one of %s", floating) - return d.foundIP(fixed[0]), nil - } - return "", fmt.Errorf("No IP found for the machine") } @@ -308,7 +301,18 @@ func (d *Driver) Create() error { if err := d.createMachine(); err != nil { return err } - if err := d.waitForInstanceToStart(); err != nil { + 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 err := d.installDocker(); err != nil { @@ -389,7 +393,7 @@ func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { args = []string{"sudo", "sh", "-c", fmt.Sprintf("'%s'", cmd)} } - log.Debug("Command: %s", args) + log.WithField("MachineId", d.MachineId).Debug("Command: %s", args) return ssh.GetSSHCommand(ip, d.SSHPort, d.SSHUser, d.sshKeyPath(), args...), nil } @@ -441,21 +445,13 @@ func (d *Driver) checkConfig() error { return nil } -func (d *Driver) foundIP(ip string) string { - log.WithFields(log.Fields{ - "IP": ip, - "MachineId": d.MachineId, - }).Debug("IP address found") - return ip -} - func (d *Driver) resolveIds() error { if d.NetworkName != "" { if err := d.initNetwork(); err != nil { return err } - networkId, err := d.client.GetNetworkId(d, d.NetworkName) + networkId, err := d.client.GetNetworkId(d) if err != nil { return err @@ -476,7 +472,7 @@ func (d *Driver) resolveIds() error { if err := d.initCompute(); err != nil { return err } - flavorId, err := d.client.GetFlavorId(d, d.FlavorName) + flavorId, err := d.client.GetFlavorId(d) if err != nil { return err @@ -497,7 +493,7 @@ func (d *Driver) resolveIds() error { if err := d.initCompute(); err != nil { return err } - imageId, err := d.client.GetImageId(d, d.ImageName) + imageId, err := d.client.GetImageId(d) if err != nil { return err @@ -514,6 +510,27 @@ func (d *Driver) resolveIds() error { }).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 } @@ -573,18 +590,94 @@ func (d *Driver) createMachine() error { return nil } -func (d *Driver) waitForInstanceToStart() error { - log.WithField("MachineId", d.MachineId).Debug("Waiting for the OpenStack instance to start...") +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...")