github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/openstack/provider.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Stub provider for OpenStack, using goose will be implemented here 5 6 package openstack 7 8 import ( 9 "fmt" 10 "log" 11 "net/url" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/juju/errors" 17 "github.com/juju/loggo" 18 "github.com/juju/names" 19 "github.com/juju/utils" 20 "github.com/juju/utils/arch" 21 "github.com/juju/version" 22 "gopkg.in/goose.v1/cinder" 23 "gopkg.in/goose.v1/client" 24 gooseerrors "gopkg.in/goose.v1/errors" 25 "gopkg.in/goose.v1/identity" 26 "gopkg.in/goose.v1/nova" 27 28 "github.com/juju/juju/cloud" 29 "github.com/juju/juju/cloudconfig/instancecfg" 30 "github.com/juju/juju/cloudconfig/providerinit" 31 "github.com/juju/juju/constraints" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/imagemetadata" 35 "github.com/juju/juju/environs/instances" 36 "github.com/juju/juju/environs/simplestreams" 37 "github.com/juju/juju/environs/tags" 38 "github.com/juju/juju/instance" 39 "github.com/juju/juju/network" 40 "github.com/juju/juju/provider/common" 41 "github.com/juju/juju/state" 42 "github.com/juju/juju/status" 43 "github.com/juju/juju/tools" 44 ) 45 46 var logger = loggo.GetLogger("juju.provider.openstack") 47 48 type EnvironProvider struct { 49 environs.ProviderCredentials 50 Configurator ProviderConfigurator 51 FirewallerFactory FirewallerFactory 52 } 53 54 var _ environs.EnvironProvider = (*EnvironProvider)(nil) 55 56 var providerInstance *EnvironProvider = &EnvironProvider{ 57 OpenstackCredentials{}, 58 &defaultConfigurator{}, 59 &firewallerFactory{}, 60 } 61 62 var makeServiceURL = client.AuthenticatingClient.MakeServiceURL 63 64 // Use shortAttempt to poll for short-term events. 65 // TODO: This was kept to a long timeout because Nova needs more time than EC2. 66 // For example, HP Cloud takes around 9.1 seconds (10 samples) to return a 67 // BUILD(spawning) status. But storage delays are handled separately now, and 68 // perhaps other polling attempts can time out faster. 69 var shortAttempt = utils.AttemptStrategy{ 70 Total: 15 * time.Second, 71 Delay: 200 * time.Millisecond, 72 } 73 74 func (p EnvironProvider) Open(cfg *config.Config) (environs.Environ, error) { 75 logger.Infof("opening model %q", cfg.Name()) 76 e := new(Environ) 77 78 e.firewaller = p.FirewallerFactory.GetFirewaller(e) 79 e.configurator = p.Configurator 80 err := e.SetConfig(cfg) 81 if err != nil { 82 return nil, err 83 } 84 e.name = cfg.Name() 85 return e, nil 86 } 87 88 // RestrictedConfigAttributes is specified in the EnvironProvider interface. 89 func (p EnvironProvider) RestrictedConfigAttributes() []string { 90 return []string{"region", "auth-url", "auth-mode"} 91 } 92 93 // DetectRegions implements environs.CloudRegionDetector. 94 func (EnvironProvider) DetectRegions() ([]cloud.Region, error) { 95 // If OS_REGION_NAME and OS_AUTH_URL are both set, 96 // return return a region using them. 97 creds := identity.CredentialsFromEnv() 98 if creds.Region == "" { 99 return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set") 100 } 101 if creds.URL == "" { 102 return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set") 103 } 104 return []cloud.Region{{ 105 Name: creds.Region, 106 Endpoint: creds.URL, 107 }}, nil 108 } 109 110 // PrepareForCreateEnvironment is specified in the EnvironProvider interface. 111 func (p EnvironProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) { 112 return cfg, nil 113 } 114 115 // BootstrapConfig is specified in the EnvironProvider interface. 116 func (p EnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) { 117 // Add credentials to the configuration. 118 attrs := map[string]interface{}{ 119 "region": args.CloudRegion, 120 "auth-url": args.CloudEndpoint, 121 } 122 credentialAttrs := args.Credentials.Attributes() 123 switch authType := args.Credentials.AuthType(); authType { 124 case cloud.UserPassAuthType: 125 // TODO(axw) we need a way of saying to use legacy auth. 126 attrs["username"] = credentialAttrs["username"] 127 attrs["password"] = credentialAttrs["password"] 128 attrs["tenant-name"] = credentialAttrs["tenant-name"] 129 attrs["domain-name"] = credentialAttrs["domain-name"] 130 attrs["auth-mode"] = AuthUserPass 131 case cloud.AccessKeyAuthType: 132 attrs["access-key"] = credentialAttrs["access-key"] 133 attrs["secret-key"] = credentialAttrs["secret-key"] 134 attrs["tenant-name"] = credentialAttrs["tenant-name"] 135 attrs["auth-mode"] = AuthKeyPair 136 default: 137 return nil, errors.NotSupportedf("%q auth-type", authType) 138 } 139 140 // Set the default block-storage source. 141 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 142 attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType 143 } 144 145 cfg, err := args.Config.Apply(attrs) 146 if err != nil { 147 return nil, errors.Trace(err) 148 } 149 return p.PrepareForCreateEnvironment(cfg) 150 } 151 152 // PrepareForBootstrap is specified in the EnvironProvider interface. 153 func (p EnvironProvider) PrepareForBootstrap( 154 ctx environs.BootstrapContext, 155 cfg *config.Config, 156 ) (environs.Environ, error) { 157 e, err := p.Open(cfg) 158 if err != nil { 159 return nil, err 160 } 161 // Verify credentials. 162 if err := authenticateClient(e.(*Environ)); err != nil { 163 return nil, err 164 } 165 return e, nil 166 } 167 168 // MetadataLookupParams returns parameters which are used to query image metadata to 169 // find matching image information. 170 func (p EnvironProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 171 if region == "" { 172 return nil, errors.Errorf("region must be specified") 173 } 174 return &simplestreams.MetadataLookupParams{ 175 Region: region, 176 Architectures: arch.AllSupportedArches, 177 }, nil 178 } 179 180 func (p EnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { 181 m := make(map[string]string) 182 ecfg, err := p.newConfig(cfg) 183 if err != nil { 184 return nil, err 185 } 186 m["username"] = ecfg.username() 187 m["password"] = ecfg.password() 188 m["tenant-name"] = ecfg.tenantName() 189 return m, nil 190 } 191 192 func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) { 193 valid, err := p.Validate(cfg, nil) 194 if err != nil { 195 return nil, err 196 } 197 return &environConfig{valid, valid.UnknownAttrs()}, nil 198 } 199 200 type Environ struct { 201 common.SupportsUnitPlacementPolicy 202 203 name string 204 205 // archMutex gates access to supportedArchitectures 206 archMutex sync.Mutex 207 // supportedArchitectures caches the architectures 208 // for which images can be instantiated. 209 supportedArchitectures []string 210 211 ecfgMutex sync.Mutex 212 ecfgUnlocked *environConfig 213 client client.AuthenticatingClient 214 novaUnlocked *nova.Client 215 216 // keystoneImageDataSource caches the result of getKeystoneImageSource. 217 keystoneImageDataSourceMutex sync.Mutex 218 keystoneImageDataSource simplestreams.DataSource 219 220 // keystoneToolsDataSource caches the result of getKeystoneToolsSource. 221 keystoneToolsDataSourceMutex sync.Mutex 222 keystoneToolsDataSource simplestreams.DataSource 223 224 availabilityZonesMutex sync.Mutex 225 availabilityZones []common.AvailabilityZone 226 firewaller Firewaller 227 configurator ProviderConfigurator 228 } 229 230 var _ environs.Environ = (*Environ)(nil) 231 var _ simplestreams.HasRegion = (*Environ)(nil) 232 var _ state.Prechecker = (*Environ)(nil) 233 var _ state.InstanceDistributor = (*Environ)(nil) 234 var _ environs.InstanceTagger = (*Environ)(nil) 235 236 type openstackInstance struct { 237 e *Environ 238 instType *instances.InstanceType 239 arch *string 240 241 mu sync.Mutex 242 serverDetail *nova.ServerDetail 243 // floatingIP is non-nil iff use-floating-ip is true. 244 floatingIP *nova.FloatingIP 245 } 246 247 func (inst *openstackInstance) String() string { 248 return string(inst.Id()) 249 } 250 251 var _ instance.Instance = (*openstackInstance)(nil) 252 253 func (inst *openstackInstance) Refresh() error { 254 inst.mu.Lock() 255 defer inst.mu.Unlock() 256 server, err := inst.e.nova().GetServer(inst.serverDetail.Id) 257 if err != nil { 258 return err 259 } 260 inst.serverDetail = server 261 return nil 262 } 263 264 func (inst *openstackInstance) getServerDetail() *nova.ServerDetail { 265 inst.mu.Lock() 266 defer inst.mu.Unlock() 267 return inst.serverDetail 268 } 269 270 func (inst *openstackInstance) Id() instance.Id { 271 return instance.Id(inst.getServerDetail().Id) 272 } 273 274 func (inst *openstackInstance) Status() instance.InstanceStatus { 275 instStatus := inst.getServerDetail().Status 276 jujuStatus := status.StatusPending 277 switch instStatus { 278 case nova.StatusActive: 279 jujuStatus = status.StatusRunning 280 case nova.StatusError: 281 jujuStatus = status.StatusProvisioningError 282 case nova.StatusBuild, nova.StatusBuildSpawning, 283 nova.StatusDeleted, nova.StatusHardReboot, 284 nova.StatusPassword, nova.StatusReboot, 285 nova.StatusRebuild, nova.StatusRescue, 286 nova.StatusResize, nova.StatusShutoff, 287 nova.StatusSuspended, nova.StatusVerifyResize: 288 jujuStatus = status.StatusEmpty 289 case nova.StatusUnknown: 290 jujuStatus = status.StatusUnknown 291 default: 292 jujuStatus = status.StatusEmpty 293 } 294 return instance.InstanceStatus{ 295 Status: jujuStatus, 296 Message: instStatus, 297 } 298 } 299 300 func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics { 301 hc := &instance.HardwareCharacteristics{Arch: inst.arch} 302 if inst.instType != nil { 303 hc.Mem = &inst.instType.Mem 304 // openstack is special in that a 0-size root disk means that 305 // the root disk will result in an instance with a root disk 306 // the same size as the image that created it, so we just set 307 // the HardwareCharacteristics to nil to signal that we don't 308 // know what the correct size is. 309 if inst.instType.RootDisk == 0 { 310 hc.RootDisk = nil 311 } else { 312 hc.RootDisk = &inst.instType.RootDisk 313 } 314 hc.CpuCores = &inst.instType.CpuCores 315 hc.CpuPower = inst.instType.CpuPower 316 // tags not currently supported on openstack 317 } 318 hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone 319 return hc 320 } 321 322 // getAddresses returns the existing server information on addresses, 323 // but fetches the details over the api again if no addresses exist. 324 func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) { 325 addrs := inst.getServerDetail().Addresses 326 if len(addrs) == 0 { 327 server, err := inst.e.nova().GetServer(string(inst.Id())) 328 if err != nil { 329 return nil, err 330 } 331 addrs = server.Addresses 332 } 333 return addrs, nil 334 } 335 336 // Addresses implements network.Addresses() returning generic address 337 // details for the instances, and calling the openstack api if needed. 338 func (inst *openstackInstance) Addresses() ([]network.Address, error) { 339 addresses, err := inst.getAddresses() 340 if err != nil { 341 return nil, err 342 } 343 var floatingIP string 344 if inst.floatingIP != nil && inst.floatingIP.IP != "" { 345 floatingIP = inst.floatingIP.IP 346 logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP) 347 } 348 return convertNovaAddresses(floatingIP, addresses), nil 349 } 350 351 // convertNovaAddresses returns nova addresses in generic format 352 func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address { 353 var machineAddresses []network.Address 354 if publicIP != "" { 355 publicAddr := network.NewScopedAddress(publicIP, network.ScopePublic) 356 machineAddresses = append(machineAddresses, publicAddr) 357 } 358 // TODO(gz) Network ordering may be significant but is not preserved by 359 // the map, see lp:1188126 for example. That could potentially be fixed 360 // in goose, or left to be derived by other means. 361 for netName, ips := range addresses { 362 networkScope := network.ScopeUnknown 363 if netName == "public" { 364 networkScope = network.ScopePublic 365 } 366 for _, address := range ips { 367 // If this address has already been added as a floating IP, skip it. 368 if publicIP == address.Address { 369 continue 370 } 371 // Assume IPv4 unless specified otherwise 372 addrtype := network.IPv4Address 373 if address.Version == 6 { 374 addrtype = network.IPv6Address 375 } 376 machineAddr := network.NewScopedAddress(address.Address, networkScope) 377 if machineAddr.Type != addrtype { 378 logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype) 379 } 380 machineAddresses = append(machineAddresses, machineAddr) 381 } 382 } 383 return machineAddresses 384 } 385 386 func (inst *openstackInstance) OpenPorts(machineId string, ports []network.PortRange) error { 387 return inst.e.firewaller.OpenInstancePorts(inst, machineId, ports) 388 } 389 390 func (inst *openstackInstance) ClosePorts(machineId string, ports []network.PortRange) error { 391 return inst.e.firewaller.CloseInstancePorts(inst, machineId, ports) 392 } 393 394 func (inst *openstackInstance) Ports(machineId string) ([]network.PortRange, error) { 395 return inst.e.firewaller.InstancePorts(inst, machineId) 396 } 397 398 func (e *Environ) ecfg() *environConfig { 399 e.ecfgMutex.Lock() 400 ecfg := e.ecfgUnlocked 401 e.ecfgMutex.Unlock() 402 return ecfg 403 } 404 405 func (e *Environ) nova() *nova.Client { 406 e.ecfgMutex.Lock() 407 nova := e.novaUnlocked 408 e.ecfgMutex.Unlock() 409 return nova 410 } 411 412 // SupportedArchitectures is specified on the EnvironCapability interface. 413 func (e *Environ) SupportedArchitectures() ([]string, error) { 414 e.archMutex.Lock() 415 defer e.archMutex.Unlock() 416 if e.supportedArchitectures != nil { 417 return e.supportedArchitectures, nil 418 } 419 // Create a filter to get all images from our region and for the correct stream. 420 cloudSpec, err := e.Region() 421 if err != nil { 422 return nil, err 423 } 424 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 425 CloudSpec: cloudSpec, 426 Stream: e.Config().ImageStream(), 427 }) 428 e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint) 429 return e.supportedArchitectures, err 430 } 431 432 var unsupportedConstraints = []string{ 433 constraints.Tags, 434 constraints.CpuPower, 435 } 436 437 // ConstraintsValidator is defined on the Environs interface. 438 func (e *Environ) ConstraintsValidator() (constraints.Validator, error) { 439 validator := constraints.NewValidator() 440 validator.RegisterConflicts( 441 []string{constraints.InstanceType}, 442 []string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores}) 443 validator.RegisterUnsupported(unsupportedConstraints) 444 supportedArches, err := e.SupportedArchitectures() 445 if err != nil { 446 return nil, err 447 } 448 validator.RegisterVocabulary(constraints.Arch, supportedArches) 449 novaClient := e.nova() 450 flavors, err := novaClient.ListFlavorsDetail() 451 if err != nil { 452 return nil, err 453 } 454 instTypeNames := make([]string, len(flavors)) 455 for i, flavor := range flavors { 456 instTypeNames[i] = flavor.Name 457 } 458 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 459 validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"}) 460 return validator, nil 461 } 462 463 var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones 464 465 type openstackAvailabilityZone struct { 466 nova.AvailabilityZone 467 } 468 469 func (z *openstackAvailabilityZone) Name() string { 470 return z.AvailabilityZone.Name 471 } 472 473 func (z *openstackAvailabilityZone) Available() bool { 474 return z.AvailabilityZone.State.Available 475 } 476 477 // AvailabilityZones returns a slice of availability zones. 478 func (e *Environ) AvailabilityZones() ([]common.AvailabilityZone, error) { 479 e.availabilityZonesMutex.Lock() 480 defer e.availabilityZonesMutex.Unlock() 481 if e.availabilityZones == nil { 482 zones, err := novaListAvailabilityZones(e.nova()) 483 if gooseerrors.IsNotImplemented(err) { 484 return nil, errors.NotImplementedf("availability zones") 485 } 486 if err != nil { 487 return nil, err 488 } 489 e.availabilityZones = make([]common.AvailabilityZone, len(zones)) 490 for i, z := range zones { 491 e.availabilityZones[i] = &openstackAvailabilityZone{z} 492 } 493 } 494 return e.availabilityZones, nil 495 } 496 497 // InstanceAvailabilityZoneNames returns the availability zone names for each 498 // of the specified instances. 499 func (e *Environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) { 500 instances, err := e.Instances(ids) 501 if err != nil && err != environs.ErrPartialInstances { 502 return nil, err 503 } 504 zones := make([]string, len(instances)) 505 for i, inst := range instances { 506 if inst == nil { 507 continue 508 } 509 zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone 510 } 511 return zones, err 512 } 513 514 type openstackPlacement struct { 515 availabilityZone nova.AvailabilityZone 516 } 517 518 func (e *Environ) parsePlacement(placement string) (*openstackPlacement, error) { 519 pos := strings.IndexRune(placement, '=') 520 if pos == -1 { 521 return nil, errors.Errorf("unknown placement directive: %v", placement) 522 } 523 switch key, value := placement[:pos], placement[pos+1:]; key { 524 case "zone": 525 availabilityZone := value 526 zones, err := e.AvailabilityZones() 527 if err != nil { 528 return nil, err 529 } 530 for _, z := range zones { 531 if z.Name() == availabilityZone { 532 return &openstackPlacement{ 533 z.(*openstackAvailabilityZone).AvailabilityZone, 534 }, nil 535 } 536 } 537 return nil, errors.Errorf("invalid availability zone %q", availabilityZone) 538 } 539 return nil, errors.Errorf("unknown placement directive: %v", placement) 540 } 541 542 // PrecheckInstance is defined on the state.Prechecker interface. 543 func (e *Environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { 544 if placement != "" { 545 if _, err := e.parsePlacement(placement); err != nil { 546 return err 547 } 548 } 549 if !cons.HasInstanceType() { 550 return nil 551 } 552 // Constraint has an instance-type constraint so let's see if it is valid. 553 novaClient := e.nova() 554 flavors, err := novaClient.ListFlavorsDetail() 555 if err != nil { 556 return err 557 } 558 for _, flavor := range flavors { 559 if flavor.Name == *cons.InstanceType { 560 return nil 561 } 562 } 563 return errors.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType) 564 } 565 566 func (e *Environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 567 // The client's authentication may have been reset when finding tools if the agent-version 568 // attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated. 569 // An authenticated client is needed for the URL() call below. 570 if err := authenticateClient(e); err != nil { 571 return nil, err 572 } 573 return common.Bootstrap(ctx, e, args) 574 } 575 576 func (e *Environ) ControllerInstances() ([]instance.Id, error) { 577 // Find all instances tagged with tags.JujuIsController. 578 instances, err := e.AllInstances() 579 if err != nil { 580 return nil, errors.Trace(err) 581 } 582 ids := make([]instance.Id, 0, 1) 583 for _, instance := range instances { 584 detail := instance.(*openstackInstance).getServerDetail() 585 if detail.Metadata[tags.JujuIsController] == "true" { 586 ids = append(ids, instance.Id()) 587 } 588 } 589 if len(ids) == 0 { 590 return nil, environs.ErrNoInstances 591 } 592 return ids, nil 593 } 594 595 func (e *Environ) Config() *config.Config { 596 return e.ecfg().Config 597 } 598 599 func newCredentials(ecfg *environConfig) (identity.Credentials, identity.AuthMode) { 600 cred := identity.Credentials{ 601 User: ecfg.username(), 602 Secrets: ecfg.password(), 603 Region: ecfg.region(), 604 TenantName: ecfg.tenantName(), 605 URL: ecfg.authURL(), 606 DomainName: ecfg.domainName(), 607 } 608 // authModeCfg has already been validated so we know it's one of the values below. 609 var authMode identity.AuthMode 610 switch AuthMode(ecfg.authMode()) { 611 case AuthLegacy: 612 authMode = identity.AuthLegacy 613 case AuthUserPass: 614 authMode = identity.AuthUserPass 615 if cred.DomainName != "" { 616 authMode = identity.AuthUserPassV3 617 } 618 case AuthKeyPair: 619 authMode = identity.AuthKeyPair 620 cred.User = ecfg.accessKey() 621 cred.Secrets = ecfg.secretKey() 622 } 623 624 return cred, authMode 625 } 626 627 func determineBestClient( 628 options identity.AuthOptions, 629 client client.AuthenticatingClient, 630 cred identity.Credentials, 631 newClient func(*identity.Credentials, identity.AuthMode, *log.Logger) client.AuthenticatingClient, 632 ) client.AuthenticatingClient { 633 for _, option := range options { 634 if option.Mode != identity.AuthUserPassV3 { 635 continue 636 } 637 cred.URL = option.Endpoint 638 v3client := newClient(&cred, identity.AuthUserPassV3, nil) 639 // V3 being advertised is not necessaritly a guarantee that it will 640 // work. 641 err := v3client.Authenticate() 642 if err == nil { 643 return v3client 644 } 645 } 646 return client 647 } 648 649 func authClient(ecfg *environConfig) (client.AuthenticatingClient, error) { 650 651 identityClientVersion, err := identityClientVersion(ecfg.authURL()) 652 if err != nil { 653 return nil, errors.Annotate(err, "cannot create a client") 654 } 655 cred, authMode := newCredentials(ecfg) 656 657 newClient := client.NewClient 658 if ecfg.SSLHostnameVerification() == false { 659 newClient = client.NewNonValidatingClient 660 } 661 client := newClient(&cred, authMode, nil) 662 663 // before returning, lets make sure that we want to have AuthMode 664 // AuthUserPass instead of its V3 counterpart. 665 if authMode == identity.AuthUserPass && (identityClientVersion == -1 || identityClientVersion == 3) { 666 options, err := client.IdentityAuthOptions() 667 if err != nil { 668 logger.Errorf("cannot determine available auth versions %v", err) 669 } else { 670 client = determineBestClient(options, client, cred, newClient) 671 } 672 } 673 674 // By default, the client requires "compute" and 675 // "object-store". Juju only requires "compute". 676 client.SetRequiredServiceTypes([]string{"compute"}) 677 return client, nil 678 } 679 680 var authenticateClient = func(e *Environ) error { 681 err := e.client.Authenticate() 682 if err != nil { 683 // Log the error in case there are any useful hints, 684 // but provide a readable and helpful error message 685 // to the user. 686 logger.Debugf("authentication failed: %v", err) 687 return errors.New(`authentication failed. 688 689 Please ensure the credentials are correct. A common mistake is 690 to specify the wrong tenant. Use the OpenStack "project" name 691 for tenant-name in your model configuration.`) 692 } 693 return nil 694 } 695 696 func (e *Environ) SetConfig(cfg *config.Config) error { 697 ecfg, err := providerInstance.newConfig(cfg) 698 if err != nil { 699 return err 700 } 701 // At this point, the authentication method config value has been validated so we extract it's value here 702 // to avoid having to validate again each time when creating the OpenStack client. 703 e.ecfgMutex.Lock() 704 defer e.ecfgMutex.Unlock() 705 e.ecfgUnlocked = ecfg 706 707 client, err := authClient(ecfg) 708 if err != nil { 709 return errors.Annotate(err, "cannot set config") 710 } 711 e.client = client 712 e.novaUnlocked = nova.New(e.client) 713 return nil 714 } 715 716 func identityClientVersion(authURL string) (int, error) { 717 url, err := url.Parse(authURL) 718 if err != nil { 719 return -1, err 720 } else if url.Path == "" { 721 return -1, err 722 } 723 // The last part of the path should be the version #. 724 // Example: https://keystone.foo:443/v3/ 725 logger.Debugf("authURL: %s", authURL) 726 versionNumStr := url.Path[2:] 727 if versionNumStr[len(versionNumStr)-1] == '/' { 728 versionNumStr = versionNumStr[:len(versionNumStr)-1] 729 } 730 major, _, err := version.ParseMajorMinor(versionNumStr) 731 return major, err 732 } 733 734 // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that 735 // returns a DataSource using the "product-streams" keystone URL. 736 func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) { 737 e, ok := env.(*Environ) 738 if !ok { 739 return nil, errors.NotSupportedf("non-openstack model") 740 } 741 return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams") 742 } 743 744 // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that 745 // returns a DataSource using the "juju-tools" keystone URL. 746 func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) { 747 e, ok := env.(*Environ) 748 if !ok { 749 return nil, errors.NotSupportedf("non-openstack model") 750 } 751 return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools") 752 } 753 754 func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) { 755 mu.Lock() 756 defer mu.Unlock() 757 if *datasource != nil { 758 return *datasource, nil 759 } 760 if !e.client.IsAuthenticated() { 761 if err := authenticateClient(e); err != nil { 762 return nil, err 763 } 764 } 765 766 url, err := makeServiceURL(e.client, keystoneName, nil) 767 if err != nil { 768 return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err)) 769 } 770 verify := utils.VerifySSLHostnames 771 if !e.Config().SSLHostnameVerification() { 772 verify = utils.NoVerifySSLHostnames 773 } 774 *datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify, simplestreams.SPECIFIC_CLOUD_DATA, false) 775 return *datasource, nil 776 } 777 778 // resolveNetwork takes either a network id or label and returns a network id 779 func (e *Environ) resolveNetwork(networkName string) (string, error) { 780 if utils.IsValidUUIDString(networkName) { 781 // Network id supplied, assume valid as boot will fail if not 782 return networkName, nil 783 } 784 // Network label supplied, resolve to a network id 785 networks, err := e.nova().ListNetworks() 786 if err != nil { 787 return "", err 788 } 789 var networkIds = []string{} 790 for _, network := range networks { 791 if network.Label == networkName { 792 networkIds = append(networkIds, network.Id) 793 } 794 } 795 switch len(networkIds) { 796 case 1: 797 return networkIds[0], nil 798 case 0: 799 return "", errors.Errorf("No networks exist with label %q", networkName) 800 } 801 return "", errors.Errorf("Multiple networks with label %q: %v", networkName, networkIds) 802 } 803 804 // allocatePublicIP tries to find an available floating IP address, or 805 // allocates a new one, returning it, or an error 806 func (e *Environ) allocatePublicIP() (*nova.FloatingIP, error) { 807 fips, err := e.nova().ListFloatingIPs() 808 if err != nil { 809 return nil, err 810 } 811 var newfip *nova.FloatingIP 812 for _, fip := range fips { 813 newfip = &fip 814 if fip.InstanceId != nil && *fip.InstanceId != "" { 815 // unavailable, skip 816 newfip = nil 817 continue 818 } else { 819 logger.Debugf("found unassigned public ip: %v", newfip.IP) 820 // unassigned, we can use it 821 return newfip, nil 822 } 823 } 824 if newfip == nil { 825 // allocate a new IP and use it 826 newfip, err = e.nova().AllocateFloatingIP() 827 if err != nil { 828 return nil, err 829 } 830 logger.Debugf("allocated new public IP: %v", newfip.IP) 831 } 832 return newfip, nil 833 } 834 835 // assignPublicIP tries to assign the given floating IP address to the 836 // specified server, or returns an error. 837 func (e *Environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) { 838 if fip == nil { 839 return errors.Errorf("cannot assign a nil public IP to %q", serverId) 840 } 841 if fip.InstanceId != nil && *fip.InstanceId == serverId { 842 // IP already assigned, nothing to do 843 return nil 844 } 845 // At startup nw_info is not yet cached so this may fail 846 // temporarily while the server is being built 847 for a := common.LongAttempt.Start(); a.Next(); { 848 err = e.nova().AddServerFloatingIP(serverId, fip.IP) 849 if err == nil { 850 return nil 851 } 852 } 853 return err 854 } 855 856 // DistributeInstances implements the state.InstanceDistributor policy. 857 func (e *Environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) { 858 return common.DistributeInstances(e, candidates, distributionGroup) 859 } 860 861 var availabilityZoneAllocations = common.AvailabilityZoneAllocations 862 863 // MaintainInstance is specified in the InstanceBroker interface. 864 func (*Environ) MaintainInstance(args environs.StartInstanceParams) error { 865 return nil 866 } 867 868 // StartInstance is specified in the InstanceBroker interface. 869 func (e *Environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 870 var availabilityZones []string 871 if args.Placement != "" { 872 placement, err := e.parsePlacement(args.Placement) 873 if err != nil { 874 return nil, err 875 } 876 if !placement.availabilityZone.State.Available { 877 return nil, errors.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name) 878 } 879 availabilityZones = append(availabilityZones, placement.availabilityZone.Name) 880 } 881 882 // If no availability zone is specified, then automatically spread across 883 // the known zones for optimal spread across the instance distribution 884 // group. 885 if len(availabilityZones) == 0 { 886 var group []instance.Id 887 var err error 888 if args.DistributionGroup != nil { 889 group, err = args.DistributionGroup() 890 if err != nil { 891 return nil, err 892 } 893 } 894 zoneInstances, err := availabilityZoneAllocations(e, group) 895 if errors.IsNotImplemented(err) { 896 // Availability zones are an extension, so we may get a 897 // not implemented error; ignore these. 898 } else if err != nil { 899 return nil, err 900 } else { 901 for _, zone := range zoneInstances { 902 availabilityZones = append(availabilityZones, zone.ZoneName) 903 } 904 } 905 if len(availabilityZones) == 0 { 906 // No explicitly selectable zones available, so use an unspecified zone. 907 availabilityZones = []string{""} 908 } 909 } 910 911 series := args.Tools.OneSeries() 912 arches := args.Tools.Arches() 913 spec, err := findInstanceSpec(e, &instances.InstanceConstraint{ 914 Region: e.ecfg().region(), 915 Series: series, 916 Arches: arches, 917 Constraints: args.Constraints, 918 }, args.ImageMetadata) 919 if err != nil { 920 return nil, err 921 } 922 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 923 if err != nil { 924 return nil, errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) 925 } 926 927 if err := args.InstanceConfig.SetTools(tools); err != nil { 928 return nil, errors.Trace(err) 929 } 930 931 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil { 932 return nil, err 933 } 934 cloudcfg, err := e.configurator.GetCloudConfig(args) 935 if err != nil { 936 return nil, errors.Trace(err) 937 } 938 userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, OpenstackRenderer{}) 939 if err != nil { 940 return nil, errors.Annotate(err, "cannot make user data") 941 } 942 logger.Debugf("openstack user data; %d bytes", len(userData)) 943 944 var networks = e.firewaller.InitialNetworks() 945 usingNetwork := e.ecfg().network() 946 if usingNetwork != "" { 947 networkId, err := e.resolveNetwork(usingNetwork) 948 if err != nil { 949 return nil, err 950 } 951 logger.Debugf("using network id %q", networkId) 952 networks = append(networks, nova.ServerNetworks{NetworkId: networkId}) 953 } 954 withPublicIP := e.ecfg().useFloatingIP() 955 var publicIP *nova.FloatingIP 956 if withPublicIP { 957 logger.Debugf("allocating public IP address for openstack node") 958 if fip, err := e.allocatePublicIP(); err != nil { 959 return nil, errors.Annotate(err, "cannot allocate a public IP as needed") 960 } else { 961 publicIP = fip 962 logger.Infof("allocated public IP %s", publicIP.IP) 963 } 964 } 965 966 cfg := e.Config() 967 var groupNames = make([]nova.SecurityGroupName, 0) 968 groups, err := e.firewaller.SetUpGroups(args.InstanceConfig.MachineId, cfg.APIPort()) 969 if err != nil { 970 return nil, errors.Annotate(err, "cannot set up groups") 971 } 972 973 for _, g := range groups { 974 groupNames = append(groupNames, nova.SecurityGroupName{g.Name}) 975 } 976 machineName := resourceName( 977 names.NewMachineTag(args.InstanceConfig.MachineId), 978 e.Config().UUID(), 979 ) 980 981 tryStartNovaInstance := func( 982 attempts utils.AttemptStrategy, 983 client *nova.Client, 984 instanceOpts nova.RunServerOpts, 985 ) (server *nova.Entity, err error) { 986 for a := attempts.Start(); a.Next(); { 987 server, err = client.RunServer(instanceOpts) 988 if err == nil || gooseerrors.IsNotFound(err) == false { 989 break 990 } 991 } 992 return server, err 993 } 994 995 tryStartNovaInstanceAcrossAvailZones := func( 996 attempts utils.AttemptStrategy, 997 client *nova.Client, 998 instanceOpts nova.RunServerOpts, 999 availabilityZones []string, 1000 ) (server *nova.Entity, err error) { 1001 for _, zone := range availabilityZones { 1002 instanceOpts.AvailabilityZone = zone 1003 e.configurator.ModifyRunServerOptions(&instanceOpts) 1004 server, err = tryStartNovaInstance(attempts, client, instanceOpts) 1005 if err == nil || isNoValidHostsError(err) == false { 1006 break 1007 } 1008 1009 logger.Infof("no valid hosts available in zone %q, trying another availability zone", zone) 1010 } 1011 1012 if err != nil { 1013 err = errors.Annotate(err, "cannot run instance") 1014 } 1015 1016 return server, err 1017 } 1018 1019 var opts = nova.RunServerOpts{ 1020 Name: machineName, 1021 FlavorId: spec.InstanceType.Id, 1022 ImageId: spec.Image.Id, 1023 UserData: userData, 1024 SecurityGroupNames: groupNames, 1025 Networks: networks, 1026 Metadata: args.InstanceConfig.Tags, 1027 } 1028 server, err := tryStartNovaInstanceAcrossAvailZones(shortAttempt, e.nova(), opts, availabilityZones) 1029 if err != nil { 1030 return nil, errors.Trace(err) 1031 } 1032 1033 detail, err := e.nova().GetServer(server.Id) 1034 if err != nil { 1035 return nil, errors.Annotate(err, "cannot get started instance") 1036 } 1037 1038 inst := &openstackInstance{ 1039 e: e, 1040 serverDetail: detail, 1041 arch: &spec.Image.Arch, 1042 instType: &spec.InstanceType, 1043 } 1044 logger.Infof("started instance %q", inst.Id()) 1045 if withPublicIP { 1046 if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil { 1047 if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil { 1048 // ignore the failure at this stage, just log it 1049 logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err) 1050 } 1051 return nil, errors.Annotatef(err, "cannot assign public address %s to instance %q", publicIP.IP, inst.Id()) 1052 } 1053 inst.floatingIP = publicIP 1054 logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id()) 1055 } 1056 return &environs.StartInstanceResult{ 1057 Instance: inst, 1058 Hardware: inst.hardwareCharacteristics(), 1059 }, nil 1060 } 1061 1062 func isNoValidHostsError(err error) bool { 1063 if gooseErr, ok := err.(gooseerrors.Error); ok { 1064 if cause := gooseErr.Cause(); cause != nil { 1065 return strings.Contains(cause.Error(), "No valid host was found") 1066 } 1067 } 1068 return false 1069 } 1070 1071 func (e *Environ) StopInstances(ids ...instance.Id) error { 1072 // If in instance firewall mode, gather the security group names. 1073 securityGroupNames, err := e.firewaller.GetSecurityGroups(ids...) 1074 if err == environs.ErrNoInstances { 1075 return nil 1076 } 1077 if err != nil { 1078 return err 1079 } 1080 logger.Debugf("terminating instances %v", ids) 1081 if err := e.terminateInstances(ids); err != nil { 1082 return err 1083 } 1084 if securityGroupNames != nil { 1085 return e.deleteSecurityGroups(securityGroupNames) 1086 } 1087 return nil 1088 } 1089 1090 func (e *Environ) isAliveServer(server nova.ServerDetail) bool { 1091 switch server.Status { 1092 // HPCloud uses "BUILD(spawning)" as an intermediate BUILD state 1093 // once networking is available. 1094 case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended: 1095 return true 1096 } 1097 return false 1098 } 1099 1100 func (e *Environ) listServers(ids []instance.Id) ([]nova.ServerDetail, error) { 1101 wantedServers := make([]nova.ServerDetail, 0, len(ids)) 1102 if len(ids) == 1 { 1103 // Common case, single instance, may return NotFound 1104 var maybeServer *nova.ServerDetail 1105 maybeServer, err := e.nova().GetServer(string(ids[0])) 1106 if err != nil { 1107 return nil, err 1108 } 1109 // Only return server details if it is currently alive 1110 if maybeServer != nil && e.isAliveServer(*maybeServer) { 1111 wantedServers = append(wantedServers, *maybeServer) 1112 } 1113 return wantedServers, nil 1114 } 1115 // List all servers that may be in the environment 1116 servers, err := e.nova().ListServersDetail(e.machinesFilter()) 1117 if err != nil { 1118 return nil, err 1119 } 1120 // Create a set of the ids of servers that are wanted 1121 idSet := make(map[string]struct{}, len(ids)) 1122 for _, id := range ids { 1123 idSet[string(id)] = struct{}{} 1124 } 1125 // Return only servers with the wanted ids that are currently alive 1126 for _, server := range servers { 1127 if _, ok := idSet[server.Id]; ok && e.isAliveServer(server) { 1128 wantedServers = append(wantedServers, server) 1129 } 1130 } 1131 return wantedServers, nil 1132 } 1133 1134 // updateFloatingIPAddresses updates the instances with any floating IP address 1135 // that have been assigned to those instances. 1136 func (e *Environ) updateFloatingIPAddresses(instances map[string]instance.Instance) error { 1137 fips, err := e.nova().ListFloatingIPs() 1138 if err != nil { 1139 return err 1140 } 1141 for _, fip := range fips { 1142 if fip.InstanceId != nil && *fip.InstanceId != "" { 1143 instId := *fip.InstanceId 1144 if inst, ok := instances[instId]; ok { 1145 instFip := fip 1146 inst.(*openstackInstance).floatingIP = &instFip 1147 } 1148 } 1149 } 1150 return nil 1151 } 1152 1153 func (e *Environ) Instances(ids []instance.Id) ([]instance.Instance, error) { 1154 if len(ids) == 0 { 1155 return nil, nil 1156 } 1157 // Make a series of requests to cope with eventual consistency. 1158 // Each request will attempt to add more instances to the requested 1159 // set. 1160 var foundServers []nova.ServerDetail 1161 for a := shortAttempt.Start(); a.Next(); { 1162 var err error 1163 foundServers, err = e.listServers(ids) 1164 if err != nil { 1165 logger.Debugf("error listing servers: %v", err) 1166 if !gooseerrors.IsNotFound(err) { 1167 return nil, err 1168 } 1169 } 1170 if len(foundServers) == len(ids) { 1171 break 1172 } 1173 } 1174 logger.Tracef("%d/%d live servers found", len(foundServers), len(ids)) 1175 if len(foundServers) == 0 { 1176 return nil, environs.ErrNoInstances 1177 } 1178 1179 instsById := make(map[string]instance.Instance, len(foundServers)) 1180 for i, server := range foundServers { 1181 // TODO(wallyworld): lookup the flavor details to fill in the 1182 // instance type data 1183 instsById[server.Id] = &openstackInstance{ 1184 e: e, 1185 serverDetail: &foundServers[i], 1186 } 1187 } 1188 1189 // Update the instance structs with any floating IP address that has been assigned to the instance. 1190 if e.ecfg().useFloatingIP() { 1191 if err := e.updateFloatingIPAddresses(instsById); err != nil { 1192 return nil, err 1193 } 1194 } 1195 1196 insts := make([]instance.Instance, len(ids)) 1197 var err error 1198 for i, id := range ids { 1199 if inst := instsById[string(id)]; inst != nil { 1200 insts[i] = inst 1201 } else { 1202 err = environs.ErrPartialInstances 1203 } 1204 } 1205 return insts, err 1206 } 1207 1208 // AllInstances returns all instances in this environment. 1209 func (e *Environ) AllInstances() ([]instance.Instance, error) { 1210 filter := e.machinesFilter() 1211 allInstances, err := e.allControllerManagedInstances(filter, e.ecfg().useFloatingIP()) 1212 if err != nil { 1213 return nil, errors.Trace(err) 1214 } 1215 modelUUID := e.Config().UUID() 1216 matching := make([]instance.Instance, 0, len(allInstances)) 1217 for _, inst := range allInstances { 1218 if inst.(*openstackInstance).serverDetail.Metadata[tags.JujuModel] != modelUUID { 1219 continue 1220 } 1221 matching = append(matching, inst) 1222 } 1223 return matching, nil 1224 } 1225 1226 // allControllerManagedInstances returns all instances managed by this 1227 // environment's controller, matching the optionally specified filter. 1228 func (e *Environ) allControllerManagedInstances(filter *nova.Filter, updateFloatingIPAddresses bool) ([]instance.Instance, error) { 1229 servers, err := e.nova().ListServersDetail(filter) 1230 if err != nil { 1231 return nil, err 1232 } 1233 instsById := make(map[string]instance.Instance) 1234 controllerUUID := e.Config().ControllerUUID() 1235 for _, server := range servers { 1236 if server.Metadata[tags.JujuController] != controllerUUID { 1237 continue 1238 } 1239 if e.isAliveServer(server) { 1240 var s = server 1241 // TODO(wallyworld): lookup the flavor details to fill in the instance type data 1242 instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s} 1243 } 1244 } 1245 if updateFloatingIPAddresses { 1246 if err := e.updateFloatingIPAddresses(instsById); err != nil { 1247 return nil, err 1248 } 1249 } 1250 insts := make([]instance.Instance, 0, len(instsById)) 1251 for _, inst := range instsById { 1252 insts = append(insts, inst) 1253 } 1254 return insts, nil 1255 } 1256 1257 func (e *Environ) Destroy() error { 1258 err := common.Destroy(e) 1259 if err != nil { 1260 return errors.Trace(err) 1261 } 1262 cfg := e.Config() 1263 if cfg.UUID() == cfg.ControllerUUID() { 1264 // In case any hosted environment hasn't been cleaned up yet, 1265 // we also attempt to delete their resources when the controller 1266 // environment is destroyed. 1267 if err := e.destroyControllerManagedEnvirons(); err != nil { 1268 return errors.Annotate(err, "destroying managed environs") 1269 } 1270 } 1271 // Delete all security groups remaining in the model. 1272 return e.firewaller.DeleteAllGroups() 1273 } 1274 1275 // destroyControllerManagedEnvirons destroys all environments managed by this 1276 // environment's controller. 1277 func (e *Environ) destroyControllerManagedEnvirons() error { 1278 // Terminate all instances managed by the controller. 1279 insts, err := e.allControllerManagedInstances(nil, false) 1280 if err != nil { 1281 return errors.Annotate(err, "listing instances") 1282 } 1283 instIds := make([]instance.Id, len(insts)) 1284 for i, inst := range insts { 1285 instIds[i] = inst.Id() 1286 } 1287 if err := e.terminateInstances(instIds); err != nil { 1288 return errors.Annotate(err, "terminating instances") 1289 } 1290 1291 // Delete all volumes managed by the controller. 1292 cfg := e.Config() 1293 storageAdapter, err := newOpenstackStorageAdapter(cfg) 1294 if err == nil { 1295 volIds, err := allControllerManagedVolumes(storageAdapter, cfg.ControllerUUID()) 1296 if err != nil { 1297 return errors.Annotate(err, "listing volumes") 1298 } 1299 errs := destroyVolumes(storageAdapter, volIds) 1300 for i, err := range errs { 1301 if err == nil { 1302 continue 1303 } 1304 return errors.Annotatef(err, "destroying volume %q", volIds[i], err) 1305 } 1306 } else if !errors.IsNotSupported(err) { 1307 return errors.Trace(err) 1308 } 1309 1310 // Security groups for hosted models are destroyed by the 1311 // DeleteAllGroups method call from Destroy(). 1312 return nil 1313 } 1314 1315 func allControllerManagedVolumes(storageAdapter openstackStorage, controllerUUID string) ([]string, error) { 1316 volumes, err := listVolumes(storageAdapter, func(v *cinder.Volume) bool { 1317 return v.Metadata[tags.JujuController] == controllerUUID 1318 }) 1319 if err != nil { 1320 return nil, errors.Trace(err) 1321 } 1322 volIds := make([]string, len(volumes)) 1323 for i, v := range volumes { 1324 volIds[i] = v.VolumeId 1325 } 1326 return volIds, nil 1327 } 1328 1329 func resourceName(tag names.Tag, envName string) string { 1330 return fmt.Sprintf("juju-%s-%s", envName, tag) 1331 } 1332 1333 // machinesFilter returns a nova.Filter matching all machines in the environment. 1334 func (e *Environ) machinesFilter() *nova.Filter { 1335 filter := nova.NewFilter() 1336 modelUUID := e.Config().UUID() 1337 filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", modelUUID)) 1338 return filter 1339 } 1340 1341 // portsToRuleInfo maps port ranges to nova rules 1342 func portsToRuleInfo(groupId string, ports []network.PortRange) []nova.RuleInfo { 1343 rules := make([]nova.RuleInfo, len(ports)) 1344 for i, portRange := range ports { 1345 rules[i] = nova.RuleInfo{ 1346 ParentGroupId: groupId, 1347 FromPort: portRange.FromPort, 1348 ToPort: portRange.ToPort, 1349 IPProtocol: portRange.Protocol, 1350 Cidr: "0.0.0.0/0", 1351 } 1352 } 1353 return rules 1354 } 1355 1356 func (e *Environ) OpenPorts(ports []network.PortRange) error { 1357 return e.firewaller.OpenPorts(ports) 1358 } 1359 1360 func (e *Environ) ClosePorts(ports []network.PortRange) error { 1361 return e.firewaller.ClosePorts(ports) 1362 } 1363 1364 func (e *Environ) Ports() ([]network.PortRange, error) { 1365 return e.firewaller.Ports() 1366 } 1367 1368 func (e *Environ) Provider() environs.EnvironProvider { 1369 return providerInstance 1370 } 1371 1372 // deleteSecurityGroups deletes the given security groups. If a security 1373 // group is also used by another environment (see bug #1300755), an attempt 1374 // to delete this group fails. A warning is logged in this case. 1375 func (e *Environ) deleteSecurityGroups(securityGroupNames []string) error { 1376 novaclient := e.nova() 1377 allSecurityGroups, err := novaclient.ListSecurityGroups() 1378 if err != nil { 1379 return err 1380 } 1381 for _, securityGroup := range allSecurityGroups { 1382 for _, name := range securityGroupNames { 1383 if securityGroup.Name == name { 1384 deleteSecurityGroup(novaclient, name, securityGroup.Id) 1385 break 1386 } 1387 } 1388 } 1389 return nil 1390 } 1391 1392 func (e *Environ) terminateInstances(ids []instance.Id) error { 1393 if len(ids) == 0 { 1394 return nil 1395 } 1396 var firstErr error 1397 novaClient := e.nova() 1398 for _, id := range ids { 1399 err := novaClient.DeleteServer(string(id)) 1400 if gooseerrors.IsNotFound(err) { 1401 err = nil 1402 } 1403 if err != nil && firstErr == nil { 1404 logger.Debugf("error terminating instance %q: %v", id, err) 1405 firstErr = err 1406 } 1407 } 1408 return firstErr 1409 } 1410 1411 // MetadataLookupParams returns parameters which are used to query simplestreams metadata. 1412 func (e *Environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 1413 if region == "" { 1414 region = e.ecfg().region() 1415 } 1416 cloudSpec, err := e.cloudSpec(region) 1417 if err != nil { 1418 return nil, err 1419 } 1420 return &simplestreams.MetadataLookupParams{ 1421 Series: config.PreferredSeries(e.ecfg()), 1422 Region: cloudSpec.Region, 1423 Endpoint: cloudSpec.Endpoint, 1424 Architectures: arch.AllSupportedArches, 1425 }, nil 1426 } 1427 1428 // Region is specified in the HasRegion interface. 1429 func (e *Environ) Region() (simplestreams.CloudSpec, error) { 1430 return e.cloudSpec(e.ecfg().region()) 1431 } 1432 1433 func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) { 1434 return simplestreams.CloudSpec{ 1435 Region: region, 1436 Endpoint: e.ecfg().authURL(), 1437 }, nil 1438 } 1439 1440 // TagInstance implements environs.InstanceTagger. 1441 func (e *Environ) TagInstance(id instance.Id, tags map[string]string) error { 1442 if err := e.nova().SetServerMetadata(string(id), tags); err != nil { 1443 return errors.Annotate(err, "setting server metadata") 1444 } 1445 return nil 1446 }