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