github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/subnets.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "net" 8 9 "github.com/juju/errors" 10 "gopkg.in/mgo.v2" 11 "gopkg.in/mgo.v2/bson" 12 "gopkg.in/mgo.v2/txn" 13 14 "github.com/juju/juju/network" 15 ) 16 17 // SubnetInfo describes a single subnet. 18 type SubnetInfo struct { 19 // ProviderId is a provider-specific network id. This may be empty. 20 ProviderId network.Id 21 22 // CIDR of the network, in 123.45.67.89/24 format. 23 CIDR string 24 25 // VLANTag needs to be between 1 and 4094 for VLANs and 0 for normal 26 // networks. It's defined by IEEE 802.1Q standard. 27 VLANTag int 28 29 // AvailabilityZone describes which availability zone this subnet is in. It can 30 // be empty if the provider does not support availability zones. 31 AvailabilityZone string 32 33 // SpaceName is the name of the space the subnet is associated with. It 34 // can be empty if the subnet is not associated with a space yet. 35 SpaceName string 36 } 37 38 type Subnet struct { 39 st *State 40 doc subnetDoc 41 } 42 43 type subnetDoc struct { 44 DocID string `bson:"_id"` 45 ModelUUID string `bson:"model-uuid"` 46 Life Life `bson:"life"` 47 ProviderId string `bson:"providerid,omitempty"` 48 CIDR string `bson:"cidr"` 49 VLANTag int `bson:"vlantag,omitempty"` 50 AvailabilityZone string `bson:"availabilityzone,omitempty"` 51 // TODO: add IsPublic to SubnetArgs, add an IsPublic method and add 52 // IsPublic to migration import/export. 53 IsPublic bool `bson:"is-public,omitempty"` 54 SpaceName string `bson:"space-name,omitempty"` 55 } 56 57 // Life returns whether the subnet is Alive, Dying or Dead. 58 func (s *Subnet) Life() Life { 59 return s.doc.Life 60 } 61 62 // ID returns the unique id for the subnet, for other entities to reference it. 63 func (s *Subnet) ID() string { 64 return s.doc.DocID 65 } 66 67 // String implements fmt.Stringer. 68 func (s *Subnet) String() string { 69 return s.CIDR() 70 } 71 72 // GoString implements fmt.GoStringer. 73 func (s *Subnet) GoString() string { 74 return s.String() 75 } 76 77 // EnsureDead sets the Life of the subnet to Dead, if it's Alive. If the subnet 78 // is already Dead, no error is returned. When the subnet is no longer Alive or 79 // already removed, errNotAlive is returned. 80 func (s *Subnet) EnsureDead() (err error) { 81 defer errors.DeferredAnnotatef(&err, "cannot set subnet %q to dead", s) 82 83 if s.doc.Life == Dead { 84 return nil 85 } 86 87 ops := []txn.Op{{ 88 C: subnetsC, 89 Id: s.doc.DocID, 90 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 91 Assert: isAliveDoc, 92 }} 93 94 txnErr := s.st.runTransaction(ops) 95 if txnErr == nil { 96 s.doc.Life = Dead 97 return nil 98 } 99 return onAbort(txnErr, errNotAlive) 100 } 101 102 // Remove removes a Dead subnet. If the subnet is not Dead or it is already 103 // removed, an error is returned. On success, all IP addresses added to the 104 // subnet are also removed. 105 func (s *Subnet) Remove() (err error) { 106 defer errors.DeferredAnnotatef(&err, "cannot remove subnet %q", s) 107 108 if s.doc.Life != Dead { 109 return errors.New("subnet is not dead") 110 } 111 112 ops := []txn.Op{{ 113 C: subnetsC, 114 Id: s.doc.DocID, 115 Remove: true, 116 Assert: isDeadDoc, 117 }} 118 if s.doc.ProviderId != "" { 119 op := s.st.networkEntityGlobalKeyRemoveOp("subnet", s.ProviderId()) 120 ops = append(ops, op) 121 } 122 123 txnErr := s.st.runTransaction(ops) 124 if txnErr == nil { 125 return nil 126 } 127 return onAbort(txnErr, errors.New("not found or not dead")) 128 } 129 130 // ProviderId returns the provider-specific id of the subnet. 131 func (s *Subnet) ProviderId() network.Id { 132 return network.Id(s.doc.ProviderId) 133 } 134 135 // CIDR returns the subnet CIDR (e.g. 192.168.50.0/24). 136 func (s *Subnet) CIDR() string { 137 return s.doc.CIDR 138 } 139 140 // VLANTag returns the subnet VLAN tag. It's a number between 1 and 141 // 4094 for VLANs and 0 if the network is not a VLAN. 142 func (s *Subnet) VLANTag() int { 143 return s.doc.VLANTag 144 } 145 146 // AvailabilityZone returns the availability zone of the subnet. If the subnet 147 // is not associated with an availability zone it will be the empty string. 148 func (s *Subnet) AvailabilityZone() string { 149 return s.doc.AvailabilityZone 150 } 151 152 // SpaceName returns the space the subnet is associated with. If the subnet is 153 // not associated with a space it will be the empty string. 154 func (s *Subnet) SpaceName() string { 155 return s.doc.SpaceName 156 } 157 158 // Validate validates the subnet, checking the CIDR, and VLANTag, if present. 159 func (s *Subnet) Validate() error { 160 if s.doc.CIDR != "" { 161 _, _, err := net.ParseCIDR(s.doc.CIDR) 162 if err != nil { 163 return errors.Trace(err) 164 } 165 } else { 166 return errors.Errorf("missing CIDR") 167 } 168 169 if s.doc.VLANTag < 0 || s.doc.VLANTag > 4094 { 170 return errors.Errorf("invalid VLAN tag %d: must be between 0 and 4094", s.doc.VLANTag) 171 } 172 173 return nil 174 } 175 176 // Refresh refreshes the contents of the Subnet from the underlying 177 // state. It an error that satisfies errors.IsNotFound if the Subnet has 178 // been removed. 179 func (s *Subnet) Refresh() error { 180 subnets, closer := s.st.getCollection(subnetsC) 181 defer closer() 182 183 err := subnets.FindId(s.doc.DocID).One(&s.doc) 184 if err == mgo.ErrNotFound { 185 return errors.NotFoundf("subnet %q", s) 186 } 187 if err != nil { 188 return errors.Errorf("cannot refresh subnet %q: %v", s, err) 189 } 190 return nil 191 } 192 193 // AddSubnet creates and returns a new subnet 194 func (st *State) AddSubnet(args SubnetInfo) (subnet *Subnet, err error) { 195 defer errors.DeferredAnnotatef(&err, "adding subnet %q", args.CIDR) 196 197 subnet, err = st.newSubnetFromArgs(args) 198 if err != nil { 199 return nil, errors.Trace(err) 200 } 201 ops := st.addSubnetOps(args) 202 ops = append(ops, assertModelActiveOp(st.ModelUUID())) 203 buildTxn := func(attempt int) ([]txn.Op, error) { 204 205 if attempt != 0 { 206 if err := checkModelActive(st); err != nil { 207 return nil, errors.Trace(err) 208 } 209 if _, err = st.Subnet(args.CIDR); err == nil { 210 return nil, errors.AlreadyExistsf("subnet %q", args.CIDR) 211 } 212 if err := subnet.Refresh(); err != nil { 213 if errors.IsNotFound(err) { 214 return nil, errors.Errorf("ProviderId %q not unique", args.ProviderId) 215 } 216 return nil, errors.Trace(err) 217 } 218 } 219 return ops, nil 220 } 221 err = st.run(buildTxn) 222 if err != nil { 223 return nil, errors.Trace(err) 224 } 225 return subnet, nil 226 } 227 228 func (st *State) newSubnetFromArgs(args SubnetInfo) (*Subnet, error) { 229 subnetID := st.docID(args.CIDR) 230 subDoc := subnetDoc{ 231 DocID: subnetID, 232 ModelUUID: st.ModelUUID(), 233 Life: Alive, 234 CIDR: args.CIDR, 235 VLANTag: args.VLANTag, 236 ProviderId: string(args.ProviderId), 237 AvailabilityZone: args.AvailabilityZone, 238 SpaceName: args.SpaceName, 239 } 240 subnet := &Subnet{doc: subDoc, st: st} 241 err := subnet.Validate() 242 if err != nil { 243 return nil, errors.Trace(err) 244 } 245 return subnet, nil 246 } 247 248 func (st *State) addSubnetOps(args SubnetInfo) []txn.Op { 249 subnetID := st.docID(args.CIDR) 250 subDoc := subnetDoc{ 251 DocID: subnetID, 252 ModelUUID: st.ModelUUID(), 253 Life: Alive, 254 CIDR: args.CIDR, 255 VLANTag: args.VLANTag, 256 ProviderId: string(args.ProviderId), 257 AvailabilityZone: args.AvailabilityZone, 258 SpaceName: args.SpaceName, 259 } 260 ops := []txn.Op{ 261 { 262 C: subnetsC, 263 Id: subnetID, 264 Assert: txn.DocMissing, 265 Insert: subDoc, 266 }, 267 } 268 if args.ProviderId != "" { 269 ops = append(ops, st.networkEntityGlobalKeyOp("subnet", args.ProviderId)) 270 } 271 return ops 272 } 273 274 // Subnet returns the subnet specified by the cidr. 275 func (st *State) Subnet(cidr string) (*Subnet, error) { 276 subnets, closer := st.getCollection(subnetsC) 277 defer closer() 278 279 doc := &subnetDoc{} 280 err := subnets.FindId(cidr).One(doc) 281 if err == mgo.ErrNotFound { 282 return nil, errors.NotFoundf("subnet %q", cidr) 283 } 284 if err != nil { 285 return nil, errors.Annotatef(err, "cannot get subnet %q", cidr) 286 } 287 return &Subnet{st, *doc}, nil 288 } 289 290 // AllSubnets returns all known subnets in the model. 291 func (st *State) AllSubnets() (subnets []*Subnet, err error) { 292 subnetsCollection, closer := st.getCollection(subnetsC) 293 defer closer() 294 295 docs := []subnetDoc{} 296 err = subnetsCollection.Find(nil).All(&docs) 297 if err != nil { 298 return nil, errors.Annotatef(err, "cannot get all subnets") 299 } 300 for _, doc := range docs { 301 subnets = append(subnets, &Subnet{st, doc}) 302 } 303 return subnets, nil 304 }