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  }