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