github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/filesystem.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "path" 9 "regexp" 10 "strings" 11 12 "github.com/juju/charm/v12" 13 "github.com/juju/errors" 14 "github.com/juju/mgo/v3" 15 "github.com/juju/mgo/v3/bson" 16 "github.com/juju/mgo/v3/txn" 17 "github.com/juju/names/v5" 18 jujutxn "github.com/juju/txn/v3" 19 20 "github.com/juju/juju/core/paths" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/storage" 23 ) 24 25 // ErrNoBackingVolume is returned by Filesystem.Volume() for filesystems 26 // without a backing volume. 27 var ErrNoBackingVolume = errors.New("filesystem has no backing volume") 28 29 // Filesystem describes a filesystem in the model. Filesystems may be 30 // backed by a volume, and managed by Juju; otherwise they are first-class 31 // entities managed by a filesystem provider. 32 type Filesystem interface { 33 GlobalEntity 34 Lifer 35 status.StatusGetter 36 status.StatusSetter 37 38 // FilesystemTag returns the tag for the filesystem. 39 FilesystemTag() names.FilesystemTag 40 41 // Storage returns the tag of the storage instance that this 42 // filesystem is assigned to, if any. If the filesystem is not 43 // assigned to a storage instance, an error satisfying 44 // errors.IsNotAssigned will be returned. 45 // 46 // A filesystem can be assigned to at most one storage instance, and 47 // a storage instance can have at most one associated filesystem. 48 Storage() (names.StorageTag, error) 49 50 // Volume returns the tag of the volume backing this filesystem, 51 // or ErrNoBackingVolume if the filesystem is not backed by a volume 52 // managed by Juju. 53 Volume() (names.VolumeTag, error) 54 55 // Info returns the filesystem's FilesystemInfo, or a NotProvisioned 56 // error if the filesystem has not yet been provisioned. 57 Info() (FilesystemInfo, error) 58 59 // Params returns the parameters for provisioning the filesystem, 60 // if it needs to be provisioned. Params returns true if the returned 61 // parameters are usable for provisioning, otherwise false. 62 Params() (FilesystemParams, bool) 63 64 // Detachable reports whether or not the filesystem is detachable. 65 Detachable() bool 66 67 // Releasing reports whether or not the filesystem is to be released 68 // from the model when it is Dying/Dead. 69 Releasing() bool 70 } 71 72 // FilesystemAttachment describes an attachment of a filesystem to a machine. 73 type FilesystemAttachment interface { 74 Lifer 75 76 // Filesystem returns the tag of the related Filesystem. 77 Filesystem() names.FilesystemTag 78 79 // Host returns the tag of the entity to which this attachment belongs. 80 Host() names.Tag 81 82 // Info returns the filesystem attachment's FilesystemAttachmentInfo, or a 83 // NotProvisioned error if the attachment has not yet been made. 84 // 85 // Note that the presence of FilesystemAttachmentInfo does not necessarily 86 // imply that the filesystem is mounted; model storage providers may 87 // need to prepare a filesystem for attachment to a machine before it can 88 // be mounted. 89 Info() (FilesystemAttachmentInfo, error) 90 91 // Params returns the parameters for creating the filesystem attachment, 92 // if it has not already been made. Params returns true if the returned 93 // parameters are usable for creating an attachment, otherwise false. 94 Params() (FilesystemAttachmentParams, bool) 95 } 96 97 type filesystem struct { 98 mb modelBackend 99 doc filesystemDoc 100 } 101 102 type filesystemAttachment struct { 103 doc filesystemAttachmentDoc 104 } 105 106 // filesystemDoc records information about a filesystem in the model. 107 type filesystemDoc struct { 108 DocID string `bson:"_id"` 109 FilesystemId string `bson:"filesystemid"` 110 ModelUUID string `bson:"model-uuid"` 111 Life Life `bson:"life"` 112 Releasing bool `bson:"releasing,omitempty"` 113 StorageId string `bson:"storageid,omitempty"` 114 VolumeId string `bson:"volumeid,omitempty"` 115 AttachmentCount int `bson:"attachmentcount"` 116 Info *FilesystemInfo `bson:"info,omitempty"` 117 Params *FilesystemParams `bson:"params,omitempty"` 118 119 // HostId is the ID of the host that a non-detachable 120 // volume is initially attached to. We use this to identify 121 // the filesystem as being non-detachable, and to determine 122 // which filesystems must be removed along with said machine. 123 HostId string `bson:"hostid,omitempty"` 124 } 125 126 // filesystemAttachmentDoc records information about a filesystem attachment. 127 type filesystemAttachmentDoc struct { 128 // DocID is the machine global key followed by the filesystem name. 129 DocID string `bson:"_id"` 130 ModelUUID string `bson:"model-uuid"` 131 Filesystem string `bson:"filesystemid"` 132 133 Host string `bson:"hostid"` 134 Life Life `bson:"life"` 135 Info *FilesystemAttachmentInfo `bson:"info,omitempty"` 136 Params *FilesystemAttachmentParams `bson:"params,omitempty"` 137 } 138 139 // FilesystemParams records parameters for provisioning a new filesystem. 140 type FilesystemParams struct { 141 // storage, if non-zero, is the tag of the storage instance 142 // that the filesystem is to be assigned to. 143 storage names.StorageTag 144 145 // filesystemId, if non-empty, is the provider-allocated unique ID 146 // of the filesystem. This will be unspecified for filesystems backed 147 // by volumes. This is only set when creating a filesystem entity 148 // for an existing, non-volume backed, filesystem. 149 filesystemId string 150 151 // volumeInfo, if non-empty, is the information for an already 152 // provisioned backing volume. This is only set when creating a 153 // filesystem entity for an existing volume backed filesystem. 154 volumeInfo *VolumeInfo 155 156 Pool string `bson:"pool"` 157 Size uint64 `bson:"size"` 158 } 159 160 // FilesystemInfo describes information about a filesystem. 161 type FilesystemInfo struct { 162 Size uint64 `bson:"size"` 163 Pool string `bson:"pool"` 164 165 // FilesystemId is the provider-allocated unique ID of the 166 // filesystem. This will be the string representation of 167 // the filesystem tag for filesystems backed by volumes. 168 FilesystemId string `bson:"filesystemid"` 169 } 170 171 // FilesystemAttachmentInfo describes information about a filesystem attachment. 172 type FilesystemAttachmentInfo struct { 173 // MountPoint is the path at which the filesystem is mounted on the 174 // machine. MountPoint may be empty, meaning that the filesystem is 175 // not mounted yet. 176 MountPoint string `bson:"mountpoint"` 177 ReadOnly bool `bson:"read-only"` 178 } 179 180 // FilesystemAttachmentParams records parameters for attaching a filesystem to a 181 // machine. 182 type FilesystemAttachmentParams struct { 183 // locationAutoGenerated records whether or not the Location 184 // field's value was automatically generated, and thus known 185 // to be unique. This is used to optimise away mount point 186 // conflict checks. 187 locationAutoGenerated bool 188 Location string `bson:"location"` 189 ReadOnly bool `bson:"read-only"` 190 } 191 192 // validate validates the contents of the filesystem document. 193 func (f *filesystemDoc) validate() error { 194 return nil 195 } 196 197 // globalKey is required to implement GlobalEntity. 198 func (f *filesystem) globalKey() string { 199 return filesystemGlobalKey(f.doc.FilesystemId) 200 } 201 202 // Tag is required to implement GlobalEntity. 203 func (f *filesystem) Tag() names.Tag { 204 return f.FilesystemTag() 205 } 206 207 // FilesystemTag is required to implement Filesystem. 208 func (f *filesystem) FilesystemTag() names.FilesystemTag { 209 return names.NewFilesystemTag(f.doc.FilesystemId) 210 } 211 212 // Life is required to implement Filesystem. 213 func (f *filesystem) Life() Life { 214 return f.doc.Life 215 } 216 217 // Storage is required to implement Filesystem. 218 func (f *filesystem) Storage() (names.StorageTag, error) { 219 if f.doc.StorageId == "" { 220 msg := fmt.Sprintf("filesystem %q is not assigned to any storage instance", f.Tag().Id()) 221 return names.StorageTag{}, errors.NewNotAssigned(nil, msg) 222 } 223 return names.NewStorageTag(f.doc.StorageId), nil 224 } 225 226 // Volume is required to implement Filesystem. 227 func (f *filesystem) Volume() (names.VolumeTag, error) { 228 if f.doc.VolumeId == "" { 229 return names.VolumeTag{}, ErrNoBackingVolume 230 } 231 return names.NewVolumeTag(f.doc.VolumeId), nil 232 } 233 234 // Info is required to implement Filesystem. 235 func (f *filesystem) Info() (FilesystemInfo, error) { 236 if f.doc.Info == nil { 237 return FilesystemInfo{}, errors.NotProvisionedf("filesystem %q", f.doc.FilesystemId) 238 } 239 return *f.doc.Info, nil 240 } 241 242 // Params is required to implement Filesystem. 243 func (f *filesystem) Params() (FilesystemParams, bool) { 244 if f.doc.Params == nil { 245 return FilesystemParams{}, false 246 } 247 return *f.doc.Params, true 248 } 249 250 // Releasing is required to implement Filesystem. 251 func (f *filesystem) Releasing() bool { 252 return f.doc.Releasing 253 } 254 255 // Status is required to implement StatusGetter. 256 func (f *filesystem) Status() (status.StatusInfo, error) { 257 return getStatus(f.mb.db(), filesystemGlobalKey(f.FilesystemTag().Id()), "filesystem") 258 } 259 260 // SetStatus is required to implement StatusSetter. 261 func (f *filesystem) SetStatus(fsStatus status.StatusInfo) error { 262 switch fsStatus.Status { 263 case status.Attaching, status.Attached, status.Detaching, status.Detached, status.Destroying: 264 case status.Error: 265 if fsStatus.Message == "" { 266 return errors.Errorf("cannot set status %q without info", fsStatus.Status) 267 } 268 case status.Pending: 269 // If a filesystem is not yet provisioned, we allow its status 270 // to be set back to pending (when a retry is to occur). 271 // First refresh. 272 f, err := getFilesystemByTag(f.mb, f.FilesystemTag()) 273 if err != nil { 274 return errors.Trace(err) 275 } 276 _, err = f.Info() 277 if errors.IsNotProvisioned(err) { 278 break 279 } 280 return errors.Errorf("cannot set status %q", fsStatus.Status) 281 default: 282 return errors.Errorf("cannot set invalid status %q", fsStatus.Status) 283 } 284 return setStatus(f.mb.db(), setStatusParams{ 285 badge: "filesystem", 286 globalKey: filesystemGlobalKey(f.FilesystemTag().Id()), 287 status: fsStatus.Status, 288 message: fsStatus.Message, 289 rawData: fsStatus.Data, 290 updated: timeOrNow(fsStatus.Since, f.mb.clock()), 291 }) 292 } 293 294 // Filesystem is required to implement FilesystemAttachment. 295 func (f *filesystemAttachment) Filesystem() names.FilesystemTag { 296 return names.NewFilesystemTag(f.doc.Filesystem) 297 } 298 299 func storageAttachmentHost(id string) names.Tag { 300 if names.IsValidUnit(id) { 301 return names.NewUnitTag(id) 302 } 303 return names.NewMachineTag(id) 304 } 305 306 // Host is required to implement FilesystemAttachment. 307 func (f *filesystemAttachment) Host() names.Tag { 308 return storageAttachmentHost(f.doc.Host) 309 } 310 311 // Life is required to implement FilesystemAttachment. 312 func (f *filesystemAttachment) Life() Life { 313 return f.doc.Life 314 } 315 316 // Info is required to implement FilesystemAttachment. 317 func (f *filesystemAttachment) Info() (FilesystemAttachmentInfo, error) { 318 if f.doc.Info == nil { 319 hostTag := storageAttachmentHost(f.doc.Host) 320 return FilesystemAttachmentInfo{}, errors.NotProvisionedf( 321 "filesystem attachment %q on %q", f.doc.Filesystem, names.ReadableString(hostTag)) 322 } 323 return *f.doc.Info, nil 324 } 325 326 // Params is required to implement FilesystemAttachment. 327 func (f *filesystemAttachment) Params() (FilesystemAttachmentParams, bool) { 328 if f.doc.Params == nil { 329 return FilesystemAttachmentParams{}, false 330 } 331 return *f.doc.Params, true 332 } 333 334 // Filesystem returns the Filesystem with the specified name. 335 func (sb *storageBackend) Filesystem(tag names.FilesystemTag) (Filesystem, error) { 336 f, err := getFilesystemByTag(sb.mb, tag) 337 return f, err 338 } 339 340 func getFilesystemByTag(mb modelBackend, tag names.FilesystemTag) (*filesystem, error) { 341 doc, err := getFilesystemDocByTag(mb.db(), tag) 342 if err != nil { 343 return nil, errors.Trace(err) 344 } 345 return &filesystem{mb, doc}, nil 346 } 347 348 func (sb *storageBackend) storageInstanceFilesystem(tag names.StorageTag) (*filesystem, error) { 349 query := bson.D{{"storageid", tag.Id()}} 350 description := fmt.Sprintf("filesystem for storage instance %q", tag.Id()) 351 return sb.filesystem(query, description) 352 } 353 354 // StorageInstanceFilesystem returns the Filesystem assigned to the specified 355 // storage instance. 356 func (sb *storageBackend) StorageInstanceFilesystem(tag names.StorageTag) (Filesystem, error) { 357 f, err := sb.storageInstanceFilesystem(tag) 358 return f, err 359 } 360 361 func (sb *storageBackend) volumeFilesystem(tag names.VolumeTag) (*filesystem, error) { 362 query := bson.D{{"volumeid", tag.Id()}} 363 description := fmt.Sprintf("filesystem for volume %q", tag.Id()) 364 return sb.filesystem(query, description) 365 } 366 367 // VolumeFilesystem returns the Filesystem backed by the specified volume. 368 func (sb *storageBackend) VolumeFilesystem(tag names.VolumeTag) (Filesystem, error) { 369 f, err := sb.volumeFilesystem(tag) 370 return f, err 371 } 372 373 func (sb *storageBackend) filesystems(query interface{}) ([]*filesystem, error) { 374 fDocs, err := getFilesystemDocs(sb.mb.db(), query) 375 if err != nil { 376 return nil, errors.Trace(err) 377 } 378 filesystems := make([]*filesystem, len(fDocs)) 379 for i, doc := range fDocs { 380 filesystems[i] = &filesystem{sb.mb, doc} 381 } 382 return filesystems, nil 383 } 384 385 func (sb *storageBackend) filesystem(query bson.D, description string) (*filesystem, error) { 386 doc, err := getFilesystemDoc(sb.mb.db(), query, description) 387 if err != nil { 388 return nil, errors.Trace(err) 389 } 390 return &filesystem{sb.mb, doc}, nil 391 } 392 393 func getFilesystemDocByTag(db Database, tag names.FilesystemTag) (filesystemDoc, error) { 394 query := bson.D{{"_id", tag.Id()}} 395 description := fmt.Sprintf("filesystem %q", tag.Id()) 396 return getFilesystemDoc(db, query, description) 397 } 398 399 func getFilesystemDoc(db Database, query bson.D, description string) (filesystemDoc, error) { 400 coll, cleanup := db.GetCollection(filesystemsC) 401 defer cleanup() 402 403 var doc filesystemDoc 404 err := coll.Find(query).One(&doc) 405 if err == mgo.ErrNotFound { 406 return doc, errors.NotFoundf(description) 407 } else if err != nil { 408 return doc, errors.Annotate(err, "cannot get filesystem") 409 } 410 if err := doc.validate(); err != nil { 411 return doc, errors.Annotate(err, "validating filesystem") 412 } 413 return doc, nil 414 } 415 416 func getFilesystemDocs(db Database, query interface{}) ([]filesystemDoc, error) { 417 coll, cleanup := db.GetCollection(filesystemsC) 418 defer cleanup() 419 420 var docs []filesystemDoc 421 err := coll.Find(query).All(&docs) 422 if err != nil { 423 return nil, errors.Trace(err) 424 } 425 for _, doc := range docs { 426 if err := doc.validate(); err != nil { 427 return nil, errors.Annotate(err, "filesystem validation failed") 428 } 429 } 430 return docs, nil 431 } 432 433 // FilesystemAttachment returns the FilesystemAttachment corresponding to 434 // the specified filesystem and machine. 435 func (sb *storageBackend) FilesystemAttachment(host names.Tag, filesystem names.FilesystemTag) (FilesystemAttachment, error) { 436 coll, cleanup := sb.mb.db().GetCollection(filesystemAttachmentsC) 437 defer cleanup() 438 439 var att filesystemAttachment 440 err := coll.FindId(filesystemAttachmentId(host.Id(), filesystem.Id())).One(&att.doc) 441 if err == mgo.ErrNotFound { 442 return nil, errors.NotFoundf("filesystem %q on %q", filesystem.Id(), names.ReadableString(host)) 443 } else if err != nil { 444 return nil, errors.Annotatef(err, "getting filesystem %q on %q", filesystem.Id(), names.ReadableString(host)) 445 } 446 return &att, nil 447 } 448 449 // FilesystemAttachments returns all of the FilesystemAttachments for the 450 // specified filesystem. 451 func (sb *storageBackend) FilesystemAttachments(filesystem names.FilesystemTag) ([]FilesystemAttachment, error) { 452 attachments, err := sb.filesystemAttachments(bson.D{{"filesystemid", filesystem.Id()}}) 453 if err != nil { 454 return nil, errors.Annotatef(err, "getting attachments for filesystem %q", filesystem.Id()) 455 } 456 return attachments, nil 457 } 458 459 // MachineFilesystemAttachments returns all of the FilesystemAttachments for the 460 // specified machine. 461 func (sb *storageBackend) MachineFilesystemAttachments(machine names.MachineTag) ([]FilesystemAttachment, error) { 462 attachments, err := sb.filesystemAttachments(bson.D{{"hostid", machine.Id()}}) 463 if err != nil { 464 return nil, errors.Annotatef(err, "getting filesystem attachments for %q", names.ReadableString(machine)) 465 } 466 return attachments, nil 467 } 468 469 // UnitFilesystemAttachments returns all of the FilesystemAttachments for the 470 // specified unit. 471 func (sb *storageBackend) UnitFilesystemAttachments(unit names.UnitTag) ([]FilesystemAttachment, error) { 472 attachments, err := sb.filesystemAttachments(bson.D{{"hostid", unit.Id()}}) 473 if err != nil { 474 return nil, errors.Annotatef(err, "getting filesystem attachments for %q", names.ReadableString(unit)) 475 } 476 return attachments, nil 477 } 478 479 func (sb *storageBackend) filesystemAttachments(query bson.D) ([]FilesystemAttachment, error) { 480 coll, cleanup := sb.mb.db().GetCollection(filesystemAttachmentsC) 481 defer cleanup() 482 483 var docs []filesystemAttachmentDoc 484 err := coll.Find(query).All(&docs) 485 if err == mgo.ErrNotFound { 486 return nil, nil 487 } else if err != nil { 488 return nil, errors.Trace(err) 489 } 490 attachments := make([]FilesystemAttachment, len(docs)) 491 for i, doc := range docs { 492 attachments[i] = &filesystemAttachment{doc} 493 } 494 return attachments, nil 495 } 496 497 // removeMachineFilesystemsOps returns txn.Ops to remove non-persistent filesystems 498 // attached to the specified machine. This is used when the given machine is 499 // being removed from state. 500 func (sb *storageBackend) removeMachineFilesystemsOps(m *Machine) ([]txn.Op, error) { 501 // A machine cannot transition to Dead if it has any detachable storage 502 // attached, so any attachments are for machine-bound storage. 503 // 504 // Even if a filesystem is "non-detachable", there still exist filesystem 505 // attachments, and they may be removed independently of the filesystem. 506 // For example, the user may request that the filesystem be destroyed. 507 // This will cause the filesystem to become Dying, and the attachment 508 // to be Dying, then Dead, and finally removed. Only once the attachment 509 // is removed will the filesystem transition to Dead and then be removed. 510 // Therefore, there may be filesystems that are bound, but not attached, 511 // to the machine. 512 machineFilesystems, err := sb.filesystems(bson.D{{"hostid", m.Id()}}) 513 if err != nil { 514 return nil, errors.Trace(err) 515 } 516 ops := make([]txn.Op, 0, 2*len(machineFilesystems)+len(m.doc.Filesystems)) 517 for _, filesystemId := range m.doc.Filesystems { 518 ops = append(ops, txn.Op{ 519 C: filesystemAttachmentsC, 520 Id: filesystemAttachmentId(m.Id(), filesystemId), 521 Assert: txn.DocExists, 522 Remove: true, 523 }) 524 } 525 for _, f := range machineFilesystems { 526 if f.doc.StorageId != "" { 527 // The volume is assigned to a storage instance; 528 // make sure we also remove the storage instance. 529 // There should be no storage attachments remaining, 530 // as the units must have been removed before the 531 // machine can be; and the storage attachments must 532 // have been removed before the unit can be. 533 ops = append(ops, 534 txn.Op{ 535 C: storageInstancesC, 536 Id: f.doc.StorageId, 537 Assert: txn.DocExists, 538 Remove: true, 539 }, 540 ) 541 } 542 fsOps, err := removeFilesystemOps(sb, f, f.doc.Releasing, false, nil) 543 if err != nil { 544 return nil, errors.Trace(err) 545 } 546 ops = append(ops, fsOps...) 547 } 548 return ops, nil 549 } 550 551 // isDetachableFilesystemTag reports whether or not the filesystem with the 552 // specified tag is detachable. 553 func isDetachableFilesystemTag(db Database, tag names.FilesystemTag) (bool, error) { 554 doc, err := getFilesystemDocByTag(db, tag) 555 if err != nil { 556 return false, errors.Trace(err) 557 } 558 return doc.HostId == "", nil 559 } 560 561 // Detachable reports whether or not the filesystem is detachable. 562 func (f *filesystem) Detachable() bool { 563 return f.doc.HostId == "" 564 } 565 566 func (f *filesystem) pool() string { 567 if f.doc.Info != nil { 568 return f.doc.Info.Pool 569 } 570 return f.doc.Params.Pool 571 } 572 573 // isDetachableFilesystemPool reports whether or not the given 574 // storage pool will create a filesystem that is not inherently 575 // bound to a machine, and therefore can be detached. 576 func isDetachableFilesystemPool(sb *storageBackend, pool string) (bool, error) { 577 _, provider, _, err := poolStorageProvider(sb, pool) 578 if err != nil { 579 return false, errors.Trace(err) 580 } 581 if provider.Scope() == storage.ScopeMachine { 582 // Any storage created by a machine cannot be detached from 583 // the machine, and must be destroyed along with it. 584 return false, nil 585 } 586 if !provider.Dynamic() { 587 // The storage provider only accommodates provisioning storage 588 // statically along with the machine. Such storage is bound 589 // to the machine. 590 return false, nil 591 } 592 return true, nil 593 } 594 595 // DetachFilesystem marks the filesystem attachment identified by the specified machine 596 // and filesystem tags as Dying, if it is Alive. DetachFilesystem will fail for 597 // inherently machine-bound filesystems. 598 func (sb *storageBackend) DetachFilesystem(host names.Tag, filesystem names.FilesystemTag) (err error) { 599 defer errors.DeferredAnnotatef(&err, "detaching filesystem %s from %s", filesystem.Id(), names.ReadableString(host)) 600 buildTxn := func(attempt int) ([]txn.Op, error) { 601 fsa, err := sb.FilesystemAttachment(host, filesystem) 602 if err != nil { 603 return nil, errors.Trace(err) 604 } 605 if fsa.Life() != Alive { 606 return nil, jujutxn.ErrNoOperations 607 } 608 detachable, err := isDetachableFilesystemTag(sb.mb.db(), filesystem) 609 if err != nil { 610 return nil, errors.Trace(err) 611 } 612 if !detachable { 613 return nil, errors.New("filesystem is not detachable") 614 } 615 ops := detachFilesystemOps(host, filesystem) 616 return ops, nil 617 } 618 return sb.mb.db().Run(buildTxn) 619 } 620 621 func (sb *storageBackend) filesystemVolumeAttachment(host names.Tag, f names.FilesystemTag) (VolumeAttachment, error) { 622 filesystem, err := getFilesystemByTag(sb.mb, f) 623 if err != nil { 624 return nil, errors.Trace(err) 625 } 626 v, err := filesystem.Volume() 627 if err != nil { 628 return nil, errors.Trace(err) 629 } 630 return sb.VolumeAttachment(host, v) 631 } 632 633 func detachFilesystemOps(host names.Tag, f names.FilesystemTag) []txn.Op { 634 return []txn.Op{{ 635 C: filesystemAttachmentsC, 636 Id: filesystemAttachmentId(host.Id(), f.Id()), 637 Assert: isAliveDoc, 638 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 639 }} 640 } 641 642 // RemoveFilesystemAttachment removes the filesystem attachment from state. 643 // Removing a volume-backed filesystem attachment will cause the volume to 644 // be detached. 645 func (sb *storageBackend) RemoveFilesystemAttachment(host names.Tag, filesystem names.FilesystemTag, force bool) (err error) { 646 defer errors.DeferredAnnotatef(&err, "removing attachment of filesystem %s from %s", filesystem.Id(), names.ReadableString(host)) 647 buildTxn := func(attempt int) ([]txn.Op, error) { 648 attachment, err := sb.FilesystemAttachment(host, filesystem) 649 if errors.IsNotFound(err) && attempt > 0 { 650 // We only ignore IsNotFound on attempts after the 651 // first, since we expect the filesystem attachment to 652 // be there initially. 653 return nil, jujutxn.ErrNoOperations 654 } 655 if err != nil { 656 return nil, errors.Trace(err) 657 } 658 if attachment.Life() != Dying { 659 return nil, errors.New("filesystem attachment is not dying") 660 } 661 f, err := getFilesystemByTag(sb.mb, filesystem) 662 if err != nil { 663 return nil, errors.Trace(err) 664 } 665 ops, err := removeFilesystemAttachmentOps(sb, host, f, force) 666 if err != nil { 667 return nil, errors.Trace(err) 668 } 669 volumeAttachment, err := sb.filesystemVolumeAttachment(host, filesystem) 670 if err != nil { 671 if errors.Cause(err) != ErrNoBackingVolume && !errors.IsNotFound(err) { 672 return nil, errors.Trace(err) 673 } 674 } else { 675 // The filesystem is backed by a volume. Since the 676 // filesystem has been detached, we should now 677 // detach the volume as well if it is detachable. 678 // If the volume is not detachable, we'll just 679 // destroy it along with the filesystem. 680 volume := volumeAttachment.Volume() 681 v, err := sb.Volume(volume) 682 if err != nil { 683 return nil, errors.Trace(err) 684 } 685 if v.Detachable() { 686 plans, err := sb.machineVolumeAttachmentPlans(host, volume) 687 if err != nil { 688 return nil, errors.Trace(err) 689 } 690 // NOTE(gsamfira): if we're upgrading, we might not have plans set up, 691 // so we check if volume plans were created, and if not, just skip to 692 // detaching the actual disk 693 var volOps []txn.Op 694 if len(plans) == 0 { 695 volOps, err = sb.detachVolumeOps(host, volume, force) 696 } else { 697 volOps, err = sb.detachVolumeAttachmentPlanOps(host, volume, force) 698 } 699 if err != nil { 700 return nil, errors.Trace(err) 701 } 702 ops = append(ops, volOps...) 703 } 704 } 705 return ops, nil 706 } 707 return sb.mb.db().Run(buildTxn) 708 } 709 710 func removeFilesystemAttachmentOps(sb *storageBackend, host names.Tag, f *filesystem, force bool) ([]txn.Op, error) { 711 var ops []txn.Op 712 if f.doc.VolumeId != "" && f.doc.Life == Dying && f.doc.AttachmentCount == 1 { 713 // Volume-backed filesystems are removed immediately, instead 714 // of transitioning to Dead. 715 assert := bson.D{ 716 {"life", Dying}, 717 {"attachmentcount", 1}, 718 } 719 removeFilesystemOps, err := removeFilesystemOps(sb, f, f.doc.Releasing, force, assert) 720 if err != nil { 721 return nil, errors.Trace(err) 722 } 723 ops = removeFilesystemOps 724 } else { 725 decrefFilesystemOp := machineStorageDecrefOp( 726 filesystemsC, f.doc.FilesystemId, 727 f.doc.AttachmentCount, f.doc.Life, 728 ) 729 ops = []txn.Op{decrefFilesystemOp} 730 } 731 ops = append(ops, txn.Op{ 732 C: filesystemAttachmentsC, 733 Id: filesystemAttachmentId(host.Id(), f.doc.FilesystemId), 734 Assert: bson.D{{"life", Dying}}, 735 Remove: true, 736 }) 737 if host.Kind() == names.MachineTagKind { 738 ops = append(ops, txn.Op{ 739 C: machinesC, 740 Id: host.Id(), 741 Assert: txn.DocExists, 742 Update: bson.D{{"$pull", bson.D{{"filesystems", f.doc.FilesystemId}}}}, 743 }) 744 } 745 return ops, nil 746 } 747 748 // DestroyFilesystem ensures that the filesystem and any attachments to it will 749 // be destroyed and removed from state at some point in the future. 750 func (sb *storageBackend) DestroyFilesystem(tag names.FilesystemTag, force bool) (err error) { 751 defer errors.DeferredAnnotatef(&err, "destroying filesystem %s", tag.Id()) 752 buildTxn := func(attempt int) ([]txn.Op, error) { 753 filesystem, err := getFilesystemByTag(sb.mb, tag) 754 if errors.IsNotFound(err) && attempt > 0 { 755 // On the first attempt, we expect it to exist. 756 return nil, jujutxn.ErrNoOperations 757 } else if err != nil { 758 return nil, errors.Trace(err) 759 } 760 761 if !force && filesystem.Life() != Alive || filesystem.Life() == Dead { 762 return nil, jujutxn.ErrNoOperations 763 } 764 765 // If we are not forcing the destruction of this file system, 766 // it must not be attached to a storage instance. 767 if filesystem.doc.StorageId != "" { 768 err := errors.Errorf("filesystem is assigned to %s", 769 names.ReadableString(names.NewStorageTag(filesystem.doc.StorageId))) 770 if !force { 771 return nil, err 772 } 773 logger.Warningf("%s", err.Error()) 774 } 775 776 var assertNoStorageAssignment bson.D 777 if !force { 778 assertNoStorageAssignment = bson.D{{"$or", []bson.D{ 779 {{"storageid", ""}}, 780 {{"storageid", bson.D{{"$exists", false}}}}, 781 }}} 782 } 783 784 ops, err := destroyFilesystemOps(sb, filesystem, false, force, assertNoStorageAssignment) 785 return ops, errors.Trace(err) 786 } 787 788 return errors.Trace(sb.mb.db().Run(buildTxn)) 789 } 790 791 func destroyFilesystemOps(sb *storageBackend, f *filesystem, release, force bool, extraAssert bson.D) ([]txn.Op, error) { 792 lifeAssert := isAliveDoc 793 if force { 794 // Since we are force destroying, life assert should be current volume's life. 795 lifeAssert = bson.D{{"life", f.doc.Life}} 796 } 797 baseAssert := append(lifeAssert, extraAssert...) 798 setFields := bson.D{} 799 if release { 800 setFields = append(setFields, bson.DocElem{Name: "releasing", Value: true}) 801 } 802 if f.doc.AttachmentCount == 0 { 803 hasNoAttachments := bson.D{{"attachmentcount", 0}} 804 assert := append(hasNoAttachments, baseAssert...) 805 if f.doc.VolumeId != "" { 806 // Filesystem is volume-backed, and since it has no 807 // attachments, it has no provisioner responsible 808 // for it. Removing the filesystem will destroy the 809 // backing volume, which effectively destroys the 810 // filesystem contents anyway. 811 return removeFilesystemOps(sb, f, release, force, assert) 812 } 813 // The filesystem is not volume-backed, so leave it to the 814 // storage provisioner to destroy it. 815 setFields = append(setFields, bson.DocElem{Name: "life", Value: Dead}) 816 return []txn.Op{{ 817 C: filesystemsC, 818 Id: f.doc.FilesystemId, 819 Assert: assert, 820 Update: bson.D{{"$set", setFields}}, 821 }}, nil 822 } 823 hasAttachments := bson.D{{"attachmentcount", bson.D{{"$gt", 0}}}} 824 setFields = append(setFields, bson.DocElem{Name: "life", Value: Dying}) 825 ops := []txn.Op{{ 826 C: filesystemsC, 827 Id: f.doc.FilesystemId, 828 Assert: append(hasAttachments, baseAssert...), 829 Update: bson.D{{"$set", setFields}}, 830 }} 831 if !f.Detachable() { 832 // This filesystem cannot be directly detached, so we do 833 // not issue a cleanup. Since there can (should!) be only 834 // one attachment for the lifetime of the filesystem, we 835 // can look it up and destroy it along with the filesystem. 836 attachments, err := sb.FilesystemAttachments(f.FilesystemTag()) 837 if err != nil { 838 return nil, errors.Trace(err) 839 } 840 if len(attachments) != 1 { 841 return nil, errors.Errorf( 842 "expected 1 attachment, found %d", 843 len(attachments), 844 ) 845 } 846 detachOps := detachFilesystemOps( 847 attachments[0].Host(), 848 f.FilesystemTag(), 849 ) 850 ops = append(ops, detachOps...) 851 } else { 852 ops = append(ops, newCleanupOp( 853 cleanupAttachmentsForDyingFilesystem, 854 f.doc.FilesystemId, 855 )) 856 } 857 return ops, nil 858 } 859 860 // RemoveFilesystem removes the filesystem from state. RemoveFilesystem will 861 // fail if there are any attachments remaining, or if the filesystem is not 862 // Dying. Removing a volume-backed filesystem will cause the volume to be 863 // destroyed. 864 func (sb *storageBackend) RemoveFilesystem(tag names.FilesystemTag) (err error) { 865 defer errors.DeferredAnnotatef(&err, "removing filesystem %s", tag.Id()) 866 buildTxn := func(attempt int) ([]txn.Op, error) { 867 filesystem, err := getFilesystemByTag(sb.mb, tag) 868 if errors.IsNotFound(err) { 869 return nil, jujutxn.ErrNoOperations 870 } else if err != nil { 871 return nil, errors.Trace(err) 872 } 873 if filesystem.Life() != Dead { 874 return nil, errors.New("filesystem is not dead") 875 } 876 return removeFilesystemOps(sb, filesystem, false, false, isDeadDoc) 877 } 878 return sb.mb.db().Run(buildTxn) 879 } 880 881 func removeFilesystemOps(sb *storageBackend, filesystem Filesystem, release, force bool, assert interface{}) ([]txn.Op, error) { 882 ops := []txn.Op{ 883 { 884 C: filesystemsC, 885 Id: filesystem.Tag().Id(), 886 Assert: assert, 887 Remove: true, 888 }, 889 removeModelFilesystemRefOp(sb.mb, filesystem.Tag().Id()), 890 removeStatusOp(sb.mb, filesystem.globalKey()), 891 } 892 // If the filesystem is backed by a volume, the volume should 893 // be destroyed once the filesystem is removed. The volume must 894 // not be destroyed before the filesystem is removed. 895 volumeTag, err := filesystem.Volume() 896 if err == nil { 897 volume, err := getVolumeByTag(sb.mb, volumeTag) 898 if err != nil { 899 return nil, errors.Trace(err) 900 } 901 volOps, err := destroyVolumeOps(sb, volume, release, force, nil) 902 if err != nil { 903 return nil, errors.Trace(err) 904 } 905 ops = append(ops, volOps...) 906 } else if err != ErrNoBackingVolume { 907 return nil, errors.Trace(err) 908 } 909 return ops, nil 910 } 911 912 // AddExistingFilesystem imports an existing, already-provisioned 913 // filesystem into the model. The model will start out with 914 // the status "detached". The filesystem and associated backing 915 // volume (if any) will be associated with the given storage 916 // name, with the allocated storage tag being returned. 917 func (sb *storageBackend) AddExistingFilesystem( 918 info FilesystemInfo, 919 backingVolume *VolumeInfo, 920 storageName string, 921 ) (_ names.StorageTag, err error) { 922 defer errors.DeferredAnnotatef(&err, "cannot add existing filesystem") 923 if err := validateAddExistingFilesystem(sb, info, backingVolume, storageName); err != nil { 924 return names.StorageTag{}, errors.Trace(err) 925 } 926 storageId, err := newStorageInstanceId(sb.mb, storageName) 927 if err != nil { 928 return names.StorageTag{}, errors.Trace(err) 929 } 930 storageTag := names.NewStorageTag(storageId) 931 fsOps, _, volumeTag, err := sb.addFilesystemOps( 932 FilesystemParams{ 933 Pool: info.Pool, 934 Size: info.Size, 935 filesystemId: info.FilesystemId, 936 volumeInfo: backingVolume, 937 storage: storageTag, 938 }, 939 "", // no machine ID 940 ) 941 if err != nil { 942 return names.StorageTag{}, errors.Trace(err) 943 } 944 if volumeTag != (names.VolumeTag{}) && backingVolume == nil { 945 return names.StorageTag{}, errors.Errorf("backing volume info missing") 946 } 947 ops := []txn.Op{{ 948 C: storageInstancesC, 949 Id: storageId, 950 Assert: txn.DocMissing, 951 Insert: &storageInstanceDoc{ 952 Id: storageId, 953 Kind: StorageKindFilesystem, 954 StorageName: storageName, 955 Constraints: storageInstanceConstraints{ 956 Pool: info.Pool, 957 Size: info.Size, 958 }, 959 }, 960 }} 961 ops = append(ops, fsOps...) 962 if err := sb.mb.db().RunTransaction(ops); err != nil { 963 return names.StorageTag{}, errors.Trace(err) 964 } 965 return storageTag, nil 966 } 967 968 var storageNameRE = regexp.MustCompile(names.StorageNameSnippet) 969 970 func validateAddExistingFilesystem( 971 sb *storageBackend, 972 info FilesystemInfo, 973 backingVolume *VolumeInfo, 974 storageName string, 975 ) error { 976 if !storage.IsValidPoolName(info.Pool) { 977 return errors.NotValidf("pool name %q", info.Pool) 978 } 979 if !storageNameRE.MatchString(storageName) { 980 return errors.NotValidf("storage name %q", storageName) 981 } 982 if backingVolume == nil { 983 if info.FilesystemId == "" { 984 return errors.NotValidf("empty filesystem ID") 985 } 986 } else { 987 if info.FilesystemId != "" { 988 return errors.NotValidf("non-empty filesystem ID with backing volume") 989 } 990 if backingVolume.VolumeId == "" { 991 return errors.NotValidf("empty backing volume ID") 992 } 993 if backingVolume.Pool != info.Pool { 994 return errors.Errorf( 995 "volume pool %q does not match filesystem pool %q", 996 backingVolume.Pool, info.Pool, 997 ) 998 } 999 if backingVolume.Size != info.Size { 1000 return errors.Errorf( 1001 "volume size %d does not match filesystem size %d", 1002 backingVolume.Size, info.Size, 1003 ) 1004 } 1005 } 1006 _, provider, _, err := poolStorageProvider(sb, info.Pool) 1007 if err != nil { 1008 return errors.Trace(err) 1009 } 1010 if !provider.Supports(storage.StorageKindFilesystem) { 1011 if backingVolume == nil { 1012 return errors.New("backing volume info missing") 1013 } 1014 } else { 1015 if backingVolume != nil { 1016 return errors.New("unexpected volume info") 1017 } 1018 } 1019 return nil 1020 } 1021 1022 // filesystemAttachmentId returns a filesystem attachment document ID, 1023 // given the corresponding filesystem name and machine ID. 1024 func filesystemAttachmentId(hostId, filesystemId string) string { 1025 return fmt.Sprintf("%s:%s", hostId, filesystemId) 1026 } 1027 1028 // ParseFilesystemAttachmentId parses a string as a filesystem attachment ID, 1029 // returning the host and filesystem components. 1030 func ParseFilesystemAttachmentId(id string) (names.Tag, names.FilesystemTag, error) { 1031 fields := strings.SplitN(id, ":", 2) 1032 isValidHost := names.IsValidMachine(fields[0]) || names.IsValidUnit(fields[0]) 1033 if len(fields) != 2 || !isValidHost || !names.IsValidFilesystem(fields[1]) { 1034 return names.MachineTag{}, names.FilesystemTag{}, errors.Errorf("invalid filesystem attachment ID %q", id) 1035 } 1036 var hostTag names.Tag 1037 if names.IsValidMachine(fields[0]) { 1038 hostTag = names.NewMachineTag(fields[0]) 1039 } else { 1040 hostTag = names.NewUnitTag(fields[0]) 1041 } 1042 filesystemTag := names.NewFilesystemTag(fields[1]) 1043 return hostTag, filesystemTag, nil 1044 } 1045 1046 // newFilesystemId returns a unique filesystem ID. 1047 // If the host ID supplied is non-empty, the 1048 // filesystem ID will incorporate it as the 1049 // filesystem's machine scope. 1050 func newFilesystemId(mb modelBackend, hostId string) (string, error) { 1051 seq, err := sequence(mb, "filesystem") 1052 if err != nil { 1053 return "", errors.Trace(err) 1054 } 1055 id := fmt.Sprint(seq) 1056 if hostId != "" { 1057 id = hostId + "/" + id 1058 } 1059 return id, nil 1060 } 1061 1062 // addFilesystemOps returns txn.Ops to create a new filesystem with the 1063 // specified parameters. If the storage source cannot create filesystems 1064 // directly, a volume will be created and Juju will manage a filesystem 1065 // on it. 1066 func (sb *storageBackend) addFilesystemOps(params FilesystemParams, hostId string) ([]txn.Op, names.FilesystemTag, names.VolumeTag, error) { 1067 var err error 1068 params, err = sb.filesystemParamsWithDefaults(params) 1069 if err != nil { 1070 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) 1071 } 1072 detachable, err := isDetachableFilesystemPool(sb, params.Pool) 1073 if err != nil { 1074 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) 1075 } 1076 origHostId := hostId 1077 hostId, err = sb.validateFilesystemParams(params, hostId) 1078 if err != nil { 1079 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "validating filesystem params") 1080 } 1081 1082 filesystemId, err := newFilesystemId(sb.mb, hostId) 1083 if err != nil { 1084 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "cannot generate filesystem name") 1085 } 1086 filesystemTag := names.NewFilesystemTag(filesystemId) 1087 1088 // Check if the filesystem needs a volume. 1089 var volumeId string 1090 var volumeTag names.VolumeTag 1091 var ops []txn.Op 1092 _, provider, _, err := poolStorageProvider(sb, params.Pool) 1093 if err != nil { 1094 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) 1095 } 1096 if !provider.Supports(storage.StorageKindFilesystem) { 1097 var volumeOps []txn.Op 1098 if params.volumeInfo != nil { 1099 // The filesystem ID for volume-backed filesystems 1100 // is the string representation of the filesystem tag. 1101 params.filesystemId = filesystemTag.String() 1102 } 1103 volumeParams := VolumeParams{ 1104 params.storage, 1105 params.volumeInfo, 1106 params.Pool, 1107 params.Size, 1108 } 1109 volumeOps, volumeTag, err = sb.addVolumeOps(volumeParams, hostId) 1110 if err != nil { 1111 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "creating backing volume") 1112 } 1113 volumeId = volumeTag.Id() 1114 ops = append(ops, volumeOps...) 1115 } 1116 1117 statusDoc := statusDoc{ 1118 Status: status.Pending, 1119 Updated: sb.mb.clock().Now().UnixNano(), 1120 } 1121 doc := filesystemDoc{ 1122 FilesystemId: filesystemId, 1123 VolumeId: volumeId, 1124 StorageId: params.storage.Id(), 1125 } 1126 if params.filesystemId != "" { 1127 // We're importing an already provisioned filesystem into the 1128 // model. Set provisioned info rather than params, and set the 1129 // status to "detached". 1130 statusDoc.Status = status.Detached 1131 doc.Info = &FilesystemInfo{ 1132 Size: params.Size, 1133 Pool: params.Pool, 1134 FilesystemId: params.filesystemId, 1135 } 1136 } else { 1137 // Every new filesystem is created with one attachment. 1138 doc.Params = ¶ms 1139 doc.AttachmentCount = 1 1140 } 1141 if !detachable { 1142 doc.HostId = origHostId 1143 } 1144 ops = append(ops, sb.newFilesystemOps(doc, statusDoc)...) 1145 return ops, filesystemTag, volumeTag, nil 1146 } 1147 1148 func (sb *storageBackend) newFilesystemOps(doc filesystemDoc, status statusDoc) []txn.Op { 1149 return []txn.Op{ 1150 createStatusOp(sb.mb, filesystemGlobalKey(doc.FilesystemId), status), 1151 { 1152 C: filesystemsC, 1153 Id: doc.FilesystemId, 1154 Assert: txn.DocMissing, 1155 Insert: &doc, 1156 }, 1157 addModelFilesystemRefOp(sb.mb, doc.FilesystemId), 1158 } 1159 } 1160 1161 func (sb *storageBackend) filesystemParamsWithDefaults(params FilesystemParams) (FilesystemParams, error) { 1162 if params.Pool == "" { 1163 modelConfig, err := sb.config() 1164 if err != nil { 1165 return FilesystemParams{}, errors.Trace(err) 1166 } 1167 cons := StorageConstraints{ 1168 Pool: params.Pool, 1169 Size: params.Size, 1170 Count: 1, 1171 } 1172 poolName, err := defaultStoragePool(sb.modelType, modelConfig, storage.StorageKindFilesystem, cons) 1173 if err != nil { 1174 return FilesystemParams{}, errors.Annotate(err, "getting default filesystem storage pool") 1175 } 1176 params.Pool = poolName 1177 } 1178 return params, nil 1179 } 1180 1181 // validateFilesystemParams validates the filesystem parameters, and returns the 1182 // machine ID to use as the scope in the filesystem tag. 1183 func (sb *storageBackend) validateFilesystemParams(params FilesystemParams, machineId string) (maybeMachineId string, _ error) { 1184 err := validateStoragePool(sb, params.Pool, storage.StorageKindFilesystem, &machineId) 1185 if err != nil { 1186 return "", errors.Trace(err) 1187 } 1188 if params.Size == 0 { 1189 return "", errors.New("invalid size 0") 1190 } 1191 return machineId, nil 1192 } 1193 1194 type filesystemAttachmentTemplate struct { 1195 tag names.FilesystemTag 1196 storage names.StorageTag // may be zero-value 1197 params FilesystemAttachmentParams 1198 existing bool 1199 } 1200 1201 // createMachineFilesystemAttachmentInfo creates filesystem 1202 // attachments for the specified host, and attachment 1203 // parameters keyed by filesystem tags. 1204 func createMachineFilesystemAttachmentsOps(hostId string, attachments []filesystemAttachmentTemplate) []txn.Op { 1205 ops := make([]txn.Op, len(attachments)) 1206 for i, attachment := range attachments { 1207 paramsCopy := attachment.params 1208 ops[i] = txn.Op{ 1209 C: filesystemAttachmentsC, 1210 Id: filesystemAttachmentId(hostId, attachment.tag.Id()), 1211 Assert: txn.DocMissing, 1212 Insert: &filesystemAttachmentDoc{ 1213 Filesystem: attachment.tag.Id(), 1214 Host: hostId, 1215 Params: ¶msCopy, 1216 }, 1217 } 1218 if attachment.existing { 1219 ops = append(ops, txn.Op{ 1220 C: filesystemsC, 1221 Id: attachment.tag.Id(), 1222 Assert: txn.DocExists, 1223 Update: bson.D{{"$inc", bson.D{{"attachmentcount", 1}}}}, 1224 }) 1225 } 1226 } 1227 return ops 1228 } 1229 1230 // SetFilesystemInfo sets the FilesystemInfo for the specified filesystem. 1231 func (sb *storageBackend) SetFilesystemInfo(tag names.FilesystemTag, info FilesystemInfo) (err error) { 1232 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem %q", tag.Id()) 1233 1234 if info.FilesystemId == "" { 1235 return errors.New("filesystem ID not set") 1236 } 1237 fs, err := sb.Filesystem(tag) 1238 if err != nil { 1239 return errors.Trace(err) 1240 } 1241 // If the filesystem is volume-backed, the volume must be provisioned 1242 // and attached first. 1243 if volumeTag, err := fs.Volume(); err == nil { 1244 volumeAttachments, err := sb.VolumeAttachments(volumeTag) 1245 if err != nil { 1246 return errors.Trace(err) 1247 } 1248 var anyAttached bool 1249 for _, a := range volumeAttachments { 1250 if _, err := a.Info(); err == nil { 1251 anyAttached = true 1252 } else if !errors.IsNotProvisioned(err) { 1253 return err 1254 } 1255 } 1256 if !anyAttached { 1257 return errors.Errorf( 1258 "backing volume %q is not attached", 1259 volumeTag.Id(), 1260 ) 1261 } 1262 } else if errors.Cause(err) != ErrNoBackingVolume { 1263 return errors.Trace(err) 1264 } 1265 buildTxn := func(attempt int) ([]txn.Op, error) { 1266 if attempt > 0 { 1267 fs, err = sb.Filesystem(tag) 1268 if err != nil { 1269 return nil, errors.Trace(err) 1270 } 1271 } 1272 // If the filesystem has parameters, unset them 1273 // when we set info for the first time, ensuring 1274 // that params and info are mutually exclusive. 1275 var unsetParams bool 1276 if params, ok := fs.Params(); ok { 1277 info.Pool = params.Pool 1278 unsetParams = true 1279 } else { 1280 // Ensure immutable properties do not change. 1281 oldInfo, err := fs.Info() 1282 if err != nil { 1283 return nil, err 1284 } 1285 if err := validateFilesystemInfoChange(info, oldInfo); err != nil { 1286 return nil, err 1287 } 1288 } 1289 ops := setFilesystemInfoOps(tag, info, unsetParams) 1290 return ops, nil 1291 } 1292 return sb.mb.db().Run(buildTxn) 1293 } 1294 1295 func validateFilesystemInfoChange(newInfo, oldInfo FilesystemInfo) error { 1296 if newInfo.Pool != oldInfo.Pool { 1297 return errors.Errorf( 1298 "cannot change pool from %q to %q", 1299 oldInfo.Pool, newInfo.Pool, 1300 ) 1301 } 1302 if newInfo.FilesystemId != oldInfo.FilesystemId { 1303 return errors.Errorf( 1304 "cannot change filesystem ID from %q to %q", 1305 oldInfo.FilesystemId, newInfo.FilesystemId, 1306 ) 1307 } 1308 return nil 1309 } 1310 1311 func setFilesystemInfoOps(tag names.FilesystemTag, info FilesystemInfo, unsetParams bool) []txn.Op { 1312 asserts := isAliveDoc 1313 update := bson.D{ 1314 {"$set", bson.D{{"info", &info}}}, 1315 } 1316 if unsetParams { 1317 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 1318 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 1319 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 1320 } 1321 return []txn.Op{{ 1322 C: filesystemsC, 1323 Id: tag.Id(), 1324 Assert: asserts, 1325 Update: update, 1326 }} 1327 } 1328 1329 // SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the 1330 // specified filesystem attachment. 1331 func (sb *storageBackend) SetFilesystemAttachmentInfo( 1332 hostTag names.Tag, 1333 filesystemTag names.FilesystemTag, 1334 info FilesystemAttachmentInfo, 1335 ) (err error) { 1336 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), hostTag.Id()) 1337 f, err := sb.Filesystem(filesystemTag) 1338 if err != nil { 1339 return errors.Trace(err) 1340 } 1341 // Ensure filesystem is provisioned before setting attachment info. 1342 // A filesystem cannot go from being provisioned to unprovisioned, 1343 // so there is no txn.Op for this below. 1344 if _, err := f.Info(); err != nil { 1345 return errors.Trace(err) 1346 } 1347 // Also ensure the machine is provisioned. 1348 if _, ok := hostTag.(names.MachineTag); ok { 1349 m, err := sb.machine(hostTag.Id()) 1350 if err != nil { 1351 return errors.Trace(err) 1352 } 1353 if _, err := m.InstanceId(); err != nil { 1354 return errors.Trace(err) 1355 } 1356 } 1357 buildTxn := func(attempt int) ([]txn.Op, error) { 1358 fsa, err := sb.FilesystemAttachment(hostTag, filesystemTag) 1359 if err != nil { 1360 return nil, errors.Trace(err) 1361 } 1362 // If the filesystem attachment has parameters, unset them 1363 // when we set info for the first time, ensuring that params 1364 // and info are mutually exclusive. 1365 _, unsetParams := fsa.Params() 1366 ops := setFilesystemAttachmentInfoOps(hostTag, filesystemTag, info, unsetParams) 1367 return ops, nil 1368 } 1369 return sb.mb.db().Run(buildTxn) 1370 } 1371 1372 func setFilesystemAttachmentInfoOps( 1373 host names.Tag, 1374 filesystem names.FilesystemTag, 1375 info FilesystemAttachmentInfo, 1376 unsetParams bool, 1377 ) []txn.Op { 1378 asserts := isAliveDoc 1379 update := bson.D{ 1380 {"$set", bson.D{{"info", &info}}}, 1381 } 1382 if unsetParams { 1383 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 1384 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 1385 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 1386 } 1387 return []txn.Op{{ 1388 C: filesystemAttachmentsC, 1389 Id: filesystemAttachmentId(host.Id(), filesystem.Id()), 1390 Assert: asserts, 1391 Update: update, 1392 }} 1393 } 1394 1395 // FilesystemMountPoint returns a mount point to use for the given charm 1396 // storage. For stores with potentially multiple instances, the instance 1397 // name is appended to the location. 1398 func FilesystemMountPoint( 1399 meta charm.Storage, 1400 tag names.StorageTag, 1401 osname string, 1402 ) (string, error) { 1403 storageDir := paths.StorageDir(paths.OSType(osname)) 1404 if strings.HasPrefix(meta.Location, storageDir) { 1405 return "", errors.Errorf( 1406 "invalid location %q: must not fall within %q", 1407 meta.Location, storageDir, 1408 ) 1409 } 1410 if meta.Location != "" && meta.CountMax == 1 { 1411 // The location is specified and it's a singleton 1412 // store, so just use the location as-is. 1413 return meta.Location, nil 1414 } 1415 // If the location is unspecified then we use 1416 // <storage-dir>/<storage-id> as the location. 1417 // Otherwise, we use <location>/<storage-id>. 1418 if meta.Location != "" { 1419 storageDir = meta.Location 1420 } 1421 return path.Join(storageDir, tag.Id()), nil 1422 } 1423 1424 // validateFilesystemMountPoints validates the mount points of filesystems 1425 // being attached to the specified machine. If there are any mount point 1426 // path conflicts, an error will be returned. 1427 func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error { 1428 sb, err := NewStorageBackend(m.st) 1429 if err != nil { 1430 return errors.Trace(err) 1431 } 1432 1433 attachments, err := sb.MachineFilesystemAttachments(m.MachineTag()) 1434 if err != nil { 1435 return errors.Trace(err) 1436 } 1437 existing := make(map[names.FilesystemTag]string) 1438 for _, a := range attachments { 1439 params, ok := a.Params() 1440 if ok { 1441 existing[a.Filesystem()] = params.Location 1442 continue 1443 } 1444 info, err := a.Info() 1445 if err != nil { 1446 return errors.Trace(err) 1447 } 1448 existing[a.Filesystem()] = info.MountPoint 1449 } 1450 1451 storageName := func( 1452 filesystemTag names.FilesystemTag, 1453 storageTag names.StorageTag, 1454 ) string { 1455 if storageTag == (names.StorageTag{}) { 1456 return names.ReadableString(filesystemTag) 1457 } 1458 // We know the tag is valid, so ignore the error. 1459 storageName, _ := names.StorageName(storageTag.Id()) 1460 return fmt.Sprintf("%q storage", storageName) 1461 } 1462 1463 containsPath := func(a, b string) bool { 1464 a = path.Clean(a) + "/" 1465 b = path.Clean(b) + "/" 1466 return strings.HasPrefix(b, a) 1467 } 1468 1469 // These sets are expected to be small, so sorting and comparing 1470 // adjacent values is not worth the cost of creating a reverse 1471 // lookup from location to filesystem. 1472 for _, template := range newFilesystems { 1473 newMountPoint := template.params.Location 1474 for oldFilesystemTag, oldMountPoint := range existing { 1475 var conflicted, swapOrder bool 1476 if containsPath(oldMountPoint, newMountPoint) { 1477 conflicted = true 1478 } else if containsPath(newMountPoint, oldMountPoint) { 1479 conflicted = true 1480 swapOrder = true 1481 } 1482 if !conflicted { 1483 continue 1484 } 1485 1486 // Get a helpful identifier for the new filesystem. If it 1487 // is being created for a storage instance, then use 1488 // the storage name; otherwise use the filesystem name. 1489 newStorageName := storageName(template.tag, template.storage) 1490 1491 // Likewise for the old filesystem, but this time we'll 1492 // need to consult state. 1493 oldFilesystem, err := sb.Filesystem(oldFilesystemTag) 1494 if err != nil { 1495 return errors.Trace(err) 1496 } 1497 storageTag, err := oldFilesystem.Storage() 1498 if errors.IsNotAssigned(err) { 1499 storageTag = names.StorageTag{} 1500 } else if err != nil { 1501 return errors.Trace(err) 1502 } 1503 oldStorageName := storageName(oldFilesystemTag, storageTag) 1504 1505 lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName) 1506 rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName) 1507 if swapOrder { 1508 lhs, rhs = rhs, lhs 1509 } 1510 return errors.Errorf("%s contains %s", lhs, rhs) 1511 } 1512 } 1513 return nil 1514 } 1515 1516 // AllFilesystems returns all Filesystems for this state. 1517 func (sb *storageBackend) AllFilesystems() ([]Filesystem, error) { 1518 filesystems, err := sb.filesystems(nil) 1519 if err != nil { 1520 return nil, errors.Annotate(err, "cannot get filesystems") 1521 } 1522 return filesystemsToInterfaces(filesystems), nil 1523 } 1524 1525 func filesystemsToInterfaces(sb []*filesystem) []Filesystem { 1526 result := make([]Filesystem, len(sb)) 1527 for i, f := range sb { 1528 result[i] = f 1529 } 1530 return result 1531 } 1532 1533 func filesystemGlobalKey(name string) string { 1534 return "f#" + name 1535 }