1package openstack 2 3import ( 4 "crypto/tls" 5 "crypto/x509" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "time" 10 11 "github.com/docker/machine/libmachine/log" 12 "github.com/docker/machine/libmachine/mcnutils" 13 "github.com/docker/machine/libmachine/version" 14 "github.com/rackspace/gophercloud" 15 "github.com/rackspace/gophercloud/openstack" 16 compute_ips "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" 17 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" 18 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop" 19 "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" 20 "github.com/rackspace/gophercloud/openstack/compute/v2/images" 21 "github.com/rackspace/gophercloud/openstack/compute/v2/servers" 22 "github.com/rackspace/gophercloud/openstack/identity/v2/tenants" 23 "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" 24 "github.com/rackspace/gophercloud/openstack/networking/v2/networks" 25 "github.com/rackspace/gophercloud/openstack/networking/v2/ports" 26 "github.com/rackspace/gophercloud/pagination" 27) 28 29type Client interface { 30 Authenticate(d *Driver) error 31 InitComputeClient(d *Driver) error 32 InitIdentityClient(d *Driver) error 33 InitNetworkClient(d *Driver) error 34 35 CreateInstance(d *Driver) (string, error) 36 GetInstanceState(d *Driver) (string, error) 37 StartInstance(d *Driver) error 38 StopInstance(d *Driver) error 39 RestartInstance(d *Driver) error 40 DeleteInstance(d *Driver) error 41 WaitForInstanceStatus(d *Driver, status string) error 42 GetInstanceIPAddresses(d *Driver) ([]IPAddress, error) 43 GetPublicKey(keyPairName string) ([]byte, error) 44 CreateKeyPair(d *Driver, name string, publicKey string) error 45 DeleteKeyPair(d *Driver, name string) error 46 GetNetworkID(d *Driver) (string, error) 47 GetFlavorID(d *Driver) (string, error) 48 GetImageID(d *Driver) (string, error) 49 AssignFloatingIP(d *Driver, floatingIP *FloatingIP) error 50 GetFloatingIPs(d *Driver) ([]FloatingIP, error) 51 GetFloatingIPPoolID(d *Driver) (string, error) 52 GetInstancePortID(d *Driver) (string, error) 53 GetTenantID(d *Driver) (string, error) 54} 55 56type GenericClient struct { 57 Provider *gophercloud.ProviderClient 58 Compute *gophercloud.ServiceClient 59 Identity *gophercloud.ServiceClient 60 Network *gophercloud.ServiceClient 61} 62 63func (c *GenericClient) CreateInstance(d *Driver) (string, error) { 64 serverOpts := servers.CreateOpts{ 65 Name: d.MachineName, 66 FlavorRef: d.FlavorId, 67 ImageRef: d.ImageId, 68 UserData: d.UserData, 69 SecurityGroups: d.SecurityGroups, 70 AvailabilityZone: d.AvailabilityZone, 71 } 72 if d.NetworkId != "" { 73 serverOpts.Networks = []servers.Network{ 74 { 75 UUID: d.NetworkId, 76 }, 77 } 78 } 79 80 log.Info("Creating machine...") 81 82 server, err := servers.Create(c.Compute, keypairs.CreateOptsExt{ 83 serverOpts, 84 d.KeyPairName, 85 }).Extract() 86 if err != nil { 87 return "", err 88 } 89 return server.ID, nil 90} 91 92const ( 93 Floating string = "floating" 94 Fixed string = "fixed" 95) 96 97type IPAddress struct { 98 Network string 99 AddressType string 100 Address string 101 Version int 102 Mac string 103} 104 105type FloatingIP struct { 106 Id string 107 Ip string 108 NetworkId string 109 PortId string 110 Pool string 111 MachineId string 112} 113 114func (c *GenericClient) GetInstanceState(d *Driver) (string, error) { 115 server, err := c.GetServerDetail(d) 116 if err != nil { 117 return "", err 118 } 119 return server.Status, nil 120} 121 122func (c *GenericClient) StartInstance(d *Driver) error { 123 if result := startstop.Start(c.Compute, d.MachineId); result.Err != nil { 124 return result.Err 125 } 126 return nil 127} 128 129func (c *GenericClient) StopInstance(d *Driver) error { 130 if result := startstop.Stop(c.Compute, d.MachineId); result.Err != nil { 131 return result.Err 132 } 133 return nil 134} 135 136func (c *GenericClient) RestartInstance(d *Driver) error { 137 if result := servers.Reboot(c.Compute, d.MachineId, servers.SoftReboot); result.Err != nil { 138 return result.Err 139 } 140 return nil 141} 142 143func (c *GenericClient) DeleteInstance(d *Driver) error { 144 if result := servers.Delete(c.Compute, d.MachineId); result.Err != nil { 145 return result.Err 146 } 147 return nil 148} 149 150func (c *GenericClient) WaitForInstanceStatus(d *Driver, status string) error { 151 return mcnutils.WaitForSpecificOrError(func() (bool, error) { 152 current, err := servers.Get(c.Compute, d.MachineId).Extract() 153 if err != nil { 154 return true, err 155 } 156 157 if current.Status == "ERROR" { 158 return true, fmt.Errorf("Instance creation failed. Instance is in ERROR state") 159 } 160 161 if current.Status == status { 162 return true, nil 163 } 164 165 return false, nil 166 }, (d.ActiveTimeout / 4), 4*time.Second) 167} 168 169func (c *GenericClient) GetInstanceIPAddresses(d *Driver) ([]IPAddress, error) { 170 server, err := c.GetServerDetail(d) 171 if err != nil { 172 return nil, err 173 } 174 addresses := []IPAddress{} 175 for network, networkAddresses := range server.Addresses { 176 for _, element := range networkAddresses.([]interface{}) { 177 address := element.(map[string]interface{}) 178 version, ok := address["version"].(float64) 179 if !ok { 180 // Assume IPv4 if no version present. 181 version = 4 182 } 183 184 addr := IPAddress{ 185 Network: network, 186 AddressType: Fixed, 187 Address: address["addr"].(string), 188 Version: int(version), 189 } 190 191 if tp, ok := address["OS-EXT-IPS:type"]; ok { 192 addr.AddressType = tp.(string) 193 } 194 if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { 195 addr.Mac = mac.(string) 196 } 197 198 addresses = append(addresses, addr) 199 } 200 } 201 202 return addresses, nil 203} 204 205func (c *GenericClient) GetNetworkID(d *Driver) (string, error) { 206 return c.getNetworkID(d, d.NetworkName) 207} 208 209func (c *GenericClient) GetFloatingIPPoolID(d *Driver) (string, error) { 210 return c.getNetworkID(d, d.FloatingIpPool) 211} 212 213func (c *GenericClient) getNetworkID(d *Driver, networkName string) (string, error) { 214 opts := networks.ListOpts{Name: networkName} 215 pager := networks.List(c.Network, opts) 216 networkID := "" 217 218 err := pager.EachPage(func(page pagination.Page) (bool, error) { 219 networkList, err := networks.ExtractNetworks(page) 220 if err != nil { 221 return false, err 222 } 223 224 for _, n := range networkList { 225 if n.Name == networkName { 226 networkID = n.ID 227 return false, nil 228 } 229 } 230 231 return true, nil 232 }) 233 234 return networkID, err 235} 236 237func (c *GenericClient) GetFlavorID(d *Driver) (string, error) { 238 pager := flavors.ListDetail(c.Compute, nil) 239 flavorID := "" 240 241 err := pager.EachPage(func(page pagination.Page) (bool, error) { 242 flavorList, err := flavors.ExtractFlavors(page) 243 if err != nil { 244 return false, err 245 } 246 247 for _, f := range flavorList { 248 if f.Name == d.FlavorName { 249 flavorID = f.ID 250 return false, nil 251 } 252 } 253 254 return true, nil 255 }) 256 257 return flavorID, err 258} 259 260func (c *GenericClient) GetImageID(d *Driver) (string, error) { 261 opts := images.ListOpts{Name: d.ImageName} 262 pager := images.ListDetail(c.Compute, opts) 263 imageID := "" 264 265 err := pager.EachPage(func(page pagination.Page) (bool, error) { 266 imageList, err := images.ExtractImages(page) 267 if err != nil { 268 return false, err 269 } 270 271 for _, i := range imageList { 272 if i.Name == d.ImageName { 273 imageID = i.ID 274 return false, nil 275 } 276 } 277 278 return true, nil 279 }) 280 281 return imageID, err 282} 283 284func (c *GenericClient) GetTenantID(d *Driver) (string, error) { 285 pager := tenants.List(c.Identity, nil) 286 tenantId := "" 287 288 err := pager.EachPage(func(page pagination.Page) (bool, error) { 289 tenantList, err := tenants.ExtractTenants(page) 290 if err != nil { 291 return false, err 292 } 293 294 for _, i := range tenantList { 295 if i.Name == d.TenantName { 296 tenantId = i.ID 297 return false, nil 298 } 299 } 300 301 return true, nil 302 }) 303 304 return tenantId, err 305} 306 307func (c *GenericClient) GetPublicKey(keyPairName string) ([]byte, error) { 308 kp, err := keypairs.Get(c.Compute, keyPairName).Extract() 309 if err != nil { 310 return nil, err 311 } 312 return []byte(kp.PublicKey), nil 313} 314 315func (c *GenericClient) CreateKeyPair(d *Driver, name string, publicKey string) error { 316 opts := keypairs.CreateOpts{ 317 Name: name, 318 PublicKey: publicKey, 319 } 320 if result := keypairs.Create(c.Compute, opts); result.Err != nil { 321 return result.Err 322 } 323 return nil 324} 325 326func (c *GenericClient) DeleteKeyPair(d *Driver, name string) error { 327 if result := keypairs.Delete(c.Compute, name); result.Err != nil { 328 return result.Err 329 } 330 return nil 331} 332 333func (c *GenericClient) GetServerDetail(d *Driver) (*servers.Server, error) { 334 server, err := servers.Get(c.Compute, d.MachineId).Extract() 335 if err != nil { 336 return nil, err 337 } 338 return server, nil 339} 340 341func (c *GenericClient) AssignFloatingIP(d *Driver, floatingIP *FloatingIP) error { 342 if d.ComputeNetwork { 343 return c.assignNovaFloatingIP(d, floatingIP) 344 } 345 return c.assignNeutronFloatingIP(d, floatingIP) 346} 347 348func (c *GenericClient) assignNovaFloatingIP(d *Driver, floatingIP *FloatingIP) error { 349 if floatingIP.Ip == "" { 350 f, err := compute_ips.Create(c.Compute, compute_ips.CreateOpts{ 351 Pool: d.FloatingIpPool, 352 }).Extract() 353 if err != nil { 354 return err 355 } 356 floatingIP.Ip = f.IP 357 floatingIP.Pool = f.Pool 358 } 359 return compute_ips.Associate(c.Compute, d.MachineId, floatingIP.Ip).Err 360} 361 362func (c *GenericClient) assignNeutronFloatingIP(d *Driver, floatingIP *FloatingIP) error { 363 portID, err := c.GetInstancePortID(d) 364 if err != nil { 365 return err 366 } 367 if floatingIP.Id == "" { 368 f, err := floatingips.Create(c.Network, floatingips.CreateOpts{ 369 FloatingNetworkID: d.FloatingIpPoolId, 370 PortID: portID, 371 }).Extract() 372 if err != nil { 373 return err 374 } 375 floatingIP.Id = f.ID 376 floatingIP.Ip = f.FloatingIP 377 floatingIP.NetworkId = f.FloatingNetworkID 378 floatingIP.PortId = f.PortID 379 return nil 380 } 381 _, err = floatingips.Update(c.Network, floatingIP.Id, floatingips.UpdateOpts{ 382 PortID: portID, 383 }).Extract() 384 if err != nil { 385 return err 386 } 387 return nil 388} 389 390func (c *GenericClient) GetFloatingIPs(d *Driver) ([]FloatingIP, error) { 391 if d.ComputeNetwork { 392 return c.getNovaNetworkFloatingIPs(d) 393 } 394 return c.getNeutronNetworkFloatingIPs(d) 395} 396 397func (c *GenericClient) getNovaNetworkFloatingIPs(d *Driver) ([]FloatingIP, error) { 398 pager := compute_ips.List(c.Compute) 399 400 ips := []FloatingIP{} 401 err := pager.EachPage(func(page pagination.Page) (continue_paging bool, err error) { 402 continue_paging, err = true, nil 403 ipListing, err := compute_ips.ExtractFloatingIPs(page) 404 405 for _, ip := range ipListing { 406 if ip.InstanceID == "" && ip.Pool == d.FloatingIpPool { 407 ips = append(ips, FloatingIP{ 408 Id: ip.ID, 409 Ip: ip.IP, 410 Pool: ip.Pool, 411 }) 412 } 413 } 414 return 415 }) 416 return ips, err 417} 418 419func (c *GenericClient) getNeutronNetworkFloatingIPs(d *Driver) ([]FloatingIP, error) { 420 log.Debug("Listing floating IPs", map[string]string{ 421 "FloatingNetworkId": d.FloatingIpPoolId, 422 "TenantID": d.TenantId, 423 }) 424 pager := floatingips.List(c.Network, floatingips.ListOpts{ 425 FloatingNetworkID: d.FloatingIpPoolId, 426 TenantID: d.TenantId, 427 }) 428 429 ips := []FloatingIP{} 430 err := pager.EachPage(func(page pagination.Page) (bool, error) { 431 floatingipList, err := floatingips.ExtractFloatingIPs(page) 432 if err != nil { 433 return false, err 434 } 435 for _, f := range floatingipList { 436 ips = append(ips, FloatingIP{ 437 Id: f.ID, 438 Ip: f.FloatingIP, 439 NetworkId: f.FloatingNetworkID, 440 PortId: f.PortID, 441 }) 442 } 443 return true, nil 444 }) 445 446 if err != nil { 447 return nil, err 448 } 449 return ips, nil 450} 451 452func (c *GenericClient) GetInstancePortID(d *Driver) (string, error) { 453 pager := ports.List(c.Network, ports.ListOpts{ 454 DeviceID: d.MachineId, 455 NetworkID: d.NetworkId, 456 }) 457 458 var portID string 459 err := pager.EachPage(func(page pagination.Page) (bool, error) { 460 portList, err := ports.ExtractPorts(page) 461 if err != nil { 462 return false, err 463 } 464 for _, port := range portList { 465 portID = port.ID 466 return false, nil 467 } 468 return true, nil 469 }) 470 471 if err != nil { 472 return "", err 473 } 474 return portID, nil 475} 476 477func (c *GenericClient) InitComputeClient(d *Driver) error { 478 if c.Compute != nil { 479 return nil 480 } 481 482 compute, err := openstack.NewComputeV2(c.Provider, gophercloud.EndpointOpts{ 483 Region: d.Region, 484 Availability: c.getEndpointType(d), 485 }) 486 if err != nil { 487 return err 488 } 489 c.Compute = compute 490 return nil 491} 492 493func (c *GenericClient) InitIdentityClient(d *Driver) error { 494 if c.Identity != nil { 495 return nil 496 } 497 498 identity := openstack.NewIdentityV2(c.Provider) 499 c.Identity = identity 500 return nil 501} 502 503func (c *GenericClient) InitNetworkClient(d *Driver) error { 504 if c.Network != nil { 505 return nil 506 } 507 508 network, err := openstack.NewNetworkV2(c.Provider, gophercloud.EndpointOpts{ 509 Region: d.Region, 510 Availability: c.getEndpointType(d), 511 }) 512 if err != nil { 513 return err 514 } 515 c.Network = network 516 return nil 517} 518 519func (c *GenericClient) getEndpointType(d *Driver) gophercloud.Availability { 520 switch d.EndpointType { 521 case "internalURL": 522 return gophercloud.AvailabilityInternal 523 case "adminURL": 524 return gophercloud.AvailabilityAdmin 525 } 526 return gophercloud.AvailabilityPublic 527} 528 529func (c *GenericClient) Authenticate(d *Driver) error { 530 if c.Provider != nil { 531 return nil 532 } 533 534 log.Debug("Authenticating...", map[string]interface{}{ 535 "AuthUrl": d.AuthUrl, 536 "Insecure": d.Insecure, 537 "CaCert": d.CaCert, 538 "DomainID": d.DomainID, 539 "DomainName": d.DomainName, 540 "Username": d.Username, 541 "TenantName": d.TenantName, 542 "TenantID": d.TenantId, 543 }) 544 545 opts := gophercloud.AuthOptions{ 546 IdentityEndpoint: d.AuthUrl, 547 DomainID: d.DomainID, 548 DomainName: d.DomainName, 549 Username: d.Username, 550 Password: d.Password, 551 TenantName: d.TenantName, 552 TenantID: d.TenantId, 553 AllowReauth: true, 554 } 555 556 provider, err := openstack.NewClient(opts.IdentityEndpoint) 557 if err != nil { 558 return err 559 } 560 561 c.Provider = provider 562 563 c.Provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%d", version.APIVersion)) 564 565 err = c.SetTLSConfig(d) 566 if err != nil { 567 return err 568 } 569 570 err = openstack.Authenticate(c.Provider, opts) 571 if err != nil { 572 return err 573 } 574 575 return nil 576} 577 578func (c *GenericClient) SetTLSConfig(d *Driver) error { 579 580 config := &tls.Config{} 581 config.InsecureSkipVerify = d.Insecure 582 583 if d.CaCert != "" { 584 // Use custom CA certificate(s) for root of trust 585 certpool := x509.NewCertPool() 586 pem, err := ioutil.ReadFile(d.CaCert) 587 if err != nil { 588 log.Error("Unable to read specified CA certificate(s)") 589 return err 590 } 591 592 ok := certpool.AppendCertsFromPEM(pem) 593 if !ok { 594 return fmt.Errorf("Ill-formed CA certificate(s) PEM file") 595 } 596 config.RootCAs = certpool 597 } 598 599 transport := &http.Transport{TLSClientConfig: config} 600 c.Provider.HTTPClient.Transport = transport 601 return nil 602} 603