github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "strings" 10 "time" 11 12 "github.com/juju/errors" 13 jujutxn "github.com/juju/txn" 14 "gopkg.in/juju/charm.v6-unstable" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/mgo.v2" 17 "gopkg.in/mgo.v2/bson" 18 "gopkg.in/mgo.v2/txn" 19 20 "github.com/juju/juju/juju/paths" 21 "github.com/juju/juju/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 LifeBinder 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 65 // FilesystemAttachment describes an attachment of a filesystem to a machine. 66 type FilesystemAttachment interface { 67 Lifer 68 69 // Filesystem returns the tag of the related Filesystem. 70 Filesystem() names.FilesystemTag 71 72 // Machine returns the tag of the related Machine. 73 Machine() names.MachineTag 74 75 // Info returns the filesystem attachment's FilesystemAttachmentInfo, or a 76 // NotProvisioned error if the attachment has not yet been made. 77 // 78 // Note that the presence of FilesystemAttachmentInfo does not necessarily 79 // imply that the filesystem is mounted; model storage providers may 80 // need to prepare a filesystem for attachment to a machine before it can 81 // be mounted. 82 Info() (FilesystemAttachmentInfo, error) 83 84 // Params returns the parameters for creating the filesystem attachment, 85 // if it has not already been made. Params returns true if the returned 86 // parameters are usable for creating an attachment, otherwise false. 87 Params() (FilesystemAttachmentParams, bool) 88 } 89 90 type filesystem struct { 91 st *State 92 doc filesystemDoc 93 } 94 95 type filesystemAttachment struct { 96 doc filesystemAttachmentDoc 97 } 98 99 // filesystemDoc records information about a filesystem in the model. 100 type filesystemDoc struct { 101 DocID string `bson:"_id"` 102 FilesystemId string `bson:"filesystemid"` 103 ModelUUID string `bson:"model-uuid"` 104 Life Life `bson:"life"` 105 StorageId string `bson:"storageid,omitempty"` 106 VolumeId string `bson:"volumeid,omitempty"` 107 // TODO(axw) 2015-06-22 #1467379 108 // upgrade step to set "attachmentcount" and "binding" 109 // for 1.24 models. 110 AttachmentCount int `bson:"attachmentcount"` 111 Binding string `bson:"binding,omitempty"` 112 Info *FilesystemInfo `bson:"info,omitempty"` 113 Params *FilesystemParams `bson:"params,omitempty"` 114 } 115 116 // filesystemAttachmentDoc records information about a filesystem attachment. 117 type filesystemAttachmentDoc struct { 118 // DocID is the machine global key followed by the filesystem name. 119 DocID string `bson:"_id"` 120 ModelUUID string `bson:"model-uuid"` 121 Filesystem string `bson:"filesystemid"` 122 Machine string `bson:"machineid"` 123 Life Life `bson:"life"` 124 Info *FilesystemAttachmentInfo `bson:"info,omitempty"` 125 Params *FilesystemAttachmentParams `bson:"params,omitempty"` 126 } 127 128 // FilesystemParams records parameters for provisioning a new filesystem. 129 type FilesystemParams struct { 130 // storage, if non-zero, is the tag of the storage instance 131 // that the filesystem is to be assigned to. 132 storage names.StorageTag 133 134 // binding, if non-nil, is the tag of the entity to which 135 // the filesystem's lifecycle will be bound. 136 binding names.Tag 137 138 Pool string `bson:"pool"` 139 Size uint64 `bson:"size"` 140 } 141 142 // FilesystemInfo describes information about a filesystem. 143 type FilesystemInfo struct { 144 Size uint64 `bson:"size"` 145 Pool string `bson:"pool"` 146 147 // FilesystemId is the provider-allocated unique ID of the 148 // filesystem. This will be unspecified for filesystems 149 // backed by volumes. 150 FilesystemId string `bson:"filesystemid"` 151 } 152 153 // FilesystemAttachmentInfo describes information about a filesystem attachment. 154 type FilesystemAttachmentInfo struct { 155 // MountPoint is the path at which the filesystem is mounted on the 156 // machine. MountPoint may be empty, meaning that the filesystem is 157 // not mounted yet. 158 MountPoint string `bson:"mountpoint"` 159 ReadOnly bool `bson:"read-only"` 160 } 161 162 // FilesystemAttachmentParams records parameters for attaching a filesystem to a 163 // machine. 164 type FilesystemAttachmentParams struct { 165 // locationAutoGenerated records whether or not the Location 166 // field's value was automatically generated, and thus known 167 // to be unique. This is used to optimise away mount point 168 // conflict checks. 169 locationAutoGenerated bool 170 Location string `bson:"location"` 171 ReadOnly bool `bson:"read-only"` 172 } 173 174 // validate validates the contents of the filesystem document. 175 func (f *filesystem) validate() error { 176 if f.doc.Binding != "" { 177 tag, err := names.ParseTag(f.doc.Binding) 178 if err != nil { 179 return errors.Annotate(err, "parsing binding") 180 } 181 switch tag.(type) { 182 case names.ModelTag: 183 // TODO(axw) support binding to model 184 return errors.NotSupportedf("binding to model") 185 case names.MachineTag: 186 case names.StorageTag: 187 default: 188 return errors.Errorf("invalid binding: %v", f.doc.Binding) 189 } 190 } 191 return nil 192 } 193 194 // globalKey is required to implement GlobalEntity. 195 func (f *filesystem) globalKey() string { 196 return filesystemGlobalKey(f.doc.FilesystemId) 197 } 198 199 // Tag is required to implement GlobalEntity. 200 func (f *filesystem) Tag() names.Tag { 201 return f.FilesystemTag() 202 } 203 204 // FilesystemTag is required to implement Filesystem. 205 func (f *filesystem) FilesystemTag() names.FilesystemTag { 206 return names.NewFilesystemTag(f.doc.FilesystemId) 207 } 208 209 // Life is required to implement Filesystem. 210 func (f *filesystem) Life() Life { 211 return f.doc.Life 212 } 213 214 // LifeBinding is required to implement LifeBinder. 215 // 216 // Below is the set of possible entity types that a volume may be bound 217 // to, and a description of the effects of doing so: 218 // 219 // Machine: If the filesystem is bound to a machine, then the 220 // filesystem will be destroyed when it is detached from 221 // the machine. It is not permitted for a filesystem to 222 // be attached to multiple machines while it is bound to 223 // a machine. 224 // Storage: If the filesystem is bound to a storage instance, 225 // then the filesystem will be destroyed when the 226 // storage insance is removed from state. 227 // Model: If the filesystem is bound to the model, then 228 // the filesystem must be destroyed prior to the 229 // model being destroyed. 230 func (f *filesystem) LifeBinding() names.Tag { 231 if f.doc.Binding == "" { 232 return nil 233 } 234 // Tag is validated in filesystem.validate. 235 tag, _ := names.ParseTag(f.doc.Binding) 236 return tag 237 } 238 239 // Storage is required to implement Filesystem. 240 func (f *filesystem) Storage() (names.StorageTag, error) { 241 if f.doc.StorageId == "" { 242 msg := fmt.Sprintf("filesystem %q is not assigned to any storage instance", f.Tag().Id()) 243 return names.StorageTag{}, errors.NewNotAssigned(nil, msg) 244 } 245 return names.NewStorageTag(f.doc.StorageId), nil 246 } 247 248 // Volume is required to implement Filesystem. 249 func (f *filesystem) Volume() (names.VolumeTag, error) { 250 if f.doc.VolumeId == "" { 251 return names.VolumeTag{}, ErrNoBackingVolume 252 } 253 return names.NewVolumeTag(f.doc.VolumeId), nil 254 } 255 256 // Info is required to implement Filesystem. 257 func (f *filesystem) Info() (FilesystemInfo, error) { 258 if f.doc.Info == nil { 259 return FilesystemInfo{}, errors.NotProvisionedf("filesystem %q", f.doc.FilesystemId) 260 } 261 return *f.doc.Info, nil 262 } 263 264 // Params is required to implement Filesystem. 265 func (f *filesystem) Params() (FilesystemParams, bool) { 266 if f.doc.Params == nil { 267 return FilesystemParams{}, false 268 } 269 return *f.doc.Params, true 270 } 271 272 // Status is required to implement StatusGetter. 273 func (f *filesystem) Status() (status.StatusInfo, error) { 274 return f.st.FilesystemStatus(f.FilesystemTag()) 275 } 276 277 // SetStatus is required to implement StatusSetter. 278 func (f *filesystem) SetStatus(fsStatus status.StatusInfo) error { 279 return f.st.SetFilesystemStatus(f.FilesystemTag(), fsStatus.Status, fsStatus.Message, fsStatus.Data, fsStatus.Since) 280 } 281 282 // Filesystem is required to implement FilesystemAttachment. 283 func (f *filesystemAttachment) Filesystem() names.FilesystemTag { 284 return names.NewFilesystemTag(f.doc.Filesystem) 285 } 286 287 // Machine is required to implement FilesystemAttachment. 288 func (f *filesystemAttachment) Machine() names.MachineTag { 289 return names.NewMachineTag(f.doc.Machine) 290 } 291 292 // Life is required to implement FilesystemAttachment. 293 func (f *filesystemAttachment) Life() Life { 294 return f.doc.Life 295 } 296 297 // Info is required to implement FilesystemAttachment. 298 func (f *filesystemAttachment) Info() (FilesystemAttachmentInfo, error) { 299 if f.doc.Info == nil { 300 return FilesystemAttachmentInfo{}, errors.NotProvisionedf("filesystem attachment %q on %q", f.doc.Filesystem, f.doc.Machine) 301 } 302 return *f.doc.Info, nil 303 } 304 305 // Params is required to implement FilesystemAttachment. 306 func (f *filesystemAttachment) Params() (FilesystemAttachmentParams, bool) { 307 if f.doc.Params == nil { 308 return FilesystemAttachmentParams{}, false 309 } 310 return *f.doc.Params, true 311 } 312 313 // Filesystem returns the Filesystem with the specified name. 314 func (st *State) Filesystem(tag names.FilesystemTag) (Filesystem, error) { 315 f, err := st.filesystemByTag(tag) 316 return f, err 317 } 318 319 func (st *State) filesystemByTag(tag names.FilesystemTag) (*filesystem, error) { 320 query := bson.D{{"_id", tag.Id()}} 321 description := fmt.Sprintf("filesystem %q", tag.Id()) 322 return st.filesystem(query, description) 323 } 324 325 func (st *State) storageInstanceFilesystem(tag names.StorageTag) (*filesystem, error) { 326 query := bson.D{{"storageid", tag.Id()}} 327 description := fmt.Sprintf("filesystem for storage instance %q", tag.Id()) 328 return st.filesystem(query, description) 329 } 330 331 // StorageInstanceFilesystem returns the Filesystem assigned to the specified 332 // storage instance. 333 func (st *State) StorageInstanceFilesystem(tag names.StorageTag) (Filesystem, error) { 334 f, err := st.storageInstanceFilesystem(tag) 335 return f, err 336 } 337 338 func (st *State) volumeFilesystem(tag names.VolumeTag) (*filesystem, error) { 339 query := bson.D{{"volumeid", tag.Id()}} 340 description := fmt.Sprintf("filesystem for volume %q", tag.Id()) 341 return st.filesystem(query, description) 342 } 343 344 // VolumeFilesystem returns the Filesystem backed by the specified volume. 345 func (st *State) VolumeFilesystem(tag names.VolumeTag) (Filesystem, error) { 346 f, err := st.volumeFilesystem(tag) 347 return f, err 348 } 349 350 func (st *State) filesystems(query interface{}) ([]*filesystem, error) { 351 coll, cleanup := st.getCollection(filesystemsC) 352 defer cleanup() 353 354 var fDocs []filesystemDoc 355 err := coll.Find(query).All(&fDocs) 356 if err != nil { 357 return nil, errors.Trace(err) 358 } 359 filesystems := make([]*filesystem, len(fDocs)) 360 for i, doc := range fDocs { 361 f := &filesystem{st, doc} 362 if err := f.validate(); err != nil { 363 return nil, errors.Annotate(err, "filesystem validation failed") 364 } 365 filesystems[i] = f 366 } 367 return filesystems, nil 368 } 369 370 func (st *State) filesystem(query bson.D, description string) (*filesystem, error) { 371 coll, cleanup := st.getCollection(filesystemsC) 372 defer cleanup() 373 374 f := filesystem{st: st} 375 err := coll.Find(query).One(&f.doc) 376 if err == mgo.ErrNotFound { 377 return nil, errors.NotFoundf(description) 378 } else if err != nil { 379 return nil, errors.Annotate(err, "cannot get filesystem") 380 } 381 if err := f.validate(); err != nil { 382 return nil, errors.Annotate(err, "validating filesystem") 383 } 384 return &f, nil 385 } 386 387 // FilesystemAttachment returns the FilesystemAttachment corresponding to 388 // the specified filesystem and machine. 389 func (st *State) FilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (FilesystemAttachment, error) { 390 coll, cleanup := st.getCollection(filesystemAttachmentsC) 391 defer cleanup() 392 393 var att filesystemAttachment 394 err := coll.FindId(filesystemAttachmentId(machine.Id(), filesystem.Id())).One(&att.doc) 395 if err == mgo.ErrNotFound { 396 return nil, errors.NotFoundf("filesystem %q on machine %q", filesystem.Id(), machine.Id()) 397 } else if err != nil { 398 return nil, errors.Annotatef(err, "getting filesystem %q on machine %q", filesystem.Id(), machine.Id()) 399 } 400 return &att, nil 401 } 402 403 // FilesystemAttachments returns all of the FilesystemAttachments for the 404 // specified filesystem. 405 func (st *State) FilesystemAttachments(filesystem names.FilesystemTag) ([]FilesystemAttachment, error) { 406 attachments, err := st.filesystemAttachments(bson.D{{"filesystemid", filesystem.Id()}}) 407 if err != nil { 408 return nil, errors.Annotatef(err, "getting attachments for filesystem %q", filesystem.Id()) 409 } 410 return attachments, nil 411 } 412 413 // MachineFilesystemAttachments returns all of the FilesystemAttachments for the 414 // specified machine. 415 func (st *State) MachineFilesystemAttachments(machine names.MachineTag) ([]FilesystemAttachment, error) { 416 attachments, err := st.filesystemAttachments(bson.D{{"machineid", machine.Id()}}) 417 if err != nil { 418 return nil, errors.Annotatef(err, "getting filesystem attachments for machine %q", machine.Id()) 419 } 420 return attachments, nil 421 } 422 423 func (st *State) filesystemAttachments(query bson.D) ([]FilesystemAttachment, error) { 424 coll, cleanup := st.getCollection(filesystemAttachmentsC) 425 defer cleanup() 426 427 var docs []filesystemAttachmentDoc 428 err := coll.Find(query).All(&docs) 429 if err == mgo.ErrNotFound { 430 return nil, nil 431 } else if err != nil { 432 return nil, errors.Trace(err) 433 } 434 attachments := make([]FilesystemAttachment, len(docs)) 435 for i, doc := range docs { 436 attachments[i] = &filesystemAttachment{doc} 437 } 438 return attachments, nil 439 } 440 441 // removeMachineFilesystemsOps returns txn.Ops to remove non-persistent filesystems 442 // attached to the specified machine. This is used when the given machine is 443 // being removed from state. 444 func (st *State) removeMachineFilesystemsOps(machine names.MachineTag) ([]txn.Op, error) { 445 attachments, err := st.MachineFilesystemAttachments(machine) 446 if err != nil { 447 return nil, errors.Trace(err) 448 } 449 ops := make([]txn.Op, 0, len(attachments)) 450 for _, a := range attachments { 451 filesystemTag := a.Filesystem() 452 // When removing the machine, there should only remain 453 // non-persistent storage. This will be implicitly 454 // removed when the machine is removed, so we do not 455 // use removeFilesystemAttachmentOps or removeFilesystemOps, 456 // which track and update related documents. 457 ops = append(ops, txn.Op{ 458 C: filesystemAttachmentsC, 459 Id: filesystemAttachmentId(machine.Id(), filesystemTag.Id()), 460 Assert: txn.DocExists, 461 Remove: true, 462 }) 463 canRemove, err := isFilesystemInherentlyMachineBound(st, filesystemTag) 464 if err != nil { 465 return nil, errors.Trace(err) 466 } 467 if !canRemove { 468 return nil, errors.Errorf("machine has non-machine bound filesystem %v", filesystemTag.Id()) 469 } 470 ops = append(ops, txn.Op{ 471 C: filesystemsC, 472 Id: filesystemTag.Id(), 473 Assert: txn.DocExists, 474 Remove: true, 475 }) 476 } 477 return ops, nil 478 } 479 480 // isFilesystemInherentlyMachineBound reports whether or not the filesystem 481 // with the specified tag is inherently bound to the lifetime of the machine, 482 // and will be removed along with it, leaving no resources dangling. 483 func isFilesystemInherentlyMachineBound(st *State, tag names.FilesystemTag) (bool, error) { 484 // TODO(axw) when we have support for persistent filesystems, 485 // e.g. NFS shares, then we need to check the filesystem info 486 // to decide whether or not to remove. 487 return true, nil 488 } 489 490 // DetachFilesystem marks the filesystem attachment identified by the specified machine 491 // and filesystem tags as Dying, if it is Alive. 492 func (st *State) DetachFilesystem(machine names.MachineTag, filesystem names.FilesystemTag) (err error) { 493 defer errors.DeferredAnnotatef(&err, "detaching filesystem %s from machine %s", filesystem.Id(), machine.Id()) 494 buildTxn := func(attempt int) ([]txn.Op, error) { 495 fsa, err := st.FilesystemAttachment(machine, filesystem) 496 if err != nil { 497 return nil, errors.Trace(err) 498 } 499 if fsa.Life() != Alive { 500 return nil, jujutxn.ErrNoOperations 501 } 502 ops := detachFilesystemOps(machine, filesystem) 503 return ops, nil 504 } 505 return st.run(buildTxn) 506 } 507 508 func (st *State) filesystemVolumeAttachment(m names.MachineTag, f names.FilesystemTag) (VolumeAttachment, error) { 509 filesystem, err := st.Filesystem(f) 510 if err != nil { 511 return nil, errors.Trace(err) 512 } 513 v, err := filesystem.Volume() 514 if err != nil { 515 return nil, errors.Trace(err) 516 } 517 return st.VolumeAttachment(m, v) 518 } 519 520 func detachFilesystemOps(m names.MachineTag, f names.FilesystemTag) []txn.Op { 521 return []txn.Op{{ 522 C: filesystemAttachmentsC, 523 Id: filesystemAttachmentId(m.Id(), f.Id()), 524 Assert: isAliveDoc, 525 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 526 }} 527 } 528 529 // RemoveFilesystemAttachment removes the filesystem attachment from state. 530 // Removing a volume-backed filesystem attachment will cause the volume to 531 // be detached. 532 func (st *State) RemoveFilesystemAttachment(machine names.MachineTag, filesystem names.FilesystemTag) (err error) { 533 defer errors.DeferredAnnotatef(&err, "removing attachment of filesystem %s from machine %s", filesystem.Id(), machine.Id()) 534 buildTxn := func(attempt int) ([]txn.Op, error) { 535 attachment, err := st.FilesystemAttachment(machine, filesystem) 536 if errors.IsNotFound(err) && attempt > 0 { 537 // We only ignore IsNotFound on attempts after the 538 // first, since we expect the filesystem attachment to 539 // be there initially. 540 return nil, jujutxn.ErrNoOperations 541 } 542 if err != nil { 543 return nil, errors.Trace(err) 544 } 545 if attachment.Life() != Dying { 546 return nil, errors.New("filesystem attachment is not dying") 547 } 548 f, err := st.filesystemByTag(filesystem) 549 if err != nil { 550 return nil, errors.Trace(err) 551 } 552 ops := removeFilesystemAttachmentOps(machine, f) 553 // If the filesystem is backed by a volume, the volume 554 // attachment can and should be destroyed once the 555 // filesystem attachment is removed. 556 volumeAttachment, err := st.filesystemVolumeAttachment(machine, filesystem) 557 if err != nil { 558 if errors.Cause(err) != ErrNoBackingVolume && !errors.IsNotFound(err) { 559 return nil, errors.Trace(err) 560 } 561 } else if volumeAttachment.Life() == Alive { 562 ops = append(ops, detachVolumeOps(machine, volumeAttachment.Volume())...) 563 } 564 return ops, nil 565 } 566 return st.run(buildTxn) 567 } 568 569 func removeFilesystemAttachmentOps(m names.MachineTag, f *filesystem) []txn.Op { 570 decrefFilesystemOp := machineStorageDecrefOp( 571 filesystemsC, f.doc.FilesystemId, 572 f.doc.AttachmentCount, f.doc.Life, 573 m, f.doc.Binding, 574 ) 575 return []txn.Op{{ 576 C: filesystemAttachmentsC, 577 Id: filesystemAttachmentId(m.Id(), f.doc.FilesystemId), 578 Assert: bson.D{{"life", Dying}}, 579 Remove: true, 580 }, decrefFilesystemOp, { 581 C: machinesC, 582 Id: m.Id(), 583 Assert: txn.DocExists, 584 Update: bson.D{{"$pull", bson.D{{"filesystems", f.doc.FilesystemId}}}}, 585 }} 586 } 587 588 // DestroyFilesystem ensures that the filesystem and any attachments to it will 589 // be destroyed and removed from state at some point in the future. 590 func (st *State) DestroyFilesystem(tag names.FilesystemTag) (err error) { 591 buildTxn := func(attempt int) ([]txn.Op, error) { 592 filesystem, err := st.filesystemByTag(tag) 593 if errors.IsNotFound(err) { 594 return nil, jujutxn.ErrNoOperations 595 } else if err != nil { 596 return nil, errors.Trace(err) 597 } 598 if filesystem.doc.Life != Alive { 599 return nil, jujutxn.ErrNoOperations 600 } 601 return destroyFilesystemOps(st, filesystem), nil 602 } 603 return st.run(buildTxn) 604 } 605 606 func destroyFilesystemOps(st *State, f *filesystem) []txn.Op { 607 if f.doc.AttachmentCount == 0 { 608 hasNoAttachments := bson.D{{"attachmentcount", 0}} 609 return []txn.Op{{ 610 C: filesystemsC, 611 Id: f.doc.FilesystemId, 612 Assert: append(hasNoAttachments, isAliveDoc...), 613 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 614 }} 615 } 616 hasAttachments := bson.D{{"attachmentcount", bson.D{{"$gt", 0}}}} 617 cleanupOp := newCleanupOp(cleanupAttachmentsForDyingFilesystem, f.doc.FilesystemId) 618 return []txn.Op{{ 619 C: filesystemsC, 620 Id: f.doc.FilesystemId, 621 Assert: append(hasAttachments, isAliveDoc...), 622 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 623 }, cleanupOp} 624 } 625 626 // RemoveFilesystem removes the filesystem from state. RemoveFilesystem will 627 // fail if there are any attachments remaining, or if the filesystem is not 628 // Dying. Removing a volume-backed filesystem will cause the volume to be 629 // destroyed. 630 func (st *State) RemoveFilesystem(tag names.FilesystemTag) (err error) { 631 defer errors.DeferredAnnotatef(&err, "removing filesystem %s", tag.Id()) 632 buildTxn := func(attempt int) ([]txn.Op, error) { 633 filesystem, err := st.Filesystem(tag) 634 if errors.IsNotFound(err) { 635 return nil, jujutxn.ErrNoOperations 636 } else if err != nil { 637 return nil, errors.Trace(err) 638 } 639 if filesystem.Life() != Dead { 640 return nil, errors.New("filesystem is not dead") 641 } 642 return removeFilesystemOps(st, filesystem) 643 } 644 return st.run(buildTxn) 645 } 646 647 func removeFilesystemOps(st *State, filesystem Filesystem) ([]txn.Op, error) { 648 ops := []txn.Op{ 649 { 650 C: filesystemsC, 651 Id: filesystem.Tag().Id(), 652 Assert: txn.DocExists, 653 Remove: true, 654 }, 655 removeStatusOp(st, filesystem.globalKey()), 656 } 657 // If the filesystem is backed by a volume, the volume should 658 // be destroyed once the filesystem is removed if it is bound 659 // to the filesystem. 660 volumeTag, err := filesystem.Volume() 661 if err == nil { 662 volume, err := st.volumeByTag(volumeTag) 663 if err != nil { 664 return nil, errors.Trace(err) 665 } 666 if volume.LifeBinding() == filesystem.Tag() { 667 ops = append(ops, destroyVolumeOps(st, volume)...) 668 } 669 } else if err != ErrNoBackingVolume { 670 return nil, errors.Trace(err) 671 } 672 return ops, nil 673 } 674 675 // filesystemAttachmentId returns a filesystem attachment document ID, 676 // given the corresponding filesystem name and machine ID. 677 func filesystemAttachmentId(machineId, filesystemId string) string { 678 return fmt.Sprintf("%s:%s", machineId, filesystemId) 679 } 680 681 // ParseFilesystemAttachmentId parses a string as a filesystem attachment ID, 682 // returning the machine and filesystem components. 683 func ParseFilesystemAttachmentId(id string) (names.MachineTag, names.FilesystemTag, error) { 684 fields := strings.SplitN(id, ":", 2) 685 if len(fields) != 2 || !names.IsValidMachine(fields[0]) || !names.IsValidFilesystem(fields[1]) { 686 return names.MachineTag{}, names.FilesystemTag{}, errors.Errorf("invalid filesystem attachment ID %q", id) 687 } 688 machineTag := names.NewMachineTag(fields[0]) 689 filesystemTag := names.NewFilesystemTag(fields[1]) 690 return machineTag, filesystemTag, nil 691 } 692 693 // newFilesystemId returns a unique filesystem ID. 694 // If the machine ID supplied is non-empty, the 695 // filesystem ID will incorporate it as the 696 // filesystem's machine scope. 697 func newFilesystemId(st *State, machineId string) (string, error) { 698 seq, err := st.sequence("filesystem") 699 if err != nil { 700 return "", errors.Trace(err) 701 } 702 id := fmt.Sprint(seq) 703 if machineId != "" { 704 id = machineId + "/" + id 705 } 706 return id, nil 707 } 708 709 // addFilesystemOps returns txn.Ops to create a new filesystem with the 710 // specified parameters. If the storage source cannot create filesystems 711 // directly, a volume will be created and Juju will manage a filesystem 712 // on it. 713 func (st *State) addFilesystemOps(params FilesystemParams, machineId string) ([]txn.Op, names.FilesystemTag, names.VolumeTag, error) { 714 if params.binding == nil { 715 params.binding = names.NewMachineTag(machineId) 716 } 717 params, err := st.filesystemParamsWithDefaults(params) 718 if err != nil { 719 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) 720 } 721 machineId, err = st.validateFilesystemParams(params, machineId) 722 if err != nil { 723 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "validating filesystem params") 724 } 725 726 filesystemId, err := newFilesystemId(st, machineId) 727 if err != nil { 728 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "cannot generate filesystem name") 729 } 730 filesystemTag := names.NewFilesystemTag(filesystemId) 731 732 // Check if the filesystem needs a volume. 733 var volumeId string 734 var volumeTag names.VolumeTag 735 var ops []txn.Op 736 _, provider, err := poolStorageProvider(st, params.Pool) 737 if err != nil { 738 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Trace(err) 739 } 740 if !provider.Supports(storage.StorageKindFilesystem) { 741 var volumeOps []txn.Op 742 volumeParams := VolumeParams{ 743 params.storage, 744 filesystemTag, // volume is bound to filesystem 745 params.Pool, 746 params.Size, 747 } 748 volumeOps, volumeTag, err = st.addVolumeOps(volumeParams, machineId) 749 if err != nil { 750 return nil, names.FilesystemTag{}, names.VolumeTag{}, errors.Annotate(err, "creating backing volume") 751 } 752 volumeId = volumeTag.Id() 753 ops = append(ops, volumeOps...) 754 } 755 756 status := statusDoc{ 757 Status: status.Pending, 758 Updated: st.clock.Now().UnixNano(), 759 } 760 doc := filesystemDoc{ 761 FilesystemId: filesystemId, 762 VolumeId: volumeId, 763 StorageId: params.storage.Id(), 764 Binding: params.binding.String(), 765 Params: ¶ms, 766 // Every filesystem is created with one attachment. 767 AttachmentCount: 1, 768 } 769 ops = append(ops, st.newFilesystemOps(doc, status)...) 770 return ops, filesystemTag, volumeTag, nil 771 } 772 773 func (st *State) newFilesystemOps(doc filesystemDoc, status statusDoc) []txn.Op { 774 return []txn.Op{ 775 createStatusOp(st, filesystemGlobalKey(doc.FilesystemId), status), 776 { 777 C: filesystemsC, 778 Id: doc.FilesystemId, 779 Assert: txn.DocMissing, 780 Insert: &doc, 781 }, 782 } 783 } 784 785 func (st *State) filesystemParamsWithDefaults(params FilesystemParams) (FilesystemParams, error) { 786 if params.Pool != "" { 787 return params, nil 788 } 789 envConfig, err := st.ModelConfig() 790 if err != nil { 791 return FilesystemParams{}, errors.Trace(err) 792 } 793 cons := StorageConstraints{ 794 Pool: params.Pool, 795 Size: params.Size, 796 Count: 1, 797 } 798 poolName, err := defaultStoragePool(envConfig, storage.StorageKindFilesystem, cons) 799 if err != nil { 800 return FilesystemParams{}, errors.Annotate(err, "getting default filesystem storage pool") 801 } 802 params.Pool = poolName 803 return params, nil 804 } 805 806 // validateFilesystemParams validates the filesystem parameters, and returns the 807 // machine ID to use as the scope in the filesystem tag. 808 func (st *State) validateFilesystemParams(params FilesystemParams, machineId string) (maybeMachineId string, _ error) { 809 err := validateStoragePool(st, params.Pool, storage.StorageKindFilesystem, &machineId) 810 if err != nil { 811 return "", errors.Trace(err) 812 } 813 if params.Size == 0 { 814 return "", errors.New("invalid size 0") 815 } 816 return machineId, nil 817 } 818 819 type filesystemAttachmentTemplate struct { 820 tag names.FilesystemTag 821 storage names.StorageTag // may be zero-value 822 params FilesystemAttachmentParams 823 } 824 825 // createMachineFilesystemAttachmentInfo creates filesystem 826 // attachments for the specified machine, and attachment 827 // parameters keyed by filesystem tags. 828 func createMachineFilesystemAttachmentsOps(machineId string, attachments []filesystemAttachmentTemplate) []txn.Op { 829 ops := make([]txn.Op, len(attachments)) 830 for i, attachment := range attachments { 831 paramsCopy := attachment.params 832 ops[i] = txn.Op{ 833 C: filesystemAttachmentsC, 834 Id: filesystemAttachmentId(machineId, attachment.tag.Id()), 835 Assert: txn.DocMissing, 836 Insert: &filesystemAttachmentDoc{ 837 Filesystem: attachment.tag.Id(), 838 Machine: machineId, 839 Params: ¶msCopy, 840 }, 841 } 842 } 843 return ops 844 } 845 846 // SetFilesystemInfo sets the FilesystemInfo for the specified filesystem. 847 func (st *State) SetFilesystemInfo(tag names.FilesystemTag, info FilesystemInfo) (err error) { 848 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem %q", tag.Id()) 849 if info.FilesystemId == "" { 850 return errors.New("filesystem ID not set") 851 } 852 fs, err := st.Filesystem(tag) 853 if err != nil { 854 return errors.Trace(err) 855 } 856 // If the filesystem is volume-backed, the volume must be provisioned 857 // and attachment first. 858 if volumeTag, err := fs.Volume(); err == nil { 859 machineTag, ok := names.FilesystemMachine(tag) 860 if !ok { 861 return errors.Errorf("filesystem %s is not machine-scoped, but volume-backed", tag.Id()) 862 } 863 volumeAttachment, err := st.VolumeAttachment(machineTag, volumeTag) 864 if err != nil { 865 return errors.Trace(err) 866 } 867 if _, err := volumeAttachment.Info(); err != nil { 868 return errors.Trace(err) 869 } 870 } else if errors.Cause(err) != ErrNoBackingVolume { 871 return errors.Trace(err) 872 } 873 buildTxn := func(attempt int) ([]txn.Op, error) { 874 if attempt > 0 { 875 fs, err = st.Filesystem(tag) 876 if err != nil { 877 return nil, errors.Trace(err) 878 } 879 } 880 // If the filesystem has parameters, unset them 881 // when we set info for the first time, ensuring 882 // that params and info are mutually exclusive. 883 var unsetParams bool 884 if params, ok := fs.Params(); ok { 885 info.Pool = params.Pool 886 unsetParams = true 887 } else { 888 // Ensure immutable properties do not change. 889 oldInfo, err := fs.Info() 890 if err != nil { 891 return nil, err 892 } 893 if err := validateFilesystemInfoChange(info, oldInfo); err != nil { 894 return nil, err 895 } 896 } 897 ops := setFilesystemInfoOps(tag, info, unsetParams) 898 return ops, nil 899 } 900 return st.run(buildTxn) 901 } 902 903 func validateFilesystemInfoChange(newInfo, oldInfo FilesystemInfo) error { 904 if newInfo.Pool != oldInfo.Pool { 905 return errors.Errorf( 906 "cannot change pool from %q to %q", 907 oldInfo.Pool, newInfo.Pool, 908 ) 909 } 910 if newInfo.FilesystemId != oldInfo.FilesystemId { 911 return errors.Errorf( 912 "cannot change filesystem ID from %q to %q", 913 oldInfo.FilesystemId, newInfo.FilesystemId, 914 ) 915 } 916 return nil 917 } 918 919 func setFilesystemInfoOps(tag names.FilesystemTag, info FilesystemInfo, unsetParams bool) []txn.Op { 920 asserts := isAliveDoc 921 update := bson.D{ 922 {"$set", bson.D{{"info", &info}}}, 923 } 924 if unsetParams { 925 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 926 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 927 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 928 } 929 return []txn.Op{{ 930 C: filesystemsC, 931 Id: tag.Id(), 932 Assert: asserts, 933 Update: update, 934 }} 935 } 936 937 // SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the 938 // specified filesystem attachment. 939 func (st *State) SetFilesystemAttachmentInfo( 940 machineTag names.MachineTag, 941 filesystemTag names.FilesystemTag, 942 info FilesystemAttachmentInfo, 943 ) (err error) { 944 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), machineTag.Id()) 945 f, err := st.Filesystem(filesystemTag) 946 if err != nil { 947 return errors.Trace(err) 948 } 949 // Ensure filesystem is provisioned before setting attachment info. 950 // A filesystem cannot go from being provisioned to unprovisioned, 951 // so there is no txn.Op for this below. 952 if _, err := f.Info(); err != nil { 953 return errors.Trace(err) 954 } 955 // Also ensure the machine is provisioned. 956 m, err := st.Machine(machineTag.Id()) 957 if err != nil { 958 return errors.Trace(err) 959 } 960 if _, err := m.InstanceId(); err != nil { 961 return errors.Trace(err) 962 } 963 buildTxn := func(attempt int) ([]txn.Op, error) { 964 fsa, err := st.FilesystemAttachment(machineTag, filesystemTag) 965 if err != nil { 966 return nil, errors.Trace(err) 967 } 968 // If the filesystem attachment has parameters, unset them 969 // when we set info for the first time, ensuring that params 970 // and info are mutually exclusive. 971 _, unsetParams := fsa.Params() 972 ops := setFilesystemAttachmentInfoOps(machineTag, filesystemTag, info, unsetParams) 973 return ops, nil 974 } 975 return st.run(buildTxn) 976 } 977 978 func setFilesystemAttachmentInfoOps( 979 machine names.MachineTag, 980 filesystem names.FilesystemTag, 981 info FilesystemAttachmentInfo, 982 unsetParams bool, 983 ) []txn.Op { 984 asserts := isAliveDoc 985 update := bson.D{ 986 {"$set", bson.D{{"info", &info}}}, 987 } 988 if unsetParams { 989 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 990 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 991 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 992 } 993 return []txn.Op{{ 994 C: filesystemAttachmentsC, 995 Id: filesystemAttachmentId(machine.Id(), filesystem.Id()), 996 Assert: asserts, 997 Update: update, 998 }} 999 } 1000 1001 // filesystemMountPoint returns a mount point to use for the given charm 1002 // storage. For stores with potentially multiple instances, the instance 1003 // name is appended to the location. 1004 func filesystemMountPoint( 1005 meta charm.Storage, 1006 tag names.StorageTag, 1007 series string, 1008 ) (string, error) { 1009 storageDir, err := paths.StorageDir(series) 1010 if err != nil { 1011 return "", errors.Trace(err) 1012 } 1013 if strings.HasPrefix(meta.Location, storageDir) { 1014 return "", errors.Errorf( 1015 "invalid location %q: must not fall within %q", 1016 meta.Location, storageDir, 1017 ) 1018 } 1019 if meta.Location != "" && meta.CountMax == 1 { 1020 // The location is specified and it's a singleton 1021 // store, so just use the location as-is. 1022 return meta.Location, nil 1023 } 1024 // If the location is unspecified then we use 1025 // <storage-dir>/<storage-id> as the location. 1026 // Otherwise, we use <location>/<storage-id>. 1027 if meta.Location != "" { 1028 storageDir = meta.Location 1029 } 1030 return path.Join(storageDir, tag.Id()), nil 1031 } 1032 1033 // validateFilesystemMountPoints validates the mount points of filesystems 1034 // being attached to the specified machine. If there are any mount point 1035 // path conflicts, an error will be returned. 1036 func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error { 1037 attachments, err := m.st.MachineFilesystemAttachments(m.MachineTag()) 1038 if err != nil { 1039 return errors.Trace(err) 1040 } 1041 existing := make(map[names.FilesystemTag]string) 1042 for _, a := range attachments { 1043 params, ok := a.Params() 1044 if ok { 1045 existing[a.Filesystem()] = params.Location 1046 continue 1047 } 1048 info, err := a.Info() 1049 if err != nil { 1050 return errors.Trace(err) 1051 } 1052 existing[a.Filesystem()] = info.MountPoint 1053 } 1054 1055 storageName := func( 1056 filesystemTag names.FilesystemTag, 1057 storageTag names.StorageTag, 1058 ) string { 1059 if storageTag == (names.StorageTag{}) { 1060 return names.ReadableString(filesystemTag) 1061 } 1062 // We know the tag is valid, so ignore the error. 1063 storageName, _ := names.StorageName(storageTag.Id()) 1064 return fmt.Sprintf("%q storage", storageName) 1065 } 1066 1067 containsPath := func(a, b string) bool { 1068 a = path.Clean(a) + "/" 1069 b = path.Clean(b) + "/" 1070 return strings.HasPrefix(b, a) 1071 } 1072 1073 // These sets are expected to be small, so sorting and comparing 1074 // adjacent values is not worth the cost of creating a reverse 1075 // lookup from location to filesystem. 1076 for _, template := range newFilesystems { 1077 newMountPoint := template.params.Location 1078 for oldFilesystemTag, oldMountPoint := range existing { 1079 var conflicted, swapOrder bool 1080 if containsPath(oldMountPoint, newMountPoint) { 1081 conflicted = true 1082 } else if containsPath(newMountPoint, oldMountPoint) { 1083 conflicted = true 1084 swapOrder = true 1085 } 1086 if !conflicted { 1087 continue 1088 } 1089 1090 // Get a helpful identifier for the new filesystem. If it 1091 // is being created for a storage instance, then use 1092 // the storage name; otherwise use the filesystem name. 1093 newStorageName := storageName(template.tag, template.storage) 1094 1095 // Likewise for the old filesystem, but this time we'll 1096 // need to consult state. 1097 oldFilesystem, err := m.st.Filesystem(oldFilesystemTag) 1098 if err != nil { 1099 return errors.Trace(err) 1100 } 1101 storageTag, err := oldFilesystem.Storage() 1102 if errors.IsNotAssigned(err) { 1103 storageTag = names.StorageTag{} 1104 } else if err != nil { 1105 return errors.Trace(err) 1106 } 1107 oldStorageName := storageName(oldFilesystemTag, storageTag) 1108 1109 lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName) 1110 rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName) 1111 if swapOrder { 1112 lhs, rhs = rhs, lhs 1113 } 1114 return errors.Errorf("%s contains %s", lhs, rhs) 1115 } 1116 } 1117 return nil 1118 } 1119 1120 // AllFilesystems returns all Filesystems for this state. 1121 func (st *State) AllFilesystems() ([]Filesystem, error) { 1122 filesystems, err := st.filesystems(nil) 1123 if err != nil { 1124 return nil, errors.Annotate(err, "cannot get filesystems") 1125 } 1126 return filesystemsToInterfaces(filesystems), nil 1127 } 1128 1129 func filesystemsToInterfaces(fs []*filesystem) []Filesystem { 1130 result := make([]Filesystem, len(fs)) 1131 for i, f := range fs { 1132 result[i] = f 1133 } 1134 return result 1135 } 1136 1137 func filesystemGlobalKey(name string) string { 1138 return "f#" + name 1139 } 1140 1141 // FilesystemStatus returns the status of the specified filesystem. 1142 func (st *State) FilesystemStatus(tag names.FilesystemTag) (status.StatusInfo, error) { 1143 return getStatus(st, filesystemGlobalKey(tag.Id()), "filesystem") 1144 } 1145 1146 // SetFilesystemStatus sets the status of the specified filesystem. 1147 func (st *State) SetFilesystemStatus(tag names.FilesystemTag, fsStatus status.Status, info string, data map[string]interface{}, updated *time.Time) error { 1148 switch fsStatus { 1149 case status.Attaching, status.Attached, status.Detaching, status.Detached, status.Destroying: 1150 case status.Error: 1151 if info == "" { 1152 return errors.Errorf("cannot set status %q without info", fsStatus) 1153 } 1154 case status.Pending: 1155 // If a filesystem is not yet provisioned, we allow its status 1156 // to be set back to pending (when a retry is to occur). 1157 v, err := st.Filesystem(tag) 1158 if err != nil { 1159 return errors.Trace(err) 1160 } 1161 _, err = v.Info() 1162 if errors.IsNotProvisioned(err) { 1163 break 1164 } 1165 return errors.Errorf("cannot set status %q", fsStatus) 1166 default: 1167 return errors.Errorf("cannot set invalid status %q", fsStatus) 1168 } 1169 return setStatus(st, setStatusParams{ 1170 badge: "filesystem", 1171 globalKey: filesystemGlobalKey(tag.Id()), 1172 status: fsStatus, 1173 message: info, 1174 rawData: data, 1175 updated: updated, 1176 }) 1177 }