github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"launchpad.net/juju-core/charm"
    12  	"launchpad.net/juju-core/constraints"
    13  	"launchpad.net/juju-core/instance"
    14  	"launchpad.net/juju-core/names"
    15  	"launchpad.net/juju-core/state"
    16  )
    17  
    18  // DeployServiceParams contains the arguments required to deploy the referenced
    19  // charm.
    20  type DeployServiceParams struct {
    21  	ServiceName    string
    22  	Charm          *state.Charm
    23  	ConfigSettings charm.Settings
    24  	Constraints    constraints.Value
    25  	NumUnits       int
    26  	// ToMachineSpec is either:
    27  	// - an existing machine/container id eg "1" or "1/lxc/2"
    28  	// - a new container on an existing machine eg "lxc:1"
    29  	// Use string to avoid ambiguity around machine 0.
    30  	ToMachineSpec string
    31  }
    32  
    33  // DeployService takes a charm and various parameters and deploys it.
    34  func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) {
    35  	if args.NumUnits > 1 && args.ToMachineSpec != "" {
    36  		return nil, errors.New("cannot use --num-units with --to")
    37  	}
    38  	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	if args.Charm.Meta().Subordinate {
    43  		if args.NumUnits != 0 || args.ToMachineSpec != "" {
    44  			return nil, fmt.Errorf("subordinate service must be deployed without units")
    45  		}
    46  		if !constraints.IsEmpty(&args.Constraints) {
    47  			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
    48  		}
    49  	}
    50  	// TODO(fwereade): transactional State.AddService including settings, constraints
    51  	// (minimumUnitCount, initialMachineIds?).
    52  	service, err := st.AddService(args.ServiceName, "user-admin", args.Charm)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	if len(settings) > 0 {
    57  		if err := service.UpdateConfigSettings(settings); err != nil {
    58  			return nil, err
    59  		}
    60  	}
    61  	if args.Charm.Meta().Subordinate {
    62  		return service, nil
    63  	}
    64  	if !constraints.IsEmpty(&args.Constraints) {
    65  		if err := service.SetConstraints(args.Constraints); err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  	if args.NumUnits > 0 {
    70  		if _, err := AddUnits(st, service, args.NumUnits, args.ToMachineSpec); err != nil {
    71  			return nil, err
    72  		}
    73  	}
    74  	return service, nil
    75  }
    76  
    77  // AddUnits starts n units of the given service and allocates machines
    78  // to them as necessary.
    79  func AddUnits(st *state.State, svc *state.Service, n int, machineIdSpec string) ([]*state.Unit, error) {
    80  	units := make([]*state.Unit, n)
    81  	// Hard code for now till we implement a different approach.
    82  	policy := state.AssignCleanEmpty
    83  	// TODO what do we do if we fail half-way through this process?
    84  	for i := 0; i < n; i++ {
    85  		unit, err := svc.AddUnit()
    86  		if err != nil {
    87  			return nil, fmt.Errorf("cannot add unit %d/%d to service %q: %v", i+1, n, svc.Name(), err)
    88  		}
    89  		if machineIdSpec != "" {
    90  			if n != 1 {
    91  				return nil, fmt.Errorf("cannot add multiple units of service %q to a single machine", svc.Name())
    92  			}
    93  			// machineIdSpec may be an existing machine or container, eg 3/lxc/2
    94  			// or a new container on a machine, eg lxc:3
    95  			mid := machineIdSpec
    96  			var containerType instance.ContainerType
    97  			specParts := strings.SplitN(machineIdSpec, ":", 2)
    98  			if len(specParts) > 1 {
    99  				firstPart := specParts[0]
   100  				var err error
   101  				if containerType, err = instance.ParseContainerType(firstPart); err == nil {
   102  					mid = specParts[1]
   103  				} else {
   104  					mid = machineIdSpec
   105  				}
   106  			}
   107  			if !names.IsMachine(mid) {
   108  				return nil, fmt.Errorf("invalid force machine id %q", mid)
   109  			}
   110  
   111  			var err error
   112  			var m *state.Machine
   113  			// If a container is to be used, create it.
   114  			if containerType != "" {
   115  				// Create the new machine marked as dirty so that
   116  				// nothing else will grab it before we assign the unit to it.
   117  				template := state.MachineTemplate{
   118  					Series: unit.Series(),
   119  					Jobs:   []state.MachineJob{state.JobHostUnits},
   120  					Dirty:  true,
   121  				}
   122  				m, err = st.AddMachineInsideMachine(template, mid, containerType)
   123  			} else {
   124  				m, err = st.Machine(mid)
   125  			}
   126  			if err != nil {
   127  				return nil, fmt.Errorf("cannot assign unit %q to machine: %v", unit.Name(), err)
   128  			}
   129  			err = unit.AssignToMachine(m)
   130  
   131  			if err != nil {
   132  				return nil, err
   133  			}
   134  		} else if err := st.AssignUnit(unit, policy); err != nil {
   135  			return nil, err
   136  		}
   137  		units[i] = unit
   138  	}
   139  	return units, nil
   140  }