github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"fmt"
     8  	"net"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/mgo/v3"
    15  	"github.com/juju/mgo/v3/bson"
    16  	"github.com/juju/mgo/v3/txn"
    17  	"github.com/juju/names/v5"
    18  
    19  	"github.com/juju/juju/core/network"
    20  	stateerrors "github.com/juju/juju/state/errors"
    21  )
    22  
    23  // Space represents the state of a juju network space.
    24  type Space struct {
    25  	st  *State
    26  	doc spaceDoc
    27  }
    28  
    29  type spaceDoc struct {
    30  	DocId      string `bson:"_id"`
    31  	Id         string `bson:"spaceid"`
    32  	Life       Life   `bson:"life"`
    33  	Name       string `bson:"name"`
    34  	IsPublic   bool   `bson:"is-public"`
    35  	ProviderId string `bson:"providerid,omitempty"`
    36  }
    37  
    38  // Id returns the space ID.
    39  func (s *Space) Id() string {
    40  	return s.doc.Id
    41  }
    42  
    43  // Life returns whether the space is Alive, Dying or Dead.
    44  func (s *Space) Life() Life {
    45  	return s.doc.Life
    46  }
    47  
    48  // String implements fmt.Stringer.
    49  func (s *Space) String() string {
    50  	return s.doc.Name
    51  }
    52  
    53  // Name returns the name of the Space.
    54  func (s *Space) Name() string {
    55  	return s.doc.Name
    56  }
    57  
    58  // IsPublic returns whether the space is public or not.
    59  func (s *Space) IsPublic() bool {
    60  	return s.doc.IsPublic
    61  }
    62  
    63  // ProviderId returns the provider id of the space. This will be the empty
    64  // string except on substrates that directly support spaces.
    65  func (s *Space) ProviderId() network.Id {
    66  	return network.Id(s.doc.ProviderId)
    67  }
    68  
    69  // NetworkSpace maps the space fields into a network.SpaceInfo.
    70  // This method materialises subnets for each call.
    71  // If calling multiple times, consider using AllSpaceInfos and filtering
    72  // in-place.
    73  func (s *Space) NetworkSpace() (network.SpaceInfo, error) {
    74  	subs, err := s.st.AllSubnetInfos()
    75  	if err != nil {
    76  		return network.SpaceInfo{}, errors.Trace(err)
    77  	}
    78  
    79  	space, err := s.networkSpace(subs)
    80  	return space, errors.Trace(err)
    81  }
    82  
    83  // networkSpace transforms a Space into a network.SpaceInfo using the
    84  // materialised subnet information.
    85  func (s *Space) networkSpace(subnets network.SubnetInfos) (network.SpaceInfo, error) {
    86  	spaceSubs, err := subnets.GetBySpaceID(s.Id())
    87  	if err != nil {
    88  		return network.SpaceInfo{}, errors.Trace(err)
    89  	}
    90  
    91  	for i := range spaceSubs {
    92  		spaceSubs[i].SpaceID = s.Id()
    93  		spaceSubs[i].SpaceName = s.Name()
    94  		spaceSubs[i].ProviderSpaceId = s.ProviderId()
    95  	}
    96  
    97  	return network.SpaceInfo{
    98  		ID:         s.Id(),
    99  		Name:       network.SpaceName(s.Name()),
   100  		ProviderId: s.ProviderId(),
   101  		Subnets:    spaceSubs,
   102  	}, nil
   103  }
   104  
   105  // RemoveSpaceOps returns txn.Ops to remove the space
   106  func (s *Space) RemoveSpaceOps() ([]txn.Op, error) {
   107  	spaceInfo, err := s.NetworkSpace()
   108  	if err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  	var ops []txn.Op
   112  	for _, subnet := range spaceInfo.Subnets {
   113  		ops = append(ops, s.st.UpdateSubnetSpaceOps(subnet.ID.String(), s.Id())...)
   114  	}
   115  
   116  	return append(ops, txn.Op{
   117  		C:      spacesC,
   118  		Id:     s.doc.DocId,
   119  		Assert: txn.DocExists,
   120  		Remove: true,
   121  	}), nil
   122  }
   123  
   124  // RenameSpaceOps returns the database transaction operations required to
   125  // rename the input space `fromName` to input `toName`.
   126  func (s *Space) RenameSpaceOps(toName string) []txn.Op {
   127  	renameSpaceOps := []txn.Op{{
   128  		C:      spacesC,
   129  		Id:     s.doc.DocId,
   130  		Update: bson.D{{"$set", bson.D{{"name", toName}}}},
   131  	}}
   132  	return renameSpaceOps
   133  }
   134  
   135  // AddSpace creates and returns a new space.
   136  func (st *State) AddSpace(
   137  	name string, providerId network.Id, subnetIDs []string, isPublic bool) (newSpace *Space, err error,
   138  ) {
   139  	defer errors.DeferredAnnotatef(&err, "adding space %q", name)
   140  	if !names.IsValidSpace(name) {
   141  		return nil, errors.NewNotValid(nil, "invalid space name")
   142  	}
   143  
   144  	buildTxn := func(attempt int) ([]txn.Op, error) {
   145  		if _, err := st.SpaceByName(name); err != nil {
   146  			if !errors.IsNotFound(err) {
   147  				return nil, errors.Annotatef(err, "checking for existing space")
   148  			}
   149  		} else {
   150  			return nil, errors.AlreadyExistsf("space %q", name)
   151  		}
   152  
   153  		for _, subnetId := range subnetIDs {
   154  			subnet, err := st.Subnet(subnetId)
   155  			if err != nil {
   156  				return nil, errors.Trace(err)
   157  			}
   158  			if subnet.FanLocalUnderlay() != "" {
   159  				return nil, errors.Errorf(
   160  					"cannot set space for FAN subnet %q - it is always inherited from underlay", subnet.CIDR())
   161  			}
   162  		}
   163  
   164  		// The ops will assert that the ID is unique,
   165  		// but we check explicitly in order to return an indicative error.
   166  		if providerId != "" {
   167  			exists, err := st.networkEntityGlobalKeyExists("space", providerId)
   168  			if err != nil {
   169  				return nil, errors.Trace(err)
   170  			}
   171  			if exists {
   172  				return nil, errors.Errorf("provider ID %q not unique", providerId)
   173  			}
   174  		}
   175  
   176  		ops, err := st.addSpaceWithSubnetsTxnOps(name, providerId, subnetIDs, isPublic)
   177  		return ops, errors.Trace(err)
   178  	}
   179  
   180  	err = st.db().Run(buildTxn)
   181  	if err != nil {
   182  		err = onAbort(err, stateerrors.ErrDead)
   183  		logger.Errorf("cannot add space to the model: %v", err)
   184  		return nil, errors.Trace(err)
   185  	}
   186  
   187  	space, err := st.SpaceByName(name)
   188  	return space, errors.Trace(err)
   189  }
   190  
   191  func (st *State) addSpaceWithSubnetsTxnOps(
   192  	name string, providerId network.Id, subnetIDs []string, isPublic bool,
   193  ) ([]txn.Op, error) {
   194  	// Space with ID zero is the default space; start at 1.
   195  	seq, err := sequenceWithMin(st, "space", 1)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	id := strconv.Itoa(seq)
   200  
   201  	ops := st.addSpaceTxnOps(id, name, providerId, isPublic)
   202  
   203  	for _, subnetID := range subnetIDs {
   204  		// TODO:(mfoord) once we have refcounting for subnets we should
   205  		// also assert that the refcount is zero as moving the space of a
   206  		// subnet in use is not permitted.
   207  		ops = append(ops, txn.Op{
   208  			C:      subnetsC,
   209  			Id:     subnetID,
   210  			Assert: bson.D{bson.DocElem{Name: "fan-local-underlay", Value: bson.D{{"$exists", false}}}},
   211  			Update: bson.D{{"$set", bson.D{{"space-id", id}}}},
   212  		})
   213  	}
   214  
   215  	return ops, nil
   216  }
   217  
   218  func (st *State) addSpaceTxnOps(id, name string, providerId network.Id, isPublic bool) []txn.Op {
   219  	doc := spaceDoc{
   220  		DocId:      st.docID(id),
   221  		Id:         id,
   222  		Life:       Alive,
   223  		Name:       name,
   224  		IsPublic:   isPublic,
   225  		ProviderId: string(providerId),
   226  	}
   227  
   228  	ops := []txn.Op{{
   229  		C:      spacesC,
   230  		Id:     doc.DocId,
   231  		Assert: txn.DocMissing,
   232  		Insert: doc,
   233  	}}
   234  
   235  	if providerId != "" {
   236  		ops = append(ops, st.networkEntityGlobalKeyOp("space", providerId))
   237  	}
   238  
   239  	return ops
   240  }
   241  
   242  // Space returns a space from state that matches the input ID.
   243  // An error is returned if the space does not exist or if there was a problem
   244  // accessing its information.
   245  func (st *State) Space(id string) (*Space, error) {
   246  	spaces, closer := st.db().GetCollection(spacesC)
   247  	defer closer()
   248  
   249  	var doc spaceDoc
   250  	err := spaces.Find(bson.M{"spaceid": id}).One(&doc)
   251  	if err == mgo.ErrNotFound {
   252  		return nil, errors.NotFoundf("space id %q", id)
   253  	}
   254  	if err != nil {
   255  		return nil, errors.Annotatef(err, "cannot get space id %q", id)
   256  	}
   257  	return &Space{st, doc}, nil
   258  }
   259  
   260  // SpaceByName returns a space from state that matches the input name.
   261  // An error is returned if the space does not exist or if there was a problem
   262  // accessing its information.
   263  func (st *State) SpaceByName(name string) (*Space, error) {
   264  	spaces, closer := st.db().GetCollection(spacesC)
   265  	defer closer()
   266  
   267  	var doc spaceDoc
   268  	err := spaces.Find(bson.M{"name": name}).One(&doc)
   269  	if err == mgo.ErrNotFound {
   270  		return nil, errors.NotFoundf("space %q", name)
   271  	}
   272  	if err != nil {
   273  		return nil, errors.Annotatef(err, "cannot get space %q", name)
   274  	}
   275  	return &Space{st, doc}, nil
   276  }
   277  
   278  // AllSpaceInfos returns SpaceInfos for all spaces in the model.
   279  func (st *State) AllSpaceInfos() (network.SpaceInfos, error) {
   280  	spaces, err := st.AllSpaces()
   281  	if err != nil {
   282  		return nil, errors.Trace(err)
   283  	}
   284  
   285  	subs, err := st.AllSubnetInfos()
   286  	if err != nil {
   287  		return nil, errors.Trace(err)
   288  	}
   289  
   290  	result := make(network.SpaceInfos, len(spaces))
   291  	for i, space := range spaces {
   292  		if result[i], err = space.networkSpace(subs); err != nil {
   293  			return nil, err
   294  		}
   295  	}
   296  	return result, nil
   297  }
   298  
   299  // AllSpaces returns all spaces for the model.
   300  func (st *State) AllSpaces() ([]*Space, error) {
   301  	spacesCollection, closer := st.db().GetCollection(spacesC)
   302  	defer closer()
   303  
   304  	var docs []spaceDoc
   305  	err := spacesCollection.Find(nil).All(&docs)
   306  	if err != nil {
   307  		return nil, errors.Annotatef(err, "cannot get all spaces")
   308  	}
   309  	spaces := make([]*Space, len(docs))
   310  	for i, doc := range docs {
   311  		spaces[i] = &Space{st: st, doc: doc}
   312  	}
   313  	return spaces, nil
   314  }
   315  
   316  // EnsureDead sets the Life of the space to Dead, if it's Alive. If the space is
   317  // already Dead, no error is returned. When the space is no longer Alive or
   318  // already removed, errNotAlive is returned.
   319  func (s *Space) EnsureDead() (err error) {
   320  	defer errors.DeferredAnnotatef(&err, "cannot set space %q to dead", s)
   321  
   322  	if s.doc.Life == Dead {
   323  		return nil
   324  	}
   325  
   326  	ops := []txn.Op{{
   327  		C:      spacesC,
   328  		Id:     s.doc.DocId,
   329  		Update: bson.D{{"$set", bson.D{{"life", Dead}}}},
   330  		Assert: isAliveDoc,
   331  	}}
   332  
   333  	txnErr := s.st.db().RunTransaction(ops)
   334  	if txnErr == nil {
   335  		s.doc.Life = Dead
   336  		return nil
   337  	}
   338  	return onAbort(txnErr, spaceNotAliveErr)
   339  }
   340  
   341  // Remove removes a Dead space. If the space is not Dead or it is already
   342  // removed, an error is returned.
   343  func (s *Space) Remove() (err error) {
   344  	defer errors.DeferredAnnotatef(&err, "cannot remove space %q", s)
   345  
   346  	if s.doc.Life != Dead {
   347  		return errors.New("space is not dead")
   348  	}
   349  
   350  	ops := []txn.Op{{
   351  		C:      spacesC,
   352  		Id:     s.doc.Id,
   353  		Remove: true,
   354  		Assert: isDeadDoc,
   355  	}}
   356  	if s.ProviderId() != "" {
   357  		ops = append(ops, s.st.networkEntityGlobalKeyRemoveOp("space", s.ProviderId()))
   358  	}
   359  
   360  	txnErr := s.st.db().RunTransaction(ops)
   361  	if txnErr == nil {
   362  		return nil
   363  	}
   364  	return onAbort(txnErr, errors.New("not found or not dead"))
   365  }
   366  
   367  // Refresh refreshes the contents of the Space from the underlying state. It
   368  // returns an error that satisfies errors.IsNotFound if the Space has been
   369  // removed.
   370  func (s *Space) Refresh() error {
   371  	spaces, closer := s.st.db().GetCollection(spacesC)
   372  	defer closer()
   373  
   374  	var doc spaceDoc
   375  	err := spaces.FindId(s.doc.Id).One(&doc)
   376  	if err == mgo.ErrNotFound {
   377  		return errors.NotFoundf("space %q", s)
   378  	} else if err != nil {
   379  		return errors.Errorf("cannot refresh space %q: %v", s, err)
   380  	}
   381  	s.doc = doc
   382  	return nil
   383  }
   384  
   385  // createDefaultSpaceOp returns a transaction operation
   386  // that creates the default space (id=0).
   387  func (st *State) createDefaultSpaceOp() txn.Op {
   388  	return txn.Op{
   389  		C:      spacesC,
   390  		Id:     st.docID(network.AlphaSpaceId),
   391  		Assert: txn.DocMissing,
   392  		Insert: spaceDoc{
   393  			Id:       network.AlphaSpaceId,
   394  			Life:     Alive,
   395  			Name:     network.AlphaSpaceName,
   396  			IsPublic: true,
   397  		},
   398  	}
   399  }
   400  
   401  func (st *State) getModelSubnets() (set.Strings, error) {
   402  	subnets, err := st.AllSubnets()
   403  	if err != nil {
   404  		return nil, errors.Trace(err)
   405  	}
   406  	modelSubnetIds := make(set.Strings)
   407  	for _, subnet := range subnets {
   408  		modelSubnetIds.Add(string(subnet.ProviderId()))
   409  	}
   410  	return modelSubnetIds, nil
   411  }
   412  
   413  // SaveProviderSubnets loads subnets into state.
   414  // Currently it does not delete removed subnets.
   415  func (st *State) SaveProviderSubnets(subnets []network.SubnetInfo, spaceID string) error {
   416  	modelSubnetIds, err := st.getModelSubnets()
   417  	if err != nil {
   418  		return errors.Trace(err)
   419  	}
   420  
   421  	for _, subnet := range subnets {
   422  		ip, _, err := net.ParseCIDR(subnet.CIDR)
   423  		if err != nil {
   424  			return errors.Trace(err)
   425  		}
   426  		if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
   427  			continue
   428  		}
   429  
   430  		subnet.SpaceID = spaceID
   431  		if modelSubnetIds.Contains(string(subnet.ProviderId)) {
   432  			err = st.SubnetUpdate(subnet)
   433  		} else {
   434  			_, err = st.AddSubnet(subnet)
   435  		}
   436  
   437  		if err != nil {
   438  			return errors.Trace(err)
   439  		}
   440  	}
   441  
   442  	// We process FAN subnets separately for clarity.
   443  	m, err := st.Model()
   444  	if err != nil {
   445  		return errors.Trace(err)
   446  	}
   447  	cfg, err := m.ModelConfig()
   448  	if err != nil {
   449  		return errors.Trace(err)
   450  	}
   451  	fans, err := cfg.FanConfig()
   452  	if err != nil {
   453  		return errors.Trace(err)
   454  	}
   455  	if len(fans) == 0 {
   456  		return nil
   457  	}
   458  
   459  	for _, subnet := range subnets {
   460  		for _, fan := range fans {
   461  			_, subnetNet, err := net.ParseCIDR(subnet.CIDR)
   462  			if err != nil {
   463  				return errors.Trace(err)
   464  			}
   465  			subnetWithDashes := strings.Replace(strings.Replace(subnetNet.String(), ".", "-", -1), "/", "-", -1)
   466  			id := fmt.Sprintf("%s-%s-%s", subnet.ProviderId, network.InFan, subnetWithDashes)
   467  			if modelSubnetIds.Contains(id) {
   468  				continue
   469  			}
   470  			if subnetNet.IP.To4() == nil {
   471  				logger.Debugf("%s address is not an IPv4 address.", subnetNet.IP)
   472  				continue
   473  			}
   474  			overlaySegment, err := network.CalculateOverlaySegment(subnet.CIDR, fan)
   475  			if err != nil {
   476  				return errors.Trace(err)
   477  			}
   478  			if overlaySegment != nil {
   479  				subnet.ProviderId = network.Id(id)
   480  				subnet.SpaceID = spaceID
   481  				subnet.SetFan(subnet.CIDR, fan.Overlay.String())
   482  				subnet.CIDR = overlaySegment.String()
   483  
   484  				_, err := st.AddSubnet(subnet)
   485  				if err != nil {
   486  					return errors.Trace(err)
   487  				}
   488  			}
   489  		}
   490  	}
   491  
   492  	return nil
   493  }