github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/addmachine.go (about) 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 "github.com/juju/errors" 11 "github.com/juju/mgo/v3/txn" 12 "github.com/juju/names/v5" 13 14 "github.com/juju/juju/core/constraints" 15 "github.com/juju/juju/core/instance" 16 "github.com/juju/juju/core/network" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/storage" 19 ) 20 21 // MachineTemplate holds attributes that are to be associated 22 // with a newly created machine. 23 type MachineTemplate struct { 24 // Base is the base to be associated with the new machine. 25 Base Base 26 27 // Constraints are the constraints to be used when finding 28 // an instance for the machine. 29 Constraints constraints.Value 30 31 // Jobs holds the jobs to run on the machine's instance. 32 // A machine must have at least one job to do. 33 // JobManageModel can only be part of the jobs 34 // when the first (bootstrap) machine is added. 35 Jobs []MachineJob 36 37 // Addresses holds the addresses to be associated with the 38 // new machine. 39 // 40 // TODO(dimitern): This should be removed once all addresses 41 // come from link-layer device addresses. 42 Addresses network.SpaceAddresses 43 44 // InstanceId holds the instance id to associate with the machine. 45 // If this is empty, the provisioner will try to provision the machine. 46 // If this is non-empty, the HardwareCharacteristics and Nonce 47 // fields must be set appropriately. 48 InstanceId instance.Id 49 50 // DisplayName holds the human readable name for the instance 51 // associated with the machine. 52 DisplayName string 53 54 // HardwareCharacteristics holds the h/w characteristics to 55 // be associated with the machine. 56 HardwareCharacteristics instance.HardwareCharacteristics 57 58 // LinkLayerDevices holds a list of arguments for setting link-layer devices 59 // on the machine. 60 LinkLayerDevices []LinkLayerDeviceArgs 61 62 // Volumes holds the parameters for volumes that are to be created 63 // and attached to the machine. 64 Volumes []HostVolumeParams 65 66 // VolumeAttachments holds the parameters for attaching existing 67 // volumes to the machine. 68 VolumeAttachments map[names.VolumeTag]VolumeAttachmentParams 69 70 // Filesystems holds the parameters for filesystems that are to be 71 // created and attached to the machine. 72 Filesystems []HostFilesystemParams 73 74 // FilesystemAttachments holds the parameters for attaching existing 75 // filesystems to the machine. 76 FilesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams 77 78 // Nonce holds a unique value that can be used to check 79 // if a new instance was really started for this machine. 80 // See Machine.SetProvisioned. This must be set if InstanceId is set. 81 Nonce string 82 83 // Dirty signifies whether the new machine will be treated 84 // as unclean for unit-assignment purposes. 85 Dirty bool 86 87 // Placement holds the placement directive that will be associated 88 // with the machine. 89 Placement string 90 91 // principals holds the principal units that will 92 // associated with the machine. 93 principals []string 94 } 95 96 // HostVolumeParams holds the parameters for creating a volume and 97 // attaching it to a new host. 98 type HostVolumeParams struct { 99 Volume VolumeParams 100 Attachment VolumeAttachmentParams 101 } 102 103 // HostFilesystemParams holds the parameters for creating a filesystem 104 // and attaching it to a new host. 105 type HostFilesystemParams struct { 106 Filesystem FilesystemParams 107 Attachment FilesystemAttachmentParams 108 } 109 110 // AddMachineInsideNewMachine creates a new machine within a container 111 // of the given type inside another new machine. The two given templates 112 // specify the form of the child and parent respectively. 113 func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) { 114 mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType) 115 if err != nil { 116 return nil, errors.Annotate(err, "cannot add a new machine") 117 } 118 return st.addMachine(mdoc, ops) 119 } 120 121 // AddMachineInsideMachine adds a machine inside a container of the 122 // given type on the existing machine with id=parentId. 123 func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) { 124 mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType) 125 if err != nil { 126 return nil, errors.Annotate(err, "cannot add a new machine") 127 } 128 return st.addMachine(mdoc, ops) 129 } 130 131 // AddMachine adds a machine with the given series and jobs. 132 // It is deprecated and around for testing purposes only. 133 func (st *State) AddMachine(base Base, jobs ...MachineJob) (*Machine, error) { 134 ms, err := st.AddMachines(MachineTemplate{ 135 Base: base, 136 Jobs: jobs, 137 }) 138 if err != nil { 139 return nil, err 140 } 141 return ms[0], nil 142 } 143 144 // AddOneMachine machine adds a new machine configured according to the 145 // given template. 146 func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) { 147 ms, err := st.AddMachines(template) 148 if err != nil { 149 return nil, err 150 } 151 return ms[0], nil 152 } 153 154 // AddMachines adds new machines configured according to the 155 // given templates. 156 func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) { 157 defer errors.DeferredAnnotatef(&err, "cannot add a new machine") 158 var ms []*Machine 159 var ops []txn.Op 160 var controllerIds []string 161 for _, template := range templates { 162 mdoc, addOps, err := st.addMachineOps(template) 163 if err != nil { 164 return nil, errors.Trace(err) 165 } 166 if isController(mdoc) { 167 controllerIds = append(controllerIds, mdoc.Id) 168 } 169 ms = append(ms, newMachine(st, mdoc)) 170 ops = append(ops, addOps...) 171 } 172 ssOps, err := st.maintainControllersOps(controllerIds, true) 173 if err != nil { 174 return nil, errors.Trace(err) 175 } 176 ops = append(ops, ssOps...) 177 ops = append(ops, assertModelActiveOp(st.ModelUUID())) 178 if err := st.db().RunTransaction(ops); err != nil { 179 if errors.Cause(err) == txn.ErrAborted { 180 if err := checkModelActive(st); err != nil { 181 return nil, errors.Trace(err) 182 } 183 } 184 return nil, errors.Trace(err) 185 } 186 return ms, nil 187 } 188 189 func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) { 190 ops = append([]txn.Op{assertModelActiveOp(st.ModelUUID())}, ops...) 191 if err := st.db().RunTransaction(ops); err != nil { 192 if errors.Cause(err) == txn.ErrAborted { 193 if err := checkModelActive(st); err != nil { 194 return nil, errors.Trace(err) 195 } 196 } 197 return nil, errors.Trace(err) 198 } 199 return newMachine(st, mdoc), nil 200 } 201 202 func (st *State) resolveMachineConstraints(cons constraints.Value) (constraints.Value, error) { 203 mcons, err := st.ResolveConstraints(cons) 204 if err != nil { 205 return constraints.Value{}, err 206 } 207 // Machine constraints do not use a container constraint value. 208 // Both provisioning and deployment constraints use the same 209 // constraints.Value struct so here we clear the container 210 // value. Provisioning ignores the container value but clearing 211 // it avoids potential confusion. 212 mcons.Container = nil 213 return mcons, nil 214 } 215 216 // effectiveMachineTemplate verifies that the given template is 217 // valid and combines it with values from the state 218 // to produce a resulting template that more accurately 219 // represents the data that will be inserted into the state. 220 func (st *State) effectiveMachineTemplate(p MachineTemplate, allowController bool) (tmpl MachineTemplate, err error) { 221 // First check for obvious errors. 222 if p.Base.String() == "" { 223 return tmpl, errors.New("no base specified") 224 } 225 if p.InstanceId != "" { 226 if p.Nonce == "" { 227 return tmpl, errors.New("cannot add a machine with an instance id and no nonce") 228 } 229 } else if p.Nonce != "" { 230 return tmpl, errors.New("cannot specify a nonce without an instance id") 231 } 232 233 // We ignore all constraints if there's a placement directive. 234 if p.Placement == "" { 235 p.Constraints, err = st.resolveMachineConstraints(p.Constraints) 236 if err != nil { 237 return tmpl, err 238 } 239 } 240 241 if len(p.Jobs) == 0 { 242 return tmpl, errors.New("no jobs specified") 243 } 244 jset := make(map[MachineJob]bool) 245 for _, j := range p.Jobs { 246 if jset[j] { 247 return MachineTemplate{}, errors.Errorf("duplicate job: %s", j) 248 } 249 jset[j] = true 250 } 251 if jset[JobManageModel] { 252 if !allowController { 253 return tmpl, errControllerNotAllowed 254 } 255 } 256 return p, nil 257 } 258 259 // addMachineOps returns operations to add a new top level machine 260 // based on the given template. It also returns the machine document 261 // that will be inserted. 262 func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) { 263 template, err := st.effectiveMachineTemplate(template, st.IsController()) 264 if err != nil { 265 return nil, nil, err 266 } 267 if template.InstanceId == "" { 268 volumeAttachments, err := st.machineTemplateVolumeAttachmentParams(template) 269 if err != nil { 270 return nil, nil, err 271 } 272 if err := st.precheckInstance( 273 template.Base, 274 template.Constraints, 275 template.Placement, 276 volumeAttachments, 277 ); err != nil { 278 return nil, nil, err 279 } 280 } 281 seq, err := sequence(st, "machine") 282 if err != nil { 283 return nil, nil, err 284 } 285 mdoc := st.machineDocForTemplate(template, strconv.Itoa(seq)) 286 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 287 if err != nil { 288 return nil, nil, errors.Trace(err) 289 } 290 291 prereqOps = append(prereqOps, assertModelActiveOp(st.ModelUUID())) 292 prereqOps = append(prereqOps, insertNewContainerRefOp(st, mdoc.Id)) 293 if template.InstanceId != "" { 294 prereqOps = append(prereqOps, txn.Op{ 295 C: instanceDataC, 296 Id: mdoc.DocID, 297 Assert: txn.DocMissing, 298 Insert: &instanceData{ 299 DocID: mdoc.DocID, 300 MachineId: mdoc.Id, 301 InstanceId: template.InstanceId, 302 DisplayName: template.DisplayName, 303 ModelUUID: mdoc.ModelUUID, 304 Arch: template.HardwareCharacteristics.Arch, 305 Mem: template.HardwareCharacteristics.Mem, 306 RootDisk: template.HardwareCharacteristics.RootDisk, 307 RootDiskSource: template.HardwareCharacteristics.RootDiskSource, 308 CpuCores: template.HardwareCharacteristics.CpuCores, 309 CpuPower: template.HardwareCharacteristics.CpuPower, 310 Tags: template.HardwareCharacteristics.Tags, 311 AvailZone: template.HardwareCharacteristics.AvailabilityZone, 312 VirtType: template.HardwareCharacteristics.VirtType, 313 }, 314 }) 315 } 316 if isController(mdoc) { 317 prereqOps = append(prereqOps, addControllerNodeOp(st, mdoc.Id, false)) 318 } 319 320 return mdoc, append(prereqOps, machineOp), nil 321 } 322 323 // supportsContainerType reports whether the machine supports the given 324 // container type. If the machine's supportedContainers attribute is 325 // set, this decision can be made right here, otherwise we assume that 326 // everything will be ok and later on put the container into an error 327 // state if necessary. 328 func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool { 329 supportedContainers, ok := m.SupportedContainers() 330 if !ok { 331 // We don't know yet, so we report that we support the container. 332 return true 333 } 334 for _, ct := range supportedContainers { 335 if ct == ctype { 336 return true 337 } 338 } 339 return false 340 } 341 342 // addMachineInsideMachineOps returns operations to add a machine inside 343 // a container of the given type on an existing machine. 344 func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 345 if template.InstanceId != "" { 346 return nil, nil, errors.New("cannot specify instance id for a new container") 347 } 348 template, err := st.effectiveMachineTemplate(template, false) 349 if err != nil { 350 return nil, nil, err 351 } 352 if containerType == "" { 353 return nil, nil, errors.New("no container type specified") 354 } 355 356 // If a parent machine is specified, make sure it exists 357 // and can support the requested container type. 358 parent, err := st.Machine(parentId) 359 if err != nil { 360 return nil, nil, err 361 } 362 if !parent.supportsContainerType(containerType) { 363 return nil, nil, errors.Errorf("machine %s cannot host %s containers", parentId, containerType) 364 } 365 366 // Ensure that the machine is not locked for series-upgrade. 367 locked, err := parent.IsLockedForSeriesUpgrade() 368 if err != nil { 369 return nil, nil, err 370 } 371 if locked { 372 return nil, nil, errors.Errorf("machine %s is locked for series upgrade", parentId) 373 } 374 375 newId, err := st.newContainerId(parentId, containerType) 376 if err != nil { 377 return nil, nil, err 378 } 379 mdoc := st.machineDocForTemplate(template, newId) 380 mdoc.ContainerType = string(containerType) 381 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 382 if err != nil { 383 return nil, nil, errors.Trace(err) 384 } 385 prereqOps = append(prereqOps, 386 // Update containers record for host machine. 387 addChildToContainerRefOp(st, parentId, mdoc.Id), 388 // Create a containers reference document for the container itself. 389 insertNewContainerRefOp(st, mdoc.Id), 390 ) 391 return mdoc, append(prereqOps, machineOp), nil 392 } 393 394 // newContainerId returns a new id for a machine within the machine 395 // with id parentId and the given container type. 396 func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) { 397 name := fmt.Sprintf("machine%s%sContainer", parentId, containerType) 398 seq, err := sequence(st, name) 399 if err != nil { 400 return "", err 401 } 402 return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil 403 } 404 405 // addMachineInsideNewMachineOps returns operations to create a new 406 // machine within a container of the given type inside another 407 // new machine. The two given templates specify the form 408 // of the child and parent respectively. 409 func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 410 if template.InstanceId != "" || parentTemplate.InstanceId != "" { 411 return nil, nil, errors.New("cannot specify instance id for a new container") 412 } 413 seq, err := sequence(st, "machine") 414 if err != nil { 415 return nil, nil, err 416 } 417 parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false) 418 if err != nil { 419 return nil, nil, err 420 } 421 if containerType == "" { 422 return nil, nil, errors.New("no container type specified") 423 } 424 if parentTemplate.InstanceId == "" { 425 volumeAttachments, err := st.machineTemplateVolumeAttachmentParams(parentTemplate) 426 if err != nil { 427 return nil, nil, err 428 } 429 if err := st.precheckInstance( 430 parentTemplate.Base, 431 parentTemplate.Constraints, 432 parentTemplate.Placement, 433 volumeAttachments, 434 ); err != nil { 435 return nil, nil, err 436 } 437 } 438 439 parentDoc := st.machineDocForTemplate(parentTemplate, strconv.Itoa(seq)) 440 newId, err := st.newContainerId(parentDoc.Id, containerType) 441 if err != nil { 442 return nil, nil, err 443 } 444 template, err = st.effectiveMachineTemplate(template, false) 445 if err != nil { 446 return nil, nil, err 447 } 448 mdoc := st.machineDocForTemplate(template, newId) 449 mdoc.ContainerType = string(containerType) 450 parentPrereqOps, parentOp, err := st.insertNewMachineOps(parentDoc, parentTemplate) 451 if err != nil { 452 return nil, nil, errors.Trace(err) 453 } 454 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 455 if err != nil { 456 return nil, nil, errors.Trace(err) 457 } 458 prereqOps = append(prereqOps, parentPrereqOps...) 459 prereqOps = append(prereqOps, 460 // The host machine doesn't exist yet, create a new containers record. 461 insertNewContainerRefOp(st, mdoc.Id), 462 // Create a containers reference document for the container itself. 463 insertNewContainerRefOp(st, parentDoc.Id, mdoc.Id), 464 ) 465 return mdoc, append(prereqOps, parentOp, machineOp), nil 466 } 467 468 func (st *State) machineTemplateVolumeAttachmentParams(t MachineTemplate) ([]storage.VolumeAttachmentParams, error) { 469 sb, err := NewStorageBackend(st) 470 if err != nil { 471 return nil, errors.Trace(err) 472 } 473 out := make([]storage.VolumeAttachmentParams, 0, len(t.VolumeAttachments)) 474 for volumeTag, a := range t.VolumeAttachments { 475 v, err := sb.Volume(volumeTag) 476 if err != nil { 477 return nil, errors.Trace(err) 478 } 479 volumeInfo, err := v.Info() 480 if err != nil { 481 return nil, errors.Trace(err) 482 } 483 providerType, _, _, err := poolStorageProvider(sb, volumeInfo.Pool) 484 if err != nil { 485 return nil, errors.Trace(err) 486 } 487 out = append(out, storage.VolumeAttachmentParams{ 488 AttachmentParams: storage.AttachmentParams{ 489 Provider: providerType, 490 ReadOnly: a.ReadOnly, 491 }, 492 Volume: volumeTag, 493 VolumeId: volumeInfo.VolumeId, 494 }) 495 } 496 return out, nil 497 } 498 499 func (st *State) machineDocForTemplate(template MachineTemplate, id string) *machineDoc { 500 // We ignore the error from Select*Address as an error indicates 501 // no address is available, in which case the empty address is returned 502 // and setting the preferred address to an empty one is the correct 503 // thing to do when none is available. 504 privateAddr, _ := template.Addresses.OneMatchingScope(network.ScopeMatchCloudLocal) 505 publicAddr, _ := template.Addresses.OneMatchingScope(network.ScopeMatchPublic) 506 logger.Infof( 507 "new machine %q has preferred addresses: private %q, public %q", 508 id, privateAddr, publicAddr, 509 ) 510 base := template.Base.Normalise() 511 return &machineDoc{ 512 DocID: st.docID(id), 513 Id: id, 514 ModelUUID: st.ModelUUID(), 515 Base: base, 516 Jobs: template.Jobs, 517 Clean: !template.Dirty, 518 Principals: template.principals, 519 Life: Alive, 520 Nonce: template.Nonce, 521 Addresses: fromNetworkAddresses(template.Addresses, network.OriginMachine), 522 PreferredPrivateAddress: fromNetworkAddress(privateAddr, network.OriginMachine), 523 PreferredPublicAddress: fromNetworkAddress(publicAddr, network.OriginMachine), 524 Placement: template.Placement, 525 } 526 } 527 528 // insertNewMachineOps returns operations to insert the given machine document 529 // into the database, based on the given template. Only the constraints are 530 // taken from the template. 531 func (st *State) insertNewMachineOps(mdoc *machineDoc, template MachineTemplate) (prereqOps []txn.Op, machineOp txn.Op, err error) { 532 now := st.clock().Now() 533 machineStatusDoc := statusDoc{ 534 Status: status.Pending, 535 ModelUUID: st.ModelUUID(), 536 Updated: now.UnixNano(), 537 } 538 instanceStatusDoc := statusDoc{ 539 Status: status.Pending, 540 ModelUUID: st.ModelUUID(), 541 Updated: now.UnixNano(), 542 } 543 modificationStatusDoc := statusDoc{ 544 Status: status.Idle, 545 ModelUUID: st.ModelUUID(), 546 Updated: now.UnixNano(), 547 } 548 549 prereqOps, machineOp = st.baseNewMachineOps( 550 mdoc, 551 machineStatusDoc, 552 instanceStatusDoc, 553 modificationStatusDoc, 554 template.Constraints, 555 ) 556 557 sb, err := NewStorageBackend(st) 558 if err != nil { 559 return nil, txn.Op{}, errors.Trace(err) 560 } 561 storageOps, volumeAttachments, filesystemAttachments, err := sb.hostStorageOps( 562 mdoc.Id, &storageParams{ 563 filesystems: template.Filesystems, 564 filesystemAttachments: template.FilesystemAttachments, 565 volumes: template.Volumes, 566 volumeAttachments: template.VolumeAttachments, 567 }, 568 ) 569 if err != nil { 570 return nil, txn.Op{}, errors.Trace(err) 571 } 572 for _, a := range volumeAttachments { 573 mdoc.Volumes = append(mdoc.Volumes, a.tag.Id()) 574 } 575 for _, a := range filesystemAttachments { 576 mdoc.Filesystems = append(mdoc.Filesystems, a.tag.Id()) 577 } 578 prereqOps = append(prereqOps, storageOps...) 579 580 // At the last moment we still have statusDoc in scope, set the initial 581 // history entry. This is risky, and may lead to extra entries, but that's 582 // an intrinsic problem with mixing txn and non-txn ops -- we can't sync 583 // them cleanly. 584 _, _ = probablyUpdateStatusHistory(st.db(), machineGlobalKey(mdoc.Id), machineStatusDoc) 585 _, _ = probablyUpdateStatusHistory(st.db(), machineGlobalInstanceKey(mdoc.Id), instanceStatusDoc) 586 _, _ = probablyUpdateStatusHistory(st.db(), machineGlobalModificationKey(mdoc.Id), modificationStatusDoc) 587 return prereqOps, machineOp, nil 588 } 589 590 func (st *State) baseNewMachineOps(mdoc *machineDoc, 591 machineStatusDoc, instanceStatusDoc, modificationStatusDoc statusDoc, 592 cons constraints.Value, 593 ) (prereqOps []txn.Op, machineOp txn.Op) { 594 machineOp = txn.Op{ 595 C: machinesC, 596 Id: mdoc.DocID, 597 Assert: txn.DocMissing, 598 Insert: mdoc, 599 } 600 601 globalKey := machineGlobalKey(mdoc.Id) 602 globalInstanceKey := machineGlobalInstanceKey(mdoc.Id) 603 globalModificationKey := machineGlobalModificationKey(mdoc.Id) 604 605 prereqOps = []txn.Op{ 606 createConstraintsOp(globalKey, cons), 607 createStatusOp(st, globalKey, machineStatusDoc), 608 createStatusOp(st, globalInstanceKey, instanceStatusDoc), 609 createStatusOp(st, globalModificationKey, modificationStatusDoc), 610 createMachineBlockDevicesOp(mdoc.Id), 611 addModelMachineRefOp(st, mdoc.Id), 612 } 613 return prereqOps, machineOp 614 }