github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 stdcontext "context" 10 "crypto/tls" 11 "crypto/x509" 12 "fmt" 13 "math/rand" 14 "net/url" 15 "path" 16 "sort" 17 "strconv" 18 "strings" 19 "sync" 20 "time" 21 22 "github.com/go-goose/goose/v5/cinder" 23 "github.com/go-goose/goose/v5/client" 24 gooseerrors "github.com/go-goose/goose/v5/errors" 25 "github.com/go-goose/goose/v5/identity" 26 "github.com/go-goose/goose/v5/neutron" 27 "github.com/go-goose/goose/v5/nova" 28 "github.com/juju/clock" 29 "github.com/juju/collections/set" 30 "github.com/juju/collections/transform" 31 "github.com/juju/errors" 32 "github.com/juju/http/v2" 33 "github.com/juju/jsonschema" 34 "github.com/juju/loggo" 35 "github.com/juju/names/v5" 36 "github.com/juju/retry" 37 "github.com/juju/utils/v3" 38 "github.com/juju/version/v2" 39 40 "github.com/juju/juju/cloud" 41 "github.com/juju/juju/cloudconfig/cloudinit" 42 "github.com/juju/juju/cloudconfig/instancecfg" 43 "github.com/juju/juju/cloudconfig/providerinit" 44 "github.com/juju/juju/cmd/juju/interact" 45 "github.com/juju/juju/core/constraints" 46 "github.com/juju/juju/core/instance" 47 "github.com/juju/juju/core/network" 48 "github.com/juju/juju/core/network/firewall" 49 "github.com/juju/juju/core/status" 50 "github.com/juju/juju/environs" 51 environscloudspec "github.com/juju/juju/environs/cloudspec" 52 "github.com/juju/juju/environs/config" 53 "github.com/juju/juju/environs/context" 54 "github.com/juju/juju/environs/imagemetadata" 55 "github.com/juju/juju/environs/instances" 56 "github.com/juju/juju/environs/simplestreams" 57 "github.com/juju/juju/environs/tags" 58 "github.com/juju/juju/provider/common" 59 "github.com/juju/juju/storage" 60 ) 61 62 var logger = loggo.GetLogger("juju.provider.openstack") 63 64 type EnvironProvider struct { 65 environs.ProviderCredentials 66 Configurator ProviderConfigurator 67 FirewallerFactory FirewallerFactory 68 FlavorFilter FlavorFilter 69 70 // ClientFromEndpoint returns an Openstack client for the given endpoint. 71 ClientFromEndpoint func(endpoint string) client.AuthenticatingClient 72 } 73 74 var ( 75 _ environs.CloudEnvironProvider = (*EnvironProvider)(nil) 76 _ environs.ProviderSchema = (*EnvironProvider)(nil) 77 ) 78 79 var providerInstance = &EnvironProvider{ 80 ProviderCredentials: OpenstackCredentials{}, 81 Configurator: &defaultConfigurator{}, 82 FirewallerFactory: &firewallerFactory{}, 83 FlavorFilter: FlavorFilterFunc(AcceptAllFlavors), 84 ClientFromEndpoint: newGooseClient, 85 } 86 87 var cloudSchema = &jsonschema.Schema{ 88 Type: []jsonschema.Type{jsonschema.ObjectType}, 89 Required: []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey}, 90 Order: []string{cloud.EndpointKey, cloud.CertFilenameKey, cloud.AuthTypesKey, cloud.RegionsKey}, 91 Properties: map[string]*jsonschema.Schema{ 92 cloud.EndpointKey: { 93 Singular: "the API endpoint url for the cloud", 94 Type: []jsonschema.Type{jsonschema.StringType}, 95 Format: jsonschema.FormatURI, 96 Default: "", 97 EnvVars: []string{"OS_AUTH_URL"}, 98 }, 99 cloud.CertFilenameKey: { 100 Singular: "a path to the CA certificate for your cloud if one is required to access it. (optional)", 101 Type: []jsonschema.Type{jsonschema.StringType}, 102 Format: interact.FormatCertFilename, 103 Default: "", 104 PromptDefault: "none", 105 EnvVars: []string{"OS_CACERT"}, 106 }, 107 cloud.AuthTypesKey: { 108 Singular: "auth type", 109 Plural: "auth types", 110 Type: []jsonschema.Type{jsonschema.ArrayType}, 111 UniqueItems: jsonschema.Bool(true), 112 Items: &jsonschema.ItemSpec{ 113 Schemas: []*jsonschema.Schema{{ 114 Type: []jsonschema.Type{jsonschema.StringType}, 115 Enum: []interface{}{ 116 string(cloud.AccessKeyAuthType), 117 string(cloud.UserPassAuthType), 118 }, 119 }}, 120 }, 121 }, 122 cloud.RegionsKey: { 123 Type: []jsonschema.Type{jsonschema.ObjectType}, 124 Singular: "region", 125 Plural: "regions", 126 Default: "", 127 EnvVars: []string{"OS_REGION_NAME"}, 128 AdditionalProperties: &jsonschema.Schema{ 129 Type: []jsonschema.Type{jsonschema.ObjectType}, 130 Required: []string{cloud.EndpointKey}, 131 MaxProperties: jsonschema.Int(1), 132 Properties: map[string]*jsonschema.Schema{ 133 cloud.EndpointKey: { 134 Singular: "the API endpoint url for the region", 135 Type: []jsonschema.Type{jsonschema.StringType}, 136 Format: jsonschema.FormatURI, 137 Default: "", 138 PromptDefault: "use cloud api url", 139 }, 140 }, 141 }, 142 }, 143 }, 144 } 145 146 var makeServiceURL = client.AuthenticatingClient.MakeServiceURL 147 148 // TODO: shortAttempt was kept to a long timeout because Nova needs 149 // more time than EC2. Storage delays are handled separately now, and 150 // perhaps other polling attempts can time out faster. 151 152 // shortAttempt is used when polling for short-term events in tests. 153 var shortAttempt = utils.AttemptStrategy{ 154 Total: 15 * time.Second, 155 Delay: 200 * time.Millisecond, 156 } 157 158 // Version is part of the EnvironProvider interface. 159 func (EnvironProvider) Version() int { 160 return 0 161 } 162 163 func (p EnvironProvider) Open(ctx stdcontext.Context, args environs.OpenParams) (environs.Environ, error) { 164 logger.Infof("opening model %q", args.Config.Name()) 165 uuid := args.Config.UUID() 166 namespace, err := instance.NewNamespace(uuid) 167 if err != nil { 168 return nil, errors.Annotate(err, "creating instance namespace") 169 } 170 171 e := &Environ{ 172 name: args.Config.Name(), 173 uuid: uuid, 174 namespace: namespace, 175 clock: clock.WallClock, 176 configurator: p.Configurator, 177 flavorFilter: p.FlavorFilter, 178 } 179 180 if err := e.SetConfig(args.Config); err != nil { 181 return nil, errors.Trace(err) 182 } 183 if err := e.SetCloudSpec(ctx, args.Cloud); err != nil { 184 return nil, errors.Trace(err) 185 } 186 187 e.networking, e.firewaller, err = p.getEnvironNetworkingFirewaller(e) 188 if err != nil { 189 return nil, errors.Trace(err) 190 } 191 192 return e, nil 193 } 194 195 // getEnvironNetworkingFirewaller returns Networking and Firewaller for the 196 // new Environ. Both require Neutron to be support by the OpenStack cloud, 197 // so create together. 198 func (p EnvironProvider) getEnvironNetworkingFirewaller(e *Environ) (Networking, Firewaller, error) { 199 // TODO (hml) 2019-12-05 200 // We want to ensure a failure if an old nova networking OpenStack is 201 // added as a new model to a multi-cloud controller. However the 202 // current OpenStack testservice does not implement EndpointsForRegions(), 203 // thus causing failures and panics in the setup of the majority of 204 // provider unit tests. Or a rewrite of code and/or tests. 205 // See LP:1855343 206 if err := authenticateClient(e.client()); err != nil { 207 return nil, nil, errors.Trace(err) 208 } 209 if !e.supportsNeutron() { 210 // This should turn into a failure, left as an Error message for now to help 211 // provide context for failing networking calls by this environ. Previously 212 // this was covered by switchingNetworking{} and switchingFirewaller{}. 213 logger.Errorf("Using unsupported OpenStack APIs. Neutron networking " + 214 "is not supported by this OpenStack cloud.\n Please use OpenStack Queens or " + 215 "newer to maintain compatibility.") 216 } 217 networking := newNetworking(e) 218 return networking, p.FirewallerFactory.GetFirewaller(e), nil 219 } 220 221 // DetectRegions implements environs.CloudRegionDetector. 222 func (EnvironProvider) DetectRegions() ([]cloud.Region, error) { 223 // If OS_REGION_NAME and OS_AUTH_URL are both set, 224 // return a region using them. 225 creds, err := identity.CredentialsFromEnv() 226 if err != nil { 227 return nil, errors.Errorf("failed to retrieve credential from env : %v", err) 228 } 229 if creds.Region == "" { 230 return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set") 231 } 232 if creds.URL == "" { 233 return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set") 234 } 235 return []cloud.Region{{ 236 Name: creds.Region, 237 Endpoint: creds.URL, 238 }}, nil 239 } 240 241 // CloudSchema returns the schema for adding new clouds of this type. 242 func (p EnvironProvider) CloudSchema() *jsonschema.Schema { 243 return cloudSchema 244 } 245 246 // Ping tests the connection to the cloud, to verify the endpoint is valid. 247 func (p EnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 248 c := p.ClientFromEndpoint(endpoint) 249 if _, err := c.IdentityAuthOptions(); err != nil { 250 handleCredentialError(err, ctx) 251 return errors.Annotatef(err, "No Openstack server running at %s", endpoint) 252 } 253 return nil 254 } 255 256 // newGooseClient is the default function in EnvironProvider.ClientFromEndpoint. 257 func newGooseClient(endpoint string) client.AuthenticatingClient { 258 // Use NonValidatingClient, in case the endpoint is behind a cert 259 return client.NewNonValidatingClient(&identity.Credentials{URL: endpoint}, 0, nil) 260 } 261 262 // PrepareConfig is specified in the EnvironProvider interface. 263 func (p EnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 264 if err := validateCloudSpec(args.Cloud); err != nil { 265 return nil, errors.Annotate(err, "validating cloud spec") 266 } 267 268 // Set the default block-storage source. 269 attrs := make(map[string]interface{}) 270 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 271 attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType 272 } 273 274 cfg, err := args.Config.Apply(attrs) 275 if err != nil { 276 return nil, errors.Trace(err) 277 } 278 return cfg, nil 279 } 280 281 // AgentMetadataLookupParams returns parameters which are used to query agent metadata to 282 // find matching image information. 283 func (p EnvironProvider) AgentMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 284 return p.metadataLookupParams(region) 285 } 286 287 // ImageMetadataLookupParams returns parameters which are used to query image metadata to 288 // find matching image information. 289 func (p EnvironProvider) ImageMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 290 return p.metadataLookupParams(region) 291 } 292 293 func (p EnvironProvider) metadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 294 if region == "" { 295 return nil, errors.Errorf("region must be specified") 296 } 297 return &simplestreams.MetadataLookupParams{ 298 Region: region, 299 }, nil 300 } 301 302 func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) { 303 valid, err := p.Validate(cfg, nil) 304 if err != nil { 305 return nil, err 306 } 307 return &environConfig{valid, valid.UnknownAttrs()}, nil 308 } 309 310 type Environ struct { 311 environs.NoSpaceDiscoveryEnviron 312 313 name string 314 uuid string 315 namespace instance.Namespace 316 317 ecfgMutex sync.Mutex 318 ecfgUnlocked *environConfig 319 cloudUnlocked environscloudspec.CloudSpec 320 clientUnlocked client.AuthenticatingClient 321 novaUnlocked *nova.Client 322 neutronUnlocked *neutron.Client 323 volumeURL *url.URL 324 325 // keystoneImageDataSource caches the result of getKeystoneImageSource. 326 keystoneImageDataSourceMutex sync.Mutex 327 keystoneImageDataSource simplestreams.DataSource 328 329 // keystoneToolsDataSource caches the result of getKeystoneToolsSource. 330 keystoneToolsDataSourceMutex sync.Mutex 331 keystoneToolsDataSource simplestreams.DataSource 332 333 // usingSecurityGroups tracks whether this model is using security groups 334 // for firewalling. This will be false if a network has port_security disabled, 335 // true otherwise. 336 // However, once a model security group is created, it is not removed if such a model 337 // is added, this option sticks to true 338 usingSecurityGroups bool 339 340 firewaller Firewaller 341 networking Networking 342 configurator ProviderConfigurator 343 flavorFilter FlavorFilter 344 345 // Clock is defined so it can be replaced for testing 346 clock clock.Clock 347 348 publicIPMutex sync.Mutex 349 } 350 351 var _ environs.Environ = (*Environ)(nil) 352 var _ environs.NetworkingEnviron = (*Environ)(nil) 353 var _ simplestreams.HasRegion = (*Environ)(nil) 354 var _ context.Distributor = (*Environ)(nil) 355 var _ environs.InstanceTagger = (*Environ)(nil) 356 357 type openstackInstance struct { 358 e *Environ 359 instType *instances.InstanceType 360 arch *string 361 362 mu sync.Mutex 363 serverDetail *nova.ServerDetail 364 // floatingIP is non-nil iff use-floating-ip is true. 365 floatingIP *string 366 367 // runOpts is only set in the response from StartInstance. 368 runOpts *nova.RunServerOpts 369 } 370 371 // NovaInstanceStartedWithOpts exposes run options used to start an instance. 372 // Used by unit testing. 373 func (inst *openstackInstance) NovaInstanceStartedWithOpts() *nova.RunServerOpts { 374 return inst.runOpts 375 } 376 377 func (inst *openstackInstance) String() string { 378 return string(inst.Id()) 379 } 380 381 var _ instances.Instance = (*openstackInstance)(nil) 382 383 func (inst *openstackInstance) Refresh(ctx context.ProviderCallContext) error { 384 inst.mu.Lock() 385 defer inst.mu.Unlock() 386 server, err := inst.e.nova().GetServer(inst.serverDetail.Id) 387 if err != nil { 388 handleCredentialError(err, ctx) 389 return err 390 } 391 inst.serverDetail = server 392 return nil 393 } 394 395 func (inst *openstackInstance) getServerDetail() *nova.ServerDetail { 396 inst.mu.Lock() 397 defer inst.mu.Unlock() 398 return inst.serverDetail 399 } 400 401 func (inst *openstackInstance) Id() instance.Id { 402 return instance.Id(inst.getServerDetail().Id) 403 } 404 405 func (inst *openstackInstance) Status(ctx context.ProviderCallContext) instance.Status { 406 instStatus := inst.getServerDetail().Status 407 var jujuStatus status.Status 408 switch instStatus { 409 case nova.StatusActive: 410 jujuStatus = status.Running 411 case nova.StatusError: 412 jujuStatus = status.ProvisioningError 413 case nova.StatusBuild, nova.StatusBuildSpawning, 414 nova.StatusDeleted, nova.StatusHardReboot, 415 nova.StatusPassword, nova.StatusReboot, 416 nova.StatusRebuild, nova.StatusRescue, 417 nova.StatusResize, nova.StatusShutoff, 418 nova.StatusSuspended, nova.StatusVerifyResize: 419 jujuStatus = status.Empty 420 case nova.StatusUnknown: 421 jujuStatus = status.Unknown 422 default: 423 jujuStatus = status.Empty 424 } 425 return instance.Status{ 426 Status: jujuStatus, 427 Message: instStatus, 428 } 429 } 430 431 func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics { 432 hc := &instance.HardwareCharacteristics{Arch: inst.arch} 433 if inst.instType != nil { 434 hc.Mem = &inst.instType.Mem 435 // openstack is special in that a 0-size root disk means that 436 // the root disk will result in an instance with a root disk 437 // the same size as the image that created it, so we just set 438 // the HardwareCharacteristics to nil to signal that we don't 439 // know what the correct size is. 440 if inst.instType.RootDisk == 0 { 441 hc.RootDisk = nil 442 } else { 443 hc.RootDisk = &inst.instType.RootDisk 444 } 445 hc.CpuCores = &inst.instType.CpuCores 446 hc.CpuPower = inst.instType.CpuPower 447 // tags not currently supported on openstack 448 } 449 hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone 450 // If the instance was started with a volume block device mapping, select the first 451 // boot disk as the reported RootDisk size. 452 if inst.runOpts != nil { 453 for _, blockDevice := range inst.runOpts.BlockDeviceMappings { 454 if blockDevice.BootIndex == 0 && 455 blockDevice.DestinationType == rootDiskSourceVolume { 456 rootDiskSize := uint64(blockDevice.VolumeSize * 1024) 457 hc.RootDisk = &rootDiskSize 458 break 459 } 460 } 461 } 462 return hc 463 } 464 465 // getAddresses returns the existing server information on addresses, 466 // but fetches the details over the api again if no addresses exist. 467 func (inst *openstackInstance) getAddresses(ctx context.ProviderCallContext) (map[string][]nova.IPAddress, error) { 468 addrs := inst.getServerDetail().Addresses 469 if len(addrs) == 0 { 470 server, err := inst.e.nova().GetServer(string(inst.Id())) 471 if err != nil { 472 handleCredentialError(err, ctx) 473 return nil, err 474 } 475 addrs = server.Addresses 476 } 477 return addrs, nil 478 } 479 480 // Addresses implements network.Addresses() returning generic address 481 // details for the instances, and calling the openstack api if needed. 482 func (inst *openstackInstance) Addresses(ctx context.ProviderCallContext) (network.ProviderAddresses, error) { 483 addresses, err := inst.getAddresses(ctx) 484 if err != nil { 485 return nil, err 486 } 487 var floatingIP string 488 if inst.floatingIP != nil { 489 floatingIP = *inst.floatingIP 490 logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP) 491 } 492 return convertNovaAddresses(floatingIP, addresses), nil 493 } 494 495 // convertNovaAddresses returns nova addresses in generic format 496 func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) network.ProviderAddresses { 497 var machineAddresses []network.ProviderAddress 498 if publicIP != "" { 499 publicAddr := network.NewMachineAddress(publicIP, network.WithScope(network.ScopePublic)).AsProviderAddress() 500 machineAddresses = append(machineAddresses, publicAddr) 501 } 502 // TODO(gz) Network ordering may be significant but is not preserved by 503 // the map, see lp:1188126 for example. That could potentially be fixed 504 // in goose, or left to be derived by other means. 505 for netName, ips := range addresses { 506 networkScope := network.ScopeUnknown 507 if netName == "public" { 508 networkScope = network.ScopePublic 509 } 510 for _, address := range ips { 511 // If this address has already been added as a floating IP, skip it. 512 if publicIP == address.Address { 513 continue 514 } 515 // Assume IPv4 unless specified otherwise 516 addrType := network.IPv4Address 517 if address.Version == 6 { 518 addrType = network.IPv6Address 519 } 520 machineAddr := network.NewMachineAddress(address.Address, network.WithScope(networkScope)).AsProviderAddress() 521 if machineAddr.Type != addrType { 522 logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrType) 523 } 524 machineAddresses = append(machineAddresses, machineAddr) 525 } 526 } 527 return machineAddresses 528 } 529 530 func (inst *openstackInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error { 531 return inst.e.firewaller.OpenInstancePorts(ctx, inst, machineId, rules) 532 } 533 534 func (inst *openstackInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error { 535 return inst.e.firewaller.CloseInstancePorts(ctx, inst, machineId, rules) 536 } 537 538 func (inst *openstackInstance) IngressRules(ctx context.ProviderCallContext, machineId string) (firewall.IngressRules, error) { 539 return inst.e.firewaller.InstanceIngressRules(ctx, inst, machineId) 540 } 541 542 func (e *Environ) ecfg() *environConfig { 543 e.ecfgMutex.Lock() 544 ecfg := e.ecfgUnlocked 545 e.ecfgMutex.Unlock() 546 return ecfg 547 } 548 549 func (e *Environ) cloud() environscloudspec.CloudSpec { 550 e.ecfgMutex.Lock() 551 cloud := e.cloudUnlocked 552 e.ecfgMutex.Unlock() 553 return cloud 554 } 555 556 func (e *Environ) client() client.AuthenticatingClient { 557 e.ecfgMutex.Lock() 558 client := e.clientUnlocked 559 e.ecfgMutex.Unlock() 560 return client 561 } 562 563 func (e *Environ) nova() *nova.Client { 564 e.ecfgMutex.Lock() 565 nova := e.novaUnlocked 566 e.ecfgMutex.Unlock() 567 return nova 568 } 569 570 func (e *Environ) neutron() *neutron.Client { 571 e.ecfgMutex.Lock() 572 neutron := e.neutronUnlocked 573 e.ecfgMutex.Unlock() 574 return neutron 575 } 576 577 var unsupportedConstraints = []string{ 578 constraints.Tags, 579 constraints.CpuPower, 580 } 581 582 // ConstraintsValidator is defined on the Environs interface. 583 func (e *Environ) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) { 584 validator := constraints.NewValidator() 585 validator.RegisterConflicts( 586 []string{constraints.InstanceType}, 587 // TODO: move to a dynamic conflict for arch when openstack supports defining arch in flavors 588 []string{constraints.Mem, constraints.Cores}) 589 // NOTE: RootDiskSource and RootDisk constraints are validated in PrecheckInstance. 590 validator.RegisterUnsupported(unsupportedConstraints) 591 novaClient := e.nova() 592 flavors, err := novaClient.ListFlavorsDetail() 593 if err != nil { 594 handleCredentialError(err, ctx) 595 return nil, err 596 } 597 instTypeNames := make([]string, len(flavors)) 598 for i, flavor := range flavors { 599 instTypeNames[i] = flavor.Name 600 } 601 sort.Strings(instTypeNames) 602 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 603 validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"}) 604 validator.RegisterVocabulary(constraints.RootDiskSource, []string{rootDiskSourceVolume, rootDiskSourceLocal}) 605 return validator, nil 606 } 607 608 var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones 609 610 type openstackAvailabilityZone struct { 611 nova.AvailabilityZone 612 } 613 614 func (z *openstackAvailabilityZone) Name() string { 615 return z.AvailabilityZone.Name 616 } 617 618 func (z *openstackAvailabilityZone) Available() bool { 619 return z.AvailabilityZone.State.Available 620 } 621 622 // AvailabilityZones returns a slice of availability zones. 623 func (e *Environ) AvailabilityZones(ctx context.ProviderCallContext) (network.AvailabilityZones, error) { 624 zones, err := novaListAvailabilityZones(e.nova()) 625 if gooseerrors.IsNotImplemented(err) { 626 return nil, errors.NotImplementedf("availability zones") 627 } 628 if err != nil { 629 handleCredentialError(err, ctx) 630 return nil, err 631 } 632 633 availabilityZones := make(network.AvailabilityZones, len(zones)) 634 for i, z := range zones { 635 availabilityZones[i] = &openstackAvailabilityZone{z} 636 } 637 return availabilityZones, nil 638 } 639 640 // InstanceAvailabilityZoneNames returns the availability zone names for each 641 // of the specified instances. 642 func (e *Environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 643 instances, err := e.Instances(ctx, ids) 644 if err != nil && err != environs.ErrPartialInstances { 645 handleCredentialError(err, ctx) 646 return nil, errors.Trace(err) 647 } 648 zones := make(map[instance.Id]string) 649 for _, inst := range instances { 650 if inst == nil { 651 continue 652 } 653 oInst, ok := inst.(*openstackInstance) 654 if !ok { 655 continue 656 } 657 zones[inst.Id()] = oInst.serverDetail.AvailabilityZone 658 } 659 return zones, nil 660 } 661 662 type openstackPlacement struct { 663 zoneName string 664 } 665 666 // DeriveAvailabilityZones is part of the common.ZonedEnviron interface. 667 func (e *Environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) { 668 availabilityZone, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments) 669 if err != nil && !errors.IsNotImplemented(err) { 670 handleCredentialError(err, ctx) 671 return nil, errors.Trace(err) 672 } 673 if availabilityZone != "" { 674 return []string{availabilityZone}, nil 675 } 676 return nil, nil 677 } 678 679 func (e *Environ) parsePlacement(ctx context.ProviderCallContext, placement string) (*openstackPlacement, error) { 680 pos := strings.IndexRune(placement, '=') 681 if pos == -1 { 682 return nil, errors.Errorf("unknown placement directive: %v", placement) 683 } 684 switch key, value := placement[:pos], placement[pos+1:]; key { 685 case "zone": 686 zones, err := e.AvailabilityZones(ctx) 687 if err != nil { 688 handleCredentialError(err, ctx) 689 return nil, errors.Trace(err) 690 } 691 if err := zones.Validate(value); err != nil { 692 return nil, errors.Trace(err) 693 } 694 695 return &openstackPlacement{zoneName: value}, nil 696 } 697 return nil, errors.Errorf("unknown placement directive: %v", placement) 698 } 699 700 // PrecheckInstance is defined on the environs.InstancePrechecker interface. 701 func (e *Environ) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error { 702 if _, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments); err != nil { 703 return errors.Trace(err) 704 } 705 usingVolumeRootDisk := false 706 if args.Constraints.HasRootDiskSource() && args.Constraints.HasRootDisk() && 707 *args.Constraints.RootDiskSource == rootDiskSourceVolume { 708 usingVolumeRootDisk = true 709 } 710 if args.Constraints.HasRootDisk() && args.Constraints.HasInstanceType() && !usingVolumeRootDisk { 711 return errors.Errorf("constraint %s cannot be specified with %s unless constraint %s=%s", 712 constraints.RootDisk, constraints.InstanceType, 713 constraints.RootDiskSource, rootDiskSourceVolume) 714 } 715 if args.Constraints.HasInstanceType() { 716 // Constraint has an instance-type constraint so let's see if it is valid. 717 novaClient := e.nova() 718 flavors, err := novaClient.ListFlavorsDetail() 719 if err != nil { 720 handleCredentialError(err, ctx) 721 return err 722 } 723 flavorFound := false 724 for _, flavor := range flavors { 725 if flavor.Name == *args.Constraints.InstanceType { 726 flavorFound = true 727 break 728 } 729 } 730 if !flavorFound { 731 return errors.Errorf("invalid Openstack flavour %q specified", *args.Constraints.InstanceType) 732 } 733 } 734 735 return nil 736 } 737 738 // PrepareForBootstrap is part of the Environ interface. 739 func (e *Environ) PrepareForBootstrap(_ environs.BootstrapContext, _ string) error { 740 // Verify credentials. 741 if err := authenticateClient(e.client()); err != nil { 742 return err 743 } 744 if !e.supportsNeutron() { 745 logger.Errorf(`Using unsupported OpenStack APIs. 746 747 Neutron networking is not supported by this OpenStack cloud. 748 749 Please use OpenStack Queens or newer to maintain compatibility. 750 751 `, 752 ) 753 return errors.NewNotFound(nil, "OpenStack Neutron service") 754 } 755 return nil 756 } 757 758 // Create is part of the Environ interface. 759 func (e *Environ) Create(ctx context.ProviderCallContext, args environs.CreateParams) error { 760 // Verify credentials. 761 if err := authenticateClient(e.client()); err != nil { 762 handleCredentialError(err, ctx) 763 return err 764 } 765 // TODO(axw) 2016-08-04 #1609643 766 // Create global security group(s) here. 767 return nil 768 } 769 770 func (e *Environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 771 // The client's authentication may have been reset when finding tools if the agent-version 772 // attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated. 773 // An authenticated client is needed for the URL() call below. 774 if err := authenticateClient(e.client()); err != nil { 775 handleCredentialError(err, callCtx) 776 return nil, err 777 } 778 result, err := common.Bootstrap(ctx, e, callCtx, args) 779 if err != nil { 780 handleCredentialError(err, callCtx) 781 return nil, err 782 } 783 return result, nil 784 } 785 786 func (e *Environ) supportsNeutron() bool { 787 client := e.client() 788 endpointMap := client.EndpointsForRegion(e.cloud().Region) 789 _, ok := endpointMap["network"] 790 return ok 791 } 792 793 func (e *Environ) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) { 794 // Find all instances tagged with tags.JujuIsController. 795 instances, err := e.allControllerManagedInstances(ctx, controllerUUID) 796 if err != nil { 797 return nil, errors.Trace(err) 798 } 799 ids := make([]instance.Id, 0, 1) 800 for _, instance := range instances { 801 detail := instance.(*openstackInstance).getServerDetail() 802 if detail.Metadata[tags.JujuIsController] == "true" { 803 ids = append(ids, instance.Id()) 804 } 805 } 806 if len(ids) == 0 { 807 return nil, environs.ErrNoInstances 808 } 809 return ids, nil 810 } 811 812 func (e *Environ) Config() *config.Config { 813 return e.ecfg().Config 814 } 815 816 func newCredentials(spec environscloudspec.CloudSpec) (identity.Credentials, identity.AuthMode, error) { 817 credAttrs := spec.Credential.Attributes() 818 cred := identity.Credentials{ 819 Region: spec.Region, 820 URL: spec.Endpoint, 821 TenantName: credAttrs[CredAttrTenantName], 822 TenantID: credAttrs[CredAttrTenantID], 823 } 824 825 // AuthType is validated when the environment is opened, so it's known 826 // to be one of these values. 827 var authMode identity.AuthMode 828 switch spec.Credential.AuthType() { 829 case cloud.UserPassAuthType: 830 // TODO(axw) we need a way of saying to use legacy auth. 831 cred.User = credAttrs[CredAttrUserName] 832 cred.Secrets = credAttrs[CredAttrPassword] 833 cred.ProjectDomain = credAttrs[CredAttrProjectDomainName] 834 cred.UserDomain = credAttrs[CredAttrUserDomainName] 835 cred.Domain = credAttrs[CredAttrDomainName] 836 if credAttrs[CredAttrVersion] != "" { 837 version, err := strconv.Atoi(credAttrs[CredAttrVersion]) 838 if err != nil { 839 return identity.Credentials{}, 0, 840 errors.Errorf("cred.Version is not a valid integer type : %v", err) 841 } 842 if version < 3 { 843 authMode = identity.AuthUserPass 844 } else { 845 authMode = identity.AuthUserPassV3 846 } 847 cred.Version = version 848 } else if cred.Domain != "" || cred.UserDomain != "" || cred.ProjectDomain != "" { 849 authMode = identity.AuthUserPassV3 850 } else { 851 authMode = identity.AuthUserPass 852 } 853 case cloud.AccessKeyAuthType: 854 cred.User = credAttrs[CredAttrAccessKey] 855 cred.Secrets = credAttrs[CredAttrSecretKey] 856 authMode = identity.AuthKeyPair 857 } 858 return cred, authMode, nil 859 } 860 861 func tlsConfig(certStrs []string) *tls.Config { 862 pool := x509.NewCertPool() 863 for _, cert := range certStrs { 864 pool.AppendCertsFromPEM([]byte(cert)) 865 } 866 tlsConfig := http.SecureTLSConfig() 867 tlsConfig.RootCAs = pool 868 return tlsConfig 869 } 870 871 type authenticator interface { 872 Authenticate() error 873 } 874 875 var authenticateClient = func(auth authenticator) error { 876 err := auth.Authenticate() 877 if err != nil { 878 // Log the error in case there are any useful hints, 879 // but provide a readable and helpful error message 880 // to the user. 881 logger.Debugf("Authenticate() failed: %v", err) 882 if gooseerrors.IsUnauthorised(err) { 883 return errors.Errorf("authentication failed : %v\n"+ 884 "Please ensure the credentials are correct. A common mistake is\n"+ 885 "to specify the wrong tenant. Use the OpenStack project name\n"+ 886 "for tenant-name in your model configuration. \n", err) 887 } 888 return errors.Annotate(err, "authentication failed.") 889 } 890 return nil 891 } 892 893 func (e *Environ) SetConfig(cfg *config.Config) error { 894 ecfg, err := providerInstance.newConfig(cfg) 895 if err != nil { 896 return err 897 } 898 // At this point, the authentication method config value has been validated so we extract it's value here 899 // to avoid having to validate again each time when creating the OpenStack client. 900 e.ecfgMutex.Lock() 901 defer e.ecfgMutex.Unlock() 902 e.ecfgUnlocked = ecfg 903 904 return nil 905 } 906 907 // SetCloudSpec is specified in the environs.Environ interface. 908 func (e *Environ) SetCloudSpec(_ stdcontext.Context, spec environscloudspec.CloudSpec) error { 909 e.ecfgMutex.Lock() 910 defer e.ecfgMutex.Unlock() 911 912 if err := validateCloudSpec(spec); err != nil { 913 return errors.Annotate(err, "validating cloud spec") 914 } 915 e.cloudUnlocked = spec 916 917 // Create a new client factory, that creates the clients according to the 918 // auth client version, cloudspec and configuration. 919 // 920 // In theory we should be able to create one client factory and then every 921 // time openstack wants a goose client, we should be just get one from 922 // the factory. 923 factory := NewClientFactory(spec, e.ecfgUnlocked) 924 if err := factory.Init(); err != nil { 925 return errors.Trace(err) 926 } 927 e.clientUnlocked = factory.AuthClient() 928 929 // The following uses different clients for the different openstack clients 930 // and we create them in the factory. 931 var err error 932 e.novaUnlocked, err = factory.Nova() 933 if err != nil { 934 return errors.Trace(err) 935 } 936 e.neutronUnlocked, err = factory.Neutron() 937 if err != nil { 938 return errors.Trace(err) 939 } 940 return nil 941 } 942 943 func identityClientVersion(authURL string) (int, error) { 944 url, err := url.Parse(authURL) 945 if err != nil { 946 // Return 0 as this is the lowest invalid number according to openstack codebase: 947 // -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions. 948 return 0, err 949 } 950 if url.Path == authURL { 951 // This means we could not parse URL into url structure 952 // with protocols, domain, port, etc. 953 // For example, specifying "keystone.foo" instead of "https://keystone.foo:443/v3/" 954 // falls into this category. 955 return 0, errors.Errorf("url %s is malformed", authURL) 956 } 957 if url.Path == "" || url.Path == "/" { 958 // User explicitly did not provide any version, it is empty. 959 return -1, nil 960 } 961 // The last part of the path should be the version #, prefixed with a 'v' or 'V' 962 // Example: https://keystone.foo:443/v3/ 963 // Example: https://sharedhost.foo:443/identity/v3/ 964 var tail string 965 urlpath := strings.ToLower(url.Path) 966 urlpath, tail = path.Split(urlpath) 967 if len(tail) == 0 && len(urlpath) > 2 { 968 // trailing /, remove it and split again 969 _, tail = path.Split(strings.TrimRight(urlpath, "/")) 970 } 971 versionNumStr := strings.TrimPrefix(tail, "v") 972 logger.Debugf("authURL: %s", authURL) 973 major, _, err := version.ParseMajorMinor(versionNumStr) 974 if len(tail) < 2 || tail[0] != 'v' || err != nil { 975 // There must be a '/v' in the URL path. 976 // At this stage only '/Vxxx' and '/vxxx' are valid where xxx is major.minor version. 977 // Return 0 as this is the lowest invalid number according to openstack codebase: 978 // -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions. 979 return 0, errors.NotValidf("version part of identity url %s", authURL) 980 } 981 return major, err 982 } 983 984 // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that 985 // returns a DataSource using the "product-streams" keystone URL. 986 func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) { 987 e, ok := env.(*Environ) 988 if !ok { 989 return nil, errors.NotSupportedf("non-openstack model") 990 } 991 return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams") 992 } 993 994 // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that 995 // returns a DataSource using the "juju-tools" keystone URL. 996 func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) { 997 e, ok := env.(*Environ) 998 if !ok { 999 return nil, errors.NotSupportedf("non-openstack model") 1000 } 1001 return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools") 1002 } 1003 1004 func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) { 1005 mu.Lock() 1006 defer mu.Unlock() 1007 if *datasource != nil { 1008 return *datasource, nil 1009 } 1010 1011 cl := e.client() 1012 if !cl.IsAuthenticated() { 1013 if err := authenticateClient(cl); err != nil { 1014 return nil, err 1015 } 1016 } 1017 1018 serviceURL, err := makeServiceURL(cl, keystoneName, "", nil) 1019 if err != nil { 1020 return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err)) 1021 } 1022 cfg := simplestreams.Config{ 1023 Description: "keystone catalog", 1024 BaseURL: serviceURL, 1025 HostnameVerification: e.Config().SSLHostnameVerification(), 1026 Priority: simplestreams.SPECIFIC_CLOUD_DATA, 1027 CACertificates: e.cloudUnlocked.CACertificates, 1028 } 1029 if err := cfg.Validate(); err != nil { 1030 return nil, errors.Annotate(err, "simplestreams config validation failed") 1031 } 1032 *datasource = simplestreams.NewDataSource(cfg) 1033 return *datasource, nil 1034 } 1035 1036 // assignPublicIP tries to assign the given floating IP address to the 1037 // specified server, or returns an error. 1038 func (e *Environ) assignPublicIP(fip *string, serverId string) (err error) { 1039 if *fip == "" { 1040 return errors.Errorf("cannot assign a nil public IP to %q", serverId) 1041 } 1042 // At startup nw_info is not yet cached so this may fail 1043 // temporarily while the server is being built 1044 for a := common.LongAttempt.Start(); a.Next(); { 1045 err = e.nova().AddServerFloatingIP(serverId, *fip) 1046 if err == nil { 1047 return nil 1048 } 1049 } 1050 return err 1051 } 1052 1053 // DistributeInstances implements the state.InstanceDistributor policy. 1054 func (e *Environ) DistributeInstances( 1055 ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string, 1056 ) ([]instance.Id, error) { 1057 valid, err := common.DistributeInstances(e, ctx, candidates, distributionGroup, limitZones) 1058 if err != nil { 1059 handleCredentialError(err, ctx) 1060 return valid, err 1061 } 1062 return valid, nil 1063 } 1064 1065 // StartInstance is specified in the InstanceBroker interface. 1066 func (e *Environ) StartInstance( 1067 ctx context.ProviderCallContext, args environs.StartInstanceParams, 1068 ) (*environs.StartInstanceResult, error) { 1069 res, err := e.startInstance(ctx, args) 1070 handleCredentialError(err, ctx) 1071 return res, errors.Trace(err) 1072 } 1073 1074 func (e *Environ) startInstance( 1075 ctx context.ProviderCallContext, args environs.StartInstanceParams, 1076 ) (_ *environs.StartInstanceResult, err error) { 1077 if err := e.validateAvailabilityZone(ctx, args); err != nil { 1078 return nil, errors.Trace(err) 1079 } 1080 1081 arch, err := args.Tools.OneArch() 1082 if err != nil { 1083 return nil, errors.Trace(err) 1084 } 1085 spec, err := findInstanceSpec(e, instances.InstanceConstraint{ 1086 Region: e.cloud().Region, 1087 Base: args.InstanceConfig.Base, 1088 Arch: arch, 1089 Constraints: args.Constraints, 1090 }, args.ImageMetadata) 1091 if err != nil { 1092 return nil, environs.ZoneIndependentError(err) 1093 } 1094 if err := args.InstanceConfig.SetTools(args.Tools); err != nil { 1095 return nil, environs.ZoneIndependentError(err) 1096 } 1097 1098 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil { 1099 return nil, environs.ZoneIndependentError(err) 1100 } 1101 1102 cloudCfg, err := e.configurator.GetCloudConfig(args) 1103 if err != nil { 1104 return nil, environs.ZoneIndependentError(err) 1105 } 1106 1107 networks, err := e.networksForInstance(args, cloudCfg) 1108 if err != nil { 1109 return nil, environs.ZoneIndependentError(err) 1110 } 1111 1112 userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudCfg, OpenstackRenderer{}) 1113 if err != nil { 1114 return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot make user data")) 1115 } 1116 logger.Debugf("openstack user data; %d bytes", len(userData)) 1117 1118 machineName := resourceName( 1119 e.namespace, 1120 e.name, 1121 args.InstanceConfig.MachineId, 1122 ) 1123 1124 if e.ecfg().useOpenstackGBP() { 1125 neutronClient := e.neutron() 1126 ptArg := neutron.PolicyTargetV2{ 1127 Name: fmt.Sprintf("juju-policytarget-%s", machineName), 1128 PolicyTargetGroupId: e.ecfg().policyTargetGroup(), 1129 } 1130 pt, err := neutronClient.CreatePolicyTargetV2(ptArg) 1131 if err != nil { 1132 return nil, errors.Trace(err) 1133 } 1134 networks = append(networks, nova.ServerNetworks{PortId: pt.PortId}) 1135 } 1136 1137 // For BUG 1680787: openstack: add support for neutron networks where port 1138 // security is disabled. 1139 // If any network specified for instance boot has PortSecurityEnabled equals 1140 // false, don't create security groups, instance boot will fail. 1141 createSecurityGroups := true 1142 if len(networks) > 0 && e.supportsNeutron() { 1143 neutronClient := e.neutron() 1144 for _, n := range networks { 1145 if n.NetworkId == "" { 1146 // It's a GBP network. 1147 continue 1148 } 1149 net, err := neutronClient.GetNetworkV2(n.NetworkId) 1150 if err != nil { 1151 return nil, environs.ZoneIndependentError(err) 1152 } 1153 if net.PortSecurityEnabled != nil && !*net.PortSecurityEnabled { 1154 createSecurityGroups = *net.PortSecurityEnabled 1155 logger.Infof("network %q has port_security_enabled set to false. Not using security groups.", net.Id) 1156 break 1157 } 1158 } 1159 } 1160 e.usingSecurityGroups = e.usingSecurityGroups || createSecurityGroups 1161 1162 var novaGroupNames []nova.SecurityGroupName 1163 if createSecurityGroups { 1164 groupNames, err := e.firewaller.SetUpGroups(ctx, args.ControllerUUID, args.InstanceConfig.MachineId) 1165 if err != nil { 1166 return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot set up groups")) 1167 } 1168 novaGroupNames = make([]nova.SecurityGroupName, len(groupNames)) 1169 for i, name := range groupNames { 1170 novaGroupNames[i].Name = name 1171 } 1172 } 1173 1174 waitForActiveServerDetails := func( 1175 client *nova.Client, 1176 id string, 1177 timeout time.Duration, 1178 ) (server *nova.ServerDetail, err error) { 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, 1196 fmt.Sprintf("%s, wait 10 seconds before retry, attempt %d", lastError, attempt), nil) 1197 }, 1198 IsFatalError: func(err error) bool { 1199 return err != errStillBuilding 1200 }, 1201 }) 1202 if err != nil { 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 break 1217 } 1218 if server == nil { 1219 logger.Warningf("may have lost contact with nova api while creating instances, some stray instances may be around and need to be deleted") 1220 break 1221 } 1222 var serverDetail *nova.ServerDetail 1223 serverDetail, err = waitForActiveServerDetails(client, server.Id, 5*time.Minute) 1224 if err != nil || serverDetail == nil { 1225 // If we got an error back (eg. StillBuilding) 1226 // we need to terminate the instance before 1227 // retrying to avoid leaking resources. 1228 logger.Warningf("Unable to retrieve details for created instance %q: %v; attempting to terminate it", server.Id, err) 1229 if termErr := e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); termErr != nil { 1230 logger.Errorf("Failed to delete instance %q: %v; manual cleanup required", server.Id, termErr) 1231 } 1232 server = nil 1233 break 1234 } else if serverDetail.Status == nova.StatusActive { 1235 break 1236 } else if serverDetail.Status == nova.StatusError { 1237 // Perhaps there is an error case where a retry in the same AZ 1238 // is a good idea. 1239 faultMsg := " unable to determine fault details" 1240 if serverDetail.Fault != nil { 1241 faultMsg = fmt.Sprintf(" with fault %q", serverDetail.Fault.Message) 1242 } else { 1243 logger.Debugf("getting active server details from nova failed without fault details") 1244 } 1245 logger.Infof("Deleting instance %q in ERROR state%v", server.Id, faultMsg) 1246 if err = e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); err != nil { 1247 logger.Errorf("Failed to delete instance in ERROR state %q: %v; manual cleanup required", server.Id, err) 1248 } 1249 server = nil 1250 err = errors.New(faultMsg) 1251 break 1252 } 1253 } 1254 return server, err 1255 } 1256 1257 opts := nova.RunServerOpts{ 1258 Name: machineName, 1259 FlavorId: spec.InstanceType.Id, 1260 UserData: userData, 1261 SecurityGroupNames: novaGroupNames, 1262 Networks: networks, 1263 Metadata: args.InstanceConfig.Tags, 1264 AvailabilityZone: args.AvailabilityZone, 1265 } 1266 1267 err = e.configureRootDisk(ctx, args, spec, &opts) 1268 if err != nil { 1269 return nil, environs.ZoneIndependentError(err) 1270 } 1271 e.configurator.ModifyRunServerOptions(&opts) 1272 1273 server, err := tryStartNovaInstance(shortAttempt, e.nova(), opts) 1274 if err != nil || server == nil { 1275 // Attempt to clean up any security groups we created. 1276 if err := e.cleanupGroups(ctx, e.nova(), novaGroupNames); err != nil { 1277 // If we failed to clean up the security groups, we need the user 1278 // to manually clean them up. 1279 logger.Errorf("cannot cleanup security groups: %v", err) 1280 } 1281 1282 logger.Debugf("cannot run instance full error: %q", err) 1283 err = errors.Annotate(errors.Cause(err), "cannot run instance") 1284 // Improve the error message if there is no valid network. 1285 if isInvalidNetworkError(err) { 1286 err = e.userFriendlyInvalidNetworkError(err) 1287 } 1288 // 'No valid host available' is typically a resource error, 1289 // let the provisioner know it is a good idea to try another 1290 // AZ if available. 1291 if !isNoValidHostsError(err) { 1292 err = environs.ZoneIndependentError(err) 1293 } 1294 return nil, err 1295 } 1296 1297 detail, err := e.nova().GetServer(server.Id) 1298 if err != nil { 1299 return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot get started instance")) 1300 } 1301 1302 inst := &openstackInstance{ 1303 e: e, 1304 serverDetail: detail, 1305 arch: &spec.Image.Arch, 1306 instType: &spec.InstanceType, 1307 runOpts: &opts, 1308 } 1309 logger.Infof("started instance %q", inst.Id()) 1310 var withPublicIP bool 1311 // Any machine constraint for allocating a public IP address 1312 // overrides the (deprecated) model config. 1313 if args.Constraints.HasAllocatePublicIP() { 1314 withPublicIP = *args.Constraints.AllocatePublicIP 1315 } 1316 if withPublicIP { 1317 // If we don't lock here, AllocatePublicIP() can return the same 1318 // public IP for 2 different instances. Only one will successfully 1319 // be assigned the public IP, the other will not have one. 1320 e.publicIPMutex.Lock() 1321 defer e.publicIPMutex.Unlock() 1322 var publicIP *string 1323 logger.Debugf("allocating public IP address for openstack node") 1324 if fip, err := e.networking.AllocatePublicIP(inst.Id()); err != nil { 1325 if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil { 1326 // ignore the failure at this stage, just log it 1327 logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err) 1328 } 1329 return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot allocate a public IP as needed")) 1330 } else { 1331 publicIP = fip 1332 logger.Infof("allocated public IP %s", *publicIP) 1333 } 1334 1335 if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil { 1336 if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil { 1337 // ignore the failure at this stage, just log it 1338 logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err) 1339 } 1340 return nil, environs.ZoneIndependentError(errors.Annotatef(err, 1341 "cannot assign public address %s to instance %q", 1342 *publicIP, inst.Id(), 1343 )) 1344 } 1345 inst.floatingIP = publicIP 1346 } 1347 1348 return &environs.StartInstanceResult{ 1349 Instance: inst, 1350 Hardware: inst.hardwareCharacteristics(), 1351 }, nil 1352 } 1353 1354 // Clean up any groups that we have created if we fail to start the instance. 1355 func (e *Environ) cleanupGroups( 1356 ctx context.ProviderCallContext, 1357 client *nova.Client, 1358 groups []nova.SecurityGroupName, 1359 ) error { 1360 names := make([]string, len(groups)) 1361 for i, group := range groups { 1362 names[i] = group.Name 1363 } 1364 return e.firewaller.DeleteGroups(ctx, names...) 1365 } 1366 1367 func (e *Environ) userFriendlyInvalidNetworkError(err error) error { 1368 msg := fmt.Sprintf("%s\n\t%s\n\t%s", err.Error(), 1369 "This error was caused by juju attempting to create an OpenStack instance with no network defined.", 1370 "No network has been configured.") 1371 networks, err := e.networking.FindNetworks(true) 1372 if err != nil { 1373 msg += fmt.Sprintf("\n\t%s\n\t\t%s", "Error attempting to find internal networks:", err.Error()) 1374 } else { 1375 msg += fmt.Sprintf(" %s\n\t\t%q", "The following internal networks are available: ", strings.Join(networks.Values(), ", ")) 1376 } 1377 return errors.New(msg) 1378 } 1379 1380 // validateAvailabilityZone validates AZs supplied in StartInstanceParams. 1381 // args.AvailabilityZone should only be set if this OpenStack supports zones. 1382 // We need to validate it if supplied. 1383 func (e *Environ) validateAvailabilityZone(ctx context.ProviderCallContext, args environs.StartInstanceParams) error { 1384 if args.AvailabilityZone == "" { 1385 return nil 1386 } 1387 1388 volumeAttachmentsZone, err := e.volumeAttachmentsZone(args.VolumeAttachments) 1389 if err != nil { 1390 return environs.ZoneIndependentError(err) 1391 } 1392 if err := validateAvailabilityZoneConsistency(args.AvailabilityZone, volumeAttachmentsZone); err != nil { 1393 return environs.ZoneIndependentError(err) 1394 } 1395 1396 zones, err := e.AvailabilityZones(ctx) 1397 if err != nil { 1398 return errors.Trace(err) 1399 } 1400 return errors.Trace(zones.Validate(args.AvailabilityZone)) 1401 1402 } 1403 1404 // networksForInstance returns networks that will be attached 1405 // to a new Openstack instance. 1406 // Network info for all ports created is represented in the input cloud-config 1407 // reference. 1408 // This is necessary so that the correct Netplan representation for the 1409 // associated NICs is rendered in the instance that they will be attached to. 1410 func (e *Environ) networksForInstance( 1411 args environs.StartInstanceParams, cloudCfg cloudinit.NetworkingConfig, 1412 ) ([]nova.ServerNetworks, error) { 1413 networks, err := e.networksForModel() 1414 if err != nil { 1415 return nil, errors.Trace(err) 1416 } 1417 1418 // If there are no constraints or bindings to accommodate, 1419 // the instance will have a NIC for each configured internal network. 1420 // Note that uif there is no configured network, this means a NIC in 1421 // all *available* networks. 1422 if len(args.SubnetsToZones) == 0 { 1423 toServerNet := func(n neutron.NetworkV2) nova.ServerNetworks { return nova.ServerNetworks{NetworkId: n.Id} } 1424 return transform.Slice(networks, toServerNet), nil 1425 } 1426 1427 if len(networks) == 0 { 1428 return nil, errors.New( 1429 "space constraints and/or bindings were supplied, but no OpenStack networks can be determined") 1430 } 1431 1432 subnetIDForZone, err := subnetInZone(args.AvailabilityZone, args.SubnetsToZones) 1433 if err != nil { 1434 return nil, errors.Trace(err) 1435 } 1436 1437 // Set the subnetID on the network for all networks. 1438 // For each of the subnetIDs selected, create a port for each one. 1439 subnetNetworks := make([]nova.ServerNetworks, 0, len(subnetIDForZone)) 1440 netInfo := make(network.InterfaceInfos, len(subnetIDForZone)) 1441 for i, subnetID := range subnetIDForZone { 1442 subnetNet, err := networkForSubnet(networks, subnetID) 1443 if err != nil { 1444 return nil, errors.Trace(err) 1445 } 1446 1447 var port *neutron.PortV2 1448 port, err = e.networking.CreatePort(e.uuid, subnetNet.Id, subnetID) 1449 if err != nil { 1450 break 1451 } 1452 1453 logger.Infof("created new port %q connected to Openstack subnet %q", port.Id, subnetID) 1454 subnetNetworks = append(subnetNetworks, nova.ServerNetworks{ 1455 NetworkId: subnetNet.Id, 1456 PortId: port.Id, 1457 }) 1458 1459 // We expect a single address, 1460 // but for correctness we add all from the created port. 1461 ips := make([]string, len(port.FixedIPs)) 1462 for j, fixedIP := range port.FixedIPs { 1463 ips[j] = fixedIP.IPAddress 1464 } 1465 1466 netInfo[i] = network.InterfaceInfo{ 1467 InterfaceName: fmt.Sprintf("eth%d", i), 1468 MACAddress: port.MACAddress, 1469 Addresses: network.NewMachineAddresses(ips).AsProviderAddresses(), 1470 ConfigType: network.ConfigDHCP, 1471 Origin: network.OriginProvider, 1472 } 1473 } 1474 1475 err = cloudCfg.AddNetworkConfig(netInfo) 1476 1477 if err != nil { 1478 err1 := e.DeletePorts(subnetNetworks) 1479 if err1 != nil { 1480 logger.Errorf("Unable to delete ports from the provider %+v", subnetNetworks) 1481 } 1482 return nil, errors.Annotatef(err, "creating ports for instance") 1483 } 1484 1485 return subnetNetworks, nil 1486 } 1487 1488 // subnetInZone chooses a subnet at random for each entry in the input slice of 1489 // subnet:zones that is in the input availability zone. 1490 func subnetInZone(az string, subnetsToZones []map[network.Id][]string) ([]network.Id, error) { 1491 // Attempt to filter the constraint/binding subnet IDs for the supplied 1492 // availability zone. 1493 // The zone is supplied by the provisioner based on its attempt to maintain 1494 // distribution of units across zones. 1495 // The zones recorded against O7k subnets in Juju are affected by the 1496 // `availability_zone_hints` attribute on the network where they reside, 1497 // and the default AZ configuration for the project. 1498 // They are a read-only attribute. 1499 // If a subnet in the subnets-to-zones map has no zones, we assume a basic 1500 // O7k set-up where networks are not zone-limited. We log a warning and 1501 // consider all the supplied subnets. 1502 // See: 1503 // - https://docs.openstack.org/neutron/latest/admin/config-az.html#availability-zone-related-attributes 1504 // - https://docs.openstack.org/neutron/latest/admin/ovn/availability_zones.html#network-availability-zones 1505 subnetIDsForZone := make([][]network.Id, len(subnetsToZones)) 1506 for i, nic := range subnetsToZones { 1507 subnetIDs, err := network.FindSubnetIDsForAvailabilityZone(az, nic) 1508 if err != nil { 1509 // We found no subnets in the zone. 1510 // Add subnets without zone-limited networks. 1511 for subnetID, zones := range nic { 1512 if len(zones) == 0 { 1513 logger.Warningf( 1514 "subnet %q is not in a network with availability zones listed; assuming availability in zone %q", 1515 subnetID, az) 1516 subnetIDs = append(subnetIDs, subnetID) 1517 } 1518 } 1519 1520 if len(subnetIDs) == 0 { 1521 // If we still have no candidate subnets, then they are all in 1522 // networks with availability zones, and none of those zones 1523 // match the input one. Return the error we have in hand. 1524 return nil, errors.Annotatef(err, "determining subnets in zone %q", az) 1525 } 1526 } 1527 1528 subnetIDsForZone[i] = network.FilterInFanNetwork(subnetIDs) 1529 } 1530 1531 // For each list of subnet IDs that satisfy space and zone constraints, 1532 // choose a single one at random. 1533 subnetIDForZone := make([]network.Id, len(subnetIDsForZone)) 1534 for i, subnetIDs := range subnetIDsForZone { 1535 if len(subnetIDs) == 1 { 1536 subnetIDForZone[i] = subnetIDs[0] 1537 continue 1538 } 1539 subnetIDForZone[i] = subnetIDs[rand.Intn(len(subnetIDs))] 1540 } 1541 1542 return subnetIDForZone, nil 1543 } 1544 1545 // DeletePorts goes through and attempts to delete any ports that have been 1546 // created during the creation of the networks for the given instance. 1547 func (e *Environ) DeletePorts(networks []nova.ServerNetworks) error { 1548 var errs []error 1549 for _, network := range networks { 1550 if network.NetworkId != "" && network.PortId != "" { 1551 err := e.networking.DeletePortByID(network.PortId) 1552 if err != nil { 1553 errs = append(errs, err) 1554 } 1555 } 1556 } 1557 if len(errs) > 0 { 1558 // It would be nice to generalize this so we have the same expected 1559 // behaviour from all our slices of errors. 1560 for _, err := range errs { 1561 logger.Errorf("Unable to delete port with error: %v", err) 1562 } 1563 return errs[0] 1564 } 1565 return nil 1566 } 1567 1568 // networksForModel returns the Openstack network list 1569 // based on current model configuration. 1570 func (e *Environ) networksForModel() ([]neutron.NetworkV2, error) { 1571 var resolvedNetworks []neutron.NetworkV2 1572 networkIDs := set.NewStrings() 1573 cfgNets := e.ecfg().networks() 1574 1575 for _, cfgNet := range cfgNets { 1576 networks, err := e.networking.ResolveNetworks(cfgNet, false) 1577 if err != nil { 1578 logger.Warningf("filtering networks for %q", cfgNet) 1579 } 1580 1581 for _, net := range networks { 1582 if networkIDs.Contains(net.Id) { 1583 continue 1584 } 1585 1586 resolvedNetworks = append(resolvedNetworks, net) 1587 networkIDs.Add(net.Id) 1588 } 1589 } 1590 1591 if networkIDs.Size() == 0 { 1592 if len(cfgNets) == 1 && cfgNets[0] == "" { 1593 return nil, nil 1594 } 1595 return nil, errors.Errorf("unable to determine networks for configured list: %v", cfgNets) 1596 } 1597 1598 logger.Debugf("using network IDs %v", networkIDs.Values()) 1599 return resolvedNetworks, nil 1600 } 1601 1602 func (e *Environ) configureRootDisk(_ context.ProviderCallContext, args environs.StartInstanceParams, 1603 spec *instances.InstanceSpec, runOpts *nova.RunServerOpts) error { 1604 rootDiskSource := rootDiskSourceLocal 1605 if args.Constraints.HasRootDiskSource() { 1606 rootDiskSource = *args.Constraints.RootDiskSource 1607 } 1608 rootDiskMapping := nova.BlockDeviceMapping{ 1609 BootIndex: 0, 1610 UUID: spec.Image.Id, 1611 SourceType: "image", 1612 // NB constraints.RootDiskSource in the case of OpenStack represents 1613 // the type of block device to use. Either "local" to represent a local 1614 // block device or "volume" to represent a block device from the cinder 1615 // block storage service. 1616 DestinationType: rootDiskSource, 1617 DeleteOnTermination: true, 1618 } 1619 switch rootDiskSource { 1620 case rootDiskSourceLocal: 1621 if args.Constraints.HasImageID() { 1622 runOpts.ImageId = *args.Constraints.ImageID 1623 } else { 1624 runOpts.ImageId = spec.Image.Id 1625 } 1626 case rootDiskSourceVolume: 1627 if args.Constraints.HasImageID() { 1628 runOpts.ImageId = *args.Constraints.ImageID 1629 } 1630 size := uint64(0) 1631 if args.Constraints.HasRootDisk() { 1632 size = *args.Constraints.RootDisk 1633 } 1634 if size <= 0 { 1635 size = defaultRootDiskSize 1636 } 1637 sizeGB := common.MiBToGiB(size) 1638 rootDiskMapping.VolumeSize = int(sizeGB) 1639 default: 1640 return errors.Errorf("invalid %s %s", constraints.RootDiskSource, rootDiskSource) 1641 } 1642 runOpts.BlockDeviceMappings = []nova.BlockDeviceMapping{rootDiskMapping} 1643 return nil 1644 } 1645 1646 func (e *Environ) deriveAvailabilityZone( 1647 ctx context.ProviderCallContext, 1648 placement string, 1649 volumeAttachments []storage.VolumeAttachmentParams, 1650 ) (string, error) { 1651 volumeAttachmentsZone, err := e.volumeAttachmentsZone(volumeAttachments) 1652 if err != nil { 1653 handleCredentialError(err, ctx) 1654 return "", errors.Trace(err) 1655 } 1656 if placement == "" { 1657 return volumeAttachmentsZone, nil 1658 } 1659 instPlacement, err := e.parsePlacement(ctx, placement) 1660 if err != nil { 1661 return "", err 1662 } 1663 if err := validateAvailabilityZoneConsistency(instPlacement.zoneName, volumeAttachmentsZone); err != nil { 1664 return "", errors.Annotatef(err, "cannot create instance with placement %q", placement) 1665 } 1666 return instPlacement.zoneName, nil 1667 } 1668 1669 func validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone string) error { 1670 if volumeAttachmentsZone != "" && instanceZone != volumeAttachmentsZone { 1671 return errors.Errorf( 1672 "cannot create instance in zone %q, as this will prevent attaching the requested disks in zone %q", 1673 instanceZone, volumeAttachmentsZone, 1674 ) 1675 } 1676 return nil 1677 } 1678 1679 // volumeAttachmentsZone determines the availability zone for each volume 1680 // identified in the volume attachment parameters, checking that they are 1681 // all the same, and returns the availability zone name. 1682 func (e *Environ) volumeAttachmentsZone(volumeAttachments []storage.VolumeAttachmentParams) (string, error) { 1683 if len(volumeAttachments) == 0 { 1684 return "", nil 1685 } 1686 cinderProvider, err := e.cinderProvider() 1687 if err != nil { 1688 return "", errors.Trace(err) 1689 } 1690 volumes, err := modelCinderVolumes(cinderProvider.storageAdapter, cinderProvider.modelUUID) 1691 if err != nil { 1692 return "", errors.Trace(err) 1693 } 1694 var zone string 1695 for i, a := range volumeAttachments { 1696 var v *cinder.Volume 1697 for i := range volumes { 1698 if volumes[i].ID == a.VolumeId { 1699 v = &volumes[i] 1700 break 1701 } 1702 } 1703 if v == nil { 1704 return "", errors.Errorf("cannot find volume %q to attach to new instance", a.VolumeId) 1705 } 1706 if zone == "" { 1707 zone = v.AvailabilityZone 1708 } else if v.AvailabilityZone != zone { 1709 return "", errors.Errorf( 1710 "cannot attach volumes from multiple availability zones: %s is in %s, %s is in %s", 1711 volumeAttachments[i-1].VolumeId, zone, a.VolumeId, v.AvailabilityZone, 1712 ) 1713 } 1714 } 1715 return zone, nil 1716 } 1717 1718 func isNoValidHostsError(err error) bool { 1719 if cause := errors.Cause(err); cause != nil { 1720 return strings.Contains(cause.Error(), "No valid host was found") 1721 } 1722 return false 1723 } 1724 1725 func isInvalidNetworkError(err error) bool { 1726 if cause := errors.Cause(err); cause != nil { 1727 return strings.Contains(errors.Cause(err).Error(), "Invalid input for field/attribute networks") 1728 } 1729 return false 1730 } 1731 1732 func (e *Environ) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error { 1733 logger.Debugf("terminating instances %v", ids) 1734 if err := e.terminateInstances(ctx, ids); err != nil { 1735 handleCredentialError(err, ctx) 1736 return err 1737 } 1738 return nil 1739 } 1740 1741 func (e *Environ) isAliveServer(server nova.ServerDetail) bool { 1742 switch server.Status { 1743 case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended: 1744 return true 1745 } 1746 return false 1747 } 1748 1749 func (e *Environ) listServers(ctx context.ProviderCallContext, ids []instance.Id) ([]nova.ServerDetail, error) { 1750 wantedServers := make([]nova.ServerDetail, 0, len(ids)) 1751 if len(ids) == 1 { 1752 // Common case, single instance, may return NotFound 1753 var maybeServer *nova.ServerDetail 1754 maybeServer, err := e.nova().GetServer(string(ids[0])) 1755 if err != nil { 1756 handleCredentialError(err, ctx) 1757 return nil, err 1758 } 1759 // Only return server details if it is currently alive 1760 if maybeServer != nil && e.isAliveServer(*maybeServer) { 1761 wantedServers = append(wantedServers, *maybeServer) 1762 } 1763 return wantedServers, nil 1764 } 1765 // List all instances in the environment. 1766 instances, err := e.AllRunningInstances(ctx) 1767 if err != nil { 1768 handleCredentialError(err, ctx) 1769 return nil, err 1770 } 1771 // Return only servers with the wanted ids that are currently alive 1772 for _, inst := range instances { 1773 inst := inst.(*openstackInstance) 1774 serverDetail := *inst.serverDetail 1775 if !e.isAliveServer(serverDetail) { 1776 continue 1777 } 1778 for _, id := range ids { 1779 if inst.Id() != id { 1780 continue 1781 } 1782 wantedServers = append(wantedServers, serverDetail) 1783 break 1784 } 1785 } 1786 return wantedServers, nil 1787 } 1788 1789 // updateFloatingIPAddresses updates the instances with any floating IP address 1790 // that have been assigned to those instances. 1791 func (e *Environ) updateFloatingIPAddresses(ctx context.ProviderCallContext, instances map[string]instances.Instance) error { 1792 servers, err := e.nova().ListServersDetail(jujuMachineFilter()) 1793 if err != nil { 1794 handleCredentialError(err, ctx) 1795 return err 1796 } 1797 for _, server := range servers { 1798 // server.Addresses is a map with entries containing []nova.IPAddress 1799 for _, net := range server.Addresses { 1800 for _, addr := range net { 1801 if addr.Type == "floating" { 1802 instId := server.Id 1803 if inst, ok := instances[instId]; ok { 1804 instFip := &addr.Address 1805 inst.(*openstackInstance).floatingIP = instFip 1806 } 1807 } 1808 } 1809 } 1810 } 1811 return nil 1812 } 1813 1814 func (e *Environ) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) { 1815 if len(ids) == 0 { 1816 return nil, nil 1817 } 1818 1819 foundServers, err := e.listServers(ctx, ids) 1820 if err != nil { 1821 logger.Debugf("error listing servers: %v", err) 1822 if !IsNotFoundError(err) { 1823 handleCredentialError(err, ctx) 1824 return nil, err 1825 } 1826 } 1827 1828 logger.Tracef("%d/%d live servers found", len(foundServers), len(ids)) 1829 if len(foundServers) == 0 { 1830 return nil, environs.ErrNoInstances 1831 } 1832 1833 instsById := make(map[string]instances.Instance, len(foundServers)) 1834 for i, server := range foundServers { 1835 instsById[server.Id] = &openstackInstance{ 1836 e: e, 1837 serverDetail: &foundServers[i], 1838 } 1839 } 1840 1841 // Update the instance structs with any floating IP address 1842 // that has been assigned to the instance. 1843 if err = e.updateFloatingIPAddresses(ctx, instsById); err != nil { 1844 return nil, err 1845 } 1846 1847 insts := make([]instances.Instance, len(ids)) 1848 for i, id := range ids { 1849 if inst := instsById[string(id)]; inst != nil { 1850 insts[i] = inst 1851 } else { 1852 err = environs.ErrPartialInstances 1853 } 1854 } 1855 return insts, err 1856 } 1857 1858 // AdoptResources is part of the Environ interface. 1859 func (e *Environ) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 1860 var failed []string 1861 controllerTag := map[string]string{tags.JujuController: controllerUUID} 1862 1863 instances, err := e.AllInstances(ctx) 1864 if err != nil { 1865 handleCredentialError(err, ctx) 1866 return errors.Trace(err) 1867 } 1868 for _, instance := range instances { 1869 err := e.TagInstance(ctx, instance.Id(), controllerTag) 1870 if err != nil { 1871 logger.Errorf("error updating controller tag for instance %s: %v", instance.Id(), err) 1872 failed = append(failed, string(instance.Id())) 1873 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 1874 // If we have an invvalid credential, there is no need to proceed: we'll fail 100%. 1875 break 1876 } 1877 } 1878 } 1879 1880 failedVolumes, err := e.adoptVolumes(controllerTag, ctx) 1881 if err != nil { 1882 handleCredentialError(err, ctx) 1883 return errors.Trace(err) 1884 } 1885 failed = append(failed, failedVolumes...) 1886 1887 err = e.firewaller.UpdateGroupController(ctx, controllerUUID) 1888 if err != nil { 1889 handleCredentialError(err, ctx) 1890 return errors.Trace(err) 1891 } 1892 if len(failed) != 0 { 1893 return errors.Errorf("error updating controller tag for some resources: %v", failed) 1894 } 1895 return nil 1896 } 1897 1898 func (e *Environ) adoptVolumes(controllerTag map[string]string, ctx context.ProviderCallContext) ([]string, error) { 1899 cinder, err := e.cinderProvider() 1900 if errors.IsNotSupported(err) { 1901 logger.Debugf("volumes not supported: not transferring ownership for volumes") 1902 return nil, nil 1903 } 1904 if err != nil { 1905 handleCredentialError(err, ctx) 1906 return nil, errors.Trace(err) 1907 } 1908 // TODO(axw): fix the storage API. 1909 storageConfig, err := storage.NewConfig("cinder", CinderProviderType, nil) 1910 if err != nil { 1911 return nil, errors.Trace(err) 1912 } 1913 volumeSource, err := cinder.VolumeSource(storageConfig) 1914 if err != nil { 1915 handleCredentialError(err, ctx) 1916 return nil, errors.Trace(err) 1917 } 1918 volumeIds, err := volumeSource.ListVolumes(ctx) 1919 if err != nil { 1920 handleCredentialError(err, ctx) 1921 return nil, errors.Trace(err) 1922 } 1923 1924 var failed []string 1925 for _, volumeId := range volumeIds { 1926 _, err := cinder.storageAdapter.SetVolumeMetadata(volumeId, controllerTag) 1927 if err != nil { 1928 logger.Errorf("error updating controller tag for volume %s: %v", volumeId, err) 1929 failed = append(failed, volumeId) 1930 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 1931 // If we have an invvalid credential, there is no need to proceed: we'll fail 100%. 1932 break 1933 } 1934 } 1935 } 1936 return failed, nil 1937 } 1938 1939 // AllInstances returns all instances in this environment. 1940 func (e *Environ) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 1941 tagFilter := tagValue{tags.JujuModel, e.ecfg().UUID()} 1942 instances, err := e.allInstances(ctx, tagFilter) 1943 if err != nil { 1944 handleCredentialError(err, ctx) 1945 return instances, err 1946 } 1947 return instances, nil 1948 } 1949 1950 // AllRunningInstances returns all running, available instances in this environment. 1951 func (e *Environ) AllRunningInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 1952 // e.allInstances(...) already handles all instances irrespective of the state, so 1953 // here 'all' is also 'all running'. 1954 return e.AllInstances(ctx) 1955 } 1956 1957 // allControllerManagedInstances returns all instances managed by this 1958 // environment's controller, matching the optionally specified filter. 1959 func (e *Environ) allControllerManagedInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instances.Instance, error) { 1960 tagFilter := tagValue{tags.JujuController, controllerUUID} 1961 instances, err := e.allInstances(ctx, tagFilter) 1962 if err != nil { 1963 handleCredentialError(err, ctx) 1964 return instances, err 1965 } 1966 return instances, nil 1967 } 1968 1969 type tagValue struct { 1970 tag, value string 1971 } 1972 1973 // allControllerManagedInstances returns all instances managed by this 1974 // environment's controller, matching the optionally specified filter. 1975 func (e *Environ) allInstances(ctx context.ProviderCallContext, tagFilter tagValue) ([]instances.Instance, error) { 1976 servers, err := e.nova().ListServersDetail(jujuMachineFilter()) 1977 if err != nil { 1978 handleCredentialError(err, ctx) 1979 return nil, err 1980 } 1981 instsById := make(map[string]instances.Instance) 1982 for _, server := range servers { 1983 if server.Metadata[tagFilter.tag] != tagFilter.value { 1984 continue 1985 } 1986 if e.isAliveServer(server) { 1987 var s = server 1988 // TODO(wallyworld): lookup the flavor details to fill in the instance type data 1989 instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s} 1990 } 1991 } 1992 if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil { 1993 handleCredentialError(err, ctx) 1994 return nil, err 1995 } 1996 insts := make([]instances.Instance, 0, len(instsById)) 1997 for _, inst := range instsById { 1998 insts = append(insts, inst) 1999 } 2000 return insts, nil 2001 } 2002 2003 func (e *Environ) Destroy(ctx context.ProviderCallContext) error { 2004 err := common.Destroy(e, ctx) 2005 if err != nil { 2006 handleCredentialError(err, ctx) 2007 return errors.Trace(err) 2008 } 2009 // Delete all security groups remaining in the model. 2010 if err := e.firewaller.DeleteAllModelGroups(ctx); err != nil { 2011 handleCredentialError(err, ctx) 2012 return errors.Trace(err) 2013 } 2014 return nil 2015 } 2016 2017 // DestroyController implements the Environ interface. 2018 func (e *Environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error { 2019 if err := e.Destroy(ctx); err != nil { 2020 handleCredentialError(err, ctx) 2021 return errors.Annotate(err, "destroying controller model") 2022 } 2023 // In case any hosted environment hasn't been cleaned up yet, 2024 // we also attempt to delete their resources when the controller 2025 // environment is destroyed. 2026 if err := e.destroyControllerManagedEnvirons(ctx, controllerUUID); err != nil { 2027 handleCredentialError(err, ctx) 2028 return errors.Annotate(err, "destroying managed models") 2029 } 2030 if err := e.firewaller.DeleteAllControllerGroups(ctx, controllerUUID); err != nil { 2031 handleCredentialError(err, ctx) 2032 return errors.Trace(err) 2033 } 2034 return nil 2035 } 2036 2037 // destroyControllerManagedEnvirons destroys all environments managed by this 2038 // models's controller. 2039 func (e *Environ) destroyControllerManagedEnvirons(ctx context.ProviderCallContext, controllerUUID string) error { 2040 // Terminate all instances managed by the controller. 2041 insts, err := e.allControllerManagedInstances(ctx, controllerUUID) 2042 if err != nil { 2043 return errors.Annotate(err, "listing instances") 2044 } 2045 instIds := make([]instance.Id, len(insts)) 2046 for i, inst := range insts { 2047 instIds[i] = inst.Id() 2048 } 2049 if err := e.terminateInstances(ctx, instIds); err != nil { 2050 handleCredentialError(err, ctx) 2051 return errors.Annotate(err, "terminating instances") 2052 } 2053 2054 // Delete all volumes managed by the controller. 2055 cinder, err := e.cinderProvider() 2056 if err == nil { 2057 volumes, err := controllerCinderVolumes(cinder.storageAdapter, controllerUUID) 2058 if err != nil { 2059 handleCredentialError(err, ctx) 2060 return errors.Annotate(err, "listing volumes") 2061 } 2062 volIds := volumeInfoToVolumeIds(cinderToJujuVolumeInfos(volumes)) 2063 errs := foreachVolume(ctx, cinder.storageAdapter, volIds, destroyVolume) 2064 for i, err := range errs { 2065 if err == nil { 2066 continue 2067 } 2068 handleCredentialError(err, ctx) 2069 return errors.Annotatef(err, "destroying volume %q", volIds[i]) 2070 } 2071 } else if !errors.IsNotSupported(err) { 2072 handleCredentialError(err, ctx) 2073 return errors.Trace(err) 2074 } 2075 2076 // Security groups for hosted models are destroyed by the 2077 // DeleteAllControllerGroups method call from Destroy(). 2078 return nil 2079 } 2080 2081 func resourceName(namespace instance.Namespace, envName, resourceId string) string { 2082 return namespace.Value(envName + "-" + resourceId) 2083 } 2084 2085 // jujuMachineFilter returns a nova.Filter matching machines created by Juju. 2086 // The machines are not filtered to any particular environment. To do that, 2087 // instance tags must be compared. 2088 func jujuMachineFilter() *nova.Filter { 2089 filter := nova.NewFilter() 2090 filter.Set(nova.FilterServer, "juju-.*") 2091 return filter 2092 } 2093 2094 // rulesToRuleInfo maps ingress rules to nova rules 2095 func rulesToRuleInfo(groupId string, rules firewall.IngressRules) []neutron.RuleInfoV2 { 2096 var result []neutron.RuleInfoV2 2097 for _, r := range rules { 2098 ruleInfo := neutron.RuleInfoV2{ 2099 Direction: "ingress", 2100 ParentGroupId: groupId, 2101 IPProtocol: r.PortRange.Protocol, 2102 } 2103 if ruleInfo.IPProtocol != "icmp" { 2104 ruleInfo.PortRangeMin = r.PortRange.FromPort 2105 ruleInfo.PortRangeMax = r.PortRange.ToPort 2106 } 2107 sourceCIDRs := r.SourceCIDRs.Values() 2108 if len(sourceCIDRs) == 0 { 2109 sourceCIDRs = append(sourceCIDRs, firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR) 2110 } 2111 for _, sr := range sourceCIDRs { 2112 addrType, _ := network.CIDRAddressType(sr) 2113 if addrType == network.IPv4Address { 2114 ruleInfo.EthernetType = "IPv4" 2115 } else if addrType == network.IPv6Address { 2116 ruleInfo.EthernetType = "IPv6" 2117 } else { 2118 // Should never happen; ignore CIDR 2119 continue 2120 } 2121 ruleInfo.RemoteIPPrefix = sr 2122 result = append(result, ruleInfo) 2123 } 2124 } 2125 return result 2126 } 2127 2128 func (e *Environ) OpenPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error { 2129 if err := e.firewaller.OpenPorts(ctx, rules); err != nil { 2130 handleCredentialError(err, ctx) 2131 return errors.Trace(err) 2132 } 2133 return nil 2134 } 2135 2136 func (e *Environ) ClosePorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error { 2137 if err := e.firewaller.ClosePorts(ctx, rules); err != nil { 2138 handleCredentialError(err, ctx) 2139 return errors.Trace(err) 2140 } 2141 return nil 2142 } 2143 2144 func (e *Environ) IngressRules(ctx context.ProviderCallContext) (firewall.IngressRules, error) { 2145 rules, err := e.firewaller.IngressRules(ctx) 2146 if err != nil { 2147 handleCredentialError(err, ctx) 2148 return rules, errors.Trace(err) 2149 } 2150 return rules, nil 2151 } 2152 2153 func (e *Environ) OpenModelPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error { 2154 if err := e.firewaller.OpenModelPorts(ctx, rules); err != nil { 2155 handleCredentialError(err, ctx) 2156 return errors.Trace(err) 2157 } 2158 return nil 2159 } 2160 2161 func (e *Environ) CloseModelPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error { 2162 if err := e.firewaller.CloseModelPorts(ctx, rules); err != nil { 2163 handleCredentialError(err, ctx) 2164 return errors.Trace(err) 2165 } 2166 return nil 2167 } 2168 2169 func (e *Environ) ModelIngressRules(ctx context.ProviderCallContext) (firewall.IngressRules, error) { 2170 rules, err := e.firewaller.ModelIngressRules(ctx) 2171 if err != nil { 2172 handleCredentialError(err, ctx) 2173 return rules, errors.Trace(err) 2174 } 2175 return rules, nil 2176 } 2177 2178 func (e *Environ) Provider() environs.EnvironProvider { 2179 return providerInstance 2180 } 2181 2182 func (e *Environ) terminateInstances(ctx context.ProviderCallContext, ids []instance.Id) error { 2183 if len(ids) == 0 { 2184 return nil 2185 } 2186 2187 novaClient := e.nova() 2188 2189 // If in instance firewall mode, gather the security group names. 2190 securityGroupNames, err := e.firewaller.GetSecurityGroups(ctx, ids...) 2191 if err == environs.ErrNoInstances { 2192 return nil 2193 } 2194 if err != nil { 2195 logger.Debugf("error retrieving security groups for %v: %v", ids, err) 2196 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 2197 // We'll likely fail all subsequent calls if we have an invalid credential. 2198 return errors.Trace(err) 2199 } 2200 } 2201 2202 // Record the first error we encounter, as that's the one we're currently 2203 // reporting to the user. 2204 var firstErr error 2205 for _, id := range ids { 2206 // Attempt to destroy the ports that could have been created when using 2207 // spaces. 2208 if err := e.terminateInstanceNetworkPorts(id); err != nil { 2209 logger.Errorf("error attempting to remove ports associated with instance %q: %v", id, err) 2210 // Unfortunately there is nothing we can do here, there could be 2211 // orphan ports left. 2212 } 2213 2214 // Once ports have been deleted, attempt to delete the server. 2215 err = novaClient.DeleteServer(string(id)) 2216 if IsNotFoundError(err) { 2217 continue 2218 } 2219 if err != nil { 2220 logger.Debugf("error terminating instance %q: %v", id, err) 2221 if firstErr == nil { 2222 firstErr = err 2223 } 2224 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 2225 // We'll likely fail all subsequent calls if we have an invalid credential. 2226 return errors.Trace(err) 2227 } 2228 } 2229 } 2230 2231 if len(securityGroupNames) > 0 { 2232 logger.Tracef("deleting security groups %v", securityGroupNames) 2233 if err := e.firewaller.DeleteGroups(ctx, securityGroupNames...); err != nil { 2234 return err 2235 } 2236 } 2237 2238 return firstErr 2239 } 2240 2241 func (e *Environ) terminateInstanceNetworkPorts(id instance.Id) error { 2242 novaClient := e.nova() 2243 osInterfaces, err := novaClient.ListOSInterfaces(string(id)) 2244 if err != nil { 2245 return errors.Trace(err) 2246 } 2247 2248 client := e.neutron() 2249 ports, err := client.ListPortsV2() 2250 if err != nil { 2251 return errors.Trace(err) 2252 } 2253 2254 // Unfortunately we're unable to bulk delete these ports, so we have to go 2255 // over them, one by one. 2256 changes := set.NewStrings() 2257 for _, port := range ports { 2258 if !strings.HasPrefix(port.Name, fmt.Sprintf("juju-%s", e.uuid)) { 2259 continue 2260 } 2261 changes.Add(port.Id) 2262 } 2263 2264 for _, osInterface := range osInterfaces { 2265 if osInterface.PortID == "" { 2266 continue 2267 } 2268 2269 // Ensure we created the port by first checking the name. 2270 port, err := client.PortByIdV2(osInterface.PortID) 2271 if err != nil || !strings.HasPrefix(port.Name, "juju-") { 2272 continue 2273 } 2274 2275 changes.Add(osInterface.PortID) 2276 } 2277 2278 var errs []error 2279 for _, change := range changes.SortedValues() { 2280 // Delete a port. If we encounter an error add it to the list of errors 2281 // and continue until we've exhausted all the ports to delete. 2282 if err := client.DeletePortV2(change); err != nil { 2283 errs = append(errs, err) 2284 continue 2285 } 2286 } 2287 2288 if len(errs) > 0 { 2289 return errors.Errorf("error terminating network ports: %v", errs) 2290 } 2291 2292 return nil 2293 } 2294 2295 // AgentMetadataLookupParams returns parameters which are used to query agent simple-streams metadata. 2296 func (e *Environ) AgentMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 2297 base := config.PreferredBase(e.ecfg()) 2298 return e.metadataLookupParams(region, base.OS) 2299 } 2300 2301 // ImageMetadataLookupParams returns parameters which are used to query image simple-streams metadata. 2302 func (e *Environ) ImageMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 2303 base := config.PreferredBase(e.ecfg()) 2304 release, err := imagemetadata.ImageRelease(base) 2305 if err != nil { 2306 return nil, errors.Trace(err) 2307 } 2308 return e.metadataLookupParams(region, release) 2309 } 2310 2311 // MetadataLookupParams returns parameters which are used to query simple-streams metadata. 2312 func (e *Environ) metadataLookupParams(region, release string) (*simplestreams.MetadataLookupParams, error) { 2313 if region == "" { 2314 region = e.cloud().Region 2315 } 2316 cloudSpec, err := e.cloudSpec(region) 2317 if err != nil { 2318 return nil, err 2319 } 2320 return &simplestreams.MetadataLookupParams{ 2321 Release: release, 2322 Region: cloudSpec.Region, 2323 Endpoint: cloudSpec.Endpoint, 2324 }, nil 2325 } 2326 2327 // Region is specified in the HasRegion interface. 2328 func (e *Environ) Region() (simplestreams.CloudSpec, error) { 2329 return e.cloudSpec(e.cloud().Region) 2330 } 2331 2332 func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) { 2333 return simplestreams.CloudSpec{ 2334 Region: region, 2335 Endpoint: e.cloud().Endpoint, 2336 }, nil 2337 } 2338 2339 // TagInstance implements environs.InstanceTagger. 2340 func (e *Environ) TagInstance(ctx context.ProviderCallContext, id instance.Id, tags map[string]string) error { 2341 if err := e.nova().SetServerMetadata(string(id), tags); err != nil { 2342 handleCredentialError(err, ctx) 2343 return errors.Annotate(err, "setting server metadata") 2344 } 2345 return nil 2346 } 2347 2348 func (e *Environ) SetClock(clock clock.Clock) { 2349 e.clock = clock 2350 } 2351 2352 func validateCloudSpec(spec environscloudspec.CloudSpec) error { 2353 if err := spec.Validate(); err != nil { 2354 return errors.Trace(err) 2355 } 2356 if err := validateAuthURL(spec.Endpoint); err != nil { 2357 return errors.Annotate(err, "validating auth-url") 2358 } 2359 if spec.Credential == nil { 2360 return errors.NotValidf("missing credential") 2361 } 2362 switch authType := spec.Credential.AuthType(); authType { 2363 case cloud.UserPassAuthType: 2364 case cloud.AccessKeyAuthType: 2365 default: 2366 return errors.NotSupportedf("%q auth-type", authType) 2367 } 2368 return nil 2369 } 2370 2371 func validateAuthURL(authURL string) error { 2372 parts, err := url.Parse(authURL) 2373 if err != nil || parts.Host == "" || parts.Scheme == "" { 2374 return errors.NotValidf("auth-url %q", authURL) 2375 } 2376 return nil 2377 } 2378 2379 // Subnets is specified on environs.Networking. 2380 func (e *Environ) Subnets( 2381 ctx context.ProviderCallContext, instId instance.Id, subnetIds []network.Id, 2382 ) ([]network.SubnetInfo, error) { 2383 subnets, err := e.networking.Subnets(instId, subnetIds) 2384 if err != nil { 2385 handleCredentialError(err, ctx) 2386 return subnets, errors.Trace(err) 2387 } 2388 return subnets, nil 2389 } 2390 2391 // NetworkInterfaces is specified on environs.Networking. 2392 func (e *Environ) NetworkInterfaces(ctx context.ProviderCallContext, ids []instance.Id) ([]network.InterfaceInfos, error) { 2393 infos, err := e.networking.NetworkInterfaces(ids) 2394 if err != nil { 2395 handleCredentialError(err, ctx) 2396 return infos, errors.Trace(err) 2397 } 2398 2399 return infos, nil 2400 } 2401 2402 // SupportsSpaces is specified on environs.Networking. 2403 func (e *Environ) SupportsSpaces(context.ProviderCallContext) (bool, error) { 2404 return true, nil 2405 } 2406 2407 // SupportsContainerAddresses is specified on environs.Networking. 2408 func (e *Environ) SupportsContainerAddresses(context.ProviderCallContext) (bool, error) { 2409 return false, errors.NotSupportedf("container address") 2410 } 2411 2412 // SuperSubnets is specified on environs.Networking 2413 func (e *Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) { 2414 subnets, err := e.networking.Subnets("", nil) 2415 if err != nil { 2416 handleCredentialError(err, ctx) 2417 return nil, err 2418 } 2419 cidrs := make([]string, len(subnets)) 2420 for i, subnet := range subnets { 2421 cidrs[i] = subnet.CIDR 2422 } 2423 return cidrs, nil 2424 } 2425 2426 // AllocateContainerAddresses is specified on environs.Networking. 2427 func (e *Environ) AllocateContainerAddresses(ctx context.ProviderCallContext, hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo network.InterfaceInfos) (network.InterfaceInfos, error) { 2428 return nil, errors.NotSupportedf("allocate container address") 2429 } 2430 2431 // ReleaseContainerAddresses is specified on environs.Networking. 2432 func (e *Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error { 2433 return errors.NotSupportedf("release container address") 2434 } 2435 2436 // AreSpacesRoutable is specified on environs.NetworkingEnviron. 2437 func (*Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) { 2438 return false, nil 2439 } 2440 2441 // SupportsRulesWithIPV6CIDRs returns true if the environment supports ingress 2442 // rules containing IPV6 CIDRs. It is part of the FirewallFeatureQuerier 2443 // interface. 2444 func (e *Environ) SupportsRulesWithIPV6CIDRs(ctx context.ProviderCallContext) (bool, error) { 2445 return true, nil 2446 } 2447 2448 // ValidateCloudEndpoint returns nil if the current model can talk to the openstack 2449 // endpoint. Used as validation during model upgrades. 2450 // Implements environs.CloudEndpointChecker 2451 func (env *Environ) ValidateCloudEndpoint(ctx context.ProviderCallContext) error { 2452 err := env.Provider().Ping(ctx, env.cloud().Endpoint) 2453 return errors.Trace(err) 2454 }