github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oci/instance.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  	"net/http"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/juju/core/status"
    15  
    16  	"github.com/juju/juju/core/instance"
    17  	envcontext "github.com/juju/juju/environs/context"
    18  	"github.com/juju/juju/environs/instances"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/provider/oci/common"
    21  
    22  	ociCore "github.com/oracle/oci-go-sdk/core"
    23  )
    24  
    25  const (
    26  	// MinVolumeSizeMB is the minimum size in MB for a volume or boot disk
    27  	MinVolumeSizeMB = 51200
    28  
    29  	// MaxVolumeSizeMB is the maximum size in MB for a volume or boot disk
    30  	MaxVolumeSizeMB = 16777216
    31  )
    32  
    33  type ociInstance struct {
    34  	arch     string
    35  	instType *instances.InstanceType
    36  	env      *Environ
    37  	mutex    sync.Mutex
    38  	etag     *string
    39  	raw      ociCore.Instance
    40  }
    41  
    42  type vnicWithIndex struct {
    43  	Vnic ociCore.Vnic
    44  	Idx  int
    45  }
    46  
    47  var _ instances.Instance = (*ociInstance)(nil)
    48  var maxPollIterations = 30
    49  var pollTime time.Duration = 10 * time.Second
    50  
    51  var statusMap map[ociCore.InstanceLifecycleStateEnum]status.Status = map[ociCore.InstanceLifecycleStateEnum]status.Status{
    52  	ociCore.InstanceLifecycleStateProvisioning:  status.Provisioning,
    53  	ociCore.InstanceLifecycleStateRunning:       status.Running,
    54  	ociCore.InstanceLifecycleStateStarting:      status.Provisioning,
    55  	ociCore.InstanceLifecycleStateStopping:      status.Running,
    56  	ociCore.InstanceLifecycleStateStopped:       status.Running,
    57  	ociCore.InstanceLifecycleStateCreatingImage: status.Provisioning,
    58  	ociCore.InstanceLifecycleStateTerminating:   status.Running,
    59  	ociCore.InstanceLifecycleStateTerminated:    status.Running,
    60  }
    61  
    62  // newInstance returns a new oracleInstance
    63  func newInstance(raw ociCore.Instance, env *Environ) (*ociInstance, error) {
    64  	if raw.Id == nil {
    65  		return nil, errors.New(
    66  			"Instance response does not contain an ID",
    67  		)
    68  	}
    69  	instance := &ociInstance{
    70  		raw:  raw,
    71  		env:  env,
    72  		arch: "amd64",
    73  	}
    74  
    75  	return instance, nil
    76  }
    77  
    78  // SetInstance sets the raw property of ociInstance{}
    79  // Testing purposes.
    80  func (o *ociInstance) SetInstance(inst ociCore.Instance) {
    81  	o.raw = inst
    82  }
    83  
    84  func (o *ociInstance) availabilityZone() string {
    85  	return *o.raw.AvailabilityDomain
    86  }
    87  
    88  // Id implements instances.Instance
    89  func (o *ociInstance) Id() instance.Id {
    90  	return instance.Id(*o.raw.Id)
    91  }
    92  
    93  // Status implements instances.Instance
    94  func (o *ociInstance) Status(ctx envcontext.ProviderCallContext) instance.Status {
    95  	if err := o.refresh(); err != nil {
    96  		common.HandleCredentialError(err, ctx)
    97  		return instance.Status{}
    98  	}
    99  	state, ok := statusMap[o.raw.LifecycleState]
   100  	if !ok {
   101  		state = status.Unknown
   102  	}
   103  	return instance.Status{
   104  		Status:  state,
   105  		Message: strings.ToLower(string(o.raw.LifecycleState)),
   106  	}
   107  }
   108  
   109  func (o *ociInstance) getVnics() ([]vnicWithIndex, error) {
   110  	attachmentRequest := ociCore.ListVnicAttachmentsRequest{
   111  		CompartmentId: o.raw.CompartmentId,
   112  		InstanceId:    o.raw.Id,
   113  	}
   114  	attachments, err := o.env.Compute.ListVnicAttachments(context.Background(), attachmentRequest)
   115  	if err != nil {
   116  		return nil, errors.Trace(err)
   117  	}
   118  	nics := []vnicWithIndex{}
   119  	for _, val := range attachments.Items {
   120  		vnicID := val.VnicId
   121  		request := ociCore.GetVnicRequest{
   122  			VnicId: vnicID,
   123  		}
   124  		response, err := o.env.Networking.GetVnic(context.Background(), request)
   125  		if err != nil {
   126  			return nil, errors.Trace(err)
   127  		}
   128  		nics = append(nics, vnicWithIndex{Vnic: response.Vnic, Idx: *val.NicIndex})
   129  	}
   130  	return nics, nil
   131  }
   132  
   133  func (o *ociInstance) getAddresses() ([]network.Address, error) {
   134  	vnics, err := o.getVnics()
   135  	if err != nil {
   136  		return nil, errors.Trace(err)
   137  	}
   138  	addresses := []network.Address{}
   139  
   140  	for _, val := range vnics {
   141  		if val.Vnic.PrivateIp != nil {
   142  			privateAddress := network.Address{
   143  				Value: *val.Vnic.PrivateIp,
   144  				Type:  network.IPv4Address,
   145  				Scope: network.ScopeCloudLocal,
   146  			}
   147  			addresses = append(addresses, privateAddress)
   148  		}
   149  		if val.Vnic.PublicIp != nil {
   150  			publicAddress := network.Address{
   151  				Value: *val.Vnic.PublicIp,
   152  				Type:  network.IPv4Address,
   153  				Scope: network.ScopePublic,
   154  			}
   155  			addresses = append(addresses, publicAddress)
   156  		}
   157  	}
   158  	return addresses, nil
   159  }
   160  
   161  // Addresses implements instances.Instance
   162  func (o *ociInstance) Addresses(ctx envcontext.ProviderCallContext) ([]network.Address, error) {
   163  	addresses, err := o.getAddresses()
   164  	common.HandleCredentialError(err, ctx)
   165  	return addresses, err
   166  }
   167  
   168  func (o *ociInstance) isTerminating() bool {
   169  	terminatedStatus := ociCore.InstanceLifecycleStateTerminated
   170  	terminatingStatus := ociCore.InstanceLifecycleStateTerminating
   171  	if o.raw.LifecycleState == terminatedStatus || o.raw.LifecycleState == terminatingStatus {
   172  		return true
   173  	}
   174  	return false
   175  }
   176  
   177  func (o *ociInstance) waitForPublicIP(ctx envcontext.ProviderCallContext) error {
   178  	iteration := 0
   179  	startTime := time.Now()
   180  	for {
   181  		addresses, err := o.Addresses(ctx)
   182  		if err != nil {
   183  			common.HandleCredentialError(err, ctx)
   184  			return errors.Trace(err)
   185  		}
   186  		if iteration >= maxPollIterations {
   187  			logger.Debugf("could not find a public IP after %s. breaking loop", time.Since(startTime))
   188  			break
   189  		}
   190  
   191  		for _, val := range addresses {
   192  			if val.Scope == network.ScopePublic {
   193  				logger.Infof("Found public IP: %s", val)
   194  				return nil
   195  			}
   196  		}
   197  		<-o.env.clock.After(pollTime)
   198  		iteration++
   199  		continue
   200  	}
   201  	return errors.NotFoundf("failed to find public IP for instance: %s", *o.raw.Id)
   202  }
   203  
   204  func (o *ociInstance) deleteInstance(ctx envcontext.ProviderCallContext) error {
   205  	err := o.refresh()
   206  	if errors.IsNotFound(err) {
   207  		return nil
   208  	}
   209  
   210  	if o.isTerminating() {
   211  		logger.Debugf("instance %q is alrealy in terminating state", *o.raw.Id)
   212  		return nil
   213  	}
   214  	request := ociCore.TerminateInstanceRequest{
   215  		InstanceId: o.raw.Id,
   216  		IfMatch:    o.etag,
   217  	}
   218  	response, err := o.env.Compute.TerminateInstance(context.Background(), request)
   219  	if err != nil && !o.env.isNotFound(response.RawResponse) {
   220  		common.HandleCredentialError(err, ctx)
   221  		return err
   222  	}
   223  	iteration := 0
   224  	for {
   225  		if err := o.refresh(); err != nil {
   226  			if errors.IsNotFound(err) {
   227  				break
   228  			}
   229  			common.HandleCredentialError(err, ctx)
   230  			return err
   231  		}
   232  		logger.Infof("Waiting for machine to transition to Terminating: %s", o.raw.LifecycleState)
   233  		if o.isTerminating() {
   234  			break
   235  		}
   236  		if iteration >= maxPollIterations && o.raw.LifecycleState == ociCore.InstanceLifecycleStateRunning {
   237  			return errors.Errorf("Instance still in running state after %v checks", iteration)
   238  		}
   239  		<-o.env.clock.After(pollTime)
   240  		iteration++
   241  		continue
   242  	}
   243  	// TODO(gsamfira): cleanup firewall rules
   244  	// TODO(gsamfira): cleanup VNIC?
   245  	return nil
   246  }
   247  
   248  // hardwareCharacteristics returns the hardware characteristics of the current
   249  // instance
   250  func (o *ociInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   251  	if o.arch == "" {
   252  		return nil
   253  	}
   254  
   255  	hc := &instance.HardwareCharacteristics{Arch: &o.arch}
   256  	if o.instType != nil {
   257  		hc.Mem = &o.instType.Mem
   258  		hc.RootDisk = &o.instType.RootDisk
   259  		hc.CpuCores = &o.instType.CpuCores
   260  	}
   261  
   262  	return hc
   263  }
   264  
   265  func (o *ociInstance) waitForMachineStatus(state ociCore.InstanceLifecycleStateEnum, timeout time.Duration) error {
   266  	timer := o.env.clock.NewTimer(timeout)
   267  	defer timer.Stop()
   268  
   269  	for {
   270  		select {
   271  		case <-timer.Chan():
   272  			return errors.Errorf(
   273  				"Timed out waiting for instance to transition from %v to %v",
   274  				o.raw.LifecycleState, state,
   275  			)
   276  		case <-o.env.clock.After(pollTime):
   277  			err := o.refresh()
   278  			if err != nil {
   279  				return err
   280  			}
   281  			if o.raw.LifecycleState == state {
   282  				return nil
   283  			}
   284  		}
   285  	}
   286  }
   287  
   288  func (o *ociInstance) refresh() error {
   289  	o.mutex.Lock()
   290  	defer o.mutex.Unlock()
   291  	request := ociCore.GetInstanceRequest{
   292  		InstanceId: o.raw.Id,
   293  	}
   294  	response, err := o.env.Compute.GetInstance(context.Background(), request)
   295  	if err != nil {
   296  		if response.RawResponse != nil && response.RawResponse.StatusCode == http.StatusNotFound {
   297  			// If we care about 404 errors, this makes it easier to test using
   298  			// errors.IsNotFound
   299  			return errors.NotFoundf("instance %s was not found", *o.raw.Id)
   300  		}
   301  		return err
   302  	}
   303  	o.etag = response.Etag
   304  	o.raw = response.Instance
   305  	return nil
   306  }