github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "crypto/tls" 10 "crypto/x509" 11 "fmt" 12 "net/url" 13 "path" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/juju/clock" 20 "github.com/juju/errors" 21 "github.com/juju/jsonschema" 22 "github.com/juju/loggo" 23 "github.com/juju/retry" 24 "github.com/juju/utils" 25 "github.com/juju/version" 26 "gopkg.in/goose.v2/cinder" 27 "gopkg.in/goose.v2/client" 28 gooseerrors "gopkg.in/goose.v2/errors" 29 "gopkg.in/goose.v2/identity" 30 gooselogging "gopkg.in/goose.v2/logging" 31 "gopkg.in/goose.v2/neutron" 32 "gopkg.in/goose.v2/nova" 33 "gopkg.in/juju/names.v2" 34 35 "github.com/juju/juju/cloud" 36 "github.com/juju/juju/cloudconfig/instancecfg" 37 "github.com/juju/juju/cloudconfig/providerinit" 38 "github.com/juju/juju/cmd/juju/interact" 39 "github.com/juju/juju/core/constraints" 40 "github.com/juju/juju/core/instance" 41 "github.com/juju/juju/core/status" 42 "github.com/juju/juju/environs" 43 "github.com/juju/juju/environs/config" 44 "github.com/juju/juju/environs/context" 45 "github.com/juju/juju/environs/instances" 46 "github.com/juju/juju/environs/simplestreams" 47 "github.com/juju/juju/environs/tags" 48 "github.com/juju/juju/network" 49 "github.com/juju/juju/provider/common" 50 "github.com/juju/juju/storage" 51 "github.com/juju/juju/tools" 52 ) 53 54 var logger = loggo.GetLogger("juju.provider.openstack") 55 56 type EnvironProvider struct { 57 environs.ProviderCredentials 58 Configurator ProviderConfigurator 59 FirewallerFactory FirewallerFactory 60 FlavorFilter FlavorFilter 61 62 // NetworkingDecorator, if non-nil, will be used to 63 // decorate the default networking implementation. 64 // This can be used to override behaviour. 65 NetworkingDecorator NetworkingDecorator 66 67 // ClientFromEndpoint returns an Openstack client for the given endpoint. 68 ClientFromEndpoint func(endpoint string) client.AuthenticatingClient 69 } 70 71 var ( 72 _ environs.CloudEnvironProvider = (*EnvironProvider)(nil) 73 _ environs.ProviderSchema = (*EnvironProvider)(nil) 74 ) 75 76 var providerInstance *EnvironProvider = &EnvironProvider{ 77 ProviderCredentials: OpenstackCredentials{}, 78 Configurator: &defaultConfigurator{}, 79 FirewallerFactory: &firewallerFactory{}, 80 FlavorFilter: FlavorFilterFunc(AcceptAllFlavors), 81 NetworkingDecorator: nil, 82 ClientFromEndpoint: newGooseClient, 83 } 84 85 var cloudSchema = &jsonschema.Schema{ 86 Type: []jsonschema.Type{jsonschema.ObjectType}, 87 Required: []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey}, 88 Order: []string{cloud.EndpointKey, cloud.CertFilenameKey, cloud.AuthTypesKey, cloud.RegionsKey}, 89 Properties: map[string]*jsonschema.Schema{ 90 cloud.EndpointKey: { 91 Singular: "the API endpoint url for the cloud", 92 Type: []jsonschema.Type{jsonschema.StringType}, 93 Format: jsonschema.FormatURI, 94 Default: "", 95 EnvVars: []string{"OS_AUTH_URL"}, 96 }, 97 cloud.CertFilenameKey: { 98 Singular: "a path to the CA certificate for your cloud if one is required to access it. (optional)", 99 Type: []jsonschema.Type{jsonschema.StringType}, 100 Format: interact.FormatCertFilename, 101 Default: "", 102 PromptDefault: "none", 103 EnvVars: []string{"OS_CACERT"}, 104 }, 105 cloud.AuthTypesKey: { 106 Singular: "auth type", 107 Plural: "auth types", 108 Type: []jsonschema.Type{jsonschema.ArrayType}, 109 UniqueItems: jsonschema.Bool(true), 110 Items: &jsonschema.ItemSpec{ 111 Schemas: []*jsonschema.Schema{{ 112 Type: []jsonschema.Type{jsonschema.StringType}, 113 Enum: []interface{}{ 114 string(cloud.AccessKeyAuthType), 115 string(cloud.UserPassAuthType), 116 }, 117 }}, 118 }, 119 }, 120 cloud.RegionsKey: { 121 Type: []jsonschema.Type{jsonschema.ObjectType}, 122 Singular: "region", 123 Plural: "regions", 124 Default: "", 125 EnvVars: []string{"OS_REGION_NAME"}, 126 AdditionalProperties: &jsonschema.Schema{ 127 Type: []jsonschema.Type{jsonschema.ObjectType}, 128 Required: []string{cloud.EndpointKey}, 129 MaxProperties: jsonschema.Int(1), 130 Properties: map[string]*jsonschema.Schema{ 131 cloud.EndpointKey: { 132 Singular: "the API endpoint url for the region", 133 Type: []jsonschema.Type{jsonschema.StringType}, 134 Format: jsonschema.FormatURI, 135 Default: "", 136 PromptDefault: "use cloud api url", 137 }, 138 }, 139 }, 140 }, 141 }, 142 } 143 144 var makeServiceURL = client.AuthenticatingClient.MakeServiceURL 145 146 // TODO: shortAttempt was kept to a long timeout because Nova needs 147 // more time than EC2. Storage delays are handled separately now, and 148 // perhaps other polling attempts can time out faster. 149 150 // shortAttempt is used when polling for short-term events in tests. 151 var shortAttempt = utils.AttemptStrategy{ 152 Total: 15 * time.Second, 153 Delay: 200 * time.Millisecond, 154 } 155 156 // Version is part of the EnvironProvider interface. 157 func (EnvironProvider) Version() int { 158 return 0 159 } 160 161 func (p EnvironProvider) Open(args environs.OpenParams) (environs.Environ, error) { 162 logger.Infof("opening model %q", args.Config.Name()) 163 if err := validateCloudSpec(args.Cloud); err != nil { 164 return nil, errors.Annotate(err, "validating cloud spec") 165 } 166 uuid := args.Config.UUID() 167 namespace, err := instance.NewNamespace(uuid) 168 if err != nil { 169 return nil, errors.Annotate(err, "creating instance namespace") 170 } 171 172 e := &Environ{ 173 name: args.Config.Name(), 174 uuid: uuid, 175 cloud: args.Cloud, 176 namespace: namespace, 177 clock: clock.WallClock, 178 configurator: p.Configurator, 179 flavorFilter: p.FlavorFilter, 180 } 181 e.firewaller = p.FirewallerFactory.GetFirewaller(e) 182 183 var networking Networking = &switchingNetworking{env: e} 184 if p.NetworkingDecorator != nil { 185 var err error 186 networking, err = p.NetworkingDecorator.DecorateNetworking(networking) 187 if err != nil { 188 return nil, errors.Trace(err) 189 } 190 } 191 e.networking = networking 192 193 if err := e.SetConfig(args.Config); err != nil { 194 return nil, err 195 } 196 197 e.ecfgMutex.Lock() 198 defer e.ecfgMutex.Unlock() 199 client, err := authClient(e.cloud, e.ecfgUnlocked) 200 if err != nil { 201 return nil, errors.Annotate(err, "cannot set config") 202 } 203 e.clientUnlocked = client 204 e.novaUnlocked = nova.New(e.clientUnlocked) 205 e.neutronUnlocked = neutron.New(e.clientUnlocked) 206 207 return e, nil 208 } 209 210 // DetectRegions implements environs.CloudRegionDetector. 211 func (EnvironProvider) DetectRegions() ([]cloud.Region, error) { 212 // If OS_REGION_NAME and OS_AUTH_URL are both set, 213 // return return a region using them. 214 creds, err := identity.CredentialsFromEnv() 215 if err != nil { 216 return nil, errors.Errorf("failed to retrive cred from env : %v", err) 217 } 218 if creds.Region == "" { 219 return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set") 220 } 221 if creds.URL == "" { 222 return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set") 223 } 224 return []cloud.Region{{ 225 Name: creds.Region, 226 Endpoint: creds.URL, 227 }}, nil 228 } 229 230 // CloudSchema returns the schema for adding new clouds of this type. 231 func (p EnvironProvider) CloudSchema() *jsonschema.Schema { 232 return cloudSchema 233 } 234 235 // Ping tests the connection to the cloud, to verify the endpoint is valid. 236 func (p EnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 237 c := p.ClientFromEndpoint(endpoint) 238 if _, err := c.IdentityAuthOptions(); err != nil { 239 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 240 return errors.Wrap(err, errors.Errorf("No Openstack server running at %s", endpoint)) 241 } 242 return nil 243 } 244 245 // newGooseClient is the default function in EnvironProvider.ClientFromEndpoint. 246 func newGooseClient(endpoint string) client.AuthenticatingClient { 247 // Use NonValidatingClient, in case the endpoint is behind a cert 248 return client.NewNonValidatingClient(&identity.Credentials{URL: endpoint}, 0, nil) 249 } 250 251 // PrepareConfig is specified in the EnvironProvider interface. 252 func (p EnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 253 if err := validateCloudSpec(args.Cloud); err != nil { 254 return nil, errors.Annotate(err, "validating cloud spec") 255 } 256 257 // Set the default block-storage source. 258 attrs := make(map[string]interface{}) 259 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 260 attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType 261 } 262 263 cfg, err := args.Config.Apply(attrs) 264 if err != nil { 265 return nil, errors.Trace(err) 266 } 267 return cfg, nil 268 } 269 270 // MetadataLookupParams returns parameters which are used to query image metadata to 271 // find matching image information. 272 func (p EnvironProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 273 if region == "" { 274 return nil, errors.Errorf("region must be specified") 275 } 276 return &simplestreams.MetadataLookupParams{ 277 Region: region, 278 }, nil 279 } 280 281 func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) { 282 valid, err := p.Validate(cfg, nil) 283 if err != nil { 284 return nil, err 285 } 286 return &environConfig{valid, valid.UnknownAttrs()}, nil 287 } 288 289 type Environ struct { 290 name string 291 uuid string 292 cloud environs.CloudSpec 293 namespace instance.Namespace 294 295 ecfgMutex sync.Mutex 296 ecfgUnlocked *environConfig 297 clientUnlocked client.AuthenticatingClient 298 novaUnlocked *nova.Client 299 neutronUnlocked *neutron.Client 300 volumeURL *url.URL 301 302 // keystoneImageDataSource caches the result of getKeystoneImageSource. 303 keystoneImageDataSourceMutex sync.Mutex 304 keystoneImageDataSource simplestreams.DataSource 305 306 // keystoneToolsDataSource caches the result of getKeystoneToolsSource. 307 keystoneToolsDataSourceMutex sync.Mutex 308 keystoneToolsDataSource simplestreams.DataSource 309 310 availabilityZonesMutex sync.Mutex 311 availabilityZones []common.AvailabilityZone 312 firewaller Firewaller 313 networking Networking 314 configurator ProviderConfigurator 315 flavorFilter FlavorFilter 316 317 // Clock is defined so it can be replaced for testing 318 clock clock.Clock 319 320 publicIPMutex sync.Mutex 321 } 322 323 var _ environs.Environ = (*Environ)(nil) 324 var _ environs.NetworkingEnviron = (*Environ)(nil) 325 var _ simplestreams.HasRegion = (*Environ)(nil) 326 var _ context.Distributor = (*Environ)(nil) 327 var _ environs.InstanceTagger = (*Environ)(nil) 328 329 type openstackInstance struct { 330 e *Environ 331 instType *instances.InstanceType 332 arch *string 333 334 mu sync.Mutex 335 serverDetail *nova.ServerDetail 336 // floatingIP is non-nil iff use-floating-ip is true. 337 floatingIP *string 338 } 339 340 func (inst *openstackInstance) String() string { 341 return string(inst.Id()) 342 } 343 344 var _ instances.Instance = (*openstackInstance)(nil) 345 346 func (inst *openstackInstance) Refresh(ctx context.ProviderCallContext) error { 347 inst.mu.Lock() 348 defer inst.mu.Unlock() 349 server, err := inst.e.nova().GetServer(inst.serverDetail.Id) 350 if err != nil { 351 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 352 return err 353 } 354 inst.serverDetail = server 355 return nil 356 } 357 358 func (inst *openstackInstance) getServerDetail() *nova.ServerDetail { 359 inst.mu.Lock() 360 defer inst.mu.Unlock() 361 return inst.serverDetail 362 } 363 364 func (inst *openstackInstance) Id() instance.Id { 365 return instance.Id(inst.getServerDetail().Id) 366 } 367 368 func (inst *openstackInstance) Status(ctx context.ProviderCallContext) instance.Status { 369 instStatus := inst.getServerDetail().Status 370 jujuStatus := status.Pending 371 switch instStatus { 372 case nova.StatusActive: 373 jujuStatus = status.Running 374 case nova.StatusError: 375 jujuStatus = status.ProvisioningError 376 case nova.StatusBuild, nova.StatusBuildSpawning, 377 nova.StatusDeleted, nova.StatusHardReboot, 378 nova.StatusPassword, nova.StatusReboot, 379 nova.StatusRebuild, nova.StatusRescue, 380 nova.StatusResize, nova.StatusShutoff, 381 nova.StatusSuspended, nova.StatusVerifyResize: 382 jujuStatus = status.Empty 383 case nova.StatusUnknown: 384 jujuStatus = status.Unknown 385 default: 386 jujuStatus = status.Empty 387 } 388 return instance.Status{ 389 Status: jujuStatus, 390 Message: instStatus, 391 } 392 } 393 394 func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics { 395 hc := &instance.HardwareCharacteristics{Arch: inst.arch} 396 if inst.instType != nil { 397 hc.Mem = &inst.instType.Mem 398 // openstack is special in that a 0-size root disk means that 399 // the root disk will result in an instance with a root disk 400 // the same size as the image that created it, so we just set 401 // the HardwareCharacteristics to nil to signal that we don't 402 // know what the correct size is. 403 if inst.instType.RootDisk == 0 { 404 hc.RootDisk = nil 405 } else { 406 hc.RootDisk = &inst.instType.RootDisk 407 } 408 hc.CpuCores = &inst.instType.CpuCores 409 hc.CpuPower = inst.instType.CpuPower 410 // tags not currently supported on openstack 411 } 412 hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone 413 return hc 414 } 415 416 // getAddresses returns the existing server information on addresses, 417 // but fetches the details over the api again if no addresses exist. 418 func (inst *openstackInstance) getAddresses(ctx context.ProviderCallContext) (map[string][]nova.IPAddress, error) { 419 addrs := inst.getServerDetail().Addresses 420 if len(addrs) == 0 { 421 server, err := inst.e.nova().GetServer(string(inst.Id())) 422 if err != nil { 423 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 424 return nil, err 425 } 426 addrs = server.Addresses 427 } 428 return addrs, nil 429 } 430 431 // Addresses implements network.Addresses() returning generic address 432 // details for the instances, and calling the openstack api if needed. 433 func (inst *openstackInstance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) { 434 addresses, err := inst.getAddresses(ctx) 435 if err != nil { 436 return nil, err 437 } 438 var floatingIP string 439 if inst.floatingIP != nil { 440 floatingIP = *inst.floatingIP 441 logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP) 442 } 443 return convertNovaAddresses(floatingIP, addresses), nil 444 } 445 446 // convertNovaAddresses returns nova addresses in generic format 447 func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address { 448 var machineAddresses []network.Address 449 if publicIP != "" { 450 publicAddr := network.NewScopedAddress(publicIP, network.ScopePublic) 451 machineAddresses = append(machineAddresses, publicAddr) 452 } 453 // TODO(gz) Network ordering may be significant but is not preserved by 454 // the map, see lp:1188126 for example. That could potentially be fixed 455 // in goose, or left to be derived by other means. 456 for netName, ips := range addresses { 457 networkScope := network.ScopeUnknown 458 if netName == "public" { 459 networkScope = network.ScopePublic 460 } 461 for _, address := range ips { 462 // If this address has already been added as a floating IP, skip it. 463 if publicIP == address.Address { 464 continue 465 } 466 // Assume IPv4 unless specified otherwise 467 addrtype := network.IPv4Address 468 if address.Version == 6 { 469 addrtype = network.IPv6Address 470 } 471 machineAddr := network.NewScopedAddress(address.Address, networkScope) 472 if machineAddr.Type != addrtype { 473 logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype) 474 } 475 machineAddresses = append(machineAddresses, machineAddr) 476 } 477 } 478 return machineAddresses 479 } 480 481 func (inst *openstackInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 482 return inst.e.firewaller.OpenInstancePorts(ctx, inst, machineId, rules) 483 } 484 485 func (inst *openstackInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 486 return inst.e.firewaller.CloseInstancePorts(ctx, inst, machineId, rules) 487 } 488 489 func (inst *openstackInstance) IngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) { 490 return inst.e.firewaller.InstanceIngressRules(ctx, inst, machineId) 491 } 492 493 func (e *Environ) ecfg() *environConfig { 494 e.ecfgMutex.Lock() 495 ecfg := e.ecfgUnlocked 496 e.ecfgMutex.Unlock() 497 return ecfg 498 } 499 500 func (e *Environ) client() client.AuthenticatingClient { 501 e.ecfgMutex.Lock() 502 client := e.clientUnlocked 503 e.ecfgMutex.Unlock() 504 return client 505 } 506 507 func (e *Environ) nova() *nova.Client { 508 e.ecfgMutex.Lock() 509 nova := e.novaUnlocked 510 e.ecfgMutex.Unlock() 511 return nova 512 } 513 514 func (e *Environ) neutron() *neutron.Client { 515 e.ecfgMutex.Lock() 516 neutron := e.neutronUnlocked 517 e.ecfgMutex.Unlock() 518 return neutron 519 } 520 521 var unsupportedConstraints = []string{ 522 constraints.Tags, 523 constraints.CpuPower, 524 } 525 526 // ConstraintsValidator is defined on the Environs interface. 527 func (e *Environ) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) { 528 validator := constraints.NewValidator() 529 validator.RegisterConflicts( 530 []string{constraints.InstanceType}, 531 []string{constraints.Mem, constraints.RootDisk, constraints.Cores}) 532 validator.RegisterUnsupported(unsupportedConstraints) 533 novaClient := e.nova() 534 flavors, err := novaClient.ListFlavorsDetail() 535 if err != nil { 536 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 537 return nil, err 538 } 539 instTypeNames := make([]string, len(flavors)) 540 for i, flavor := range flavors { 541 instTypeNames[i] = flavor.Name 542 } 543 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 544 validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"}) 545 return validator, nil 546 } 547 548 var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones 549 550 type openstackAvailabilityZone struct { 551 nova.AvailabilityZone 552 } 553 554 func (z *openstackAvailabilityZone) Name() string { 555 return z.AvailabilityZone.Name 556 } 557 558 func (z *openstackAvailabilityZone) Available() bool { 559 return z.AvailabilityZone.State.Available 560 } 561 562 // AvailabilityZones returns a slice of availability zones. 563 func (e *Environ) AvailabilityZones(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) { 564 e.availabilityZonesMutex.Lock() 565 defer e.availabilityZonesMutex.Unlock() 566 if e.availabilityZones == nil { 567 zones, err := novaListAvailabilityZones(e.nova()) 568 if gooseerrors.IsNotImplemented(err) { 569 return nil, errors.NotImplementedf("availability zones") 570 } 571 if err != nil { 572 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 573 return nil, err 574 } 575 e.availabilityZones = make([]common.AvailabilityZone, len(zones)) 576 for i, z := range zones { 577 e.availabilityZones[i] = &openstackAvailabilityZone{z} 578 } 579 } 580 return e.availabilityZones, nil 581 } 582 583 // InstanceAvailabilityZoneNames returns the availability zone names for each 584 // of the specified instances. 585 func (e *Environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 586 instances, err := e.Instances(ctx, ids) 587 if err != nil && err != environs.ErrPartialInstances { 588 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 589 return nil, err 590 } 591 zones := make([]string, len(instances)) 592 for i, inst := range instances { 593 if inst == nil { 594 continue 595 } 596 zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone 597 } 598 return zones, err 599 } 600 601 type openstackPlacement struct { 602 zoneName string 603 } 604 605 // DeriveAvailabilityZones is part of the common.ZonedEnviron interface. 606 func (e *Environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) { 607 availabilityZone, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments) 608 if err != nil && !errors.IsNotImplemented(err) { 609 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 610 return nil, errors.Trace(err) 611 } 612 if availabilityZone != "" { 613 return []string{availabilityZone}, nil 614 } 615 return nil, nil 616 } 617 618 func (e *Environ) parsePlacement(ctx context.ProviderCallContext, placement string) (*openstackPlacement, error) { 619 pos := strings.IndexRune(placement, '=') 620 if pos == -1 { 621 return nil, errors.Errorf("unknown placement directive: %v", placement) 622 } 623 switch key, value := placement[:pos], placement[pos+1:]; key { 624 case "zone": 625 availabilityZone := value 626 err := common.ValidateAvailabilityZone(e, ctx, availabilityZone) 627 if err != nil { 628 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 629 return nil, err 630 } 631 return &openstackPlacement{zoneName: availabilityZone}, nil 632 } 633 return nil, errors.Errorf("unknown placement directive: %v", placement) 634 } 635 636 // PrecheckInstance is defined on the environs.InstancePrechecker interface. 637 func (e *Environ) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error { 638 if _, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments); err != nil { 639 return errors.Trace(err) 640 } 641 if !args.Constraints.HasInstanceType() { 642 return nil 643 } 644 // Constraint has an instance-type constraint so let's see if it is valid. 645 novaClient := e.nova() 646 flavors, err := novaClient.ListFlavorsDetail() 647 if err != nil { 648 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 649 return err 650 } 651 for _, flavor := range flavors { 652 if flavor.Name == *args.Constraints.InstanceType { 653 return nil 654 } 655 } 656 return errors.Errorf("invalid Openstack flavour %q specified", *args.Constraints.InstanceType) 657 } 658 659 // PrepareForBootstrap is part of the Environ interface. 660 func (e *Environ) PrepareForBootstrap(ctx environs.BootstrapContext) error { 661 // Verify credentials. 662 if err := authenticateClient(e.client()); err != nil { 663 return err 664 } 665 if !e.supportsNeutron() { 666 logger.Warningf(`Using deprecated OpenStack APIs. 667 668 Neutron networking is not supported by this OpenStack cloud. 669 Falling back to deprecated Nova networking. 670 671 Support for deprecated Nova networking APIs will be removed 672 in a future Juju release. Please upgrade to OpenStack Icehouse 673 or newer to maintain compatibility. 674 675 `, 676 ) 677 } 678 return nil 679 } 680 681 // Create is part of the Environ interface. 682 func (e *Environ) Create(ctx context.ProviderCallContext, args environs.CreateParams) error { 683 // Verify credentials. 684 if err := authenticateClient(e.client()); err != nil { 685 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 686 return err 687 } 688 // TODO(axw) 2016-08-04 #1609643 689 // Create global security group(s) here. 690 return nil 691 } 692 693 func (e *Environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 694 // The client's authentication may have been reset when finding tools if the agent-version 695 // attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated. 696 // An authenticated client is needed for the URL() call below. 697 if err := authenticateClient(e.client()); err != nil { 698 common.HandleCredentialError(IsAuthorisationFailure, err, callCtx) 699 return nil, err 700 } 701 result, err := common.Bootstrap(ctx, e, callCtx, args) 702 if err != nil { 703 common.HandleCredentialError(IsAuthorisationFailure, err, callCtx) 704 return nil, err 705 } 706 return result, nil 707 } 708 709 func (e *Environ) supportsNeutron() bool { 710 client := e.client() 711 endpointMap := client.EndpointsForRegion(e.cloud.Region) 712 _, ok := endpointMap["network"] 713 return ok 714 } 715 716 func (e *Environ) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) { 717 // Find all instances tagged with tags.JujuIsController. 718 instances, err := e.allControllerManagedInstances(ctx, controllerUUID, e.ecfg().useFloatingIP()) 719 if err != nil { 720 return nil, errors.Trace(err) 721 } 722 ids := make([]instance.Id, 0, 1) 723 for _, instance := range instances { 724 detail := instance.(*openstackInstance).getServerDetail() 725 if detail.Metadata[tags.JujuIsController] == "true" { 726 ids = append(ids, instance.Id()) 727 } 728 } 729 if len(ids) == 0 { 730 return nil, environs.ErrNoInstances 731 } 732 return ids, nil 733 } 734 735 func (e *Environ) Config() *config.Config { 736 return e.ecfg().Config 737 } 738 739 func newCredentials(spec environs.CloudSpec) (identity.Credentials, identity.AuthMode, error) { 740 credAttrs := spec.Credential.Attributes() 741 cred := identity.Credentials{ 742 Region: spec.Region, 743 URL: spec.Endpoint, 744 TenantName: credAttrs[CredAttrTenantName], 745 TenantID: credAttrs[CredAttrTenantID], 746 } 747 748 // AuthType is validated when the environment is opened, so it's known 749 // to be one of these values. 750 var authMode identity.AuthMode 751 switch spec.Credential.AuthType() { 752 case cloud.UserPassAuthType: 753 // TODO(axw) we need a way of saying to use legacy auth. 754 cred.User = credAttrs[CredAttrUserName] 755 cred.Secrets = credAttrs[CredAttrPassword] 756 cred.ProjectDomain = credAttrs[CredAttrProjectDomainName] 757 cred.UserDomain = credAttrs[CredAttrUserDomainName] 758 cred.Domain = credAttrs[CredAttrDomainName] 759 if credAttrs[CredAttrVersion] != "" { 760 version, err := strconv.Atoi(credAttrs[CredAttrVersion]) 761 if err != nil { 762 return identity.Credentials{}, 0, 763 errors.Errorf("cred.Version is not a valid integer type : %v", err) 764 } 765 if version < 3 { 766 authMode = identity.AuthUserPass 767 } else { 768 authMode = identity.AuthUserPassV3 769 } 770 cred.Version = version 771 } else if cred.Domain != "" || cred.UserDomain != "" || cred.ProjectDomain != "" { 772 authMode = identity.AuthUserPassV3 773 } else { 774 authMode = identity.AuthUserPass 775 } 776 case cloud.AccessKeyAuthType: 777 cred.User = credAttrs[CredAttrAccessKey] 778 cred.Secrets = credAttrs[CredAttrSecretKey] 779 authMode = identity.AuthKeyPair 780 } 781 return cred, authMode, nil 782 } 783 784 func authClient(spec environs.CloudSpec, ecfg *environConfig) (client.AuthenticatingClient, error) { 785 identityClientVersion, err := identityClientVersion(spec.Endpoint) 786 if err != nil { 787 return nil, errors.Annotate(err, "cannot create a client") 788 } 789 cred, authMode, err := newCredentials(spec) 790 if err != nil { 791 return nil, errors.Annotate(err, "cannot create credential") 792 } 793 gooseLogger := gooselogging.LoggoLogger{loggo.GetLogger("goose")} 794 795 cl, _ := newClientByType(cred, authMode, gooseLogger, ecfg.SSLHostnameVerification(), spec.CACertificates) 796 797 // before returning, lets make sure that we want to have AuthMode 798 // AuthUserPass instead of its V3 counterpart. 799 if authMode == identity.AuthUserPass && (identityClientVersion == -1 || identityClientVersion == 3) { 800 options, err := cl.IdentityAuthOptions() 801 if err != nil { 802 logger.Errorf("cannot determine available auth versions %v", err) 803 } 804 for _, option := range options { 805 if option.Mode != identity.AuthUserPassV3 { 806 continue 807 } 808 cred.URL = option.Endpoint 809 v3Cl, err := newClientByType(cred, identity.AuthUserPassV3, gooseLogger, ecfg.SSLHostnameVerification(), spec.CACertificates) 810 if err != nil { 811 return nil, err 812 } 813 // if the v3 client can authenticate, use it, otherwise fallback to the v2 client. 814 if err = v3Cl.Authenticate(); err == nil { 815 cl = v3Cl 816 break 817 } 818 } 819 } 820 821 // Juju requires "compute" at a minimum. We'll use "network" if it's 822 // available in preference to the Neutron network APIs; and "volume" or 823 // "volume2" for storage if either one is available. 824 cl.SetRequiredServiceTypes([]string{"compute"}) 825 return cl, nil 826 } 827 828 // newClientByType returns an authenticating client to talk to the 829 // OpenStack cloud. CACertificate and SSLHostnameVerification == false 830 // config options are mutually exclusive here. 831 func newClientByType( 832 cred identity.Credentials, 833 authMode identity.AuthMode, 834 gooseLogger gooselogging.CompatLogger, 835 sslHostnameVerification bool, 836 certs []string, 837 ) (client.AuthenticatingClient, error) { 838 switch { 839 case len(certs) > 0: 840 tlsConfig := tlsConfig(certs) 841 logger.Tracef("using NewClientTLSConfig") 842 return client.NewClientTLSConfig(&cred, authMode, gooseLogger, tlsConfig), nil 843 case sslHostnameVerification == false: 844 logger.Tracef("using NewNonValidatingClient") 845 return client.NewNonValidatingClient(&cred, authMode, gooseLogger), nil 846 default: 847 logger.Tracef("using NewClient") 848 return client.NewClient(&cred, authMode, gooseLogger), nil 849 } 850 } 851 852 func tlsConfig(certStrs []string) *tls.Config { 853 pool := x509.NewCertPool() 854 for _, cert := range certStrs { 855 pool.AppendCertsFromPEM([]byte(cert)) 856 } 857 tlsConfig := utils.SecureTLSConfig() 858 tlsConfig.RootCAs = pool 859 return tlsConfig 860 } 861 862 type authenticator interface { 863 Authenticate() error 864 } 865 866 var authenticateClient = func(auth authenticator) error { 867 err := auth.Authenticate() 868 if err != nil { 869 // Log the error in case there are any useful hints, 870 // but provide a readable and helpful error message 871 // to the user. 872 logger.Debugf("Authenticate() failed: %v", err) 873 if gooseerrors.IsUnauthorised(err) { 874 return errors.Errorf("authentication failed : %v\n"+ 875 "Please ensure the credentials are correct. A common mistake is\n"+ 876 "to specify the wrong tenant. Use the OpenStack project name\n"+ 877 "for tenant-name in your model configuration. \n", err) 878 } else { 879 return errors.Annotate(err, "authentication failed.") 880 } 881 } 882 return nil 883 } 884 885 func (e *Environ) SetConfig(cfg *config.Config) error { 886 ecfg, err := providerInstance.newConfig(cfg) 887 if err != nil { 888 return err 889 } 890 // At this point, the authentication method config value has been validated so we extract it's value here 891 // to avoid having to validate again each time when creating the OpenStack client. 892 e.ecfgMutex.Lock() 893 defer e.ecfgMutex.Unlock() 894 e.ecfgUnlocked = ecfg 895 896 return nil 897 } 898 899 func identityClientVersion(authURL string) (int, error) { 900 url, err := url.Parse(authURL) 901 if err != nil { 902 // Return 0 as this is the lowest invalid number according to openstack codebase: 903 // -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions. 904 return 0, err 905 } 906 if url.Path == authURL { 907 // This means we could not parse URL into url structure 908 // with protocols, domain, port, etc. 909 // For example, specifying "keystone.foo" instead of "https://keystone.foo:443/v3/" 910 // falls into this category. 911 return 0, errors.Errorf("url %s is malformed", authURL) 912 } 913 if url.Path == "" || url.Path == "/" { 914 // User explicitly did not provide any version, it is empty. 915 return -1, nil 916 } 917 // The last part of the path should be the version #, prefixed with a 'v' or 'V' 918 // Example: https://keystone.foo:443/v3/ 919 // Example: https://sharedhost.foo:443/identity/v3/ 920 urlpath := strings.ToLower(url.Path) 921 urlpath, tail := path.Split(urlpath) 922 if len(tail) == 0 && len(urlpath) > 2 { 923 // trailing /, remove it and split again 924 urlpath, tail = path.Split(strings.TrimRight(urlpath, "/")) 925 } 926 versionNumStr := strings.TrimPrefix(tail, "v") 927 logger.Tracef("authURL: %s", authURL) 928 major, _, err := version.ParseMajorMinor(versionNumStr) 929 if len(tail) < 2 || tail[0] != 'v' || err != nil { 930 // There must be a '/v' in the URL path. 931 // At this stage only '/Vxxx' and '/vxxx' are valid where xxx is major.minor version. 932 // Return 0 as this is the lowest invalid number according to openstack codebase: 933 // -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions. 934 return 0, errors.NotValidf("version part of identity url %s", authURL) 935 } 936 return major, err 937 } 938 939 // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that 940 // returns a DataSource using the "product-streams" keystone URL. 941 func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) { 942 e, ok := env.(*Environ) 943 if !ok { 944 return nil, errors.NotSupportedf("non-openstack model") 945 } 946 return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams") 947 } 948 949 // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that 950 // returns a DataSource using the "juju-tools" keystone URL. 951 func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) { 952 e, ok := env.(*Environ) 953 if !ok { 954 return nil, errors.NotSupportedf("non-openstack model") 955 } 956 return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools") 957 } 958 959 func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) { 960 mu.Lock() 961 defer mu.Unlock() 962 if *datasource != nil { 963 return *datasource, nil 964 } 965 966 client := e.client() 967 if !client.IsAuthenticated() { 968 if err := authenticateClient(client); err != nil { 969 return nil, err 970 } 971 } 972 973 url, err := makeServiceURL(client, keystoneName, "", nil) 974 if err != nil { 975 return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err)) 976 } 977 verify := utils.VerifySSLHostnames 978 if !e.Config().SSLHostnameVerification() { 979 verify = utils.NoVerifySSLHostnames 980 } 981 *datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify, simplestreams.SPECIFIC_CLOUD_DATA, false) 982 return *datasource, nil 983 } 984 985 // assignPublicIP tries to assign the given floating IP address to the 986 // specified server, or returns an error. 987 func (e *Environ) assignPublicIP(fip *string, serverId string) (err error) { 988 if *fip == "" { 989 return errors.Errorf("cannot assign a nil public IP to %q", serverId) 990 } 991 // At startup nw_info is not yet cached so this may fail 992 // temporarily while the server is being built 993 for a := common.LongAttempt.Start(); a.Next(); { 994 err = e.nova().AddServerFloatingIP(serverId, *fip) 995 if err == nil { 996 return nil 997 } 998 } 999 return err 1000 } 1001 1002 // DistributeInstances implements the state.InstanceDistributor policy. 1003 func (e *Environ) DistributeInstances( 1004 ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string, 1005 ) ([]instance.Id, error) { 1006 valid, err := common.DistributeInstances(e, ctx, candidates, distributionGroup, limitZones) 1007 if err != nil { 1008 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1009 return valid, err 1010 } 1011 return valid, nil 1012 } 1013 1014 var availabilityZoneAllocations = common.AvailabilityZoneAllocations 1015 1016 // MaintainInstance is specified in the InstanceBroker interface. 1017 func (*Environ) MaintainInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) error { 1018 return nil 1019 } 1020 1021 // StartInstance is specified in the InstanceBroker interface. 1022 func (e *Environ) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (_ *environs.StartInstanceResult, err error) { 1023 if args.AvailabilityZone != "" { 1024 // args.AvailabilityZone should only be set if this OpenStack 1025 // supports zones; validate the zone. 1026 volumeAttachmentsZone, err := e.volumeAttachmentsZone(args.VolumeAttachments) 1027 if err != nil { 1028 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1029 return nil, common.ZoneIndependentError(err) 1030 } 1031 if err := validateAvailabilityZoneConsistency(args.AvailabilityZone, volumeAttachmentsZone); err != nil { 1032 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1033 return nil, common.ZoneIndependentError(err) 1034 } 1035 if err := common.ValidateAvailabilityZone(e, ctx, args.AvailabilityZone); err != nil { 1036 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1037 return nil, errors.Trace(err) 1038 } 1039 } 1040 1041 series := args.Tools.OneSeries() 1042 arches := args.Tools.Arches() 1043 spec, err := findInstanceSpec(e, &instances.InstanceConstraint{ 1044 Region: e.cloud.Region, 1045 Series: series, 1046 Arches: arches, 1047 Constraints: args.Constraints, 1048 }, args.ImageMetadata) 1049 if err != nil { 1050 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1051 return nil, common.ZoneIndependentError(err) 1052 } 1053 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 1054 if err != nil { 1055 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1056 return nil, common.ZoneIndependentError( 1057 errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches), 1058 ) 1059 } 1060 1061 if err := args.InstanceConfig.SetTools(tools); err != nil { 1062 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1063 return nil, common.ZoneIndependentError(err) 1064 } 1065 1066 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil { 1067 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1068 return nil, common.ZoneIndependentError(err) 1069 } 1070 cloudcfg, err := e.configurator.GetCloudConfig(args) 1071 if err != nil { 1072 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1073 return nil, common.ZoneIndependentError(err) 1074 } 1075 userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, OpenstackRenderer{}) 1076 if err != nil { 1077 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1078 return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot make user data")) 1079 } 1080 logger.Debugf("openstack user data; %d bytes", len(userData)) 1081 1082 networks, err := e.networking.DefaultNetworks() 1083 if err != nil { 1084 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1085 return nil, common.ZoneIndependentError(errors.Annotate(err, "getting initial networks")) 1086 } 1087 usingNetwork := e.ecfg().network() 1088 networkId, err := e.networking.ResolveNetwork(usingNetwork, false) 1089 if err != nil { 1090 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1091 if usingNetwork == "" { 1092 // If there is no network configured, we only throw out when the 1093 // error reports multiple Openstack networks. 1094 // If there are no Openstack networks at all (such as Canonistack), 1095 // having no network config is not an error condition. 1096 if strings.HasPrefix(err.Error(), "multiple networks") { 1097 return nil, common.ZoneIndependentError(errors.New(noNetConfigMsg(err))) 1098 } 1099 } else { 1100 return nil, common.ZoneIndependentError(err) 1101 } 1102 } else { 1103 logger.Debugf("using network id %q", networkId) 1104 networks = append(networks, nova.ServerNetworks{NetworkId: networkId}) 1105 } 1106 1107 machineName := resourceName( 1108 e.namespace, 1109 e.name, 1110 args.InstanceConfig.MachineId, 1111 ) 1112 1113 if e.ecfg().useOpenstackGBP() { 1114 client := e.neutron() 1115 ptArg := neutron.PolicyTargetV2{ 1116 Name: fmt.Sprintf("juju-policytarget-%s", machineName), 1117 PolicyTargetGroupId: e.ecfg().policyTargetGroup(), 1118 } 1119 pt, err := client.CreatePolicyTargetV2(ptArg) 1120 if err != nil { 1121 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1122 return nil, errors.Trace(err) 1123 } 1124 networks = append(networks, nova.ServerNetworks{PortId: pt.PortId}) 1125 } 1126 1127 // For BUG 1680787: openstack: add support for neutron networks where port 1128 // security is disabled. 1129 // If any network specified for instance boot has PortSecurityEnabled equals 1130 // false, don't create security groups, instance boot will fail. 1131 createSecurityGroups := true 1132 if len(networks) > 0 && e.supportsNeutron() { 1133 client := e.neutron() 1134 for _, n := range networks { 1135 if n.NetworkId == "" { 1136 // It's a GBP network. 1137 continue 1138 } 1139 net, err := client.GetNetworkV2(n.NetworkId) 1140 if err != nil { 1141 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1142 return nil, common.ZoneIndependentError(err) 1143 } 1144 if net.PortSecurityEnabled != nil && 1145 *net.PortSecurityEnabled == false { 1146 createSecurityGroups = *net.PortSecurityEnabled 1147 logger.Infof("network %q has port_security_enabled set to false. Not using security groups.", net.Id) 1148 break 1149 } 1150 } 1151 } 1152 1153 var novaGroupNames = []nova.SecurityGroupName{} 1154 if createSecurityGroups { 1155 var apiPort int 1156 if args.InstanceConfig.Controller != nil { 1157 apiPort = args.InstanceConfig.Controller.Config.APIPort() 1158 } else { 1159 // All ports are the same so pick the first. 1160 apiPort = args.InstanceConfig.APIInfo.Ports()[0] 1161 } 1162 groupNames, err := e.firewaller.SetUpGroups(ctx, args.ControllerUUID, args.InstanceConfig.MachineId, apiPort) 1163 if err != nil { 1164 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1165 return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot set up groups")) 1166 } 1167 novaGroupNames = make([]nova.SecurityGroupName, len(groupNames)) 1168 for i, name := range groupNames { 1169 novaGroupNames[i].Name = name 1170 } 1171 } 1172 1173 waitForActiveServerDetails := func( 1174 client *nova.Client, 1175 id string, 1176 timeout time.Duration, 1177 ) (server *nova.ServerDetail, err error) { 1178 1179 var errStillBuilding = errors.Errorf("instance %q has status BUILD", id) 1180 err = retry.Call(retry.CallArgs{ 1181 Clock: e.clock, 1182 Delay: 10 * time.Second, 1183 MaxDuration: timeout, 1184 Func: func() error { 1185 server, err = client.GetServer(id) 1186 if err != nil { 1187 return err 1188 } 1189 if server.Status == nova.StatusBuild { 1190 return errStillBuilding 1191 } 1192 return nil 1193 }, 1194 NotifyFunc: func(lastError error, attempt int) { 1195 args.StatusCallback(status.Provisioning, fmt.Sprintf("%s, wait 10 seconds before retry, attempt %d", lastError, attempt), nil) 1196 }, 1197 IsFatalError: func(err error) bool { 1198 return err != errStillBuilding 1199 }, 1200 }) 1201 if err != nil { 1202 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1203 return nil, err 1204 } 1205 return server, nil 1206 } 1207 1208 tryStartNovaInstance := func( 1209 attempts utils.AttemptStrategy, 1210 client *nova.Client, 1211 instanceOpts nova.RunServerOpts, 1212 ) (server *nova.Entity, err error) { 1213 for a := attempts.Start(); a.Next(); { 1214 server, err = client.RunServer(instanceOpts) 1215 if err != nil { 1216 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1217 break 1218 } 1219 if server == nil { 1220 logger.Warningf("may have lost contact with nova api while creating instances, some stray instances may be around and need to be deleted") 1221 break 1222 } 1223 var serverDetail *nova.ServerDetail 1224 serverDetail, err = waitForActiveServerDetails(client, server.Id, 5*time.Minute) 1225 if err != nil || serverDetail == nil { 1226 server = nil 1227 break 1228 } else if serverDetail.Status == nova.StatusActive { 1229 break 1230 } else if serverDetail.Status == nova.StatusError { 1231 // Perhaps there is an error case where a retry in the same AZ 1232 // is a good idea. 1233 faultMsg := " unable to determine fault details" 1234 if serverDetail.Fault != nil { 1235 faultMsg = fmt.Sprintf(" with fault %q", serverDetail.Fault.Message) 1236 } else { 1237 logger.Debugf("getting active server details from nova failed without fault details") 1238 } 1239 logger.Infof("Deleting instance %q in ERROR state%v", server.Id, faultMsg) 1240 if err = e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); err != nil { 1241 logger.Debugf("Failed to delete instance in ERROR state, %q", err) 1242 } 1243 server = nil 1244 err = errors.New(faultMsg) 1245 break 1246 } 1247 } 1248 return server, err 1249 } 1250 1251 var opts = nova.RunServerOpts{ 1252 Name: machineName, 1253 FlavorId: spec.InstanceType.Id, 1254 ImageId: spec.Image.Id, 1255 UserData: userData, 1256 SecurityGroupNames: novaGroupNames, 1257 Networks: networks, 1258 Metadata: args.InstanceConfig.Tags, 1259 AvailabilityZone: args.AvailabilityZone, 1260 } 1261 e.configurator.ModifyRunServerOptions(&opts) 1262 1263 server, err := tryStartNovaInstance(shortAttempt, e.nova(), opts) 1264 if err != nil || server == nil { 1265 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1266 // 'No valid host available' is typically a resource error, 1267 // let the provisioner know it is a good idea to try another 1268 // AZ if available. 1269 err := errors.Annotate(err, "cannot run instance") 1270 zoneSpecific := isNoValidHostsError(err) 1271 if !zoneSpecific { 1272 err = common.ZoneIndependentError(err) 1273 } 1274 return nil, err 1275 } 1276 1277 detail, err := e.nova().GetServer(server.Id) 1278 if err != nil { 1279 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1280 return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot get started instance")) 1281 } 1282 1283 inst := &openstackInstance{ 1284 e: e, 1285 serverDetail: detail, 1286 arch: &spec.Image.Arch, 1287 instType: &spec.InstanceType, 1288 } 1289 logger.Infof("started instance %q", inst.Id()) 1290 withPublicIP := e.ecfg().useFloatingIP() 1291 if withPublicIP { 1292 // If we don't lock here, AllocatePublicIP() can return the same 1293 // public IP for 2 different instances. Only one will successfully 1294 // be assigned the public IP, the other will not have one. 1295 e.publicIPMutex.Lock() 1296 defer e.publicIPMutex.Unlock() 1297 var publicIP *string 1298 logger.Debugf("allocating public IP address for openstack node") 1299 if fip, err := e.networking.AllocatePublicIP(inst.Id()); err != nil { 1300 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1301 return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot allocate a public IP as needed")) 1302 } else { 1303 publicIP = fip 1304 logger.Infof("allocated public IP %s", *publicIP) 1305 } 1306 if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil { 1307 if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil { 1308 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1309 // ignore the failure at this stage, just log it 1310 logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err) 1311 } 1312 return nil, common.ZoneIndependentError(errors.Annotatef(err, 1313 "cannot assign public address %s to instance %q", 1314 *publicIP, inst.Id(), 1315 )) 1316 } 1317 inst.floatingIP = publicIP 1318 } 1319 1320 return &environs.StartInstanceResult{ 1321 Instance: inst, 1322 Hardware: inst.hardwareCharacteristics(), 1323 }, nil 1324 } 1325 1326 func (e *Environ) deriveAvailabilityZone( 1327 ctx context.ProviderCallContext, 1328 placement string, 1329 volumeAttachments []storage.VolumeAttachmentParams, 1330 ) (string, error) { 1331 volumeAttachmentsZone, err := e.volumeAttachmentsZone(volumeAttachments) 1332 if err != nil { 1333 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1334 return "", errors.Trace(err) 1335 } 1336 if placement == "" { 1337 return volumeAttachmentsZone, nil 1338 } 1339 instPlacement, err := e.parsePlacement(ctx, placement) 1340 if err != nil { 1341 return "", err 1342 } 1343 if err := validateAvailabilityZoneConsistency(instPlacement.zoneName, volumeAttachmentsZone); err != nil { 1344 return "", errors.Annotatef(err, "cannot create instance with placement %q", placement) 1345 } 1346 return instPlacement.zoneName, nil 1347 } 1348 1349 func validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone string) error { 1350 if volumeAttachmentsZone != "" && instanceZone != volumeAttachmentsZone { 1351 return errors.Errorf( 1352 "cannot create instance in zone %q, as this will prevent attaching the requested disks in zone %q", 1353 instanceZone, volumeAttachmentsZone, 1354 ) 1355 } 1356 return nil 1357 } 1358 1359 // volumeAttachmentsZone determines the availability zone for each volume 1360 // identified in the volume attachment parameters, checking that they are 1361 // all the same, and returns the availability zone name. 1362 func (e *Environ) volumeAttachmentsZone(volumeAttachments []storage.VolumeAttachmentParams) (string, error) { 1363 if len(volumeAttachments) == 0 { 1364 return "", nil 1365 } 1366 cinderProvider, err := e.cinderProvider() 1367 if err != nil { 1368 return "", errors.Trace(err) 1369 } 1370 volumes, err := modelCinderVolumes(cinderProvider.storageAdapter, cinderProvider.modelUUID) 1371 if err != nil { 1372 return "", errors.Trace(err) 1373 } 1374 var zone string 1375 for i, a := range volumeAttachments { 1376 var v *cinder.Volume 1377 for i := range volumes { 1378 if volumes[i].ID == a.VolumeId { 1379 v = &volumes[i] 1380 break 1381 } 1382 } 1383 if v == nil { 1384 return "", errors.Errorf("cannot find volume %q to attach to new instance", a.VolumeId) 1385 } 1386 if zone == "" { 1387 zone = v.AvailabilityZone 1388 } else if v.AvailabilityZone != zone { 1389 return "", errors.Errorf( 1390 "cannot attach volumes from multiple availability zones: %s is in %s, %s is in %s", 1391 volumeAttachments[i-1].VolumeId, zone, a.VolumeId, v.AvailabilityZone, 1392 ) 1393 } 1394 } 1395 return zone, nil 1396 } 1397 1398 func isNoValidHostsError(err error) bool { 1399 if cause := errors.Cause(err); cause != nil { 1400 return strings.Contains(cause.Error(), "No valid host was found") 1401 } 1402 return false 1403 } 1404 1405 func (e *Environ) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error { 1406 // If in instance firewall mode, gather the security group names. 1407 securityGroupNames, err := e.firewaller.GetSecurityGroups(ctx, ids...) 1408 if err == environs.ErrNoInstances { 1409 return nil 1410 } 1411 if err != nil { 1412 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1413 return err 1414 } 1415 logger.Debugf("terminating instances %v", ids) 1416 if err := e.terminateInstances(ctx, ids); err != nil { 1417 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1418 return err 1419 } 1420 if securityGroupNames != nil { 1421 if err := e.firewaller.DeleteGroups(ctx, securityGroupNames...); err != nil { 1422 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1423 return err 1424 } 1425 } 1426 return nil 1427 } 1428 1429 func (e *Environ) isAliveServer(server nova.ServerDetail) bool { 1430 switch server.Status { 1431 case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended: 1432 return true 1433 } 1434 return false 1435 } 1436 1437 func (e *Environ) listServers(ctx context.ProviderCallContext, ids []instance.Id) ([]nova.ServerDetail, error) { 1438 wantedServers := make([]nova.ServerDetail, 0, len(ids)) 1439 if len(ids) == 1 { 1440 // Common case, single instance, may return NotFound 1441 var maybeServer *nova.ServerDetail 1442 maybeServer, err := e.nova().GetServer(string(ids[0])) 1443 if err != nil { 1444 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1445 return nil, err 1446 } 1447 // Only return server details if it is currently alive 1448 if maybeServer != nil && e.isAliveServer(*maybeServer) { 1449 wantedServers = append(wantedServers, *maybeServer) 1450 } 1451 return wantedServers, nil 1452 } 1453 // List all instances in the environment. 1454 instances, err := e.AllInstances(ctx) 1455 if err != nil { 1456 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1457 return nil, err 1458 } 1459 // Return only servers with the wanted ids that are currently alive 1460 for _, inst := range instances { 1461 inst := inst.(*openstackInstance) 1462 serverDetail := *inst.serverDetail 1463 if !e.isAliveServer(serverDetail) { 1464 continue 1465 } 1466 for _, id := range ids { 1467 if inst.Id() != id { 1468 continue 1469 } 1470 wantedServers = append(wantedServers, serverDetail) 1471 break 1472 } 1473 } 1474 return wantedServers, nil 1475 } 1476 1477 // updateFloatingIPAddresses updates the instances with any floating IP address 1478 // that have been assigned to those instances. 1479 func (e *Environ) updateFloatingIPAddresses(ctx context.ProviderCallContext, instances map[string]instances.Instance) error { 1480 servers, err := e.nova().ListServersDetail(jujuMachineFilter()) 1481 if err != nil { 1482 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1483 return err 1484 } 1485 for _, server := range servers { 1486 // server.Addresses is a map with entries containing []nova.IPAddress 1487 for _, net := range server.Addresses { 1488 for _, addr := range net { 1489 if addr.Type == "floating" { 1490 instId := server.Id 1491 if inst, ok := instances[instId]; ok { 1492 instFip := &addr.Address 1493 inst.(*openstackInstance).floatingIP = instFip 1494 } 1495 } 1496 } 1497 } 1498 } 1499 return nil 1500 } 1501 1502 func (e *Environ) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) { 1503 if len(ids) == 0 { 1504 return nil, nil 1505 } 1506 // Make a series of requests to cope with eventual consistency. 1507 // Each request will attempt to add more instances to the requested 1508 // set. 1509 var foundServers []nova.ServerDetail 1510 for a := shortAttempt.Start(); a.Next(); { 1511 var err error 1512 foundServers, err = e.listServers(ctx, ids) 1513 if err != nil { 1514 logger.Debugf("error listing servers: %v", err) 1515 if !gooseerrors.IsNotFound(err) { 1516 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1517 return nil, err 1518 } 1519 } 1520 if len(foundServers) == len(ids) { 1521 break 1522 } 1523 } 1524 logger.Tracef("%d/%d live servers found", len(foundServers), len(ids)) 1525 if len(foundServers) == 0 { 1526 return nil, environs.ErrNoInstances 1527 } 1528 1529 instsById := make(map[string]instances.Instance, len(foundServers)) 1530 for i, server := range foundServers { 1531 // TODO(wallyworld): lookup the flavor details to fill in the 1532 // instance type data 1533 instsById[server.Id] = &openstackInstance{ 1534 e: e, 1535 serverDetail: &foundServers[i], 1536 } 1537 } 1538 1539 // Update the instance structs with any floating IP address that has been assigned to the instance. 1540 if e.ecfg().useFloatingIP() { 1541 if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil { 1542 return nil, err 1543 } 1544 } 1545 1546 insts := make([]instances.Instance, len(ids)) 1547 var err error 1548 for i, id := range ids { 1549 if inst := instsById[string(id)]; inst != nil { 1550 insts[i] = inst 1551 } else { 1552 err = environs.ErrPartialInstances 1553 } 1554 } 1555 return insts, err 1556 } 1557 1558 // AdoptResources is part of the Environ interface. 1559 func (e *Environ) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 1560 var failed []string 1561 controllerTag := map[string]string{tags.JujuController: controllerUUID} 1562 1563 instances, err := e.AllInstances(ctx) 1564 if err != nil { 1565 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1566 return errors.Trace(err) 1567 } 1568 for _, instance := range instances { 1569 err := e.TagInstance(ctx, instance.Id(), controllerTag) 1570 if err != nil { 1571 logger.Errorf("error updating controller tag for instance %s: %v", instance.Id(), err) 1572 failed = append(failed, string(instance.Id())) 1573 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 1574 // If we have an invvalid credential, there is no need to proceed: we'll fail 100%. 1575 break 1576 } 1577 } 1578 } 1579 1580 failedVolumes, err := e.adoptVolumes(controllerTag, ctx) 1581 if err != nil { 1582 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1583 return errors.Trace(err) 1584 } 1585 failed = append(failed, failedVolumes...) 1586 1587 err = e.firewaller.UpdateGroupController(ctx, controllerUUID) 1588 if err != nil { 1589 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1590 return errors.Trace(err) 1591 } 1592 if len(failed) != 0 { 1593 return errors.Errorf("error updating controller tag for some resources: %v", failed) 1594 } 1595 return nil 1596 } 1597 1598 func (e *Environ) adoptVolumes(controllerTag map[string]string, ctx context.ProviderCallContext) ([]string, error) { 1599 cinder, err := e.cinderProvider() 1600 if errors.IsNotSupported(err) { 1601 logger.Debugf("volumes not supported: not transferring ownership for volumes") 1602 return nil, nil 1603 } 1604 if err != nil { 1605 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1606 return nil, errors.Trace(err) 1607 } 1608 // TODO(axw): fix the storage API. 1609 storageConfig, err := storage.NewConfig("cinder", CinderProviderType, nil) 1610 if err != nil { 1611 return nil, errors.Trace(err) 1612 } 1613 volumeSource, err := cinder.VolumeSource(storageConfig) 1614 if err != nil { 1615 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1616 return nil, errors.Trace(err) 1617 } 1618 volumeIds, err := volumeSource.ListVolumes(ctx) 1619 if err != nil { 1620 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1621 return nil, errors.Trace(err) 1622 } 1623 1624 var failed []string 1625 for _, volumeId := range volumeIds { 1626 _, err := cinder.storageAdapter.SetVolumeMetadata(volumeId, controllerTag) 1627 if err != nil { 1628 logger.Errorf("error updating controller tag for volume %s: %v", volumeId, err) 1629 failed = append(failed, volumeId) 1630 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 1631 // If we have an invvalid credential, there is no need to proceed: we'll fail 100%. 1632 break 1633 } 1634 } 1635 } 1636 return failed, nil 1637 } 1638 1639 // AllInstances returns all instances in this environment. 1640 func (e *Environ) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 1641 tagFilter := tagValue{tags.JujuModel, e.ecfg().UUID()} 1642 instances, err := e.allInstances(ctx, tagFilter, e.ecfg().useFloatingIP()) 1643 if err != nil { 1644 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1645 return instances, err 1646 } 1647 return instances, nil 1648 } 1649 1650 // allControllerManagedInstances returns all instances managed by this 1651 // environment's controller, matching the optionally specified filter. 1652 func (e *Environ) allControllerManagedInstances(ctx context.ProviderCallContext, controllerUUID string, updateFloatingIPAddresses bool) ([]instances.Instance, error) { 1653 tagFilter := tagValue{tags.JujuController, controllerUUID} 1654 instances, err := e.allInstances(ctx, tagFilter, updateFloatingIPAddresses) 1655 if err != nil { 1656 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1657 return instances, err 1658 } 1659 return instances, nil 1660 } 1661 1662 type tagValue struct { 1663 tag, value string 1664 } 1665 1666 // allControllerManagedInstances returns all instances managed by this 1667 // environment's controller, matching the optionally specified filter. 1668 func (e *Environ) allInstances(ctx context.ProviderCallContext, tagFilter tagValue, updateFloatingIPAddresses bool) ([]instances.Instance, error) { 1669 servers, err := e.nova().ListServersDetail(jujuMachineFilter()) 1670 if err != nil { 1671 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1672 return nil, err 1673 } 1674 instsById := make(map[string]instances.Instance) 1675 for _, server := range servers { 1676 if server.Metadata[tagFilter.tag] != tagFilter.value { 1677 continue 1678 } 1679 if e.isAliveServer(server) { 1680 var s = server 1681 // TODO(wallyworld): lookup the flavor details to fill in the instance type data 1682 instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s} 1683 } 1684 } 1685 if updateFloatingIPAddresses { 1686 if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil { 1687 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1688 return nil, err 1689 } 1690 } 1691 insts := make([]instances.Instance, 0, len(instsById)) 1692 for _, inst := range instsById { 1693 insts = append(insts, inst) 1694 } 1695 return insts, nil 1696 } 1697 1698 func (e *Environ) Destroy(ctx context.ProviderCallContext) error { 1699 err := common.Destroy(e, ctx) 1700 if err != nil { 1701 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1702 return errors.Trace(err) 1703 } 1704 // Delete all security groups remaining in the model. 1705 if err := e.firewaller.DeleteAllModelGroups(ctx); err != nil { 1706 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1707 return errors.Trace(err) 1708 } 1709 return nil 1710 } 1711 1712 // DestroyController implements the Environ interface. 1713 func (e *Environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error { 1714 if err := e.Destroy(ctx); err != nil { 1715 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1716 return errors.Annotate(err, "destroying controller model") 1717 } 1718 // In case any hosted environment hasn't been cleaned up yet, 1719 // we also attempt to delete their resources when the controller 1720 // environment is destroyed. 1721 if err := e.destroyControllerManagedEnvirons(ctx, controllerUUID); err != nil { 1722 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1723 return errors.Annotate(err, "destroying managed models") 1724 } 1725 if err := e.firewaller.DeleteAllControllerGroups(ctx, controllerUUID); err != nil { 1726 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1727 return errors.Trace(err) 1728 } 1729 return nil 1730 } 1731 1732 // destroyControllerManagedEnvirons destroys all environments managed by this 1733 // models's controller. 1734 func (e *Environ) destroyControllerManagedEnvirons(ctx context.ProviderCallContext, controllerUUID string) error { 1735 // Terminate all instances managed by the controller. 1736 insts, err := e.allControllerManagedInstances(ctx, controllerUUID, false) 1737 if err != nil { 1738 return errors.Annotate(err, "listing instances") 1739 } 1740 instIds := make([]instance.Id, len(insts)) 1741 for i, inst := range insts { 1742 instIds[i] = inst.Id() 1743 } 1744 if err := e.terminateInstances(ctx, instIds); err != nil { 1745 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1746 return errors.Annotate(err, "terminating instances") 1747 } 1748 1749 // Delete all volumes managed by the controller. 1750 cinder, err := e.cinderProvider() 1751 if err == nil { 1752 volumes, err := controllerCinderVolumes(cinder.storageAdapter, controllerUUID) 1753 if err != nil { 1754 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1755 return errors.Annotate(err, "listing volumes") 1756 } 1757 volIds := volumeInfoToVolumeIds(cinderToJujuVolumeInfos(volumes)) 1758 errs := foreachVolume(ctx, cinder.storageAdapter, volIds, destroyVolume) 1759 for i, err := range errs { 1760 if err == nil { 1761 continue 1762 } 1763 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1764 return errors.Annotatef(err, "destroying volume %q", volIds[i]) 1765 } 1766 } else if !errors.IsNotSupported(err) { 1767 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1768 return errors.Trace(err) 1769 } 1770 1771 // Security groups for hosted models are destroyed by the 1772 // DeleteAllControllerGroups method call from Destroy(). 1773 return nil 1774 } 1775 1776 func resourceName(namespace instance.Namespace, envName, resourceId string) string { 1777 return namespace.Value(envName + "-" + resourceId) 1778 } 1779 1780 // jujuMachineFilter returns a nova.Filter matching machines created by Juju. 1781 // The machines are not filtered to any particular environment. To do that, 1782 // instance tags must be compared. 1783 func jujuMachineFilter() *nova.Filter { 1784 filter := nova.NewFilter() 1785 filter.Set(nova.FilterServer, "juju-.*") 1786 return filter 1787 } 1788 1789 // rulesToRuleInfo maps ingress rules to nova rules 1790 func rulesToRuleInfo(groupId string, rules []network.IngressRule) []neutron.RuleInfoV2 { 1791 var result []neutron.RuleInfoV2 1792 for _, r := range rules { 1793 ruleInfo := neutron.RuleInfoV2{ 1794 Direction: "ingress", 1795 ParentGroupId: groupId, 1796 PortRangeMin: r.FromPort, 1797 PortRangeMax: r.ToPort, 1798 IPProtocol: r.Protocol, 1799 } 1800 sourceCIDRs := r.SourceCIDRs 1801 if len(sourceCIDRs) == 0 { 1802 sourceCIDRs = []string{"0.0.0.0/0"} 1803 } 1804 for _, sr := range sourceCIDRs { 1805 ruleInfo.RemoteIPPrefix = sr 1806 result = append(result, ruleInfo) 1807 } 1808 } 1809 return result 1810 } 1811 1812 func (e *Environ) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 1813 if err := e.firewaller.OpenPorts(ctx, rules); err != nil { 1814 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1815 return errors.Trace(err) 1816 } 1817 return nil 1818 } 1819 1820 func (e *Environ) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 1821 if err := e.firewaller.ClosePorts(ctx, rules); err != nil { 1822 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1823 return errors.Trace(err) 1824 } 1825 return nil 1826 } 1827 1828 func (e *Environ) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) { 1829 rules, err := e.firewaller.IngressRules(ctx) 1830 if err != nil { 1831 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1832 return rules, errors.Trace(err) 1833 } 1834 return rules, nil 1835 } 1836 1837 func (e *Environ) Provider() environs.EnvironProvider { 1838 return providerInstance 1839 } 1840 1841 func (e *Environ) terminateInstances(ctx context.ProviderCallContext, ids []instance.Id) error { 1842 if len(ids) == 0 { 1843 return nil 1844 } 1845 var firstErr error 1846 novaClient := e.nova() 1847 for _, id := range ids { 1848 err := novaClient.DeleteServer(string(id)) 1849 if gooseerrors.IsNotFound(err) { 1850 err = nil 1851 } 1852 if err != nil && firstErr == nil { 1853 logger.Debugf("error terminating instance %q: %v", id, err) 1854 firstErr = err 1855 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 1856 // We'll 100% fail all subsequent calls if we have an invalid credential. 1857 break 1858 } 1859 } 1860 } 1861 return firstErr 1862 } 1863 1864 // MetadataLookupParams returns parameters which are used to query simplestreams metadata. 1865 func (e *Environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 1866 if region == "" { 1867 region = e.cloud.Region 1868 } 1869 cloudSpec, err := e.cloudSpec(region) 1870 if err != nil { 1871 return nil, err 1872 } 1873 return &simplestreams.MetadataLookupParams{ 1874 Series: config.PreferredSeries(e.ecfg()), 1875 Region: cloudSpec.Region, 1876 Endpoint: cloudSpec.Endpoint, 1877 }, nil 1878 } 1879 1880 // Region is specified in the HasRegion interface. 1881 func (e *Environ) Region() (simplestreams.CloudSpec, error) { 1882 return e.cloudSpec(e.cloud.Region) 1883 } 1884 1885 func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) { 1886 return simplestreams.CloudSpec{ 1887 Region: region, 1888 Endpoint: e.cloud.Endpoint, 1889 }, nil 1890 } 1891 1892 // TagInstance implements environs.InstanceTagger. 1893 func (e *Environ) TagInstance(ctx context.ProviderCallContext, id instance.Id, tags map[string]string) error { 1894 if err := e.nova().SetServerMetadata(string(id), tags); err != nil { 1895 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1896 return errors.Annotate(err, "setting server metadata") 1897 } 1898 return nil 1899 } 1900 1901 func (e *Environ) SetClock(clock clock.Clock) { 1902 e.clock = clock 1903 } 1904 1905 func validateCloudSpec(spec environs.CloudSpec) error { 1906 if err := spec.Validate(); err != nil { 1907 return errors.Trace(err) 1908 } 1909 if err := validateAuthURL(spec.Endpoint); err != nil { 1910 return errors.Annotate(err, "validating auth-url") 1911 } 1912 if spec.Credential == nil { 1913 return errors.NotValidf("missing credential") 1914 } 1915 switch authType := spec.Credential.AuthType(); authType { 1916 case cloud.UserPassAuthType: 1917 case cloud.AccessKeyAuthType: 1918 default: 1919 return errors.NotSupportedf("%q auth-type", authType) 1920 } 1921 return nil 1922 } 1923 1924 func validateAuthURL(authURL string) error { 1925 parts, err := url.Parse(authURL) 1926 if err != nil || parts.Host == "" || parts.Scheme == "" { 1927 return errors.NotValidf("auth-url %q", authURL) 1928 } 1929 return nil 1930 } 1931 1932 // Subnets is specified on environs.Networking. 1933 func (e *Environ) Subnets(ctx context.ProviderCallContext, instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1934 subnets, err := e.networking.Subnets(instId, subnetIds) 1935 if err != nil { 1936 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1937 return subnets, errors.Trace(err) 1938 } 1939 return subnets, nil 1940 } 1941 1942 // NetworkInterfaces is specified on environs.Networking. 1943 func (e *Environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) { 1944 infos, err := e.networking.NetworkInterfaces(instId) 1945 if err != nil { 1946 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1947 return infos, errors.Trace(err) 1948 } 1949 return infos, nil 1950 } 1951 1952 // SupportsSpaces is specified on environs.Networking. 1953 func (e *Environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) { 1954 return false, nil 1955 } 1956 1957 // SupportsSpaceDiscovery is specified on environs.Networking. 1958 func (e *Environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) { 1959 return false, nil 1960 } 1961 1962 // Spaces is specified on environs.Networking. 1963 func (e *Environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) { 1964 return nil, errors.NotSupportedf("spaces") 1965 } 1966 1967 // SupportsContainerAddresses is specified on environs.Networking. 1968 func (e *Environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) { 1969 return false, errors.NotSupportedf("container address") 1970 } 1971 1972 // SuperSubnets is specified on environs.Networking 1973 func (e *Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) { 1974 subnets, err := e.networking.Subnets("", nil) 1975 if err != nil { 1976 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 1977 return nil, err 1978 } 1979 cidrs := make([]string, len(subnets)) 1980 for i, subnet := range subnets { 1981 cidrs[i] = subnet.CIDR 1982 } 1983 return cidrs, nil 1984 } 1985 1986 // AllocateContainerAddresses is specified on environs.Networking. 1987 func (e *Environ) AllocateContainerAddresses(ctx context.ProviderCallContext, hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) { 1988 return nil, errors.NotSupportedf("allocate container address") 1989 } 1990 1991 // ReleaseContainerAddresses is specified on environs.Networking. 1992 func (e *Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error { 1993 return errors.NotSupportedf("release container address") 1994 } 1995 1996 // ProviderSpaceInfo is specified on environs.NetworkingEnviron. 1997 func (*Environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) { 1998 return nil, errors.NotSupportedf("provider space info") 1999 } 2000 2001 // AreSpacesRoutable is specified on environs.NetworkingEnviron. 2002 func (*Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) { 2003 return false, nil 2004 } 2005 2006 // SSHAddresses is specified on environs.SSHAddresses. 2007 func (*Environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) { 2008 return addresses, nil 2009 }