github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/spaces.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 "github.com/juju/errors" 8 "gopkg.in/juju/names.v2" 9 "gopkg.in/mgo.v2" 10 "gopkg.in/mgo.v2/bson" 11 "gopkg.in/mgo.v2/txn" 12 13 "github.com/juju/juju/network" 14 ) 15 16 // Space represents the state of a juju network space. 17 type Space struct { 18 st *State 19 doc spaceDoc 20 } 21 22 type spaceDoc struct { 23 Life Life `bson:"life"` 24 Name string `bson:"name"` 25 IsPublic bool `bson:"is-public"` 26 ProviderId string `bson:"providerid,omitempty"` 27 } 28 29 // Life returns whether the space is Alive, Dying or Dead. 30 func (s *Space) Life() Life { 31 return s.doc.Life 32 } 33 34 // String implements fmt.Stringer. 35 func (s *Space) String() string { 36 return s.doc.Name 37 } 38 39 // Name returns the name of the Space. 40 func (s *Space) Name() string { 41 return s.doc.Name 42 } 43 44 // IsPublic returns whether the space is public or not. 45 func (s *Space) IsPublic() bool { 46 return s.doc.IsPublic 47 } 48 49 // ProviderId returns the provider id of the space. This will be the empty 50 // string except on substrates that directly support spaces. 51 func (s *Space) ProviderId() network.Id { 52 return network.Id(s.doc.ProviderId) 53 } 54 55 // Subnets returns all the subnets associated with the Space. 56 func (s *Space) Subnets() (results []*Subnet, err error) { 57 defer errors.DeferredAnnotatef(&err, "cannot fetch subnets") 58 name := s.Name() 59 60 subnetsCollection, closer := s.st.getCollection(subnetsC) 61 defer closer() 62 63 var doc subnetDoc 64 iter := subnetsCollection.Find(bson.D{{"space-name", name}}).Iter() 65 defer iter.Close() 66 for iter.Next(&doc) { 67 subnet := &Subnet{s.st, doc} 68 results = append(results, subnet) 69 } 70 if err := iter.Err(); err != nil { 71 return nil, err 72 } 73 return results, nil 74 } 75 76 // AddSpace creates and returns a new space. 77 func (st *State) AddSpace(name string, providerId network.Id, subnets []string, isPublic bool) (newSpace *Space, err error) { 78 defer errors.DeferredAnnotatef(&err, "adding space %q", name) 79 if !names.IsValidSpace(name) { 80 return nil, errors.NewNotValid(nil, "invalid space name") 81 } 82 83 spaceDoc := spaceDoc{ 84 Life: Alive, 85 Name: name, 86 IsPublic: isPublic, 87 ProviderId: string(providerId), 88 } 89 newSpace = &Space{doc: spaceDoc, st: st} 90 91 ops := []txn.Op{{ 92 C: spacesC, 93 Id: name, 94 Assert: txn.DocMissing, 95 Insert: spaceDoc, 96 }} 97 98 if providerId != "" { 99 ops = append(ops, st.networkEntityGlobalKeyOp("space", providerId)) 100 } 101 102 for _, subnetId := range subnets { 103 // TODO:(mfoord) once we have refcounting for subnets we should 104 // also assert that the refcount is zero as moving the space of a 105 // subnet in use is not permitted. 106 ops = append(ops, txn.Op{ 107 C: subnetsC, 108 Id: subnetId, 109 Assert: txn.DocExists, 110 Update: bson.D{{"$set", bson.D{{"space-name", name}}}}, 111 }) 112 } 113 114 if err := st.runTransaction(ops); err == txn.ErrAborted { 115 if _, err := st.Space(name); err == nil { 116 return nil, errors.AlreadyExistsf("space %q", name) 117 } 118 for _, subnetId := range subnets { 119 if _, err := st.Subnet(subnetId); errors.IsNotFound(err) { 120 return nil, err 121 } 122 } 123 if err := newSpace.Refresh(); err != nil { 124 if errors.IsNotFound(err) { 125 return nil, errors.Errorf("ProviderId %q not unique", providerId) 126 } 127 return nil, errors.Trace(err) 128 } 129 return nil, errors.Trace(err) 130 } else if err != nil { 131 return nil, err 132 } 133 return newSpace, nil 134 } 135 136 // Space returns a space from state that matches the provided name. An error 137 // is returned if the space doesn't exist or if there was a problem accessing 138 // its information. 139 func (st *State) Space(name string) (*Space, error) { 140 spaces, closer := st.getCollection(spacesC) 141 defer closer() 142 143 var doc spaceDoc 144 err := spaces.FindId(name).One(&doc) 145 if err == mgo.ErrNotFound { 146 return nil, errors.NotFoundf("space %q", name) 147 } 148 if err != nil { 149 return nil, errors.Annotatef(err, "cannot get space %q", name) 150 } 151 return &Space{st, doc}, nil 152 } 153 154 // AllSpaces returns all spaces for the model. 155 func (st *State) AllSpaces() ([]*Space, error) { 156 spacesCollection, closer := st.getCollection(spacesC) 157 defer closer() 158 159 docs := []spaceDoc{} 160 err := spacesCollection.Find(nil).All(&docs) 161 if err != nil { 162 return nil, errors.Annotatef(err, "cannot get all spaces") 163 } 164 spaces := make([]*Space, len(docs)) 165 for i, doc := range docs { 166 spaces[i] = &Space{st: st, doc: doc} 167 } 168 return spaces, nil 169 } 170 171 // EnsureDead sets the Life of the space to Dead, if it's Alive. If the space is 172 // already Dead, no error is returned. When the space is no longer Alive or 173 // already removed, errNotAlive is returned. 174 func (s *Space) EnsureDead() (err error) { 175 defer errors.DeferredAnnotatef(&err, "cannot set space %q to dead", s) 176 177 if s.doc.Life == Dead { 178 return nil 179 } 180 181 ops := []txn.Op{{ 182 C: spacesC, 183 Id: s.doc.Name, 184 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 185 Assert: isAliveDoc, 186 }} 187 188 txnErr := s.st.runTransaction(ops) 189 if txnErr == nil { 190 s.doc.Life = Dead 191 return nil 192 } 193 return onAbort(txnErr, errNotAlive) 194 } 195 196 // Remove removes a Dead space. If the space is not Dead or it is already 197 // removed, an error is returned. 198 func (s *Space) Remove() (err error) { 199 defer errors.DeferredAnnotatef(&err, "cannot remove space %q", s) 200 201 if s.doc.Life != Dead { 202 return errors.New("space is not dead") 203 } 204 205 ops := []txn.Op{{ 206 C: spacesC, 207 Id: s.doc.Name, 208 Remove: true, 209 Assert: isDeadDoc, 210 }} 211 if s.ProviderId() != "" { 212 ops = append(ops, s.st.networkEntityGlobalKeyRemoveOp("space", s.ProviderId())) 213 } 214 215 txnErr := s.st.runTransaction(ops) 216 if txnErr == nil { 217 return nil 218 } 219 return onAbort(txnErr, errors.New("not found or not dead")) 220 } 221 222 // Refresh: refreshes the contents of the Space from the underlying state. It 223 // returns an error that satisfies errors.IsNotFound if the Space has been 224 // removed. 225 func (s *Space) Refresh() error { 226 spaces, closer := s.st.getCollection(spacesC) 227 defer closer() 228 229 var doc spaceDoc 230 err := spaces.FindId(s.doc.Name).One(&doc) 231 if err == mgo.ErrNotFound { 232 return errors.NotFoundf("space %q", s) 233 } else if err != nil { 234 return errors.Errorf("cannot refresh space %q: %v", s, err) 235 } 236 s.doc = doc 237 return nil 238 }