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