github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oci/environ.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package oci 5 6 import ( 7 "context" 8 "fmt" 9 "net/http" 10 "sync" 11 "time" 12 13 "github.com/juju/clock" 14 "github.com/juju/errors" 15 "github.com/juju/utils/arch" 16 "github.com/juju/utils/os" 17 jujuseries "github.com/juju/utils/series" 18 "github.com/juju/version" 19 "github.com/kr/pretty" 20 21 "github.com/juju/juju/cloudconfig/cloudinit" 22 "github.com/juju/juju/cloudconfig/instancecfg" 23 "github.com/juju/juju/cloudconfig/providerinit" 24 "github.com/juju/juju/core/constraints" 25 "github.com/juju/juju/core/instance" 26 "github.com/juju/juju/environs" 27 "github.com/juju/juju/environs/config" 28 envcontext "github.com/juju/juju/environs/context" 29 "github.com/juju/juju/environs/instances" 30 "github.com/juju/juju/environs/tags" 31 "github.com/juju/juju/provider/common" 32 providerCommon "github.com/juju/juju/provider/oci/common" 33 "github.com/juju/juju/storage" 34 "github.com/juju/juju/tools" 35 36 ociCommon "github.com/oracle/oci-go-sdk/common" 37 ociCore "github.com/oracle/oci-go-sdk/core" 38 ociIdentity "github.com/oracle/oci-go-sdk/identity" 39 ) 40 41 type Environ struct { 42 Compute providerCommon.OCIComputeClient 43 Networking providerCommon.OCINetworkingClient 44 Storage providerCommon.OCIStorageClient 45 Firewall providerCommon.OCIFirewallClient 46 Identity providerCommon.OCIIdentityClient 47 ociConfig ociCommon.ConfigurationProvider 48 p *EnvironProvider 49 clock clock.Clock 50 ecfgMutex sync.Mutex 51 ecfgObj *environConfig 52 namespace instance.Namespace 53 54 vcn ociCore.Vcn 55 seclist ociCore.SecurityList 56 // subnets contains one subnet for each availability domain 57 // these will get created once the environment is spun up, and 58 // will never change. 59 subnets map[string][]ociCore.Subnet 60 } 61 62 var _ common.ZonedEnviron = (*Environ)(nil) 63 var _ storage.ProviderRegistry = (*Environ)(nil) 64 var _ environs.Environ = (*Environ)(nil) 65 var _ environs.Firewaller = (*Environ)(nil) 66 var _ environs.Networking = (*Environ)(nil) 67 var _ environs.NetworkingEnviron = (*Environ)(nil) 68 69 func (e *Environ) ecfg() *environConfig { 70 e.ecfgMutex.Lock() 71 defer e.ecfgMutex.Unlock() 72 return e.ecfgObj 73 } 74 75 func (e *Environ) allInstances(ctx envcontext.ProviderCallContext, tags map[string]string) ([]*ociInstance, error) { 76 compartment := e.ecfg().compartmentID() 77 request := ociCore.ListInstancesRequest{ 78 CompartmentId: compartment, 79 } 80 response, err := e.Compute.ListInstances(context.Background(), request) 81 if err != nil { 82 providerCommon.HandleCredentialError(err, ctx) 83 return nil, errors.Trace(err) 84 } 85 86 ret := []*ociInstance{} 87 for _, val := range response.Items { 88 if val.LifecycleState == ociCore.InstanceLifecycleStateTerminated { 89 continue 90 } 91 missingTag := false 92 for i, j := range tags { 93 tagVal, ok := val.FreeformTags[i] 94 if !ok || tagVal != j { 95 missingTag = true 96 break 97 } 98 } 99 if missingTag { 100 // One of the tags was not found 101 continue 102 } 103 inst, err := newInstance(val, e) 104 if err != nil { 105 providerCommon.HandleCredentialError(err, ctx) 106 return nil, errors.Trace(err) 107 } 108 ret = append(ret, inst) 109 } 110 return ret, nil 111 } 112 113 func (e *Environ) getOCIInstance(ctx envcontext.ProviderCallContext, id instance.Id) (*ociInstance, error) { 114 instanceId := string(id) 115 request := ociCore.GetInstanceRequest{ 116 InstanceId: &instanceId, 117 } 118 119 response, err := e.Compute.GetInstance(context.Background(), request) 120 if err != nil { 121 providerCommon.HandleCredentialError(err, ctx) 122 return nil, errors.Trace(err) 123 } 124 125 return newInstance(response.Instance, e) 126 } 127 128 func (e *Environ) isNotFound(response *http.Response) bool { 129 if response.StatusCode == http.StatusNotFound { 130 return true 131 } 132 return false 133 } 134 135 // waitForResourceStatus will ping the resource until the fetch function returns true, 136 // the timeout is reached, or an error occurs. 137 func (e *Environ) waitForResourceStatus( 138 statusFunc func(resID *string) (status string, err error), 139 resId *string, desiredStatus string, 140 timeout time.Duration, 141 ) error { 142 143 var status string 144 var err error 145 timeoutTimer := e.clock.NewTimer(timeout) 146 defer timeoutTimer.Stop() 147 148 retryTimer := e.clock.NewTimer(0) 149 defer retryTimer.Stop() 150 151 for { 152 select { 153 case <-retryTimer.Chan(): 154 status, err = statusFunc(resId) 155 if err != nil { 156 return err 157 } 158 if status == desiredStatus { 159 return nil 160 } 161 retryTimer.Reset(2 * time.Second) 162 case <-timeoutTimer.Chan(): 163 return errors.Errorf( 164 "timed out waiting for resource %q to transition to %v. Current status: %q", 165 *resId, desiredStatus, status, 166 ) 167 } 168 } 169 } 170 171 func (e *Environ) ping() error { 172 request := ociIdentity.ListAvailabilityDomainsRequest{ 173 CompartmentId: e.ecfg().compartmentID(), 174 } 175 _, err := e.Identity.ListAvailabilityDomains(context.Background(), request) 176 return err 177 } 178 179 // AvailabilityZones is defined in the common.ZonedEnviron interface 180 func (e *Environ) AvailabilityZones(ctx envcontext.ProviderCallContext) ([]common.AvailabilityZone, error) { 181 request := ociIdentity.ListAvailabilityDomainsRequest{ 182 CompartmentId: e.ecfg().compartmentID(), 183 } 184 185 ociCtx := context.Background() 186 domains, err := e.Identity.ListAvailabilityDomains(ociCtx, request) 187 188 if err != nil { 189 providerCommon.HandleCredentialError(err, ctx) 190 return nil, errors.Trace(err) 191 } 192 193 zones := []common.AvailabilityZone{} 194 195 for _, val := range domains.Items { 196 zones = append(zones, NewAvailabilityZone(*val.Name)) 197 } 198 return zones, nil 199 } 200 201 // InstanceAvailabilityZoneNames implements common.ZonedEnviron. 202 func (e *Environ) InstanceAvailabilityZoneNames(ctx envcontext.ProviderCallContext, ids []instance.Id) ([]string, error) { 203 instances, err := e.Instances(ctx, ids) 204 if err != nil && err != environs.ErrPartialInstances { 205 providerCommon.HandleCredentialError(err, ctx) 206 return nil, err 207 } 208 zones := []string{} 209 for _, inst := range instances { 210 oInst := inst.(*ociInstance) 211 zones = append(zones, oInst.availabilityZone()) 212 } 213 if len(zones) < len(ids) { 214 return zones, environs.ErrPartialInstances 215 } 216 return zones, nil 217 } 218 219 // DeriveAvailabilityZones implements common.ZonedEnviron. 220 func (e *Environ) DeriveAvailabilityZones(ctx envcontext.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) { 221 return nil, nil 222 } 223 224 func (e *Environ) getOciInstances(ctx envcontext.ProviderCallContext, ids ...instance.Id) ([]*ociInstance, error) { 225 ret := []*ociInstance{} 226 227 compartmentID := e.ecfg().compartmentID() 228 request := ociCore.ListInstancesRequest{ 229 CompartmentId: compartmentID, 230 } 231 232 instances, err := e.Compute.ListInstances(context.Background(), request) 233 if err != nil { 234 providerCommon.HandleCredentialError(err, ctx) 235 return nil, errors.Trace(err) 236 } 237 238 if len(instances.Items) == 0 { 239 return nil, environs.ErrNoInstances 240 } 241 242 for _, val := range instances.Items { 243 oInstance, err := newInstance(val, e) 244 if err != nil { 245 providerCommon.HandleCredentialError(err, ctx) 246 return nil, errors.Trace(err) 247 } 248 for _, id := range ids { 249 if oInstance.Id() == id { 250 ret = append(ret, oInstance) 251 } 252 } 253 } 254 255 if len(ret) < len(ids) { 256 return ret, environs.ErrPartialInstances 257 } 258 return ret, nil 259 } 260 261 func (e *Environ) getOciInstancesAsMap(ctx envcontext.ProviderCallContext, ids ...instance.Id) (map[instance.Id]*ociInstance, error) { 262 instances, err := e.getOciInstances(ctx, ids...) 263 if err != nil { 264 providerCommon.HandleCredentialError(err, ctx) 265 return nil, errors.Trace(err) 266 } 267 ret := map[instance.Id]*ociInstance{} 268 for _, inst := range instances { 269 ret[inst.Id()] = inst 270 } 271 return ret, nil 272 } 273 274 // Instances implements environs.Environ. 275 func (e *Environ) Instances(ctx envcontext.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) { 276 if len(ids) == 0 { 277 return nil, nil 278 } 279 ociInstances, err := e.getOciInstances(ctx, ids...) 280 if err != nil && err != environs.ErrPartialInstances { 281 providerCommon.HandleCredentialError(err, ctx) 282 return nil, errors.Trace(err) 283 } 284 285 ret := []instances.Instance{} 286 for _, val := range ociInstances { 287 ret = append(ret, val) 288 } 289 290 if len(ret) < len(ids) { 291 return ret, environs.ErrPartialInstances 292 } 293 return ret, nil 294 } 295 296 // PrepareForBootstrap implements environs.Environ. 297 func (e *Environ) PrepareForBootstrap(ctx environs.BootstrapContext) error { 298 if ctx.ShouldVerifyCredentials() { 299 logger.Infof("Logging into the oracle cloud infrastructure") 300 if err := e.ping(); err != nil { 301 return errors.Trace(err) 302 } 303 } 304 305 return nil 306 } 307 308 // Bootstrap implements environs.Environ. 309 func (e *Environ) Bootstrap(ctx environs.BootstrapContext, callCtx envcontext.ProviderCallContext, params environs.BootstrapParams) (*environs.BootstrapResult, error) { 310 return common.Bootstrap(ctx, e, callCtx, params) 311 } 312 313 // Create implements environs.Environ. 314 func (e *Environ) Create(ctx envcontext.ProviderCallContext, params environs.CreateParams) error { 315 if err := e.ping(); err != nil { 316 providerCommon.HandleCredentialError(err, ctx) 317 return errors.Trace(err) 318 } 319 return nil 320 } 321 322 // AdoptResources implements environs.Environ. 323 func (e *Environ) AdoptResources(ctx envcontext.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 324 return errors.NotImplementedf("AdoptResources") 325 } 326 327 // ConstraintsValidator implements environs.Environ. 328 func (e *Environ) ConstraintsValidator(ctx envcontext.ProviderCallContext) (constraints.Validator, error) { 329 // list of unsupported OCI provider constraints 330 unsupportedConstraints := []string{ 331 constraints.Container, 332 constraints.VirtType, 333 constraints.Tags, 334 } 335 336 validator := constraints.NewValidator() 337 validator.RegisterUnsupported(unsupportedConstraints) 338 validator.RegisterVocabulary(constraints.Arch, []string{arch.AMD64}) 339 logger.Infof("Returning constraints validator: %v", validator) 340 return validator, nil 341 } 342 343 // SetConfig implements environs.Environ. 344 func (e *Environ) SetConfig(cfg *config.Config) error { 345 ecfg, err := e.p.newConfig(cfg) 346 if err != nil { 347 return err 348 } 349 350 e.ecfgMutex.Lock() 351 defer e.ecfgMutex.Unlock() 352 e.ecfgObj = ecfg 353 354 return nil 355 } 356 357 func (e *Environ) allControllerManagedInstances(ctx envcontext.ProviderCallContext, controllerUUID string) ([]*ociInstance, error) { 358 tags := map[string]string{ 359 tags.JujuController: controllerUUID, 360 } 361 return e.allInstances(ctx, tags) 362 } 363 364 // ControllerInstances implements environs.Environ. 365 func (e *Environ) ControllerInstances(ctx envcontext.ProviderCallContext, controllerUUID string) ([]instance.Id, error) { 366 tags := map[string]string{ 367 tags.JujuController: controllerUUID, 368 tags.JujuIsController: "true", 369 } 370 instances, err := e.allInstances(ctx, tags) 371 if err != nil { 372 providerCommon.HandleCredentialError(err, ctx) 373 return nil, errors.Trace(err) 374 } 375 ids := []instance.Id{} 376 for _, val := range instances { 377 ids = append(ids, val.Id()) 378 } 379 return ids, nil 380 } 381 382 // Destroy implements environs.Environ. 383 func (e *Environ) Destroy(ctx envcontext.ProviderCallContext) error { 384 return common.Destroy(e, ctx) 385 } 386 387 // DestroyController implements environs.Environ. 388 func (e *Environ) DestroyController(ctx envcontext.ProviderCallContext, controllerUUID string) error { 389 err := e.Destroy(ctx) 390 if err != nil { 391 providerCommon.HandleCredentialError(err, ctx) 392 logger.Errorf("Failed to destroy environment through controller: %s", errors.Trace(err)) 393 } 394 instances, err := e.allControllerManagedInstances(ctx, controllerUUID) 395 if err != nil { 396 if err == environs.ErrNoInstances { 397 return nil 398 } 399 providerCommon.HandleCredentialError(err, ctx) 400 return errors.Trace(err) 401 } 402 ids := make([]instance.Id, len(instances)) 403 for i, val := range instances { 404 ids[i] = val.Id() 405 } 406 407 err = e.StopInstances(ctx, ids...) 408 if err != nil { 409 providerCommon.HandleCredentialError(err, ctx) 410 return errors.Trace(err) 411 } 412 logger.Debugf("Cleaning up network resources") 413 err = e.cleanupNetworksAndSubnets(controllerUUID, "") 414 if err != nil { 415 providerCommon.HandleCredentialError(err, ctx) 416 return errors.Trace(err) 417 } 418 419 return nil 420 } 421 422 // Provider implements environs.Environ. 423 func (e *Environ) Provider() environs.EnvironProvider { 424 return e.p 425 } 426 427 // getCloudInitConfig returns a CloudConfig instance. The default oracle images come 428 // bundled with iptables-persistent on Ubuntu and firewalld on CentOS, which maintains 429 // a number of iptables firewall rules. We need to at least allow the juju API port for state 430 // machines. SSH port is allowed by default on linux images. 431 func (e *Environ) getCloudInitConfig(series string, apiPort int) (cloudinit.CloudConfig, error) { 432 // TODO (gsamfira): remove this function when the above mention bug is fixed 433 cloudcfg, err := cloudinit.New(series) 434 if err != nil { 435 return nil, errors.Annotate(err, "cannot create cloudinit template") 436 } 437 438 if apiPort == 0 { 439 return cloudcfg, nil 440 } 441 442 operatingSystem, err := jujuseries.GetOSFromSeries(series) 443 if err != nil { 444 return nil, errors.Trace(err) 445 } 446 switch operatingSystem { 447 case os.Ubuntu: 448 fwCmd := fmt.Sprintf( 449 "/sbin/iptables -I INPUT -p tcp --dport %d -j ACCEPT", apiPort) 450 cloudcfg.AddRunCmd(fwCmd) 451 cloudcfg.AddScripts("/etc/init.d/netfilter-persistent save") 452 case os.CentOS: 453 fwCmd := fmt.Sprintf("firewall-cmd --zone=public --add-port=%d/tcp --permanent", apiPort) 454 cloudcfg.AddRunCmd(fwCmd) 455 cloudcfg.AddRunCmd("firewall-cmd --reload") 456 } 457 return cloudcfg, nil 458 } 459 460 func shortenMachineId(machineId *string, nRunesShown int) string { 461 var short string 462 if machineId != nil { 463 short = *machineId 464 } 465 offset := len(short) - nRunesShown 466 if offset > 0 { 467 short = "..." + short[offset:] 468 } 469 return short 470 } 471 472 // StartInstance implements environs.InstanceBroker. 473 func (e *Environ) StartInstance(ctx envcontext.ProviderCallContext, args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 474 if args.ControllerUUID == "" { 475 return nil, errors.NotFoundf("Controller UUID") 476 } 477 478 networks, err := e.ensureNetworksAndSubnets(ctx, args.ControllerUUID, e.Config().UUID()) 479 if err != nil { 480 providerCommon.HandleCredentialError(err, ctx) 481 return nil, errors.Trace(err) 482 } 483 484 zones, err := e.AvailabilityZones(ctx) 485 if err != nil { 486 providerCommon.HandleCredentialError(err, ctx) 487 return nil, errors.Trace(err) 488 } 489 490 zone := zones[0].Name() 491 network := networks[zone][0] 492 // refresh the global image cache 493 // this only hits the API every 30 minutes, otherwise just retrieves 494 // from cache 495 imgCache, err := refreshImageCache(e.Compute, e.ecfg().compartmentID()) 496 if err != nil { 497 providerCommon.HandleCredentialError(err, ctx) 498 return nil, errors.Trace(err) 499 } 500 logger.Tracef("Image cache contains: %# v", pretty.Formatter(imgCache)) 501 502 series := args.Tools.OneSeries() 503 arches := args.Tools.Arches() 504 505 types := imgCache.SupportedShapes(series) 506 507 defaultType := string(VirtualMachine) 508 if args.Constraints.VirtType == nil { 509 args.Constraints.VirtType = &defaultType 510 } 511 512 // check if we find an image that is compliant with the 513 // constraints provided in the oracle cloud account 514 args.ImageMetadata = imgCache.ImageMetadata(series, *args.Constraints.VirtType) 515 516 spec, image, err := findInstanceSpec( 517 args.ImageMetadata, 518 types, 519 &instances.InstanceConstraint{ 520 Series: series, 521 Arches: arches, 522 Constraints: args.Constraints, 523 }, 524 ) 525 if err != nil { 526 providerCommon.HandleCredentialError(err, ctx) 527 return nil, errors.Trace(err) 528 } 529 530 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 531 if err != nil { 532 providerCommon.HandleCredentialError(err, ctx) 533 return nil, errors.Trace(err) 534 } 535 logger.Tracef("agent binaries: %v", tools) 536 if err = args.InstanceConfig.SetTools(tools); err != nil { 537 providerCommon.HandleCredentialError(err, ctx) 538 return nil, errors.Trace(err) 539 } 540 541 if err = instancecfg.FinishInstanceConfig( 542 args.InstanceConfig, 543 e.Config(), 544 ); err != nil { 545 providerCommon.HandleCredentialError(err, ctx) 546 return nil, errors.Trace(err) 547 } 548 hostname, err := e.namespace.Hostname(args.InstanceConfig.MachineId) 549 if err != nil { 550 providerCommon.HandleCredentialError(err, ctx) 551 return nil, errors.Trace(err) 552 } 553 tags := args.InstanceConfig.Tags 554 555 var apiPort int 556 var desiredStatus ociCore.InstanceLifecycleStateEnum 557 // If we are bootstrapping a new controller, we want to wait for the 558 // machine to reach running state before attempting to SSH into it, 559 // to configure the controller. 560 // If the machine that is spawning is not a controller, then userdata 561 // will take care of it's initial setup, and waiting for a running 562 // status is not necessary 563 if args.InstanceConfig.Controller != nil { 564 apiPort = args.InstanceConfig.Controller.Config.APIPort() 565 desiredStatus = ociCore.InstanceLifecycleStateRunning 566 } else { 567 desiredStatus = ociCore.InstanceLifecycleStateProvisioning 568 } 569 570 cloudcfg, err := e.getCloudInitConfig(series, apiPort) 571 if err != nil { 572 providerCommon.HandleCredentialError(err, ctx) 573 return nil, errors.Annotate(err, "cannot create cloudinit template") 574 } 575 576 // compose userdata with the cloud config template 577 logger.Debugf("Composing userdata") 578 userData, err := providerinit.ComposeUserData( 579 args.InstanceConfig, 580 cloudcfg, 581 OCIRenderer{}, 582 ) 583 if err != nil { 584 providerCommon.HandleCredentialError(err, ctx) 585 return nil, errors.Annotate(err, "cannot make user data") 586 } 587 588 var rootDiskSizeGB int 589 if args.Constraints.RootDisk != nil { 590 rootDiskSizeGB = int(*args.Constraints.RootDisk) / 1024 591 if int(*args.Constraints.RootDisk) < MinVolumeSizeMB { 592 logger.Warningf( 593 "selected disk size is too small (%d MB). Setting root disk size to minimum volume size (%d MB)", 594 int(*args.Constraints.RootDisk), MinVolumeSizeMB) 595 rootDiskSizeGB = MinVolumeSizeMB / 1024 596 } else if int(*args.Constraints.RootDisk) > MaxVolumeSizeMB { 597 logger.Warningf( 598 "selected disk size is too large (%d MB). Setting root disk size to maximum volume size (%d MB)", 599 int(*args.Constraints.RootDisk), MaxVolumeSizeMB) 600 rootDiskSizeGB = MaxVolumeSizeMB / 1024 601 } 602 } else { 603 rootDiskSizeGB = MinVolumeSizeMB / 1024 604 } 605 606 assignPublicIp := true 607 bootSource := ociCore.InstanceSourceViaImageDetails{ 608 ImageId: &image, 609 BootVolumeSizeInGBs: &rootDiskSizeGB, 610 } 611 instanceDetails := ociCore.LaunchInstanceDetails{ 612 AvailabilityDomain: &zone, 613 CompartmentId: e.ecfg().compartmentID(), 614 SourceDetails: bootSource, 615 Shape: &spec.InstanceType.Name, 616 CreateVnicDetails: &ociCore.CreateVnicDetails{ 617 SubnetId: network.Id, 618 AssignPublicIp: &assignPublicIp, 619 DisplayName: &hostname, 620 }, 621 DisplayName: &hostname, 622 Metadata: map[string]string{ 623 "user_data": string(userData), 624 }, 625 FreeformTags: tags, 626 } 627 628 request := ociCore.LaunchInstanceRequest{ 629 LaunchInstanceDetails: instanceDetails, 630 } 631 632 response, err := e.Compute.LaunchInstance(context.Background(), request) 633 if err != nil { 634 providerCommon.HandleCredentialError(err, ctx) 635 return nil, errors.Trace(err) 636 } 637 638 instance, err := newInstance(response.Instance, e) 639 if err != nil { 640 providerCommon.HandleCredentialError(err, ctx) 641 return nil, errors.Trace(err) 642 } 643 644 machineId := response.Instance.Id 645 timeout := 10 * time.Minute 646 if err := instance.waitForMachineStatus(desiredStatus, timeout); err != nil { 647 providerCommon.HandleCredentialError(err, ctx) 648 return nil, errors.Trace(err) 649 } 650 logger.Infof("started instance %q", *machineId) 651 displayName := shortenMachineId(machineId, 6) 652 653 if desiredStatus == ociCore.InstanceLifecycleStateRunning { 654 if err := instance.waitForPublicIP(ctx); err != nil { 655 providerCommon.HandleCredentialError(err, ctx) 656 return nil, errors.Trace(err) 657 } 658 } 659 660 result := &environs.StartInstanceResult{ 661 DisplayName: displayName, 662 Instance: instance, 663 Hardware: instance.hardwareCharacteristics(), 664 } 665 666 return result, nil 667 } 668 669 // StopInstances implements environs.InstanceBroker. 670 func (e *Environ) StopInstances(ctx envcontext.ProviderCallContext, ids ...instance.Id) error { 671 ociInstances, err := e.getOciInstances(ctx, ids...) 672 if err == environs.ErrNoInstances { 673 return nil 674 } else if err != nil { 675 providerCommon.HandleCredentialError(err, ctx) 676 return err 677 } 678 679 logger.Debugf("terminating instances %v", ids) 680 if err := e.terminateInstances(ctx, ociInstances...); err != nil { 681 providerCommon.HandleCredentialError(err, ctx) 682 return err 683 } 684 685 return nil 686 } 687 688 type instError struct { 689 id instance.Id 690 err error 691 } 692 693 func (o *Environ) terminateInstances(ctx envcontext.ProviderCallContext, instances ...*ociInstance) error { 694 wg := sync.WaitGroup{} 695 wg.Add(len(instances)) 696 errCh := make(chan instError, len(instances)) 697 for _, oInst := range instances { 698 go func(inst *ociInstance) { 699 defer wg.Done() 700 if err := inst.deleteInstance(ctx); err != nil { 701 errCh <- instError{id: inst.Id(), err: err} 702 providerCommon.HandleCredentialError(err, ctx) 703 return 704 } 705 err := inst.waitForMachineStatus( 706 ociCore.InstanceLifecycleStateTerminated, 707 resourcePollTimeout) 708 if err != nil && !errors.IsNotFound(err) { 709 providerCommon.HandleCredentialError(err, ctx) 710 errCh <- instError{id: inst.Id(), err: err} 711 } 712 }(oInst) 713 } 714 wg.Wait() 715 close(errCh) 716 717 var errs []error 718 var instIds []instance.Id 719 for item := range errCh { 720 errs = append(errs, item.err) 721 instIds = append(instIds, item.id) 722 } 723 724 switch len(errs) { 725 case 0: 726 return nil 727 case 1: 728 return errors.Annotatef(errs[0], "failed to stop instance %s", instIds[0]) 729 default: 730 return errors.Errorf( 731 "failed to stop instances %s: %s", 732 instIds, errs, 733 ) 734 } 735 } 736 737 // AllInstances implements environs.InstanceBroker. 738 func (e *Environ) AllInstances(ctx envcontext.ProviderCallContext) ([]instances.Instance, error) { 739 tags := map[string]string{ 740 tags.JujuModel: e.Config().UUID(), 741 } 742 allInstances, err := e.allInstances(ctx, tags) 743 if err != nil { 744 providerCommon.HandleCredentialError(err, ctx) 745 return nil, errors.Trace(err) 746 } 747 748 ret := []instances.Instance{} 749 for _, val := range allInstances { 750 ret = append(ret, val) 751 } 752 return ret, nil 753 } 754 755 // MaintainInstance implements environs.InstanceBroker. 756 func (e *Environ) MaintainInstance(ctx envcontext.ProviderCallContext, args environs.StartInstanceParams) error { 757 return nil 758 } 759 760 // Config implements environs.ConfigGetter. 761 func (e *Environ) Config() *config.Config { 762 e.ecfgMutex.Lock() 763 defer e.ecfgMutex.Unlock() 764 if e.ecfgObj == nil { 765 return nil 766 } 767 return e.ecfgObj.Config 768 } 769 770 // PrecheckInstance implements environs.InstancePrechecker. 771 func (e *Environ) PrecheckInstance(envcontext.ProviderCallContext, environs.PrecheckInstanceParams) error { 772 return nil 773 } 774 775 // InstanceTypes implements environs.InstancePrechecker. 776 func (e *Environ) InstanceTypes(envcontext.ProviderCallContext, constraints.Value) (instances.InstanceTypesWithCostMetadata, error) { 777 return instances.InstanceTypesWithCostMetadata{}, errors.NotImplementedf("InstanceTypes") 778 }