github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/spaces.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/names.v2"
     9  	"gopkg.in/mgo.v2"
    10  	"gopkg.in/mgo.v2/bson"
    11  	"gopkg.in/mgo.v2/txn"
    12  
    13  	"github.com/juju/juju/network"
    14  )
    15  
    16  // Space represents the state of a juju network space.
    17  type Space struct {
    18  	st  *State
    19  	doc spaceDoc
    20  }
    21  
    22  type spaceDoc struct {
    23  	Life       Life   `bson:"life"`
    24  	Name       string `bson:"name"`
    25  	IsPublic   bool   `bson:"is-public"`
    26  	ProviderId string `bson:"providerid,omitempty"`
    27  }
    28  
    29  // Life returns whether the space is Alive, Dying or Dead.
    30  func (s *Space) Life() Life {
    31  	return s.doc.Life
    32  }
    33  
    34  // String implements fmt.Stringer.
    35  func (s *Space) String() string {
    36  	return s.doc.Name
    37  }
    38  
    39  // Name returns the name of the Space.
    40  func (s *Space) Name() string {
    41  	return s.doc.Name
    42  }
    43  
    44  // IsPublic returns whether the space is public or not.
    45  func (s *Space) IsPublic() bool {
    46  	return s.doc.IsPublic
    47  }
    48  
    49  // ProviderId returns the provider id of the space. This will be the empty
    50  // string except on substrates that directly support spaces.
    51  func (s *Space) ProviderId() network.Id {
    52  	return network.Id(s.doc.ProviderId)
    53  }
    54  
    55  // Subnets returns all the subnets associated with the Space.
    56  func (s *Space) Subnets() (results []*Subnet, err error) {
    57  	defer errors.DeferredAnnotatef(&err, "cannot fetch subnets")
    58  	name := s.Name()
    59  
    60  	subnetsCollection, closer := s.st.getCollection(subnetsC)
    61  	defer closer()
    62  
    63  	var doc subnetDoc
    64  	iter := subnetsCollection.Find(bson.D{{"space-name", name}}).Iter()
    65  	defer iter.Close()
    66  	for iter.Next(&doc) {
    67  		subnet := &Subnet{s.st, doc}
    68  		results = append(results, subnet)
    69  	}
    70  	if err := iter.Err(); err != nil {
    71  		return nil, err
    72  	}
    73  	return results, nil
    74  }
    75  
    76  // AddSpace creates and returns a new space.
    77  func (st *State) AddSpace(name string, providerId network.Id, subnets []string, isPublic bool) (newSpace *Space, err error) {
    78  	defer errors.DeferredAnnotatef(&err, "adding space %q", name)
    79  	if !names.IsValidSpace(name) {
    80  		return nil, errors.NewNotValid(nil, "invalid space name")
    81  	}
    82  
    83  	spaceDoc := spaceDoc{
    84  		Life:       Alive,
    85  		Name:       name,
    86  		IsPublic:   isPublic,
    87  		ProviderId: string(providerId),
    88  	}
    89  	newSpace = &Space{doc: spaceDoc, st: st}
    90  
    91  	ops := []txn.Op{{
    92  		C:      spacesC,
    93  		Id:     name,
    94  		Assert: txn.DocMissing,
    95  		Insert: spaceDoc,
    96  	}}
    97  
    98  	if providerId != "" {
    99  		ops = append(ops, st.networkEntityGlobalKeyOp("space", providerId))
   100  	}
   101  
   102  	for _, subnetId := range subnets {
   103  		// TODO:(mfoord) once we have refcounting for subnets we should
   104  		// also assert that the refcount is zero as moving the space of a
   105  		// subnet in use is not permitted.
   106  		ops = append(ops, txn.Op{
   107  			C:      subnetsC,
   108  			Id:     subnetId,
   109  			Assert: txn.DocExists,
   110  			Update: bson.D{{"$set", bson.D{{"space-name", name}}}},
   111  		})
   112  	}
   113  
   114  	if err := st.runTransaction(ops); err == txn.ErrAborted {
   115  		if _, err := st.Space(name); err == nil {
   116  			return nil, errors.AlreadyExistsf("space %q", name)
   117  		}
   118  		for _, subnetId := range subnets {
   119  			if _, err := st.Subnet(subnetId); errors.IsNotFound(err) {
   120  				return nil, err
   121  			}
   122  		}
   123  		if err := newSpace.Refresh(); err != nil {
   124  			if errors.IsNotFound(err) {
   125  				return nil, errors.Errorf("ProviderId %q not unique", providerId)
   126  			}
   127  			return nil, errors.Trace(err)
   128  		}
   129  		return nil, errors.Trace(err)
   130  	} else if err != nil {
   131  		return nil, err
   132  	}
   133  	return newSpace, nil
   134  }
   135  
   136  // Space returns a space from state that matches the provided name. An error
   137  // is returned if the space doesn't exist or if there was a problem accessing
   138  // its information.
   139  func (st *State) Space(name string) (*Space, error) {
   140  	spaces, closer := st.getCollection(spacesC)
   141  	defer closer()
   142  
   143  	var doc spaceDoc
   144  	err := spaces.FindId(name).One(&doc)
   145  	if err == mgo.ErrNotFound {
   146  		return nil, errors.NotFoundf("space %q", name)
   147  	}
   148  	if err != nil {
   149  		return nil, errors.Annotatef(err, "cannot get space %q", name)
   150  	}
   151  	return &Space{st, doc}, nil
   152  }
   153  
   154  // AllSpaces returns all spaces for the model.
   155  func (st *State) AllSpaces() ([]*Space, error) {
   156  	spacesCollection, closer := st.getCollection(spacesC)
   157  	defer closer()
   158  
   159  	docs := []spaceDoc{}
   160  	err := spacesCollection.Find(nil).All(&docs)
   161  	if err != nil {
   162  		return nil, errors.Annotatef(err, "cannot get all spaces")
   163  	}
   164  	spaces := make([]*Space, len(docs))
   165  	for i, doc := range docs {
   166  		spaces[i] = &Space{st: st, doc: doc}
   167  	}
   168  	return spaces, nil
   169  }
   170  
   171  // EnsureDead sets the Life of the space to Dead, if it's Alive. If the space is
   172  // already Dead, no error is returned. When the space is no longer Alive or
   173  // already removed, errNotAlive is returned.
   174  func (s *Space) EnsureDead() (err error) {
   175  	defer errors.DeferredAnnotatef(&err, "cannot set space %q to dead", s)
   176  
   177  	if s.doc.Life == Dead {
   178  		return nil
   179  	}
   180  
   181  	ops := []txn.Op{{
   182  		C:      spacesC,
   183  		Id:     s.doc.Name,
   184  		Update: bson.D{{"$set", bson.D{{"life", Dead}}}},
   185  		Assert: isAliveDoc,
   186  	}}
   187  
   188  	txnErr := s.st.runTransaction(ops)
   189  	if txnErr == nil {
   190  		s.doc.Life = Dead
   191  		return nil
   192  	}
   193  	return onAbort(txnErr, errNotAlive)
   194  }
   195  
   196  // Remove removes a Dead space. If the space is not Dead or it is already
   197  // removed, an error is returned.
   198  func (s *Space) Remove() (err error) {
   199  	defer errors.DeferredAnnotatef(&err, "cannot remove space %q", s)
   200  
   201  	if s.doc.Life != Dead {
   202  		return errors.New("space is not dead")
   203  	}
   204  
   205  	ops := []txn.Op{{
   206  		C:      spacesC,
   207  		Id:     s.doc.Name,
   208  		Remove: true,
   209  		Assert: isDeadDoc,
   210  	}}
   211  	if s.ProviderId() != "" {
   212  		ops = append(ops, s.st.networkEntityGlobalKeyRemoveOp("space", s.ProviderId()))
   213  	}
   214  
   215  	txnErr := s.st.runTransaction(ops)
   216  	if txnErr == nil {
   217  		return nil
   218  	}
   219  	return onAbort(txnErr, errors.New("not found or not dead"))
   220  }
   221  
   222  // Refresh: refreshes the contents of the Space from the underlying state. It
   223  // returns an error that satisfies errors.IsNotFound if the Space has been
   224  // removed.
   225  func (s *Space) Refresh() error {
   226  	spaces, closer := s.st.getCollection(spacesC)
   227  	defer closer()
   228  
   229  	var doc spaceDoc
   230  	err := spaces.FindId(s.doc.Name).One(&doc)
   231  	if err == mgo.ErrNotFound {
   232  		return errors.NotFoundf("space %q", s)
   233  	} else if err != nil {
   234  		return errors.Errorf("cannot refresh space %q: %v", s, err)
   235  	}
   236  	s.doc = doc
   237  	return nil
   238  }