github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "github.com/juju/names" 14 jujutxn "github.com/juju/txn" 15 "gopkg.in/juju/charm.v6-unstable" 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.Status, info string, data map[string]interface{}) error { 279 return f.st.SetFilesystemStatus(f.FilesystemTag(), fsStatus, info, data) 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 := st.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 filesystemOps := []txn.Op{ 757 createStatusOp(st, filesystemGlobalKey(filesystemId), statusDoc{ 758 Status: status.StatusPending, 759 // TODO(fwereade): 2016-03-17 lp:1558657 760 Updated: time.Now().UnixNano(), 761 }), 762 { 763 C: filesystemsC, 764 Id: filesystemId, 765 Assert: txn.DocMissing, 766 Insert: &filesystemDoc{ 767 FilesystemId: filesystemId, 768 VolumeId: volumeId, 769 StorageId: params.storage.Id(), 770 Binding: params.binding.String(), 771 Params: ¶ms, 772 // Every filesystem is created with one attachment. 773 AttachmentCount: 1, 774 }, 775 }, 776 } 777 ops = append(ops, filesystemOps...) 778 return ops, filesystemTag, volumeTag, nil 779 } 780 781 func (st *State) filesystemParamsWithDefaults(params FilesystemParams) (FilesystemParams, error) { 782 if params.Pool != "" { 783 return params, nil 784 } 785 envConfig, err := st.ModelConfig() 786 if err != nil { 787 return FilesystemParams{}, errors.Trace(err) 788 } 789 cons := StorageConstraints{ 790 Pool: params.Pool, 791 Size: params.Size, 792 Count: 1, 793 } 794 poolName, err := defaultStoragePool(envConfig, storage.StorageKindFilesystem, cons) 795 if err != nil { 796 return FilesystemParams{}, errors.Annotate(err, "getting default filesystem storage pool") 797 } 798 params.Pool = poolName 799 return params, nil 800 } 801 802 // validateFilesystemParams validates the filesystem parameters, and returns the 803 // machine ID to use as the scope in the filesystem tag. 804 func (st *State) validateFilesystemParams(params FilesystemParams, machineId string) (maybeMachineId string, _ error) { 805 err := validateStoragePool(st, params.Pool, storage.StorageKindFilesystem, &machineId) 806 if err != nil { 807 return "", errors.Trace(err) 808 } 809 if params.Size == 0 { 810 return "", errors.New("invalid size 0") 811 } 812 return machineId, nil 813 } 814 815 type filesystemAttachmentTemplate struct { 816 tag names.FilesystemTag 817 storage names.StorageTag // may be zero-value 818 params FilesystemAttachmentParams 819 } 820 821 // createMachineFilesystemAttachmentInfo creates filesystem 822 // attachments for the specified machine, and attachment 823 // parameters keyed by filesystem tags. 824 func createMachineFilesystemAttachmentsOps(machineId string, attachments []filesystemAttachmentTemplate) []txn.Op { 825 ops := make([]txn.Op, len(attachments)) 826 for i, attachment := range attachments { 827 paramsCopy := attachment.params 828 ops[i] = txn.Op{ 829 C: filesystemAttachmentsC, 830 Id: filesystemAttachmentId(machineId, attachment.tag.Id()), 831 Assert: txn.DocMissing, 832 Insert: &filesystemAttachmentDoc{ 833 Filesystem: attachment.tag.Id(), 834 Machine: machineId, 835 Params: ¶msCopy, 836 }, 837 } 838 } 839 return ops 840 } 841 842 // SetFilesystemInfo sets the FilesystemInfo for the specified filesystem. 843 func (st *State) SetFilesystemInfo(tag names.FilesystemTag, info FilesystemInfo) (err error) { 844 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem %q", tag.Id()) 845 if info.FilesystemId == "" { 846 return errors.New("filesystem ID not set") 847 } 848 fs, err := st.Filesystem(tag) 849 if err != nil { 850 return errors.Trace(err) 851 } 852 // If the filesystem is volume-backed, the volume must be provisioned 853 // and attachment first. 854 if volumeTag, err := fs.Volume(); err == nil { 855 machineTag, ok := names.FilesystemMachine(tag) 856 if !ok { 857 return errors.Errorf("filesystem %s is not machine-scoped, but volume-backed", tag.Id()) 858 } 859 volumeAttachment, err := st.VolumeAttachment(machineTag, volumeTag) 860 if err != nil { 861 return errors.Trace(err) 862 } 863 if _, err := volumeAttachment.Info(); err != nil { 864 return errors.Trace(err) 865 } 866 } else if errors.Cause(err) != ErrNoBackingVolume { 867 return errors.Trace(err) 868 } 869 buildTxn := func(attempt int) ([]txn.Op, error) { 870 if attempt > 0 { 871 fs, err = st.Filesystem(tag) 872 if err != nil { 873 return nil, errors.Trace(err) 874 } 875 } 876 // If the filesystem has parameters, unset them 877 // when we set info for the first time, ensuring 878 // that params and info are mutually exclusive. 879 var unsetParams bool 880 if params, ok := fs.Params(); ok { 881 info.Pool = params.Pool 882 unsetParams = true 883 } else { 884 // Ensure immutable properties do not change. 885 oldInfo, err := fs.Info() 886 if err != nil { 887 return nil, err 888 } 889 if err := validateFilesystemInfoChange(info, oldInfo); err != nil { 890 return nil, err 891 } 892 } 893 ops := setFilesystemInfoOps(tag, info, unsetParams) 894 return ops, nil 895 } 896 return st.run(buildTxn) 897 } 898 899 func validateFilesystemInfoChange(newInfo, oldInfo FilesystemInfo) error { 900 if newInfo.Pool != oldInfo.Pool { 901 return errors.Errorf( 902 "cannot change pool from %q to %q", 903 oldInfo.Pool, newInfo.Pool, 904 ) 905 } 906 if newInfo.FilesystemId != oldInfo.FilesystemId { 907 return errors.Errorf( 908 "cannot change filesystem ID from %q to %q", 909 oldInfo.FilesystemId, newInfo.FilesystemId, 910 ) 911 } 912 return nil 913 } 914 915 func setFilesystemInfoOps(tag names.FilesystemTag, info FilesystemInfo, unsetParams bool) []txn.Op { 916 asserts := isAliveDoc 917 update := bson.D{ 918 {"$set", bson.D{{"info", &info}}}, 919 } 920 if unsetParams { 921 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 922 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 923 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 924 } 925 return []txn.Op{{ 926 C: filesystemsC, 927 Id: tag.Id(), 928 Assert: asserts, 929 Update: update, 930 }} 931 } 932 933 // SetFilesystemAttachmentInfo sets the FilesystemAttachmentInfo for the 934 // specified filesystem attachment. 935 func (st *State) SetFilesystemAttachmentInfo( 936 machineTag names.MachineTag, 937 filesystemTag names.FilesystemTag, 938 info FilesystemAttachmentInfo, 939 ) (err error) { 940 defer errors.DeferredAnnotatef(&err, "cannot set info for filesystem attachment %s:%s", filesystemTag.Id(), machineTag.Id()) 941 f, err := st.Filesystem(filesystemTag) 942 if err != nil { 943 return errors.Trace(err) 944 } 945 // Ensure filesystem is provisioned before setting attachment info. 946 // A filesystem cannot go from being provisioned to unprovisioned, 947 // so there is no txn.Op for this below. 948 if _, err := f.Info(); err != nil { 949 return errors.Trace(err) 950 } 951 // Also ensure the machine is provisioned. 952 m, err := st.Machine(machineTag.Id()) 953 if err != nil { 954 return errors.Trace(err) 955 } 956 if _, err := m.InstanceId(); err != nil { 957 return errors.Trace(err) 958 } 959 buildTxn := func(attempt int) ([]txn.Op, error) { 960 fsa, err := st.FilesystemAttachment(machineTag, filesystemTag) 961 if err != nil { 962 return nil, errors.Trace(err) 963 } 964 // If the filesystem attachment has parameters, unset them 965 // when we set info for the first time, ensuring that params 966 // and info are mutually exclusive. 967 _, unsetParams := fsa.Params() 968 ops := setFilesystemAttachmentInfoOps(machineTag, filesystemTag, info, unsetParams) 969 return ops, nil 970 } 971 return st.run(buildTxn) 972 } 973 974 func setFilesystemAttachmentInfoOps( 975 machine names.MachineTag, 976 filesystem names.FilesystemTag, 977 info FilesystemAttachmentInfo, 978 unsetParams bool, 979 ) []txn.Op { 980 asserts := isAliveDoc 981 update := bson.D{ 982 {"$set", bson.D{{"info", &info}}}, 983 } 984 if unsetParams { 985 asserts = append(asserts, bson.DocElem{"info", bson.D{{"$exists", false}}}) 986 asserts = append(asserts, bson.DocElem{"params", bson.D{{"$exists", true}}}) 987 update = append(update, bson.DocElem{"$unset", bson.D{{"params", nil}}}) 988 } 989 return []txn.Op{{ 990 C: filesystemAttachmentsC, 991 Id: filesystemAttachmentId(machine.Id(), filesystem.Id()), 992 Assert: asserts, 993 Update: update, 994 }} 995 } 996 997 // filesystemMountPoint returns a mount point to use for the given charm 998 // storage. For stores with potentially multiple instances, the instance 999 // name is appended to the location. 1000 func filesystemMountPoint( 1001 meta charm.Storage, 1002 tag names.StorageTag, 1003 series string, 1004 ) (string, error) { 1005 storageDir, err := paths.StorageDir(series) 1006 if err != nil { 1007 return "", errors.Trace(err) 1008 } 1009 if strings.HasPrefix(meta.Location, storageDir) { 1010 return "", errors.Errorf( 1011 "invalid location %q: must not fall within %q", 1012 meta.Location, storageDir, 1013 ) 1014 } 1015 if meta.Location != "" && meta.CountMax == 1 { 1016 // The location is specified and it's a singleton 1017 // store, so just use the location as-is. 1018 return meta.Location, nil 1019 } 1020 // If the location is unspecified then we use 1021 // <storage-dir>/<storage-id> as the location. 1022 // Otherwise, we use <location>/<storage-id>. 1023 if meta.Location != "" { 1024 storageDir = meta.Location 1025 } 1026 return path.Join(storageDir, tag.Id()), nil 1027 } 1028 1029 // validateFilesystemMountPoints validates the mount points of filesystems 1030 // being attached to the specified machine. If there are any mount point 1031 // path conflicts, an error will be returned. 1032 func validateFilesystemMountPoints(m *Machine, newFilesystems []filesystemAttachmentTemplate) error { 1033 attachments, err := m.st.MachineFilesystemAttachments(m.MachineTag()) 1034 if err != nil { 1035 return errors.Trace(err) 1036 } 1037 existing := make(map[names.FilesystemTag]string) 1038 for _, a := range attachments { 1039 params, ok := a.Params() 1040 if ok { 1041 existing[a.Filesystem()] = params.Location 1042 continue 1043 } 1044 info, err := a.Info() 1045 if err != nil { 1046 return errors.Trace(err) 1047 } 1048 existing[a.Filesystem()] = info.MountPoint 1049 } 1050 1051 storageName := func( 1052 filesystemTag names.FilesystemTag, 1053 storageTag names.StorageTag, 1054 ) string { 1055 if storageTag == (names.StorageTag{}) { 1056 return names.ReadableString(filesystemTag) 1057 } 1058 // We know the tag is valid, so ignore the error. 1059 storageName, _ := names.StorageName(storageTag.Id()) 1060 return fmt.Sprintf("%q storage", storageName) 1061 } 1062 1063 containsPath := func(a, b string) bool { 1064 a = path.Clean(a) + "/" 1065 b = path.Clean(b) + "/" 1066 return strings.HasPrefix(b, a) 1067 } 1068 1069 // These sets are expected to be small, so sorting and comparing 1070 // adjacent values is not worth the cost of creating a reverse 1071 // lookup from location to filesystem. 1072 for _, template := range newFilesystems { 1073 newMountPoint := template.params.Location 1074 for oldFilesystemTag, oldMountPoint := range existing { 1075 var conflicted, swapOrder bool 1076 if containsPath(oldMountPoint, newMountPoint) { 1077 conflicted = true 1078 } else if containsPath(newMountPoint, oldMountPoint) { 1079 conflicted = true 1080 swapOrder = true 1081 } 1082 if !conflicted { 1083 continue 1084 } 1085 1086 // Get a helpful identifier for the new filesystem. If it 1087 // is being created for a storage instance, then use 1088 // the storage name; otherwise use the filesystem name. 1089 newStorageName := storageName(template.tag, template.storage) 1090 1091 // Likewise for the old filesystem, but this time we'll 1092 // need to consult state. 1093 oldFilesystem, err := m.st.Filesystem(oldFilesystemTag) 1094 if err != nil { 1095 return errors.Trace(err) 1096 } 1097 storageTag, err := oldFilesystem.Storage() 1098 if errors.IsNotAssigned(err) { 1099 storageTag = names.StorageTag{} 1100 } else if err != nil { 1101 return errors.Trace(err) 1102 } 1103 oldStorageName := storageName(oldFilesystemTag, storageTag) 1104 1105 lhs := fmt.Sprintf("mount point %q for %s", oldMountPoint, oldStorageName) 1106 rhs := fmt.Sprintf("mount point %q for %s", newMountPoint, newStorageName) 1107 if swapOrder { 1108 lhs, rhs = rhs, lhs 1109 } 1110 return errors.Errorf("%s contains %s", lhs, rhs) 1111 } 1112 } 1113 return nil 1114 } 1115 1116 // AllFilesystems returns all Filesystems for this state. 1117 func (st *State) AllFilesystems() ([]Filesystem, error) { 1118 filesystems, err := st.filesystems(nil) 1119 if err != nil { 1120 return nil, errors.Annotate(err, "cannot get filesystems") 1121 } 1122 return filesystemsToInterfaces(filesystems), nil 1123 } 1124 1125 func filesystemsToInterfaces(fs []*filesystem) []Filesystem { 1126 result := make([]Filesystem, len(fs)) 1127 for i, f := range fs { 1128 result[i] = f 1129 } 1130 return result 1131 } 1132 1133 func filesystemGlobalKey(name string) string { 1134 return "f#" + name 1135 } 1136 1137 // FilesystemStatus returns the status of the specified filesystem. 1138 func (st *State) FilesystemStatus(tag names.FilesystemTag) (status.StatusInfo, error) { 1139 return getStatus(st, filesystemGlobalKey(tag.Id()), "filesystem") 1140 } 1141 1142 // SetFilesystemStatus sets the status of the specified filesystem. 1143 func (st *State) SetFilesystemStatus(tag names.FilesystemTag, fsStatus status.Status, info string, data map[string]interface{}) error { 1144 switch fsStatus { 1145 case status.StatusAttaching, status.StatusAttached, status.StatusDetaching, status.StatusDetached, status.StatusDestroying: 1146 case status.StatusError: 1147 if info == "" { 1148 return errors.Errorf("cannot set status %q without info", fsStatus) 1149 } 1150 case status.StatusPending: 1151 // If a filesystem is not yet provisioned, we allow its status 1152 // to be set back to pending (when a retry is to occur). 1153 v, err := st.Filesystem(tag) 1154 if err != nil { 1155 return errors.Trace(err) 1156 } 1157 _, err = v.Info() 1158 if errors.IsNotProvisioned(err) { 1159 break 1160 } 1161 return errors.Errorf("cannot set status %q", fsStatus) 1162 default: 1163 return errors.Errorf("cannot set invalid status %q", fsStatus) 1164 } 1165 return setStatus(st, setStatusParams{ 1166 badge: "filesystem", 1167 globalKey: filesystemGlobalKey(tag.Id()), 1168 status: fsStatus, 1169 message: info, 1170 rawData: data, 1171 }) 1172 }