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 }