github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "math/rand" 8 "net" 9 10 "github.com/juju/errors" 11 "gopkg.in/mgo.v2" 12 "gopkg.in/mgo.v2/bson" 13 "gopkg.in/mgo.v2/txn" 14 15 "github.com/juju/juju/network" 16 ) 17 18 // SubnetInfo describes a single subnet. 19 type SubnetInfo struct { 20 // ProviderId is a provider-specific network id. This may be empty. 21 ProviderId network.Id 22 23 // CIDR of the network, in 123.45.67.89/24 format. 24 CIDR string 25 26 // VLANTag needs to be between 1 and 4094 for VLANs and 0 for normal 27 // networks. It's defined by IEEE 802.1Q standard. 28 VLANTag int 29 30 // AllocatableIPHigh and Low describe the allocatable portion of the 31 // subnet. The remainder, if any, is reserved by the provider. 32 // Either both of these must be set or neither, if they're empty it 33 // means that none of the subnet is allocatable. If present they must 34 // be valid IP addresses within the subnet CIDR. 35 AllocatableIPHigh string 36 AllocatableIPLow string 37 38 // AvailabilityZone describes which availability zone this subnet is in. It can 39 // be empty if the provider does not support availability zones. 40 AvailabilityZone string 41 42 // SpaceName is the name of the space the subnet is associated with. It 43 // can be empty if the subnet is not associated with a space yet. 44 SpaceName string 45 } 46 47 type Subnet struct { 48 st *State 49 doc subnetDoc 50 } 51 52 type subnetDoc struct { 53 DocID string `bson:"_id"` 54 ModelUUID string `bson:"model-uuid"` 55 Life Life `bson:"life"` 56 ProviderId string `bson:"providerid,omitempty"` 57 CIDR string `bson:"cidr"` 58 AllocatableIPHigh string `bson:"allocatableiphigh,omitempty"` 59 AllocatableIPLow string `bson:"allocatableiplow,omitempty"` 60 VLANTag int `bson:"vlantag,omitempty"` 61 AvailabilityZone string `bson:"availabilityzone,omitempty"` 62 IsPublic bool `bson:"is-public,omitempty"` 63 // TODO(dooferlad 2015-08-03): add an upgrade step to insert IsPublic=false 64 SpaceName string `bson:"space-name,omitempty"` 65 } 66 67 // Life returns whether the subnet is Alive, Dying or Dead. 68 func (s *Subnet) Life() Life { 69 return s.doc.Life 70 } 71 72 // ID returns the unique id for the subnet, for other entities to reference it 73 func (s *Subnet) ID() string { 74 return s.doc.DocID 75 } 76 77 // String implements fmt.Stringer. 78 func (s *Subnet) String() string { 79 return s.CIDR() 80 } 81 82 // GoString implements fmt.GoStringer. 83 func (s *Subnet) GoString() string { 84 return s.String() 85 } 86 87 // EnsureDead sets the Life of the subnet to Dead, if it's Alive. If the subnet 88 // is already Dead, no error is returned. When the subnet is no longer Alive or 89 // already removed, errNotAlive is returned. 90 func (s *Subnet) EnsureDead() (err error) { 91 defer errors.DeferredAnnotatef(&err, "cannot set subnet %q to dead", s) 92 93 if s.doc.Life == Dead { 94 return nil 95 } 96 97 ops := []txn.Op{{ 98 C: subnetsC, 99 Id: s.doc.DocID, 100 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 101 Assert: isAliveDoc, 102 }} 103 104 txnErr := s.st.runTransaction(ops) 105 if txnErr == nil { 106 s.doc.Life = Dead 107 return nil 108 } 109 return onAbort(txnErr, errNotAlive) 110 } 111 112 // Remove removes a Dead subnet. If the subnet is not Dead or it is already 113 // removed, an error is returned. On success, all IP addresses added to the 114 // subnet are also removed. 115 func (s *Subnet) Remove() (err error) { 116 defer errors.DeferredAnnotatef(&err, "cannot remove subnet %q", s) 117 118 if s.doc.Life != Dead { 119 return errors.New("subnet is not dead") 120 } 121 122 addresses, closer := s.st.getCollection(legacyipaddressesC) 123 defer closer() 124 125 var ops []txn.Op 126 id := s.ID() 127 var doc struct { 128 DocID string `bson:"_id"` 129 } 130 iter := addresses.Find(bson.D{{"subnetid", id}}).Iter() 131 for iter.Next(&doc) { 132 ops = append(ops, txn.Op{ 133 C: legacyipaddressesC, 134 Id: doc.DocID, 135 Remove: true, 136 }) 137 } 138 if err = iter.Close(); err != nil { 139 return errors.Annotate(err, "cannot read addresses") 140 } 141 142 ops = append(ops, txn.Op{ 143 C: subnetsC, 144 Id: s.doc.DocID, 145 Remove: true, 146 Assert: isDeadDoc, 147 }) 148 149 txnErr := s.st.runTransaction(ops) 150 if txnErr == nil { 151 return nil 152 } 153 return onAbort(txnErr, errors.New("not found or not dead")) 154 } 155 156 // ProviderId returns the provider-specific id of the subnet. 157 func (s *Subnet) ProviderId() network.Id { 158 return network.Id(s.st.localID(s.doc.ProviderId)) 159 } 160 161 // CIDR returns the subnet CIDR (e.g. 192.168.50.0/24). 162 func (s *Subnet) CIDR() string { 163 return s.doc.CIDR 164 } 165 166 // VLANTag returns the subnet VLAN tag. It's a number between 1 and 167 // 4094 for VLANs and 0 if the network is not a VLAN. 168 func (s *Subnet) VLANTag() int { 169 return s.doc.VLANTag 170 } 171 172 // AllocatableIPLow returns the lowest allocatable IP address in the subnet 173 func (s *Subnet) AllocatableIPLow() string { 174 return s.doc.AllocatableIPLow 175 } 176 177 // AllocatableIPHigh returns the hightest allocatable IP address in the subnet. 178 func (s *Subnet) AllocatableIPHigh() string { 179 return s.doc.AllocatableIPHigh 180 } 181 182 // AvailabilityZone returns the availability zone of the subnet. If the subnet 183 // is not associated with an availability zone it will be the empty string. 184 func (s *Subnet) AvailabilityZone() string { 185 return s.doc.AvailabilityZone 186 } 187 188 // SpaceName returns the space the subnet is associated with. If the subnet is 189 // not associated with a space it will be the empty string. 190 func (s *Subnet) SpaceName() string { 191 return s.doc.SpaceName 192 } 193 194 // Validate validates the subnet, checking the CIDR, VLANTag and 195 // AllocatableIPHigh and Low, if present. 196 func (s *Subnet) Validate() error { 197 var mask *net.IPNet 198 var err error 199 if s.doc.CIDR != "" { 200 _, mask, err = net.ParseCIDR(s.doc.CIDR) 201 if err != nil { 202 return errors.Trace(err) 203 } 204 } else { 205 return errors.Errorf("missing CIDR") 206 } 207 if s.doc.VLANTag < 0 || s.doc.VLANTag > 4094 { 208 return errors.Errorf("invalid VLAN tag %d: must be between 0 and 4094", s.doc.VLANTag) 209 } 210 present := func(str string) bool { 211 return str != "" 212 } 213 either := present(s.doc.AllocatableIPLow) || present(s.doc.AllocatableIPHigh) 214 both := present(s.doc.AllocatableIPLow) && present(s.doc.AllocatableIPHigh) 215 216 if either && !both { 217 return errors.Errorf("either both AllocatableIPLow and AllocatableIPHigh must be set or neither set") 218 } 219 220 // TODO (mfoord 26-11-2014) we could also validate that the IPs are the 221 // same type (IPv4 or IPv6) and that IPLow is lower than or equal to 222 // IPHigh. 223 if s.doc.AllocatableIPHigh != "" { 224 highIP := net.ParseIP(s.doc.AllocatableIPHigh) 225 if highIP == nil || !mask.Contains(highIP) { 226 return errors.Errorf("invalid AllocatableIPHigh %q", s.doc.AllocatableIPHigh) 227 } 228 lowIP := net.ParseIP(s.doc.AllocatableIPLow) 229 if lowIP == nil || !mask.Contains(lowIP) { 230 return errors.Errorf("invalid AllocatableIPLow %q", s.doc.AllocatableIPLow) 231 } 232 } 233 return nil 234 } 235 236 // Refresh refreshes the contents of the Subnet from the underlying 237 // state. It an error that satisfies errors.IsNotFound if the Subnet has 238 // been removed. 239 func (s *Subnet) Refresh() error { 240 subnets, closer := s.st.getCollection(subnetsC) 241 defer closer() 242 243 err := subnets.FindId(s.doc.DocID).One(&s.doc) 244 if err == mgo.ErrNotFound { 245 return errors.NotFoundf("subnet %q", s) 246 } 247 if err != nil { 248 return errors.Errorf("cannot refresh subnet %q: %v", s, err) 249 } 250 return nil 251 } 252 253 // PickNewAddress returns a new IPAddress that isn't in use for the subnet. 254 // The address starts with AddressStateUnknown, for later allocation. 255 // This will fail if the subnet is not alive. 256 func (s *Subnet) PickNewAddress() (*IPAddress, error) { 257 for { 258 addr, err := s.attemptToPickNewAddress() 259 if err == nil { 260 return addr, err 261 } 262 if !errors.IsAlreadyExists(err) { 263 return addr, err 264 } 265 } 266 } 267 268 // attemptToPickNewAddress will try to pick a new address. It can fail 269 // with AlreadyExists due to a race condition between fetching the 270 // list of addresses already in use and allocating a new one. If the 271 // subnet is not alive, it will also fail. It is called in a loop by 272 // PickNewAddress until it gets one or there are no more available! 273 func (s *Subnet) attemptToPickNewAddress() (*IPAddress, error) { 274 if s.doc.Life != Alive { 275 return nil, errors.Errorf("cannot pick address: subnet %q is not alive", s) 276 } 277 high := s.doc.AllocatableIPHigh 278 low := s.doc.AllocatableIPLow 279 if low == "" || high == "" { 280 return nil, errors.Errorf("no allocatable IP addresses for subnet %q", s) 281 } 282 283 // convert low and high to decimals as the bounds 284 lowDecimal, err := network.IPv4ToDecimal(net.ParseIP(low)) 285 if err != nil { 286 // these addresses are validated so should never happen 287 return nil, errors.Annotatef(err, "invalid AllocatableIPLow %q for subnet %q", low, s) 288 } 289 highDecimal, err := network.IPv4ToDecimal(net.ParseIP(high)) 290 if err != nil { 291 // these addresses are validated so should never happen 292 return nil, errors.Annotatef(err, "invalid AllocatableIPHigh %q for subnet %q", high, s) 293 } 294 295 // find all addresses for this subnet and convert them to decimals 296 addresses, closer := s.st.getCollection(legacyipaddressesC) 297 defer closer() 298 299 id := s.ID() 300 var doc struct { 301 Value string 302 } 303 allocated := make(map[uint32]bool) 304 iter := addresses.Find(bson.D{{"subnetid", id}}).Iter() 305 for iter.Next(&doc) { 306 // skip invalid values. Can't happen anyway as we validate. 307 value, err := network.IPv4ToDecimal(net.ParseIP(doc.Value)) 308 if err != nil { 309 continue 310 } 311 allocated[value] = true 312 } 313 if err := iter.Close(); err != nil { 314 return nil, errors.Annotatef(err, "cannot read addresses of subnet %q", s) 315 } 316 317 // Check that the number of addresses in use is less than the 318 // difference between low and high - i.e. we haven't exhausted all 319 // possible addresses. 320 if len(allocated) >= int(highDecimal-lowDecimal)+1 { 321 return nil, errors.Errorf("allocatable IP addresses exhausted for subnet %q", s) 322 } 323 324 // pick a new random decimal between the low and high bounds that 325 // doesn't match an existing one 326 newDecimal := pickAddress(lowDecimal, highDecimal, allocated) 327 328 // convert it back to a dotted-quad 329 newIP := network.DecimalToIPv4(newDecimal) 330 newAddr := network.NewAddress(newIP.String()) 331 332 // and create a new IPAddress from it and return it 333 return s.st.AddIPAddress(newAddr, s.ID()) 334 } 335 336 // pickAddress will pick a number, representing an IPv4 address, between low 337 // and high (inclusive) that isn't in the allocated map. There must be at least 338 // one available address between low and high and not in allocated. 339 // e.g. pickAddress(uint32(2700), uint32(2800), map[uint32]bool{uint32(2701): true}) 340 // The allocated map is just being used as a set of unavailable addresses, so 341 // the bool value isn't significant. 342 var pickAddress = func(low, high uint32, allocated map[uint32]bool) uint32 { 343 // +1 because Int63n will pick a number up to, but not including, the 344 // bounds we provide. 345 bounds := uint32(high-low) + 1 346 if bounds == 1 { 347 // we've already checked that there is a free IP address, so 348 // this must be it! 349 return low 350 } 351 for { 352 inBounds := rand.Int63n(int64(bounds)) 353 value := uint32(inBounds) + low 354 if _, ok := allocated[value]; !ok { 355 return value 356 } 357 } 358 }