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 }