github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/joyent/environ_instance.go (about)

     1  // Copyright 2013 Joyent Inc.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package joyent
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/joyent/gocommon/client"
    13  	"github.com/joyent/gosdc/cloudapi"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	"github.com/juju/utils"
    17  
    18  	"github.com/juju/juju/constraints"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/environs/imagemetadata"
    21  	"github.com/juju/juju/environs/instances"
    22  	"github.com/juju/juju/environs/simplestreams"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/juju/arch"
    25  	"github.com/juju/juju/network"
    26  	"github.com/juju/juju/tools"
    27  )
    28  
    29  var (
    30  	vTypeSmartmachine   = "smartmachine"
    31  	vTypeVirtualmachine = "kvm"
    32  	signedImageDataOnly = false
    33  )
    34  
    35  type joyentCompute struct {
    36  	sync.Mutex
    37  	ecfg     *environConfig
    38  	cloudapi *cloudapi.Client
    39  }
    40  
    41  func newCompute(cfg *environConfig) (*joyentCompute, error) {
    42  	creds, err := credentials(cfg)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	client := client.NewClient(cfg.sdcUrl(), cloudapi.DefaultAPIVersion, creds, &logger)
    47  
    48  	return &joyentCompute{
    49  		ecfg:     cfg,
    50  		cloudapi: cloudapi.New(client)}, nil
    51  }
    52  
    53  func (env *joyentEnviron) machineFullName(machineId string) string {
    54  	return fmt.Sprintf("juju-%s-%s", env.Name(), names.NewMachineTag(machineId))
    55  }
    56  
    57  var unsupportedConstraints = []string{
    58  	constraints.CpuPower,
    59  	constraints.Tags,
    60  }
    61  
    62  // ConstraintsValidator is defined on the Environs interface.
    63  func (env *joyentEnviron) ConstraintsValidator() (constraints.Validator, error) {
    64  	validator := constraints.NewValidator()
    65  	validator.RegisterUnsupported(unsupportedConstraints)
    66  	supportedArches, err := env.SupportedArchitectures()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
    71  	packages, err := env.compute.cloudapi.ListPackages(nil)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	instTypeNames := make([]string, len(packages))
    76  	for i, pkg := range packages {
    77  		instTypeNames[i] = pkg.Name
    78  	}
    79  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
    80  	return validator, nil
    81  }
    82  
    83  func (env *joyentEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
    84  
    85  	if args.MachineConfig.HasNetworks() {
    86  		return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.")
    87  	}
    88  
    89  	series := args.Tools.OneSeries()
    90  	arches := args.Tools.Arches()
    91  	spec, err := env.FindInstanceSpec(&instances.InstanceConstraint{
    92  		Region:      env.Ecfg().Region(),
    93  		Series:      series,
    94  		Arches:      arches,
    95  		Constraints: args.Constraints,
    96  	})
    97  	if err != nil {
    98  		return nil, nil, nil, err
    99  	}
   100  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
   101  	if err != nil {
   102  		return nil, nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   103  	}
   104  
   105  	args.MachineConfig.Tools = tools[0]
   106  
   107  	if err := environs.FinishMachineConfig(args.MachineConfig, env.Config(), args.Constraints); err != nil {
   108  		return nil, nil, nil, err
   109  	}
   110  	userData, err := environs.ComposeUserData(args.MachineConfig, nil)
   111  	if err != nil {
   112  		return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err)
   113  	}
   114  
   115  	// Unzipping as Joyent API expects it as string
   116  	userData, err = utils.Gunzip(userData)
   117  	if err != nil {
   118  		return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err)
   119  	}
   120  	logger.Debugf("joyent user data: %d bytes", len(userData))
   121  
   122  	var machine *cloudapi.Machine
   123  	machine, err = env.compute.cloudapi.CreateMachine(cloudapi.CreateMachineOpts{
   124  		//Name:	 env.machineFullName(machineConf.MachineId),
   125  		Package:  spec.InstanceType.Name,
   126  		Image:    spec.Image.Id,
   127  		Metadata: map[string]string{"metadata.cloud-init:user-data": string(userData)},
   128  		Tags:     map[string]string{"tag.group": "juju", "tag.env": env.Name()},
   129  	})
   130  	if err != nil {
   131  		return nil, nil, nil, fmt.Errorf("cannot create instances: %v", err)
   132  	}
   133  	machineId := machine.Id
   134  
   135  	logger.Infof("provisioning instance %q", machineId)
   136  
   137  	machine, err = env.compute.cloudapi.GetMachine(machineId)
   138  	if err != nil {
   139  		return nil, nil, nil, fmt.Errorf("cannot start instances: %v", err)
   140  	}
   141  
   142  	// wait for machine to start
   143  	for !strings.EqualFold(machine.State, "running") {
   144  		time.Sleep(1 * time.Second)
   145  
   146  		machine, err = env.compute.cloudapi.GetMachine(machineId)
   147  		if err != nil {
   148  			return nil, nil, nil, fmt.Errorf("cannot start instances: %v", err)
   149  		}
   150  	}
   151  
   152  	logger.Infof("started instance %q", machineId)
   153  
   154  	inst := &joyentInstance{
   155  		machine: machine,
   156  		env:     env,
   157  	}
   158  
   159  	disk64 := uint64(machine.Disk)
   160  	hc := instance.HardwareCharacteristics{
   161  		Arch:     &spec.Image.Arch,
   162  		Mem:      &spec.InstanceType.Mem,
   163  		CpuCores: &spec.InstanceType.CpuCores,
   164  		CpuPower: spec.InstanceType.CpuPower,
   165  		RootDisk: &disk64,
   166  	}
   167  
   168  	return inst, &hc, nil, nil
   169  }
   170  
   171  func (env *joyentEnviron) AllInstances() ([]instance.Instance, error) {
   172  	instances := []instance.Instance{}
   173  
   174  	filter := cloudapi.NewFilter()
   175  	filter.Set("tag.group", "juju")
   176  	filter.Set("tag.env", env.Name())
   177  
   178  	machines, err := env.compute.cloudapi.ListMachines(filter)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("cannot retrieve instances: %v", err)
   181  	}
   182  
   183  	for _, m := range machines {
   184  		if strings.EqualFold(m.State, "provisioning") || strings.EqualFold(m.State, "running") {
   185  			copy := m
   186  			instances = append(instances, &joyentInstance{machine: &copy, env: env})
   187  		}
   188  	}
   189  
   190  	return instances, nil
   191  }
   192  
   193  func (env *joyentEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
   194  	if len(ids) == 0 {
   195  		return nil, nil
   196  	}
   197  
   198  	logger.Debugf("Looking for instances %q", ids)
   199  
   200  	instances := make([]instance.Instance, len(ids))
   201  	found := 0
   202  
   203  	allInstances, err := env.AllInstances()
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	for i, id := range ids {
   209  		for _, instance := range allInstances {
   210  			if instance.Id() == id {
   211  				instances[i] = instance
   212  				found++
   213  			}
   214  		}
   215  	}
   216  
   217  	logger.Debugf("Found %d instances %q", found, instances)
   218  
   219  	if found == 0 {
   220  		return nil, environs.ErrNoInstances
   221  	} else if found < len(ids) {
   222  		return instances, environs.ErrPartialInstances
   223  	}
   224  
   225  	return instances, nil
   226  }
   227  
   228  // AllocateAddress requests a new address to be allocated for the
   229  // given instance on the given network. This is not implemented on the
   230  // Joyent provider yet.
   231  func (*joyentEnviron) AllocateAddress(_ instance.Id, _ network.Id) (network.Address, error) {
   232  	return network.Address{}, errors.NotImplementedf("AllocateAddress")
   233  }
   234  
   235  // ListNetworks returns basic information about all networks known by
   236  // the provider for the environment. They may be unknown to juju yet
   237  // (i.e. when called initially or when a new network was created).
   238  // This is not implemented on the Joyent provider yet.
   239  func (*joyentEnviron) ListNetworks() ([]network.BasicInfo, error) {
   240  	return nil, errors.NotImplementedf("ListNetworks")
   241  }
   242  
   243  func (env *joyentEnviron) StopInstances(ids ...instance.Id) error {
   244  	// Remove all the instances in parallel so that we incur less round-trips.
   245  	var wg sync.WaitGroup
   246  	//var err error
   247  	wg.Add(len(ids))
   248  	errc := make(chan error, len(ids))
   249  	for _, id := range ids {
   250  		id := id // copy to new free var for closure
   251  		go func() {
   252  			defer wg.Done()
   253  			if err := env.stopInstance(string(id)); err != nil {
   254  				errc <- err
   255  			}
   256  		}()
   257  	}
   258  	wg.Wait()
   259  	select {
   260  	case err := <-errc:
   261  		return fmt.Errorf("cannot stop all instances: %v", err)
   262  	default:
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  func (env *joyentEnviron) stopInstance(id string) error {
   269  	// wait for machine to be running
   270  	// if machine is still provisioning stop will fail
   271  	for !env.pollMachineState(id, "running") {
   272  		time.Sleep(1 * time.Second)
   273  	}
   274  
   275  	err := env.compute.cloudapi.StopMachine(id)
   276  	if err != nil {
   277  		return fmt.Errorf("cannot stop instance %s: %v", id, err)
   278  	}
   279  
   280  	// wait for machine to be stopped
   281  	for !env.pollMachineState(id, "stopped") {
   282  		time.Sleep(1 * time.Second)
   283  	}
   284  
   285  	err = env.compute.cloudapi.DeleteMachine(id)
   286  	if err != nil {
   287  		return fmt.Errorf("cannot delete instance %s: %v", id, err)
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  func (env *joyentEnviron) pollMachineState(machineId, state string) bool {
   294  	machineConfig, err := env.compute.cloudapi.GetMachine(machineId)
   295  	if err != nil {
   296  		return false
   297  	}
   298  	return strings.EqualFold(machineConfig.State, state)
   299  }
   300  
   301  func (env *joyentEnviron) listInstanceTypes() ([]instances.InstanceType, error) {
   302  	packages, err := env.compute.cloudapi.ListPackages(nil)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	allInstanceTypes := []instances.InstanceType{}
   307  	for _, pkg := range packages {
   308  		instanceType := instances.InstanceType{
   309  			Id:       pkg.Id,
   310  			Name:     pkg.Name,
   311  			Arches:   []string{arch.AMD64},
   312  			Mem:      uint64(pkg.Memory),
   313  			CpuCores: uint64(pkg.VCPUs),
   314  			RootDisk: uint64(pkg.Disk * 1024),
   315  			VirtType: &vTypeVirtualmachine,
   316  		}
   317  		allInstanceTypes = append(allInstanceTypes, instanceType)
   318  	}
   319  	return allInstanceTypes, nil
   320  }
   321  
   322  // FindInstanceSpec returns an InstanceSpec satisfying the supplied instanceConstraint.
   323  func (env *joyentEnviron) FindInstanceSpec(ic *instances.InstanceConstraint) (*instances.InstanceSpec, error) {
   324  	allInstanceTypes, err := env.listInstanceTypes()
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   329  		CloudSpec: simplestreams.CloudSpec{ic.Region, env.Ecfg().SdcUrl()},
   330  		Series:    []string{ic.Series},
   331  		Arches:    ic.Arches,
   332  	})
   333  	sources, err := imagemetadata.GetMetadataSources(env)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  
   338  	matchingImages, _, err := imagemetadata.Fetch(sources, simplestreams.DefaultIndexPath, imageConstraint, signedImageDataOnly)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	images := instances.ImageMetadataToImages(matchingImages)
   343  	spec, err := instances.FindInstanceSpec(images, ic, allInstanceTypes)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	return spec, nil
   348  }