OLD | NEW |
(Empty) | |
| 1 // Copyright 2012, 2013 Canonical Ltd. |
| 2 // Licensed under the AGPLv3, see LICENCE file for details. |
| 3 |
| 4 package state |
| 5 |
| 6 import ( |
| 7 "fmt" |
| 8 "strconv" |
| 9 |
| 10 "labix.org/v2/mgo/txn" |
| 11 |
| 12 "launchpad.net/juju-core/constraints" |
| 13 "launchpad.net/juju-core/instance" |
| 14 "launchpad.net/juju-core/state/api/params" |
| 15 "launchpad.net/juju-core/utils" |
| 16 ) |
| 17 |
| 18 // AddMachineParams encapsulates the parameters used to create a new machine. |
| 19 type AddMachineParams struct { |
| 20 Series string |
| 21 Constraints constraints.Value |
| 22 ParentId string |
| 23 ContainerType instance.ContainerType |
| 24 InstanceId instance.Id |
| 25 HardwareCharacteristics instance.HardwareCharacteristics |
| 26 Nonce string |
| 27 Jobs []MachineJob |
| 28 } |
| 29 |
| 30 // AddMachine adds a new machine configured to run the supplied jobs on the |
| 31 // supplied series. The machine's constraints will be taken from the |
| 32 // environment constraints. |
| 33 func (st *State) AddMachine(series string, jobs ...MachineJob) (m *Machine, err
error) { |
| 34 return st.addMachine(&AddMachineParams{Series: series, Jobs: jobs}) |
| 35 } |
| 36 |
| 37 // AddMachineWithConstraints adds a new machine configured to run the supplied j
obs on the |
| 38 // supplied series. The machine's constraints and other configuration will be ta
ken from |
| 39 // the supplied params struct. |
| 40 func (st *State) AddMachineWithConstraints(params *AddMachineParams) (m *Machine
, err error) { |
| 41 if params.InstanceId != "" { |
| 42 return nil, fmt.Errorf("cannot specify an instance id when addin
g a new machine") |
| 43 } |
| 44 if params.Nonce != "" { |
| 45 return nil, fmt.Errorf("cannot specify a nonce when adding a new
machine") |
| 46 } |
| 47 |
| 48 // TODO(wallyworld) - if a container is required, and when the actual ma
chine characteristics |
| 49 // are made available, we need to check the machine constraints to ensur
e the container can be |
| 50 // created on the specifed machine. |
| 51 // ie it makes no sense asking for a 16G container on a machine with 8G. |
| 52 |
| 53 return st.addMachine(params) |
| 54 } |
| 55 |
| 56 // InjectMachine adds a new machine, corresponding to an existing provider |
| 57 // instance, configured according to the supplied params struct. |
| 58 func (st *State) InjectMachine(params *AddMachineParams) (m *Machine, err error)
{ |
| 59 if params.InstanceId == "" { |
| 60 return nil, fmt.Errorf("cannot inject a machine without an insta
nce id") |
| 61 } |
| 62 if params.Nonce == "" { |
| 63 return nil, fmt.Errorf("cannot inject a machine without a nonce"
) |
| 64 } |
| 65 return st.addMachine(params) |
| 66 } |
| 67 |
| 68 // addMachine implements AddMachine and InjectMachine. |
| 69 func (st *State) addMachine(params *AddMachineParams) (m *Machine, err error) { |
| 70 msg := "cannot add a new machine" |
| 71 if params.ParentId != "" || params.ContainerType != "" { |
| 72 msg = "cannot add a new container" |
| 73 } |
| 74 defer utils.ErrorContextf(&err, msg) |
| 75 |
| 76 cons, err := st.EnvironConstraints() |
| 77 if err != nil { |
| 78 return nil, err |
| 79 } |
| 80 cons = params.Constraints.WithFallbacks(cons) |
| 81 |
| 82 ops, instData, containerParams, err := st.addMachineContainerOps(params,
cons) |
| 83 if err != nil { |
| 84 return nil, err |
| 85 } |
| 86 mdoc := &machineDoc{ |
| 87 Series: params.Series, |
| 88 ContainerType: string(params.ContainerType), |
| 89 Jobs: params.Jobs, |
| 90 Clean: true, |
| 91 } |
| 92 if mdoc.ContainerType == "" { |
| 93 mdoc.InstanceId = params.InstanceId |
| 94 mdoc.Nonce = params.Nonce |
| 95 } |
| 96 mdoc, machineOps, err := st.addMachineOps(mdoc, instData, cons, containe
rParams) |
| 97 if err != nil { |
| 98 return nil, err |
| 99 } |
| 100 ops = append(ops, machineOps...) |
| 101 |
| 102 err = st.runTransaction(ops) |
| 103 if err != nil { |
| 104 return nil, err |
| 105 } |
| 106 // Refresh to pick the txn-revno. |
| 107 m = newMachine(st, mdoc) |
| 108 if err = m.Refresh(); err != nil { |
| 109 return nil, err |
| 110 } |
| 111 return m, nil |
| 112 } |
| 113 |
| 114 func (st *State) addMachineOps(mdoc *machineDoc, metadata *instanceData, cons co
nstraints.Value, containerParams *containerRefParams) (*machineDoc, []txn.Op, er
ror) { |
| 115 if mdoc.Series == "" { |
| 116 return nil, nil, fmt.Errorf("no series specified") |
| 117 } |
| 118 if len(mdoc.Jobs) == 0 { |
| 119 return nil, nil, fmt.Errorf("no jobs specified") |
| 120 } |
| 121 if containerParams.hostId != "" && mdoc.ContainerType == "" { |
| 122 return nil, nil, fmt.Errorf("no container type specified") |
| 123 } |
| 124 jset := make(map[MachineJob]bool) |
| 125 for _, j := range mdoc.Jobs { |
| 126 if jset[j] { |
| 127 return nil, nil, fmt.Errorf("duplicate job: %s", j) |
| 128 } |
| 129 jset[j] = true |
| 130 } |
| 131 if containerParams.hostId == "" { |
| 132 // we are creating a new machine instance (not a container). |
| 133 seq, err := st.sequence("machine") |
| 134 if err != nil { |
| 135 return nil, nil, err |
| 136 } |
| 137 mdoc.Id = strconv.Itoa(seq) |
| 138 containerParams.hostId = mdoc.Id |
| 139 containerParams.newHost = true |
| 140 } |
| 141 if mdoc.ContainerType != "" { |
| 142 // we are creating a container so set up a namespaced id. |
| 143 seq, err := st.sequence(fmt.Sprintf("machine%s%sContainer", cont
ainerParams.hostId, mdoc.ContainerType)) |
| 144 if err != nil { |
| 145 return nil, nil, err |
| 146 } |
| 147 mdoc.Id = fmt.Sprintf("%s/%s/%d", containerParams.hostId, mdoc.C
ontainerType, seq) |
| 148 containerParams.containerId = mdoc.Id |
| 149 } |
| 150 mdoc.Life = Alive |
| 151 sdoc := statusDoc{ |
| 152 Status: params.StatusPending, |
| 153 } |
| 154 // Machine constraints do not use a container constraint value. |
| 155 // Both provisioning and deployment constraints use the same constraints
.Value struct |
| 156 // so here we clear the container value. Provisioning ignores the contai
ner value but |
| 157 // clearing it avoids potential confusion. |
| 158 cons.Container = nil |
| 159 ops := []txn.Op{ |
| 160 { |
| 161 C: st.machines.Name, |
| 162 Id: mdoc.Id, |
| 163 Assert: txn.DocMissing, |
| 164 Insert: *mdoc, |
| 165 }, |
| 166 createConstraintsOp(st, machineGlobalKey(mdoc.Id), cons), |
| 167 createStatusOp(st, machineGlobalKey(mdoc.Id), sdoc), |
| 168 } |
| 169 if metadata != nil { |
| 170 ops = append(ops, txn.Op{ |
| 171 C: st.instanceData.Name, |
| 172 Id: mdoc.Id, |
| 173 Assert: txn.DocMissing, |
| 174 Insert: *metadata, |
| 175 }) |
| 176 } |
| 177 ops = append(ops, createContainerRefOp(st, containerParams)...) |
| 178 return mdoc, ops, nil |
| 179 } |
| 180 |
| 181 // addMachineContainerOps returns txn operations and associated Mongo records us
ed to create a new machine, |
| 182 // accounting for the fact that a machine may require a container and may requir
e instance data. |
| 183 // This method exists to cater for: |
| 184 // 1. InjectMachine, which is used to record in state an instantiated bootstrap
node. When adding |
| 185 // a machine to state so that it is provisioned normally, the instance id is not
known at this point. |
| 186 // 2. AssignToNewMachine, which is used to create a new machine on which to depl
oy a unit. |
| 187 func (st *State) addMachineContainerOps(params *AddMachineParams, cons constrain
ts.Value) ([]txn.Op, *instanceData, *containerRefParams, error) { |
| 188 var instData *instanceData |
| 189 if params.InstanceId != "" { |
| 190 instData = &instanceData{ |
| 191 InstanceId: params.InstanceId, |
| 192 Arch: params.HardwareCharacteristics.Arch, |
| 193 Mem: params.HardwareCharacteristics.Mem, |
| 194 RootDisk: params.HardwareCharacteristics.RootDisk, |
| 195 CpuCores: params.HardwareCharacteristics.CpuCores, |
| 196 CpuPower: params.HardwareCharacteristics.CpuPower, |
| 197 Tags: params.HardwareCharacteristics.Tags, |
| 198 } |
| 199 } |
| 200 var ops []txn.Op |
| 201 var containerParams = &containerRefParams{hostId: params.ParentId, hostO
nly: true} |
| 202 // If we are creating a container, first create the host (parent) machin
e if necessary. |
| 203 if params.ContainerType != "" { |
| 204 containerParams.hostOnly = false |
| 205 if params.ParentId == "" { |
| 206 // No parent machine is specified so create one. |
| 207 mdoc := &machineDoc{ |
| 208 Series: params.Series, |
| 209 Jobs: params.Jobs, |
| 210 Clean: true, |
| 211 } |
| 212 mdoc, parentOps, err := st.addMachineOps(mdoc, instData,
cons, &containerRefParams{}) |
| 213 if err != nil { |
| 214 return nil, nil, nil, err |
| 215 } |
| 216 ops = parentOps |
| 217 containerParams.hostId = mdoc.Id |
| 218 containerParams.newHost = true |
| 219 } else { |
| 220 // If a parent machine is specified, make sure it exists
. |
| 221 host, err := st.Machine(containerParams.hostId) |
| 222 if err != nil { |
| 223 return nil, nil, nil, err |
| 224 } |
| 225 // We will try and check if the specified parent machine
can run a container of the specified type. |
| 226 // If the machine's supportedContainers attribute is set
, this decision can be made right here. |
| 227 // If it is not yet known what containers a machine supp
orts, we will assume that everything will |
| 228 // be ok and later on put the container into an error st
ate if necessary. |
| 229 if supportedContainers, ok := host.SupportedContainers()
; ok { |
| 230 supported := false |
| 231 for _, containerType := range supportedContainer
s { |
| 232 if containerType == params.ContainerType
{ |
| 233 supported = true |
| 234 break |
| 235 } |
| 236 } |
| 237 if !supported { |
| 238 return nil, nil, nil, fmt.Errorf("machin
e %s cannot host %s containers", host, params.ContainerType) |
| 239 } |
| 240 } |
| 241 } |
| 242 } |
| 243 return ops, instData, containerParams, nil |
| 244 } |
OLD | NEW |