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  }