github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/instance.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package oracle
     5  
     6  import (
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	oci "github.com/juju/go-oracle-cloud/api"
    13  	ociCommon "github.com/juju/go-oracle-cloud/common"
    14  	"github.com/juju/go-oracle-cloud/response"
    15  
    16  	"github.com/juju/juju/core/instance"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/context"
    20  	"github.com/juju/juju/environs/instances"
    21  	"github.com/juju/juju/network"
    22  	oraclenetwork "github.com/juju/juju/provider/oracle/network"
    23  )
    24  
    25  // oracleInstance implements the instances.Instance interface
    26  type oracleInstance struct {
    27  	// name of the instance, generated after the vm creation
    28  	name string
    29  	// status holds the status of the instance
    30  	status instance.Status
    31  	// machine will hold the raw instance details obtained from
    32  	// the provider
    33  	machine response.Instance
    34  	// arch will hold the architecture information of the instance
    35  	arch     *string
    36  	instType *instances.InstanceType
    37  	mutex    *sync.Mutex
    38  	env      *OracleEnviron
    39  }
    40  
    41  // hardwareCharacteristics returns the hardware characteristics of the current
    42  // instance
    43  func (o *oracleInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
    44  	if o.arch == nil {
    45  		return nil
    46  	}
    47  
    48  	hc := &instance.HardwareCharacteristics{Arch: o.arch}
    49  	if o.instType != nil {
    50  		hc.Mem = &o.instType.Mem
    51  		hc.RootDisk = &o.instType.RootDisk
    52  		hc.CpuCores = &o.instType.CpuCores
    53  	}
    54  
    55  	return hc
    56  }
    57  
    58  // extractInstanceIDFromMachineName will return the hostname of the machine
    59  // identified by the provider ID. In the Oracle compute cloud the provider
    60  // IDs of the instances has the following format:
    61  // /Compute-tenant_domain/tenant_username/instance_hostname/instance_UUID
    62  func extractInstanceIDFromMachineName(id string) (instance.Id, error) {
    63  	var instId instance.Id
    64  	name := strings.Split(id, "/")
    65  	if len(name) < 4 {
    66  		return instId, errors.Errorf("invalid instance name: %s", id)
    67  	}
    68  	instId = instance.Id(name[3])
    69  	return instId, nil
    70  }
    71  
    72  // newInstance returns a new oracleInstance
    73  func newInstance(params response.Instance, env *OracleEnviron) (*oracleInstance, error) {
    74  	if params.Name == "" {
    75  		return nil, errors.New(
    76  			"Instance response does not contain a name",
    77  		)
    78  	}
    79  	name, err := extractInstanceIDFromMachineName(params.Name)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	mutex := &sync.Mutex{}
    84  	instance := &oracleInstance{
    85  		name: string(name),
    86  		status: instance.Status{
    87  			Status:  status.Status(params.State),
    88  			Message: "",
    89  		},
    90  		machine: params,
    91  		mutex:   mutex,
    92  		env:     env,
    93  	}
    94  
    95  	return instance, nil
    96  }
    97  
    98  // Id is defined on the instances.Instance interface.
    99  func (o *oracleInstance) Id() instance.Id {
   100  	if o.machine.Name != "" {
   101  		name, err := extractInstanceIDFromMachineName(o.machine.Name)
   102  		if err != nil {
   103  			return instance.Id(o.machine.Name)
   104  		}
   105  		return name
   106  	}
   107  
   108  	return instance.Id(o.name)
   109  }
   110  
   111  // Status is defined on the instances.Instance interface.
   112  func (o *oracleInstance) Status(ctx context.ProviderCallContext) instance.Status {
   113  	return o.status
   114  }
   115  
   116  // StorageAttachments returns the storage that was attached in the moment
   117  // of instance creation. This storage cannot be detached dynamically.
   118  // this is also needed if you wish to determine the free disk index
   119  // you can use when attaching a new disk
   120  func (o *oracleInstance) StorageAttachments() []response.Storage {
   121  	o.mutex.Lock()
   122  	defer o.mutex.Unlock()
   123  	return o.machine.Storage_attachments
   124  }
   125  
   126  // refresh refreshes the instance raw details from the oracle api
   127  // this method is mutex protected
   128  func (o *oracleInstance) refresh() error {
   129  	o.mutex.Lock()
   130  	defer o.mutex.Unlock()
   131  
   132  	machine, err := o.env.client.InstanceDetails(o.machine.Name)
   133  	// if the request failed for any reason
   134  	// we should not update the information and
   135  	// let the old one persist
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	o.machine = machine
   141  	return nil
   142  }
   143  
   144  // waitForMachineStatus will ping the machine status until the timeout
   145  // duration is reached or an error appeared
   146  func (o *oracleInstance) waitForMachineStatus(state ociCommon.InstanceState, timeout time.Duration) error {
   147  	timer := o.env.clock.NewTimer(timeout)
   148  	defer timer.Stop()
   149  
   150  	for {
   151  		select {
   152  		case <-timer.Chan():
   153  			return errors.Errorf(
   154  				"Timed out waiting for instance to transition from %v to %v",
   155  				o.machine.State, state,
   156  			)
   157  		case <-o.env.clock.After(10 * time.Second):
   158  			err := o.refresh()
   159  			if err != nil {
   160  				return err
   161  			}
   162  			if o.machine.State == state {
   163  				return nil
   164  			}
   165  		}
   166  	}
   167  }
   168  
   169  // delete will delete the instance and attempt to cleanup any instance related
   170  // resources
   171  func (o *oracleInstance) deleteInstanceAndResources(cleanup bool) error {
   172  	if cleanup {
   173  		err := o.disassociatePublicIps(true)
   174  		if err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	if err := o.env.client.DeleteInstance(o.machine.Name); err != nil {
   180  		return errors.Trace(err)
   181  	}
   182  
   183  	if cleanup {
   184  		// Wait for instance to be deleted. The oracle API does not allow us to
   185  		// delete a security list if there is still a VM associated with it.
   186  		iteration := 0
   187  		for {
   188  			if instance, err := o.env.client.InstanceDetails(o.machine.Name); !oci.IsNotFound(err) {
   189  				if instance.State == ociCommon.StateError {
   190  					logger.Warningf("Instance %s entered error state", o.machine.Name)
   191  					break
   192  				}
   193  				if iteration >= 30 && instance.State == ociCommon.StateRunning {
   194  					logger.Warningf("Instance still in running state after %v checks. breaking loop", iteration)
   195  					break
   196  				}
   197  				if oci.IsInternalApi(err) {
   198  					logger.Errorf("got internal server error from API: %q", err)
   199  				}
   200  				<-o.env.clock.After(1 * time.Second)
   201  				iteration++
   202  				continue
   203  			}
   204  			logger.Debugf("Machine %v successfully deleted", o.machine.Name)
   205  			break
   206  		}
   207  
   208  		//
   209  		// seclist, vnicset, secrules, and acl created with
   210  		// StartInstanceParams.InstanceConfig.MachineId,
   211  		// convert o.Id() to machineId for deletion.
   212  		// o.Id() returns a string in hostname form.
   213  		tag, err := o.env.namespace.MachineTag(string(o.Id()))
   214  		if err != nil {
   215  			return errors.Annotatef(err, "failed to get a machine tag to complete cleanup of instance")
   216  		}
   217  		machineId := tag.Id()
   218  
   219  		// the VM association is now gone, now we can delete the
   220  		// machine sec list
   221  		logger.Debugf("deleting seclist for instance: %s", machineId)
   222  		if err := o.env.DeleteMachineSecList(machineId); err != nil {
   223  			logger.Errorf("failed to delete seclist: %s", err)
   224  			if !oci.IsMethodNotAllowed(err) {
   225  				return errors.Trace(err)
   226  			}
   227  		}
   228  		logger.Debugf("deleting vnic set for instance: %s", machineId)
   229  		if err := o.env.DeleteMachineVnicSet(machineId); err != nil {
   230  			logger.Errorf("failed to delete vnic set: %s", err)
   231  			if !oci.IsMethodNotAllowed(err) {
   232  				return errors.Trace(err)
   233  			}
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  // unusedPublicIps returns a slice of IpReservation that are currently not used
   240  func (o *oracleInstance) unusedPublicIps() ([]response.IpReservation, error) {
   241  	filter := []oci.Filter{
   242  		{
   243  			Arg:   "permanent",
   244  			Value: "true",
   245  		},
   246  		{
   247  			Arg:   "used",
   248  			Value: "false",
   249  		},
   250  	}
   251  
   252  	res, err := o.env.client.AllIpReservations(filter)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	return res.Result, nil
   258  }
   259  
   260  // associatePublicIP associates a public IP with the current instance
   261  func (o *oracleInstance) associatePublicIP() error {
   262  	// return all unused public IPs
   263  	unusedIps, err := o.unusedPublicIps()
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	for _, val := range unusedIps {
   269  		assocPoolName := ociCommon.NewIPPool(
   270  			ociCommon.IPPool(val.Name),
   271  			ociCommon.IPReservationType,
   272  		)
   273  		// create the association for it
   274  		if _, err := o.env.client.CreateIpAssociation(
   275  			assocPoolName,
   276  			o.machine.Vcable_id,
   277  		); err != nil {
   278  			if oci.IsBadRequest(err) {
   279  				// the IP probably got allocated after we fetched it
   280  				// from the API. Move on to the next one.
   281  				continue
   282  			}
   283  
   284  			return err
   285  		} else {
   286  			if _, err = o.env.client.UpdateIpReservation(val.Name, "", val.Parentpool, val.Permanent, o.machine.Tags); err != nil {
   287  				// we don't really want to terminate execution if we fail to update
   288  				// tags
   289  				logger.Errorf("failed to update IP reservation tags: %q", err)
   290  			}
   291  			return nil
   292  		}
   293  	}
   294  
   295  	// no unused IP reservations found. Allocate a new one.
   296  	reservation, err := o.env.client.CreateIpReservation(
   297  		o.machine.Name, ociCommon.PublicIPPool, true, o.machine.Tags)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	// compose IP pool name
   303  	assocPoolName := ociCommon.NewIPPool(
   304  		ociCommon.IPPool(reservation.Name),
   305  		ociCommon.IPReservationType,
   306  	)
   307  	if _, err := o.env.client.CreateIpAssociation(
   308  		assocPoolName,
   309  		o.machine.Vcable_id,
   310  	); err != nil {
   311  		return err
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  // dissasociatePublicIps disassociates the public IP address from the current instance.
   318  // Optionally, the remove flag will also remove the IP reservation after the IP was disassociated
   319  func (o *oracleInstance) disassociatePublicIps(remove bool) error {
   320  	associations, err := o.publicAddressesAssociations()
   321  	if err != nil {
   322  		return err
   323  	}
   324  
   325  	for _, ipAssoc := range associations {
   326  		reservation := ipAssoc.Reservation
   327  		name := ipAssoc.Name
   328  		if err := o.env.client.DeleteIpAssociation(name); err != nil {
   329  			if oci.IsNotFound(err) {
   330  				continue
   331  			}
   332  
   333  			return err
   334  		}
   335  
   336  		if remove {
   337  			if err := o.env.client.DeleteIpReservation(reservation); err != nil {
   338  				if oci.IsNotFound(err) {
   339  					return nil
   340  				}
   341  				return err
   342  			}
   343  		}
   344  	}
   345  
   346  	return nil
   347  }
   348  
   349  // publicAddressesAssociations returns a slice of all IP associations for the current instance
   350  func (o *oracleInstance) publicAddressesAssociations() ([]response.IpAssociation, error) {
   351  	o.mutex.Lock()
   352  	defer o.mutex.Unlock()
   353  
   354  	filter := []oci.Filter{
   355  		{
   356  			Arg:   "vcable",
   357  			Value: string(o.machine.Vcable_id),
   358  		},
   359  	}
   360  
   361  	assoc, err := o.env.client.AllIpAssociations(filter)
   362  	if err != nil {
   363  		return nil, errors.Trace(err)
   364  	}
   365  
   366  	return assoc.Result, nil
   367  }
   368  
   369  // Addresses is defined on the instances.Instance interface.
   370  func (o *oracleInstance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   371  	addresses := []network.Address{}
   372  
   373  	ips, err := o.publicAddressesAssociations()
   374  	if err != nil {
   375  		return nil, errors.Trace(err)
   376  	}
   377  
   378  	if len(o.machine.Attributes.Network) > 0 {
   379  		for name, val := range o.machine.Attributes.Network {
   380  			if _, ip, err := oraclenetwork.GetMacAndIP(val.Address); err == nil {
   381  				address := network.NewScopedAddress(ip, network.ScopeCloudLocal)
   382  				addresses = append(addresses, address)
   383  			} else {
   384  				logger.Errorf("failed to get IP address for NIC %q: %q", name, err)
   385  			}
   386  		}
   387  	}
   388  
   389  	for _, val := range ips {
   390  		address := network.NewScopedAddress(val.Ip, network.ScopePublic)
   391  		addresses = append(addresses, address)
   392  	}
   393  
   394  	return addresses, nil
   395  }
   396  
   397  // OpenPorts is defined on the instances.Instance interface.
   398  func (o *oracleInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   399  	if o.env.Config().FirewallMode() != config.FwInstance {
   400  		return errors.Errorf(
   401  			"invalid firewall mode %q for opening ports on instance",
   402  			o.env.Config().FirewallMode(),
   403  		)
   404  	}
   405  
   406  	return o.env.OpenPortsOnInstance(ctx, machineId, rules)
   407  }
   408  
   409  // ClosePorts is defined on the instances.Instance interface.
   410  func (o *oracleInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   411  	if o.env.Config().FirewallMode() != config.FwInstance {
   412  		return errors.Errorf(
   413  			"invalid firewall mode %q for closing ports on instance",
   414  			o.env.Config().FirewallMode(),
   415  		)
   416  	}
   417  
   418  	return o.env.ClosePortsOnInstance(ctx, machineId, rules)
   419  }
   420  
   421  // IngressRules is defined on the instances.Instance interface.
   422  func (o *oracleInstance) IngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) {
   423  	return o.env.MachineIngressRules(ctx, machineId)
   424  }