github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/juju/deploy.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package juju
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	"gopkg.in/juju/charm.v4"
    13  
    14  	"github.com/juju/juju/constraints"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/instance"
    17  	"github.com/juju/juju/state"
    18  	"github.com/juju/juju/storage"
    19  )
    20  
    21  // DeployServiceParams contains the arguments required to deploy the referenced
    22  // charm.
    23  type DeployServiceParams struct {
    24  	ServiceName    string
    25  	ServiceOwner   string
    26  	Charm          *state.Charm
    27  	ConfigSettings charm.Settings
    28  	Constraints    constraints.Value
    29  	NumUnits       int
    30  	// ToMachineSpec is either:
    31  	// - an existing machine/container id eg "1" or "1/lxc/2"
    32  	// - a new container on an existing machine eg "lxc:1"
    33  	// Use string to avoid ambiguity around machine 0.
    34  	ToMachineSpec string
    35  	// Networks holds a list of networks to required to start on boot.
    36  	Networks []string
    37  	Storage  map[string]storage.Constraints
    38  }
    39  
    40  // DeployService takes a charm and various parameters and deploys it.
    41  func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) {
    42  	if args.NumUnits > 1 && args.ToMachineSpec != "" {
    43  		return nil, fmt.Errorf("cannot use --num-units with --to")
    44  	}
    45  	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	if args.Charm.Meta().Subordinate {
    50  		if args.NumUnits != 0 || args.ToMachineSpec != "" {
    51  			return nil, fmt.Errorf("subordinate service must be deployed without units")
    52  		}
    53  		if !constraints.IsEmpty(&args.Constraints) {
    54  			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
    55  		}
    56  	}
    57  	if args.ServiceOwner == "" {
    58  		env, err := st.Environment()
    59  		if err != nil {
    60  			return nil, errors.Trace(err)
    61  		}
    62  		args.ServiceOwner = env.Owner().String()
    63  	}
    64  	// TODO(fwereade): transactional State.AddService including settings, constraints
    65  	// (minimumUnitCount, initialMachineIds?).
    66  	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
    67  		conf, err := st.EnvironConfig()
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		env, err := environs.New(conf)
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  		if _, ok := environs.SupportsNetworking(env); !ok {
    76  			return nil, fmt.Errorf("cannot deploy with networks: not suppored by the environment")
    77  		}
    78  	}
    79  	service, err := st.AddService(
    80  		args.ServiceName,
    81  		args.ServiceOwner,
    82  		args.Charm,
    83  		args.Networks,
    84  		stateStorageConstraints(args.Storage),
    85  	)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	if len(settings) > 0 {
    90  		if err := service.UpdateConfigSettings(settings); err != nil {
    91  			return nil, err
    92  		}
    93  	}
    94  	if args.Charm.Meta().Subordinate {
    95  		return service, nil
    96  	}
    97  	if !constraints.IsEmpty(&args.Constraints) {
    98  		if err := service.SetConstraints(args.Constraints); err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  	if args.NumUnits > 0 {
   103  		if _, err := AddUnits(st, service, args.NumUnits, args.ToMachineSpec); err != nil {
   104  			return nil, err
   105  		}
   106  	}
   107  	return service, nil
   108  }
   109  
   110  // AddUnits starts n units of the given service and allocates machines
   111  // to them as necessary.
   112  func AddUnits(st *state.State, svc *state.Service, n int, machineIdSpec string) ([]*state.Unit, error) {
   113  	units := make([]*state.Unit, n)
   114  	// Hard code for now till we implement a different approach.
   115  	policy := state.AssignCleanEmpty
   116  	// All units should have the same networks as the service.
   117  	networks, err := svc.Networks()
   118  	if err != nil {
   119  		return nil, fmt.Errorf("cannot get service %q networks: %v", svc.Name(), err)
   120  	}
   121  	// TODO what do we do if we fail half-way through this process?
   122  	for i := 0; i < n; i++ {
   123  		unit, err := svc.AddUnit()
   124  		if err != nil {
   125  			return nil, fmt.Errorf("cannot add unit %d/%d to service %q: %v", i+1, n, svc.Name(), err)
   126  		}
   127  		if machineIdSpec != "" {
   128  			if n != 1 {
   129  				return nil, fmt.Errorf("cannot add multiple units of service %q to a single machine", svc.Name())
   130  			}
   131  			// machineIdSpec may be an existing machine or container, eg 3/lxc/2
   132  			// or a new container on a machine, eg lxc:3
   133  			mid := machineIdSpec
   134  			var containerType instance.ContainerType
   135  			specParts := strings.SplitN(machineIdSpec, ":", 2)
   136  			if len(specParts) > 1 {
   137  				firstPart := specParts[0]
   138  				var err error
   139  				if containerType, err = instance.ParseContainerType(firstPart); err == nil {
   140  					mid = specParts[1]
   141  				} else {
   142  					mid = machineIdSpec
   143  				}
   144  			}
   145  			if !names.IsValidMachine(mid) {
   146  				return nil, fmt.Errorf("invalid force machine id %q", mid)
   147  			}
   148  			var unitCons *constraints.Value
   149  			unitCons, err = unit.Constraints()
   150  			if err != nil {
   151  				return nil, err
   152  			}
   153  
   154  			var err error
   155  			var m *state.Machine
   156  			// If a container is to be used, create it.
   157  			if containerType != "" {
   158  				// Create the new machine marked as dirty so that
   159  				// nothing else will grab it before we assign the unit to it.
   160  				template := state.MachineTemplate{
   161  					Series:            unit.Series(),
   162  					Jobs:              []state.MachineJob{state.JobHostUnits},
   163  					Dirty:             true,
   164  					Constraints:       *unitCons,
   165  					RequestedNetworks: networks,
   166  				}
   167  				m, err = st.AddMachineInsideMachine(template, mid, containerType)
   168  			} else {
   169  				m, err = st.Machine(mid)
   170  			}
   171  			if err != nil {
   172  				return nil, fmt.Errorf("cannot assign unit %q to machine: %v", unit.Name(), err)
   173  			}
   174  			err = unit.AssignToMachine(m)
   175  
   176  			if err != nil {
   177  				return nil, err
   178  			}
   179  		} else if err := st.AssignUnit(unit, policy); err != nil {
   180  			return nil, err
   181  		}
   182  		units[i] = unit
   183  	}
   184  	return units, nil
   185  }
   186  
   187  func stateStorageConstraints(cons map[string]storage.Constraints) map[string]state.StorageConstraints {
   188  	result := make(map[string]state.StorageConstraints)
   189  	for name, cons := range cons {
   190  		result[name] = state.StorageConstraints{
   191  			Pool:  cons.Pool,
   192  			Size:  cons.Size,
   193  			Count: cons.Count,
   194  		}
   195  	}
   196  	return result
   197  }