This allows a user to pass a script or cloud-config file that can be used to initialize the server before the server starts up. This is particularly useful in enterprise cloud environments where the available images on openstack might not be fully compatible with the docker-machine provisioners. Signed-off-by: Wade Tandy <wtandy@bloomberg.net>
576 lines
14 KiB
Go
576 lines
14 KiB
Go
package openstack
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/docker/machine/libmachine/log"
|
|
"github.com/docker/machine/libmachine/mcnutils"
|
|
"github.com/docker/machine/libmachine/version"
|
|
"github.com/rackspace/gophercloud"
|
|
"github.com/rackspace/gophercloud/openstack"
|
|
compute_ips "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
|
"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/identity/v2/tenants"
|
|
"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
|
|
InitIdentityClient(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) error
|
|
GetInstanceIPAddresses(d *Driver) ([]IPAddress, error)
|
|
GetPublicKey(keyPairName string) ([]byte, 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) error
|
|
GetFloatingIPs(d *Driver) ([]FloatingIP, error)
|
|
GetFloatingIPPoolID(d *Driver) (string, error)
|
|
GetInstancePortID(d *Driver) (string, error)
|
|
GetTenantID(d *Driver) (string, error)
|
|
}
|
|
|
|
type GenericClient struct {
|
|
Provider *gophercloud.ProviderClient
|
|
Compute *gophercloud.ServiceClient
|
|
Identity *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,
|
|
UserData: d.UserData,
|
|
SecurityGroups: d.SecurityGroups,
|
|
AvailabilityZone: d.AvailabilityZone,
|
|
}
|
|
if d.NetworkId != "" {
|
|
serverOpts.Networks = []servers.Network{
|
|
{
|
|
UUID: d.NetworkId,
|
|
},
|
|
}
|
|
}
|
|
|
|
log.Info("Creating machine...")
|
|
|
|
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
|
|
Version int
|
|
Mac string
|
|
}
|
|
|
|
type FloatingIP struct {
|
|
Id string
|
|
Ip string
|
|
NetworkId string
|
|
PortId string
|
|
Pool string
|
|
MachineId 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) error {
|
|
return mcnutils.WaitForSpecificOrError(func() (bool, error) {
|
|
current, err := servers.Get(c.Compute, d.MachineId).Extract()
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
if current.Status == "ERROR" {
|
|
return true, fmt.Errorf("Instance creation failed. Instance is in ERROR state")
|
|
}
|
|
|
|
if current.Status == status {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}, (d.ActiveTimeout / 4), 4*time.Second)
|
|
}
|
|
|
|
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{})
|
|
version, ok := address["version"].(float64)
|
|
if !ok {
|
|
// Assume IPv4 if no version present.
|
|
version = 4
|
|
}
|
|
|
|
addr := IPAddress{
|
|
Network: network,
|
|
AddressType: Fixed,
|
|
Address: address["addr"].(string),
|
|
Version: int(version),
|
|
}
|
|
|
|
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) GetTenantID(d *Driver) (string, error) {
|
|
pager := tenants.List(c.Identity, nil)
|
|
tenantId := ""
|
|
|
|
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
tenantList, err := tenants.ExtractTenants(page)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, i := range tenantList {
|
|
if i.Name == d.TenantName {
|
|
tenantId = i.ID
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
|
|
return tenantId, err
|
|
}
|
|
|
|
func (c *GenericClient) GetPublicKey(keyPairName string) ([]byte, error) {
|
|
kp, err := keypairs.Get(c.Compute, keyPairName).Extract()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(kp.PublicKey), nil
|
|
}
|
|
|
|
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) error {
|
|
if d.ComputeNetwork {
|
|
return c.assignNovaFloatingIP(d, floatingIP)
|
|
}
|
|
return c.assignNeutronFloatingIP(d, floatingIP)
|
|
}
|
|
|
|
func (c *GenericClient) assignNovaFloatingIP(d *Driver, floatingIP *FloatingIP) error {
|
|
if floatingIP.Ip == "" {
|
|
f, err := compute_ips.Create(c.Compute, compute_ips.CreateOpts{
|
|
Pool: d.FloatingIpPool,
|
|
}).Extract()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
floatingIP.Ip = f.IP
|
|
floatingIP.Pool = f.Pool
|
|
}
|
|
return compute_ips.Associate(c.Compute, d.MachineId, floatingIP.Ip).Err
|
|
}
|
|
|
|
func (c *GenericClient) assignNeutronFloatingIP(d *Driver, floatingIP *FloatingIP) error {
|
|
portID, err := c.GetInstancePortID(d)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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) {
|
|
if d.ComputeNetwork {
|
|
return c.getNovaNetworkFloatingIPs(d)
|
|
}
|
|
return c.getNeutronNetworkFloatingIPs(d)
|
|
}
|
|
|
|
func (c *GenericClient) getNovaNetworkFloatingIPs(d *Driver) ([]FloatingIP, error) {
|
|
pager := compute_ips.List(c.Compute)
|
|
|
|
ips := []FloatingIP{}
|
|
err := pager.EachPage(func(page pagination.Page) (continue_paging bool, err error) {
|
|
continue_paging, err = true, nil
|
|
ipListing, err := compute_ips.ExtractFloatingIPs(page)
|
|
|
|
for _, ip := range ipListing {
|
|
if ip.InstanceID == "" && ip.Pool == d.FloatingIpPool {
|
|
ips = append(ips, FloatingIP{
|
|
Id: ip.ID,
|
|
Ip: ip.IP,
|
|
Pool: ip.Pool,
|
|
})
|
|
}
|
|
}
|
|
return
|
|
})
|
|
return ips, err
|
|
}
|
|
|
|
func (c *GenericClient) getNeutronNetworkFloatingIPs(d *Driver) ([]FloatingIP, error) {
|
|
log.Debug("Listing floating IPs", map[string]string{
|
|
"FloatingNetworkId": d.FloatingIpPoolId,
|
|
"TenantID": d.TenantId,
|
|
})
|
|
pager := floatingips.List(c.Network, floatingips.ListOpts{
|
|
FloatingNetworkID: d.FloatingIpPoolId,
|
|
TenantID: d.TenantId,
|
|
})
|
|
|
|
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) InitIdentityClient(d *Driver) error {
|
|
if c.Identity != nil {
|
|
return nil
|
|
}
|
|
|
|
identity := openstack.NewIdentityV2(c.Provider)
|
|
c.Identity = identity
|
|
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.Debug("Authenticating...", map[string]interface{}{
|
|
"AuthUrl": d.AuthUrl,
|
|
"Insecure": d.Insecure,
|
|
"DomainID": d.DomainID,
|
|
"DomainName": d.DomainName,
|
|
"Username": d.Username,
|
|
"TenantName": d.TenantName,
|
|
"TenantID": d.TenantId,
|
|
})
|
|
|
|
opts := gophercloud.AuthOptions{
|
|
IdentityEndpoint: d.AuthUrl,
|
|
DomainID: d.DomainID,
|
|
DomainName: d.DomainName,
|
|
Username: d.Username,
|
|
Password: d.Password,
|
|
TenantName: d.TenantName,
|
|
TenantID: d.TenantId,
|
|
AllowReauth: true,
|
|
}
|
|
|
|
provider, err := openstack.NewClient(opts.IdentityEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%d", version.APIVersion))
|
|
|
|
if d.Insecure {
|
|
// Configure custom TLS settings.
|
|
config := &tls.Config{InsecureSkipVerify: true}
|
|
transport := &http.Transport{TLSClientConfig: config}
|
|
provider.HTTPClient.Transport = transport
|
|
}
|
|
|
|
err = openstack.Authenticate(provider, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Provider = provider
|
|
|
|
return nil
|
|
}
|