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  }