github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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.v5"
    13  
    14  	"github.com/juju/juju/constraints"
    15  	"github.com/juju/juju/instance"
    16  	"github.com/juju/juju/state"
    17  	"github.com/juju/juju/storage"
    18  )
    19  
    20  // DeployServiceParams contains the arguments required to deploy the referenced
    21  // charm.
    22  type DeployServiceParams struct {
    23  	ServiceName    string
    24  	ServiceOwner   string
    25  	Charm          *state.Charm
    26  	ConfigSettings charm.Settings
    27  	Constraints    constraints.Value
    28  	NumUnits       int
    29  	// ToMachineSpec is either:
    30  	// - an existing machine/container id eg "1" or "1/lxc/2"
    31  	// - a new container on an existing machine eg "lxc:1"
    32  	// Use string to avoid ambiguity around machine 0.
    33  	ToMachineSpec string
    34  	// Placement is a list of placement directives which may be used
    35  	// instead of a machine spec.
    36  	Placement []*instance.Placement
    37  	// Networks holds a list of networks to required to start on boot.
    38  	// TODO(dimitern): Drop this in a follow-up in favor of constraints.
    39  	Networks []string
    40  	Storage  map[string]storage.Constraints
    41  }
    42  
    43  // DeployService takes a charm and various parameters and deploys it.
    44  func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) {
    45  	if args.NumUnits > 1 && len(args.Placement) == 0 && args.ToMachineSpec != "" {
    46  		return nil, fmt.Errorf("cannot use --num-units with --to")
    47  	}
    48  	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	if args.Charm.Meta().Subordinate {
    53  		if args.NumUnits != 0 || args.ToMachineSpec != "" {
    54  			return nil, fmt.Errorf("subordinate service must be deployed without units")
    55  		}
    56  		if !constraints.IsEmpty(&args.Constraints) {
    57  			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
    58  		}
    59  	}
    60  	if args.ServiceOwner == "" {
    61  		env, err := st.Environment()
    62  		if err != nil {
    63  			return nil, errors.Trace(err)
    64  		}
    65  		args.ServiceOwner = env.Owner().String()
    66  	}
    67  	// TODO(fwereade): transactional State.AddService including settings, constraints
    68  	// (minimumUnitCount, initialMachineIds?).
    69  
    70  	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
    71  		return nil, fmt.Errorf("use of --networks is deprecated. Please use spaces")
    72  	}
    73  
    74  	// TODO(dimitern): In a follow-up drop Networks and use spaces
    75  	// constraints for this when possible.
    76  	service, err := st.AddService(
    77  		args.ServiceName,
    78  		args.ServiceOwner,
    79  		args.Charm,
    80  		args.Networks,
    81  		stateStorageConstraints(args.Storage),
    82  	)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	if len(settings) > 0 {
    87  		if err := service.UpdateConfigSettings(settings); err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  	if args.Charm.Meta().Subordinate {
    92  		return service, nil
    93  	}
    94  	if !constraints.IsEmpty(&args.Constraints) {
    95  		if err := service.SetConstraints(args.Constraints); err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  	if args.NumUnits > 0 {
   100  		var err error
   101  		// We either have a machine spec or a placement directive.
   102  		// Placement directives take precedence.
   103  		if len(args.Placement) > 0 || args.ToMachineSpec == "" {
   104  			_, err = AddUnitsWithPlacement(st, service, args.NumUnits, args.Placement)
   105  		} else {
   106  			_, err = AddUnits(st, service, args.NumUnits, args.ToMachineSpec)
   107  		}
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  	}
   112  	return service, nil
   113  }
   114  
   115  func addMachineForUnit(st *state.State, unit *state.Unit, placement *instance.Placement, networks []string) (*state.Machine, error) {
   116  	unitCons, err := unit.Constraints()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	var containerType instance.ContainerType
   121  	var mid, placementDirective string
   122  	// Extract container type and parent from container placement directives.
   123  	if containerType, err = instance.ParseContainerType(placement.Scope); err == nil {
   124  		mid = placement.Directive
   125  	} else {
   126  		switch placement.Scope {
   127  		case st.EnvironUUID():
   128  			placementDirective = placement.Directive
   129  		case instance.MachineScope:
   130  			mid = placement.Directive
   131  		default:
   132  			return nil, errors.Errorf("invalid environment UUID %q", placement.Scope)
   133  		}
   134  	}
   135  
   136  	// Create any new machine marked as dirty so that
   137  	// nothing else will grab it before we assign the unit to it.
   138  
   139  	// If a container is to be used, create it.
   140  	if containerType != "" {
   141  		template := state.MachineTemplate{
   142  			Series:            unit.Series(),
   143  			Jobs:              []state.MachineJob{state.JobHostUnits},
   144  			Dirty:             true,
   145  			Constraints:       *unitCons,
   146  			RequestedNetworks: networks,
   147  		}
   148  		return st.AddMachineInsideMachine(template, mid, containerType)
   149  	}
   150  	// If a placement directive is to be used, do that here.
   151  	if placementDirective != "" {
   152  		template := state.MachineTemplate{
   153  			Series:            unit.Series(),
   154  			Jobs:              []state.MachineJob{state.JobHostUnits},
   155  			Dirty:             true,
   156  			Constraints:       *unitCons,
   157  			RequestedNetworks: networks,
   158  			Placement:         placementDirective,
   159  		}
   160  		return st.AddOneMachine(template)
   161  	}
   162  
   163  	// Otherwise use an existing machine.
   164  	return st.Machine(mid)
   165  }
   166  
   167  // AddUnits starts n units of the given service and allocates machines
   168  // to them as necessary.
   169  func AddUnits(st *state.State, svc *state.Service, n int, machineIdSpec string) ([]*state.Unit, error) {
   170  	if machineIdSpec != "" && n != 1 {
   171  		return nil, errors.Errorf("cannot add multiple units of service %q to a single machine", svc.Name())
   172  	}
   173  	var placement []*instance.Placement
   174  	if machineIdSpec != "" {
   175  		mid := machineIdSpec
   176  		scope := instance.MachineScope
   177  		var containerType instance.ContainerType
   178  		specParts := strings.SplitN(machineIdSpec, ":", 2)
   179  		if len(specParts) > 1 {
   180  			firstPart := specParts[0]
   181  			var err error
   182  			if containerType, err = instance.ParseContainerType(firstPart); err == nil {
   183  				mid = specParts[1]
   184  				scope = string(containerType)
   185  			}
   186  		}
   187  		if !names.IsValidMachine(mid) {
   188  			return nil, fmt.Errorf("invalid force machine id %q", mid)
   189  		}
   190  		placement = []*instance.Placement{
   191  			{
   192  				Scope:     scope,
   193  				Directive: mid,
   194  			},
   195  		}
   196  	}
   197  	return AddUnitsWithPlacement(st, svc, n, placement)
   198  }
   199  
   200  // AddUnitsWithPlacement starts n units of the given service using the specified placement
   201  // directives to allocate the machines.
   202  func AddUnitsWithPlacement(st *state.State, svc *state.Service, n int, placement []*instance.Placement) ([]*state.Unit, error) {
   203  	units := make([]*state.Unit, n)
   204  	// Hard code for now till we implement a different approach.
   205  	policy := state.AssignCleanEmpty
   206  	// All units should have the same networks as the service.
   207  	networks, err := svc.Networks()
   208  	if err != nil {
   209  		return nil, errors.Errorf("cannot get service %q networks", svc.Name())
   210  	}
   211  	// TODO what do we do if we fail half-way through this process?
   212  	for i := 0; i < n; i++ {
   213  		unit, err := svc.AddUnit()
   214  		if err != nil {
   215  			return nil, errors.Annotatef(err, "cannot add unit %d/%d to service %q", i+1, n, svc.Name())
   216  		}
   217  		// Are there still placement directives to use?
   218  		if i > len(placement)-1 {
   219  			if err := st.AssignUnit(unit, policy); err != nil {
   220  				return nil, errors.Trace(err)
   221  			}
   222  			units[i] = unit
   223  			continue
   224  		}
   225  		m, err := addMachineForUnit(st, unit, placement[i], networks)
   226  		if err != nil {
   227  			return nil, errors.Annotatef(err, "adding new machine to host unit %q", unit.Name())
   228  		}
   229  		if err = unit.AssignToMachine(m); err != nil {
   230  			return nil, errors.Trace(err)
   231  		}
   232  		units[i] = unit
   233  	}
   234  	return units, nil
   235  }
   236  
   237  func stateStorageConstraints(cons map[string]storage.Constraints) map[string]state.StorageConstraints {
   238  	result := make(map[string]state.StorageConstraints)
   239  	for name, cons := range cons {
   240  		result[name] = state.StorageConstraints{
   241  			Pool:  cons.Pool,
   242  			Size:  cons.Size,
   243  			Count: cons.Count,
   244  		}
   245  	}
   246  	return result
   247  }