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