/
storage.go
1293 lines (1178 loc) · 38.5 KB
/
storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package state
import (
"fmt"
"github.com/dustin/go-humanize"
"github.com/juju/errors"
jujutxn "github.com/juju/txn"
"github.com/juju/utils/set"
"gopkg.in/juju/charm.v6-unstable"
"gopkg.in/juju/names.v2"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"gopkg.in/mgo.v2/txn"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/poolmanager"
"github.com/juju/juju/storage/provider"
)
// StorageInstance represents the state of a unit or application-wide storage
// instance in the model.
type StorageInstance interface {
Entity
// StorageTag returns the tag for the storage instance.
StorageTag() names.StorageTag
// Kind returns the storage instance kind.
Kind() StorageKind
// Owner returns the tag of the application or unit that owns this storage
// instance.
Owner() names.Tag
// StorageName returns the name of the storage, as defined in the charm
// storage metadata. This does not uniquely identify storage instances,
// but identifies the group that the instances belong to.
StorageName() string
// Life reports whether the storage instance is Alive, Dying or Dead.
Life() Life
}
// StorageAttachment represents the state of a unit's attachment to a storage
// instance. A non-shared storage instance will have a single attachment for
// the storage instance's owning unit, whereas a shared storage instance will
// have an attachment for each unit of the service owning the storage instance.
type StorageAttachment interface {
// StorageInstance returns the tag of the corresponding storage
// instance.
StorageInstance() names.StorageTag
// Unit returns the tag of the corresponding unit.
Unit() names.UnitTag
// Life reports whether the storage attachment is Alive, Dying or Dead.
Life() Life
}
// StorageKind defines the type of a store: whether it is a block device
// or a filesystem.
type StorageKind int
const (
StorageKindUnknown StorageKind = iota
StorageKindBlock
StorageKindFilesystem
)
type storageInstance struct {
st *State
doc storageInstanceDoc
}
// String returns a human readable string represting the type.
func (k StorageKind) String() string {
switch k {
case StorageKindBlock:
return "block"
case StorageKindFilesystem:
return "filesystem"
default:
return "unknown"
}
}
// parseStorageKind is used by the migration code to go from the
// string representation back to the enum.
func parseStorageKind(value string) StorageKind {
switch value {
case "block":
return StorageKindBlock
case "filesystem":
return StorageKindFilesystem
default:
return StorageKindUnknown
}
}
func (s *storageInstance) Tag() names.Tag {
return s.StorageTag()
}
func (s *storageInstance) StorageTag() names.StorageTag {
return names.NewStorageTag(s.doc.Id)
}
func (s *storageInstance) Kind() StorageKind {
return s.doc.Kind
}
func (s *storageInstance) Owner() names.Tag {
tag, err := names.ParseTag(s.doc.Owner)
if err != nil {
// This should be impossible; we do not expose
// a means of modifying the owner tag.
panic(err)
}
return tag
}
func (s *storageInstance) StorageName() string {
return s.doc.StorageName
}
func (s *storageInstance) Life() Life {
return s.doc.Life
}
// entityStorageRefcountKey returns a key for refcounting charm storage
// for a specific entity. Each time a storage instance is created, the
// named store's refcount is incremented; and decremented when removed.
func entityStorageRefcountKey(owner names.Tag, storageName string) string {
return fmt.Sprintf("storage#%s#%s", owner.String(), storageName)
}
// storageInstanceDoc describes a charm storage instance.
type storageInstanceDoc struct {
DocID string `bson:"_id"`
ModelUUID string `bson:"model-uuid"`
Id string `bson:"id"`
Kind StorageKind `bson:"storagekind"`
Life Life `bson:"life"`
Owner string `bson:"owner"`
StorageName string `bson:"storagename"`
AttachmentCount int `bson:"attachmentcount"`
}
type storageAttachment struct {
doc storageAttachmentDoc
}
func (s *storageAttachment) StorageInstance() names.StorageTag {
return names.NewStorageTag(s.doc.StorageInstance)
}
func (s *storageAttachment) Unit() names.UnitTag {
return names.NewUnitTag(s.doc.Unit)
}
func (s *storageAttachment) Life() Life {
return s.doc.Life
}
// storageAttachmentDoc describes a unit's attachment to a charm storage
// instance.
type storageAttachmentDoc struct {
DocID string `bson:"_id"`
ModelUUID string `bson:"model-uuid"`
Unit string `bson:"unitid"`
StorageInstance string `bson:"storageid"`
Life Life `bson:"life"`
}
// newStorageInstanceId returns a unique storage instance name. The name
// incorporates the storage name as defined in the charm storage metadata,
// and a unique sequence number.
func newStorageInstanceId(st *State, store string) (string, error) {
seq, err := st.sequence("stores")
if err != nil {
return "", errors.Trace(err)
}
return fmt.Sprintf("%s/%v", store, seq), nil
}
func storageAttachmentId(unit string, storageInstanceId string) string {
return fmt.Sprintf("%s#%s", unitGlobalKey(unit), storageInstanceId)
}
// StorageInstance returns the StorageInstance with the specified tag.
func (st *State) StorageInstance(tag names.StorageTag) (StorageInstance, error) {
s, err := st.storageInstance(tag)
return s, err
}
func (st *State) storageInstance(tag names.StorageTag) (*storageInstance, error) {
storageInstances, cleanup := st.getCollection(storageInstancesC)
defer cleanup()
s := storageInstance{st: st}
err := storageInstances.FindId(tag.Id()).One(&s.doc)
if err == mgo.ErrNotFound {
return nil, errors.NotFoundf("storage instance %q", tag.Id())
} else if err != nil {
return nil, errors.Annotate(err, "cannot get storage instance details")
}
return &s, nil
}
// AllStorageInstances lists all storage instances currently in state
// for this Juju model.
func (st *State) AllStorageInstances() (storageInstances []StorageInstance, err error) {
storageCollection, closer := st.getCollection(storageInstancesC)
defer closer()
sdocs := []storageInstanceDoc{}
err = storageCollection.Find(nil).All(&sdocs)
if err != nil {
return nil, errors.Annotate(err, "cannot get all storage instances")
}
for _, doc := range sdocs {
storageInstances = append(storageInstances, &storageInstance{st, doc})
}
return
}
// DestroyStorageInstance ensures that the storage instance and all its
// attachments will be removed at some point; if the storage instance has
// no attachments, it will be removed immediately.
func (st *State) DestroyStorageInstance(tag names.StorageTag) (err error) {
defer errors.DeferredAnnotatef(&err, "cannot destroy storage %q", tag.Id())
buildTxn := func(attempt int) ([]txn.Op, error) {
s, err := st.storageInstance(tag)
if errors.IsNotFound(err) {
return nil, jujutxn.ErrNoOperations
} else if err != nil {
return nil, errors.Trace(err)
}
switch ops, err := st.destroyStorageInstanceOps(s); err {
case errAlreadyDying:
return nil, jujutxn.ErrNoOperations
case nil:
return ops, nil
default:
return nil, errors.Trace(err)
}
}
return st.run(buildTxn)
}
func (st *State) destroyStorageInstanceOps(s *storageInstance) ([]txn.Op, error) {
if s.doc.Life == Dying {
return nil, errAlreadyDying
}
if s.doc.AttachmentCount == 0 {
// There are no attachments remaining, so we can
// remove the storage instance immediately.
hasNoAttachments := bson.D{{"attachmentcount", 0}}
assert := append(hasNoAttachments, isAliveDoc...)
return removeStorageInstanceOps(st, s.Owner(), s.StorageTag(), assert)
}
// There are still attachments: the storage instance will be removed
// when the last attachment is removed. We schedule a cleanup to destroy
// attachments.
notLastRefs := bson.D{
{"life", Alive},
{"attachmentcount", bson.D{{"$gt", 0}}},
}
update := bson.D{{"$set", bson.D{{"life", Dying}}}}
ops := []txn.Op{
newCleanupOp(cleanupAttachmentsForDyingStorage, s.doc.Id),
{
C: storageInstancesC,
Id: s.doc.Id,
Assert: notLastRefs,
Update: update,
},
}
return ops, nil
}
// removeStorageInstanceOps removes the storage instance with the given
// tag from state, if the specified assertions hold true.
func removeStorageInstanceOps(
st *State,
owner names.Tag,
tag names.StorageTag,
assert bson.D,
) ([]txn.Op, error) {
ops := []txn.Op{{
C: storageInstancesC,
Id: tag.Id(),
Assert: assert,
Remove: true,
}}
machineStorageOp := func(c string, id string) txn.Op {
return txn.Op{
C: c,
Id: id,
Assert: bson.D{{"storageid", tag.Id()}},
Update: bson.D{{"$set", bson.D{{"storageid", ""}}}},
}
}
// If the storage instance has an assigned volume and/or filesystem,
// unassign them. Any volumes and filesystems bound to the storage
// will be destroyed.
volume, err := st.storageInstanceVolume(tag)
if err == nil {
ops = append(ops, machineStorageOp(
volumesC, volume.Tag().Id(),
))
if volume.LifeBinding() == tag {
ops = append(ops, destroyVolumeOps(st, volume)...)
}
} else if !errors.IsNotFound(err) {
return nil, errors.Trace(err)
}
filesystem, err := st.storageInstanceFilesystem(tag)
if err == nil {
ops = append(ops, machineStorageOp(
filesystemsC, filesystem.Tag().Id(),
))
if filesystem.LifeBinding() == tag {
ops = append(ops, destroyFilesystemOps(st, filesystem)...)
}
} else if !errors.IsNotFound(err) {
return nil, errors.Trace(err)
}
// Decrement the charm storage reference count.
refcounts, closer := st.getCollection(refcountsC)
defer closer()
storageName, err := names.StorageName(tag.Id())
if err != nil {
return nil, errors.Trace(err)
}
storageRefcountKey := entityStorageRefcountKey(owner, storageName)
decRefOp, _, err := nsRefcounts.DyingDecRefOp(refcounts, storageRefcountKey)
if err != nil {
return nil, errors.Trace(err)
}
ops = append(ops, decRefOp)
return ops, nil
}
// machineAssignable is used by createStorageOps to determine what machine
// storage needs to be created. This is implemented by Unit.
type machineAssignable interface {
machine() (*Machine, error)
noAssignedMachineOp() txn.Op
}
// createStorageOps returns txn.Ops for creating storage instances
// and attachments for the newly created unit or service.
//
// The entity tag identifies the entity that owns the storage instance
// either a unit or a service. Shared storage instances are owned by a
// service, and non-shared storage instances are owned by a unit.
//
// The charm metadata corresponds to the charm that the owner (service/unit)
// is or will be running, and is used to extract storage constraints,
// default values, etc.
//
// The supplied storage constraints are constraints for the storage
// instances to be created, keyed on the storage name. These constraints
// will be correlated with the charm storage metadata for validation
// and supplementing.
//
// maybeMachineAssignable may be nil, or an machineAssignable which
// describes the entity's machine assignment. If the entity is assigned
// to a machine, then machine storage will be created.
func createStorageOps(
st *State,
entityTag names.Tag,
charmMeta *charm.Meta,
cons map[string]StorageConstraints,
series string,
maybeMachineAssignable machineAssignable,
) (ops []txn.Op, numStorageAttachments int, err error) {
type template struct {
storageName string
meta charm.Storage
cons StorageConstraints
}
createdShared := false
switch entityTag := entityTag.(type) {
case names.ApplicationTag:
createdShared = true
case names.UnitTag:
default:
return nil, -1, errors.Errorf("expected application or unit tag, got %T", entityTag)
}
// Create storage instances in order of name, to simplify testing.
storageNames := set.NewStrings()
for name := range cons {
storageNames.Add(name)
}
templates := make([]template, 0, len(cons))
for _, store := range storageNames.SortedValues() {
cons := cons[store]
charmStorage, ok := charmMeta.Storage[store]
if !ok {
return nil, -1, errors.NotFoundf("charm storage %q", store)
}
if cons.Count == 0 {
continue
}
if createdShared != charmStorage.Shared {
// services only get shared storage instances,
// units only get non-shared storage instances.
continue
}
templates = append(templates, template{
storageName: store,
meta: charmStorage,
cons: cons,
})
}
refcounts, closer := st.getCollection(refcountsC)
defer closer()
ops = make([]txn.Op, 0, len(templates)*3)
for _, t := range templates {
owner := entityTag.String()
var kind StorageKind
switch t.meta.Type {
case charm.StorageBlock:
kind = StorageKindBlock
case charm.StorageFilesystem:
kind = StorageKindFilesystem
default:
return nil, -1, errors.Errorf("unknown storage type %q", t.meta.Type)
}
// Increment reference counts for the named storage for each
// instance we create. We'll use the reference counts to ensure
// we don't exceed limits when adding storage, and for
// maintaining model integrity during charm upgrades.
storageRefcountKey := entityStorageRefcountKey(entityTag, t.storageName)
incRefOp, err := nsRefcounts.CreateOrIncRefOp(refcounts, storageRefcountKey, int(t.cons.Count))
if err != nil {
return nil, -1, errors.Trace(err)
}
ops = append(ops, incRefOp)
for i := uint64(0); i < t.cons.Count; i++ {
id, err := newStorageInstanceId(st, t.storageName)
if err != nil {
return nil, -1, errors.Annotate(err, "cannot generate storage instance name")
}
doc := &storageInstanceDoc{
Id: id,
Kind: kind,
Owner: owner,
StorageName: t.storageName,
}
var machineOps []txn.Op
if unitTag, ok := entityTag.(names.UnitTag); ok {
doc.AttachmentCount = 1
storage := names.NewStorageTag(id)
ops = append(ops, createStorageAttachmentOp(storage, unitTag))
numStorageAttachments++
if maybeMachineAssignable != nil {
var err error
machineOps, err = unitAssignedMachineStorageOps(
st, unitTag, charmMeta, cons, series,
&storageInstance{st, *doc},
maybeMachineAssignable,
)
if err != nil {
return nil, -1, errors.Annotatef(
err, "creating machine storage for storage %s", id,
)
}
}
}
ops = append(ops, txn.Op{
C: storageInstancesC,
Id: id,
Assert: txn.DocMissing,
Insert: doc,
})
ops = append(ops, machineOps...)
}
}
// TODO(axw) create storage attachments for each shared storage
// instance owned by the service.
//
// TODO(axw) prevent creation of shared storage after service
// creation, because the only sane time to add storage attachments
// is when units are added to said service.
return ops, numStorageAttachments, nil
}
// unitAssignedMachineStorageOps returns ops for creating volumes, filesystems
// and their attachments to the machine that the specified unit is assigned to,
// corresponding to the specified storage instance.
//
// If the unit is not assigned to a machine, then ops will be returned to assert
// this, and no error will be returned.
func unitAssignedMachineStorageOps(
st *State,
unitTag names.UnitTag,
charmMeta *charm.Meta,
cons map[string]StorageConstraints,
series string,
storage StorageInstance,
machineAssignable machineAssignable,
) (ops []txn.Op, err error) {
storageParams, err := machineStorageParamsForStorageInstance(
st, charmMeta, unitTag, series, cons, storage,
)
if err != nil {
return nil, errors.Trace(err)
}
m, err := machineAssignable.machine()
if err != nil {
if errors.IsNotAssigned(err) {
// The unit is not assigned to a machine; return
// txn.Op that ensures that this remains the case
// until the transaction is committed.
return []txn.Op{machineAssignable.noAssignedMachineOp()}, nil
}
return nil, errors.Trace(err)
}
if err := validateDynamicMachineStorageParams(m, storageParams); err != nil {
return nil, errors.Trace(err)
}
storageOps, volumeAttachments, filesystemAttachments, err := st.machineStorageOps(
&m.doc, storageParams,
)
if err != nil {
return nil, errors.Trace(err)
}
attachmentOps, err := addMachineStorageAttachmentsOps(
m, volumeAttachments, filesystemAttachments,
)
if err != nil {
return nil, errors.Trace(err)
}
storageOps = append(storageOps, attachmentOps...)
return storageOps, nil
}
// createStorageAttachmentOps returns a txn.Op for creating a storage attachment.
// The caller is responsible for updating the attachmentcount field of the storage
// instance.
func createStorageAttachmentOp(storage names.StorageTag, unit names.UnitTag) txn.Op {
return txn.Op{
C: storageAttachmentsC,
Id: storageAttachmentId(unit.Id(), storage.Id()),
Assert: txn.DocMissing,
Insert: &storageAttachmentDoc{
Unit: unit.Id(),
StorageInstance: storage.Id(),
},
}
}
// StorageAttachments returns the StorageAttachments for the specified storage
// instance.
func (st *State) StorageAttachments(storage names.StorageTag) ([]StorageAttachment, error) {
query := bson.D{{"storageid", storage.Id()}}
attachments, err := st.storageAttachments(query)
if err != nil {
return nil, errors.Annotatef(err, "cannot get storage attachments for storage %s", storage.Id())
}
return attachments, nil
}
// UnitStorageAttachments returns the StorageAttachments for the specified unit.
func (st *State) UnitStorageAttachments(unit names.UnitTag) ([]StorageAttachment, error) {
query := bson.D{{"unitid", unit.Id()}}
attachments, err := st.storageAttachments(query)
if err != nil {
return nil, errors.Annotatef(err, "cannot get storage attachments for unit %s", unit.Id())
}
return attachments, nil
}
func (st *State) storageAttachments(query bson.D) ([]StorageAttachment, error) {
coll, closer := st.getCollection(storageAttachmentsC)
defer closer()
var docs []storageAttachmentDoc
if err := coll.Find(query).All(&docs); err != nil {
return nil, err
}
storageAttachments := make([]StorageAttachment, len(docs))
for i, doc := range docs {
storageAttachments[i] = &storageAttachment{doc}
}
return storageAttachments, nil
}
// StorageAttachment returns the StorageAttachment wit hthe specified tags.
func (st *State) StorageAttachment(storage names.StorageTag, unit names.UnitTag) (StorageAttachment, error) {
att, err := st.storageAttachment(storage, unit)
if err != nil {
return nil, errors.Trace(err)
}
return att, nil
}
func (st *State) storageAttachment(storage names.StorageTag, unit names.UnitTag) (*storageAttachment, error) {
coll, closer := st.getCollection(storageAttachmentsC)
defer closer()
var s storageAttachment
err := coll.FindId(storageAttachmentId(unit.Id(), storage.Id())).One(&s.doc)
if err == mgo.ErrNotFound {
return nil, errors.NotFoundf("storage attachment %s:%s", storage.Id(), unit.Id())
} else if err != nil {
return nil, errors.Annotatef(err, "cannot get storage attachment %s:%s", storage.Id(), unit.Id())
}
return &s, nil
}
// DestroyStorageAttachment ensures that the existing storage attachments of
// the specified unit are removed at some point.
func (st *State) DestroyUnitStorageAttachments(unit names.UnitTag) (err error) {
defer errors.DeferredAnnotatef(&err, "cannot destroy unit %s storage attachments", unit.Id())
buildTxn := func(attempt int) ([]txn.Op, error) {
attachments, err := st.UnitStorageAttachments(unit)
if err != nil {
return nil, errors.Trace(err)
}
ops := make([]txn.Op, 0, len(attachments))
for _, attachment := range attachments {
if attachment.Life() != Alive {
continue
}
ops = append(ops, destroyStorageAttachmentOps(
attachment.StorageInstance(), unit,
)...)
}
if len(ops) == 0 {
return nil, jujutxn.ErrNoOperations
}
return ops, nil
}
return st.run(buildTxn)
}
// DestroyStorageAttachment ensures that the storage attachment will be
// removed at some point.
func (st *State) DestroyStorageAttachment(storage names.StorageTag, unit names.UnitTag) (err error) {
defer errors.DeferredAnnotatef(&err, "cannot destroy storage attachment %s:%s", storage.Id(), unit.Id())
buildTxn := func(attempt int) ([]txn.Op, error) {
s, err := st.storageAttachment(storage, unit)
if errors.IsNotFound(err) {
return nil, jujutxn.ErrNoOperations
} else if err != nil {
return nil, errors.Trace(err)
}
if s.doc.Life == Dying {
return nil, jujutxn.ErrNoOperations
}
return destroyStorageAttachmentOps(storage, unit), nil
}
return st.run(buildTxn)
}
func destroyStorageAttachmentOps(storage names.StorageTag, unit names.UnitTag) []txn.Op {
ops := []txn.Op{{
C: storageAttachmentsC,
Id: storageAttachmentId(unit.Id(), storage.Id()),
Assert: isAliveDoc,
Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
}}
return ops
}
// Remove removes the storage attachment from state, and may remove its storage
// instance as well, if the storage instance is Dying and no other references to
// it exist. It will fail if the storage attachment is not Dying.
func (st *State) RemoveStorageAttachment(storage names.StorageTag, unit names.UnitTag) (err error) {
defer errors.DeferredAnnotatef(&err, "cannot remove storage attachment %s:%s", storage.Id(), unit.Id())
buildTxn := func(attempt int) ([]txn.Op, error) {
s, err := st.storageAttachment(storage, unit)
if errors.IsNotFound(err) {
return nil, jujutxn.ErrNoOperations
} else if err != nil {
return nil, errors.Trace(err)
}
inst, err := st.storageInstance(storage)
if errors.IsNotFound(err) {
// This implies that the attachment was removed
// after the call to st.storageAttachment.
return nil, jujutxn.ErrNoOperations
} else if err != nil {
return nil, errors.Trace(err)
}
ops, err := removeStorageAttachmentOps(st, s, inst)
if err != nil {
return nil, errors.Trace(err)
}
return ops, nil
}
return st.run(buildTxn)
}
func removeStorageAttachmentOps(
st *State,
s *storageAttachment,
si *storageInstance,
) ([]txn.Op, error) {
if s.doc.Life != Dying {
return nil, errors.New("storage attachment is not dying")
}
ops := []txn.Op{{
C: storageAttachmentsC,
Id: storageAttachmentId(s.doc.Unit, s.doc.StorageInstance),
Assert: bson.D{{"life", Dying}},
Remove: true,
}, {
C: unitsC,
Id: s.doc.Unit,
Assert: txn.DocExists,
Update: bson.D{{"$inc", bson.D{{"storageattachmentcount", -1}}}},
}}
if si.doc.AttachmentCount == 1 {
var hasLastRef bson.D
if si.doc.Life == Dying {
hasLastRef = bson.D{{"life", Dying}, {"attachmentcount", 1}}
} else if si.doc.Owner == names.NewUnitTag(s.doc.Unit).String() {
hasLastRef = bson.D{{"attachmentcount", 1}}
}
if len(hasLastRef) > 0 {
// Either the storage instance is dying, or its owner
// is a unit; in either case, no more attachments can
// be added to the instance, so it can be removed.
siOps, err := removeStorageInstanceOps(
st, si.Owner(), si.StorageTag(), hasLastRef,
)
if err != nil {
return nil, errors.Trace(err)
}
ops = append(ops, siOps...)
return ops, nil
}
}
decrefOp := txn.Op{
C: storageInstancesC,
Id: si.doc.Id,
Update: bson.D{{"$inc", bson.D{{"attachmentcount", -1}}}},
}
if si.doc.Life == Alive {
// This may be the last reference, but the storage instance is
// still alive. The storage instance will be removed when its
// Destroy method is called, if it has no attachments.
decrefOp.Assert = bson.D{
{"life", Alive},
{"attachmentcount", bson.D{{"$gt", 0}}},
}
} else {
// If it's not the last reference when we checked, we want to
// allow for concurrent attachment removals but want to ensure
// that we don't drop to zero without removing the storage
// instance.
decrefOp.Assert = bson.D{
{"life", Dying},
{"attachmentcount", bson.D{{"$gt", 1}}},
}
}
ops = append(ops, decrefOp)
return ops, nil
}
// removeStorageInstancesOps returns the transaction operations to remove all
// storage instances owned by the specified entity.
func removeStorageInstancesOps(st *State, owner names.Tag) ([]txn.Op, error) {
coll, closer := st.getCollection(storageInstancesC)
defer closer()
var docs []storageInstanceDoc
err := coll.Find(bson.D{{"owner", owner.String()}}).Select(bson.D{{"id", true}}).All(&docs)
if err != nil {
return nil, errors.Annotatef(err, "cannot get storage instances for %s", owner)
}
ops := make([]txn.Op, 0, len(docs))
for _, doc := range docs {
tag := names.NewStorageTag(doc.Id)
storageInstanceOps, err := removeStorageInstanceOps(st, owner, tag, nil)
if err != nil {
return nil, errors.Trace(err)
}
ops = append(ops, storageInstanceOps...)
}
return ops, nil
}
// storageConstraintsDoc contains storage constraints for an entity.
type storageConstraintsDoc struct {
DocID string `bson:"_id"`
ModelUUID string `bson:"model-uuid"`
Constraints map[string]StorageConstraints `bson:"constraints"`
}
// StorageConstraints contains the user-specified constraints for provisioning
// storage instances for a service unit.
type StorageConstraints struct {
// Pool is the name of the storage pool from which to provision the
// storage instances.
Pool string `bson:"pool"`
// Size is the required size of the storage instances, in MiB.
Size uint64 `bson:"size"`
// Count is the required number of storage instances.
Count uint64 `bson:"count"`
}
func createStorageConstraintsOp(key string, cons map[string]StorageConstraints) txn.Op {
return txn.Op{
C: storageConstraintsC,
Id: key,
Assert: txn.DocMissing,
Insert: &storageConstraintsDoc{
Constraints: cons,
},
}
}
func replaceStorageConstraintsOp(key string, cons map[string]StorageConstraints) txn.Op {
return txn.Op{
C: storageConstraintsC,
Id: key,
Assert: txn.DocExists,
Update: bson.D{{"$set", bson.D{{"constraints", cons}}}},
}
}
func removeStorageConstraintsOp(key string) txn.Op {
return txn.Op{
C: storageConstraintsC,
Id: key,
Remove: true,
}
}
func readStorageConstraints(st *State, key string) (map[string]StorageConstraints, error) {
coll, closer := st.getCollection(storageConstraintsC)
defer closer()
var doc storageConstraintsDoc
err := coll.FindId(key).One(&doc)
if err == mgo.ErrNotFound {
return nil, errors.NotFoundf("storage constraints for %q", key)
}
if err != nil {
return nil, errors.Annotatef(err, "cannot get storage constraints for %q", key)
}
return doc.Constraints, nil
}
func storageKind(storageType charm.StorageType) storage.StorageKind {
kind := storage.StorageKindUnknown
switch storageType {
case charm.StorageBlock:
kind = storage.StorageKindBlock
case charm.StorageFilesystem:
kind = storage.StorageKindFilesystem
}
return kind
}
func validateStorageConstraints(st *State, allCons map[string]StorageConstraints, charmMeta *charm.Meta) error {
err := validateStorageConstraintsAgainstCharm(st, allCons, charmMeta)
if err != nil {
return errors.Trace(err)
}
// Ensure all stores have constraints specified. Defaults should have
// been set by this point, if the user didn't specify constraints.
for name, charmStorage := range charmMeta.Storage {
if _, ok := allCons[name]; !ok && charmStorage.CountMin > 0 {
return errors.Errorf("no constraints specified for store %q", name)
}
}
return nil
}
func validateStorageConstraintsAgainstCharm(
st *State,
allCons map[string]StorageConstraints,
charmMeta *charm.Meta,
) error {
for name, cons := range allCons {
charmStorage, ok := charmMeta.Storage[name]
if !ok {
return errors.Errorf("charm %q has no store called %q", charmMeta.Name, name)
}
if charmStorage.Shared {
// TODO(axw) implement shared storage support.
return errors.Errorf(
"charm %q store %q: shared storage support not implemented",
charmMeta.Name, name,
)
}
if cons.Count < uint64(charmStorage.CountMin) {
return errors.Errorf(
"charm %q store %q: %d instances required, %d specified",
charmMeta.Name, name, charmStorage.CountMin, cons.Count,
)
}
if charmStorage.CountMax >= 0 && cons.Count > uint64(charmStorage.CountMax) {
return errors.Errorf(
"charm %q store %q: at most %d instances supported, %d specified",
charmMeta.Name, name, charmStorage.CountMax, cons.Count,
)
}
if charmStorage.MinimumSize > 0 && cons.Size < charmStorage.MinimumSize {
return errors.Errorf(
"charm %q store %q: minimum storage size is %s, %s specified",
charmMeta.Name, name,
humanize.Bytes(charmStorage.MinimumSize*humanize.MByte),
humanize.Bytes(cons.Size*humanize.MByte),
)
}
kind := storageKind(charmStorage.Type)
if err := validateStoragePool(st, cons.Pool, kind, nil); err != nil {
return err
}
}
return nil
}
// validateStoragePool validates the storage pool for the model.
// If machineId is non-nil, the storage scope will be validated against
// the machineId; if the storage is not machine-scoped, then the machineId
// will be updated to "".
func validateStoragePool(
st *State, poolName string, kind storage.StorageKind, machineId *string,
) error {
if poolName == "" {
return errors.New("pool name is required")
}
providerType, provider, err := poolStorageProvider(st, poolName)
if err != nil {
return errors.Trace(err)
}
// Ensure the storage provider supports the specified kind.
kindSupported := provider.Supports(kind)
if !kindSupported && kind == storage.StorageKindFilesystem {
// Filesystems can be created if either filesystem
// or block storage are supported.
if provider.Supports(storage.StorageKindBlock) {
kindSupported = true
// The filesystem is to be backed by a volume,
// so the filesystem must be managed on the
// machine. Skip the scope-check below by
// setting the pointer to nil.
machineId = nil
}
}
if !kindSupported {
return errors.Errorf("%q provider does not support %q storage", providerType, kind)
}
// Check the storage scope.
if machineId != nil {
switch provider.Scope() {
case storage.ScopeMachine:
if *machineId == "" {
return errors.Annotate(err, "machine unspecified for machine-scoped storage")
}
default:
// The storage is not machine-scoped, so we clear out
// the machine ID to inform the caller that the storage
// scope should be the model.
*machineId = ""
}
}
return nil
}
func poolStorageProvider(st *State, poolName string) (storage.ProviderType, storage.Provider, error) {
registry, err := st.storageProviderRegistry()