Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
305 lines (262 sloc) 7.99 KB
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package state
import (
"net"
"github.com/juju/errors"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"gopkg.in/mgo.v2/txn"
"github.com/juju/juju/network"
)
// SubnetInfo describes a single subnet.
type SubnetInfo struct {
// ProviderId is a provider-specific network id. This may be empty.
ProviderId network.Id
// CIDR of the network, in 123.45.67.89/24 format.
CIDR string
// VLANTag needs to be between 1 and 4094 for VLANs and 0 for normal
// networks. It's defined by IEEE 802.1Q standard.
VLANTag int
// AvailabilityZone describes which availability zone this subnet is in. It can
// be empty if the provider does not support availability zones.
AvailabilityZone string
// SpaceName is the name of the space the subnet is associated with. It
// can be empty if the subnet is not associated with a space yet.
SpaceName string
}
type Subnet struct {
st *State
doc subnetDoc
}
type subnetDoc struct {
DocID string `bson:"_id"`
ModelUUID string `bson:"model-uuid"`
Life Life `bson:"life"`
ProviderId string `bson:"providerid,omitempty"`
CIDR string `bson:"cidr"`
VLANTag int `bson:"vlantag,omitempty"`
AvailabilityZone string `bson:"availabilityzone,omitempty"`
// TODO: add IsPublic to SubnetArgs, add an IsPublic method and add
// IsPublic to migration import/export.
IsPublic bool `bson:"is-public,omitempty"`
SpaceName string `bson:"space-name,omitempty"`
}
// Life returns whether the subnet is Alive, Dying or Dead.
func (s *Subnet) Life() Life {
return s.doc.Life
}
// ID returns the unique id for the subnet, for other entities to reference it.
func (s *Subnet) ID() string {
return s.doc.DocID
}
// String implements fmt.Stringer.
func (s *Subnet) String() string {
return s.CIDR()
}
// GoString implements fmt.GoStringer.
func (s *Subnet) GoString() string {
return s.String()
}
// EnsureDead sets the Life of the subnet to Dead, if it's Alive. If the subnet
// is already Dead, no error is returned. When the subnet is no longer Alive or
// already removed, errNotAlive is returned.
func (s *Subnet) EnsureDead() (err error) {
defer errors.DeferredAnnotatef(&err, "cannot set subnet %q to dead", s)
if s.doc.Life == Dead {
return nil
}
ops := []txn.Op{{
C: subnetsC,
Id: s.doc.DocID,
Update: bson.D{{"$set", bson.D{{"life", Dead}}}},
Assert: isAliveDoc,
}}
txnErr := s.st.runTransaction(ops)
if txnErr == nil {
s.doc.Life = Dead
return nil
}
return onAbort(txnErr, errNotAlive)
}
// Remove removes a Dead subnet. If the subnet is not Dead or it is already
// removed, an error is returned. On success, all IP addresses added to the
// subnet are also removed.
func (s *Subnet) Remove() (err error) {
defer errors.DeferredAnnotatef(&err, "cannot remove subnet %q", s)
if s.doc.Life != Dead {
return errors.New("subnet is not dead")
}
ops := []txn.Op{{
C: subnetsC,
Id: s.doc.DocID,
Remove: true,
Assert: isDeadDoc,
}}
if s.doc.ProviderId != "" {
op := s.st.networkEntityGlobalKeyRemoveOp("subnet", s.ProviderId())
ops = append(ops, op)
}
txnErr := s.st.runTransaction(ops)
if txnErr == nil {
return nil
}
return onAbort(txnErr, errors.New("not found or not dead"))
}
// ProviderId returns the provider-specific id of the subnet.
func (s *Subnet) ProviderId() network.Id {
return network.Id(s.doc.ProviderId)
}
// CIDR returns the subnet CIDR (e.g. 192.168.50.0/24).
func (s *Subnet) CIDR() string {
return s.doc.CIDR
}
// VLANTag returns the subnet VLAN tag. It's a number between 1 and
// 4094 for VLANs and 0 if the network is not a VLAN.
func (s *Subnet) VLANTag() int {
return s.doc.VLANTag
}
// AvailabilityZone returns the availability zone of the subnet. If the subnet
// is not associated with an availability zone it will be the empty string.
func (s *Subnet) AvailabilityZone() string {
return s.doc.AvailabilityZone
}
// SpaceName returns the space the subnet is associated with. If the subnet is
// not associated with a space it will be the empty string.
func (s *Subnet) SpaceName() string {
return s.doc.SpaceName
}
// Validate validates the subnet, checking the CIDR, and VLANTag, if present.
func (s *Subnet) Validate() error {
if s.doc.CIDR != "" {
_, _, err := net.ParseCIDR(s.doc.CIDR)
if err != nil {
return errors.Trace(err)
}
} else {
return errors.Errorf("missing CIDR")
}
if s.doc.VLANTag < 0 || s.doc.VLANTag > 4094 {
return errors.Errorf("invalid VLAN tag %d: must be between 0 and 4094", s.doc.VLANTag)
}
return nil
}
// Refresh refreshes the contents of the Subnet from the underlying
// state. It an error that satisfies errors.IsNotFound if the Subnet has
// been removed.
func (s *Subnet) Refresh() error {
subnets, closer := s.st.getCollection(subnetsC)
defer closer()
err := subnets.FindId(s.doc.DocID).One(&s.doc)
if err == mgo.ErrNotFound {
return errors.NotFoundf("subnet %q", s)
}
if err != nil {
return errors.Errorf("cannot refresh subnet %q: %v", s, err)
}
return nil
}
// AddSubnet creates and returns a new subnet
func (st *State) AddSubnet(args SubnetInfo) (subnet *Subnet, err error) {
defer errors.DeferredAnnotatef(&err, "adding subnet %q", args.CIDR)
subnet, err = st.newSubnetFromArgs(args)
if err != nil {
return nil, errors.Trace(err)
}
ops := st.addSubnetOps(args)
ops = append(ops, assertModelActiveOp(st.ModelUUID()))
buildTxn := func(attempt int) ([]txn.Op, error) {
if attempt != 0 {
if err := checkModelActive(st); err != nil {
return nil, errors.Trace(err)
}
if _, err = st.Subnet(args.CIDR); err == nil {
return nil, errors.AlreadyExistsf("subnet %q", args.CIDR)
}
if err := subnet.Refresh(); err != nil {
if errors.IsNotFound(err) {
return nil, errors.Errorf("ProviderId %q not unique", args.ProviderId)
}
return nil, errors.Trace(err)
}
}
return ops, nil
}
err = st.run(buildTxn)
if err != nil {
return nil, errors.Trace(err)
}
return subnet, nil
}
func (st *State) newSubnetFromArgs(args SubnetInfo) (*Subnet, error) {
subnetID := st.docID(args.CIDR)
subDoc := subnetDoc{
DocID: subnetID,
ModelUUID: st.ModelUUID(),
Life: Alive,
CIDR: args.CIDR,
VLANTag: args.VLANTag,
ProviderId: string(args.ProviderId),
AvailabilityZone: args.AvailabilityZone,
SpaceName: args.SpaceName,
}
subnet := &Subnet{doc: subDoc, st: st}
err := subnet.Validate()
if err != nil {
return nil, errors.Trace(err)
}
return subnet, nil
}
func (st *State) addSubnetOps(args SubnetInfo) []txn.Op {
subnetID := st.docID(args.CIDR)
subDoc := subnetDoc{
DocID: subnetID,
ModelUUID: st.ModelUUID(),
Life: Alive,
CIDR: args.CIDR,
VLANTag: args.VLANTag,
ProviderId: string(args.ProviderId),
AvailabilityZone: args.AvailabilityZone,
SpaceName: args.SpaceName,
}
ops := []txn.Op{
{
C: subnetsC,
Id: subnetID,
Assert: txn.DocMissing,
Insert: subDoc,
},
}
if args.ProviderId != "" {
ops = append(ops, st.networkEntityGlobalKeyOp("subnet", args.ProviderId))
}
return ops
}
// Subnet returns the subnet specified by the cidr.
func (st *State) Subnet(cidr string) (*Subnet, error) {
subnets, closer := st.getCollection(subnetsC)
defer closer()
doc := &subnetDoc{}
err := subnets.FindId(cidr).One(doc)
if err == mgo.ErrNotFound {
return nil, errors.NotFoundf("subnet %q", cidr)
}
if err != nil {
return nil, errors.Annotatef(err, "cannot get subnet %q", cidr)
}
return &Subnet{st, *doc}, nil
}
// AllSubnets returns all known subnets in the model.
func (st *State) AllSubnets() (subnets []*Subnet, err error) {
subnetsCollection, closer := st.getCollection(subnetsC)
defer closer()
docs := []subnetDoc{}
err = subnetsCollection.Find(nil).All(&docs)
if err != nil {
return nil, errors.Annotatef(err, "cannot get all subnets")
}
for _, doc := range docs {
subnets = append(subnets, &Subnet{st, doc})
}
return subnets, nil
}