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  }