github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/space/spaces.go (about)

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package space
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  
    13  	"github.com/juju/juju/core/instance"
    14  	"github.com/juju/juju/core/network"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/state"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.environs.space")
    21  
    22  // Space represents a space that saved to state.
    23  type Space interface {
    24  	Id() string
    25  	Name() string
    26  	ProviderId() network.Id
    27  	Life() state.Life
    28  	EnsureDead() error
    29  	Remove() error
    30  }
    31  
    32  // Constraints defines the methods supported by constraints used in the space context.
    33  type Constraints interface{}
    34  
    35  // ReloadSpacesState defines an in situ point of use type for ReloadSpaces
    36  type ReloadSpacesState interface {
    37  	// AllSpaces returns all spaces for the model.
    38  	AllSpaces() ([]Space, error)
    39  	// AddSpace creates and returns a new space.
    40  	AddSpace(string, network.Id, []string, bool) (Space, error)
    41  	// SaveProviderSubnets loads subnets into state.
    42  	SaveProviderSubnets([]network.SubnetInfo, string) error
    43  	// ConstraintsBySpaceName returns all Constraints that include a positive
    44  	// or negative space constraint for the input space name.
    45  	ConstraintsBySpaceName(string) ([]Constraints, error)
    46  	// DefaultEndpointBindingSpace returns the current space ID to be used for
    47  	// the default endpoint binding.
    48  	DefaultEndpointBindingSpace() (string, error)
    49  	// AllEndpointBindingsSpaceNames returns a set of spaces names for all the
    50  	// endpoint bindings.
    51  	AllEndpointBindingsSpaceNames() (set.Strings, error)
    52  }
    53  
    54  // ReloadSpaces loads spaces and subnets from provider specified by environ into state.
    55  // Currently it's an append-only operation, no spaces/subnets are deleted.
    56  func ReloadSpaces(ctx context.ProviderCallContext, state ReloadSpacesState, environ environs.BootstrapEnviron) error {
    57  	netEnviron, ok := environs.SupportsNetworking(environ)
    58  	if !ok || netEnviron == nil {
    59  		return errors.NotSupportedf("spaces discovery in a non-networking environ")
    60  	}
    61  
    62  	canDiscoverSpaces, err := netEnviron.SupportsSpaceDiscovery(ctx)
    63  	if err != nil {
    64  		return errors.Trace(err)
    65  	}
    66  
    67  	if canDiscoverSpaces {
    68  		spaces, err := netEnviron.Spaces(ctx)
    69  		if err != nil {
    70  			return errors.Trace(err)
    71  		}
    72  
    73  		logger.Infof("discovered spaces: %s", spaces.String())
    74  
    75  		providerSpaces := NewProviderSpaces(state)
    76  		if err := providerSpaces.SaveSpaces(spaces); err != nil {
    77  			return errors.Trace(err)
    78  		}
    79  		warnings, err := providerSpaces.DeleteSpaces()
    80  		if err != nil {
    81  			return errors.Trace(err)
    82  		}
    83  		for _, warning := range warnings {
    84  			logger.Tracef(warning)
    85  		}
    86  		return nil
    87  	}
    88  
    89  	logger.Debugf("environ does not support space discovery, falling back to subnet discovery")
    90  	subnets, err := netEnviron.Subnets(ctx, instance.UnknownId, nil)
    91  	if err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  	return errors.Trace(state.SaveProviderSubnets(subnets, ""))
    95  }
    96  
    97  // ProviderSpaces defines a set of operations to perform when dealing with
    98  // provider spaces. SaveSpaces, DeleteSpaces are operations for setting state
    99  // in the persistence layer.
   100  type ProviderSpaces struct {
   101  	state         ReloadSpacesState
   102  	modelSpaceMap map[network.Id]Space
   103  	updatedSpaces network.IDSet
   104  }
   105  
   106  // NewProviderSpaces creates a new ProviderSpaces to perform a series of
   107  // operations.
   108  func NewProviderSpaces(st ReloadSpacesState) *ProviderSpaces {
   109  	return &ProviderSpaces{
   110  		state: st,
   111  
   112  		modelSpaceMap: make(map[network.Id]Space),
   113  		updatedSpaces: network.MakeIDSet(),
   114  	}
   115  }
   116  
   117  // SaveSpaces consumes provider spaces and saves the spaces as subnets on a
   118  // provider.
   119  func (s *ProviderSpaces) SaveSpaces(providerSpaces []network.SpaceInfo) error {
   120  	stateSpaces, err := s.state.AllSpaces()
   121  	if err != nil {
   122  		return errors.Trace(err)
   123  	}
   124  	spaceNames := set.NewStrings()
   125  	for _, space := range stateSpaces {
   126  		s.modelSpaceMap[space.ProviderId()] = space
   127  		spaceNames.Add(space.Name())
   128  	}
   129  
   130  	for _, spaceInfo := range providerSpaces {
   131  		// Check if the space is already in state,
   132  		// in which case we know its name.
   133  		var spaceID string
   134  		stateSpace, ok := s.modelSpaceMap[spaceInfo.ProviderId]
   135  		if ok {
   136  			spaceID = stateSpace.Id()
   137  		} else {
   138  			// The space is new, we need to create a valid name for it in state.
   139  			// Convert the name into a valid name that is not already in use.
   140  			spaceName := network.ConvertSpaceName(string(spaceInfo.Name), spaceNames)
   141  
   142  			logger.Debugf("Adding space %s from provider %s", spaceName, string(spaceInfo.ProviderId))
   143  			space, err := s.state.AddSpace(spaceName, spaceInfo.ProviderId, []string{}, false)
   144  			if err != nil {
   145  				return errors.Trace(err)
   146  			}
   147  
   148  			spaceNames.Add(spaceName)
   149  			spaceID = space.Id()
   150  
   151  			// To ensure that we can remove spaces, we back-fill the new spaces
   152  			// onto the modelSpaceMap.
   153  			s.modelSpaceMap[space.ProviderId()] = space
   154  		}
   155  
   156  		err = s.state.SaveProviderSubnets(spaceInfo.Subnets, spaceID)
   157  		if err != nil {
   158  			return errors.Trace(err)
   159  		}
   160  
   161  		s.updatedSpaces.Add(spaceInfo.ProviderId)
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // DeltaSpaces returns all the spaces that haven't been updated.
   168  func (s *ProviderSpaces) DeltaSpaces() network.IDSet {
   169  	// Workout the difference between all the current spaces vs what was
   170  	// actually changed.
   171  	allStateSpaces := network.MakeIDSet()
   172  	for providerID := range s.modelSpaceMap {
   173  		allStateSpaces.Add(providerID)
   174  	}
   175  
   176  	return allStateSpaces.Difference(s.updatedSpaces)
   177  }
   178  
   179  // DeleteSpaces will attempt to delete any unused spaces after a SaveSpaces has
   180  // been called.
   181  // If there are no spaces to be deleted, it will exit out early.
   182  func (s *ProviderSpaces) DeleteSpaces() ([]string, error) {
   183  	// Exit early if there is nothing to do.
   184  	if len(s.modelSpaceMap) == 0 {
   185  		return nil, nil
   186  	}
   187  
   188  	// Then check if the delta spaces are empty, if it's also empty, exit again.
   189  	// We do it after modelSpaceMap as we create a types to create this, which
   190  	// seems pretty wasteful.
   191  	remnantSpaces := s.DeltaSpaces()
   192  	if len(remnantSpaces) == 0 {
   193  		return nil, nil
   194  	}
   195  
   196  	defaultEndpointBinding, err := s.state.DefaultEndpointBindingSpace()
   197  	if err != nil {
   198  		return nil, errors.Trace(err)
   199  	}
   200  
   201  	allEndpointBindings, err := s.state.AllEndpointBindingsSpaceNames()
   202  	if err != nil {
   203  		return nil, errors.Trace(err)
   204  	}
   205  
   206  	var warnings []string
   207  	for _, providerID := range remnantSpaces.SortedValues() {
   208  		// If the space is not in state or the name is not in space names, then
   209  		// we can ignore it.
   210  		space, ok := s.modelSpaceMap[providerID]
   211  		if !ok {
   212  			// No warning here, the space was just not found.
   213  			continue
   214  		} else if space.Name() == network.AlphaSpaceName ||
   215  			space.Id() == defaultEndpointBinding {
   216  
   217  			warning := fmt.Sprintf("Unable to delete space %q. Space is used as the default space.", space.Name())
   218  			warnings = append(warnings, warning)
   219  			continue
   220  		}
   221  
   222  		// Check all endpoint bindings found within a model. If they reference
   223  		// a space name, then ignore then space for removal.
   224  		if allEndpointBindings.Contains(space.Name()) {
   225  			warning := fmt.Sprintf("Unable to delete space %q. Space is used as a endpoint binding.", space.Name())
   226  			warnings = append(warnings, warning)
   227  			continue
   228  		}
   229  
   230  		// Check to see if any space is within any constraints, if they are,
   231  		// ignore them for now.
   232  		if constraints, err := s.state.ConstraintsBySpaceName(space.Name()); err != nil || len(constraints) > 0 {
   233  			warning := fmt.Sprintf("Unable to delete space %q. Space is used in a constraint.", space.Name())
   234  			warnings = append(warnings, warning)
   235  			continue
   236  		}
   237  
   238  		// Check to see if the space is still alive. If the space is still alive
   239  		// we should ensure it is dead before we remove the space.
   240  		// Note: we currently don't test for Life in any space usage, so that
   241  		// means that we have to be very careful in the usage of this. Currently
   242  		// MAAS is the only usage of this, but when others follow we should take
   243  		// a long hard look at this.
   244  		// The real fix for this is to call ensure dead, but not remove the
   245  		// space until all remnants of the topology of that space are
   246  		// terminated.
   247  		if space.Life() == state.Alive {
   248  			if err := space.EnsureDead(); err != nil {
   249  				return warnings, errors.Trace(err)
   250  			}
   251  		}
   252  
   253  		// Finally remove the space.
   254  		if err := space.Remove(); err != nil {
   255  			return warnings, errors.Trace(err)
   256  		}
   257  	}
   258  
   259  	return warnings, nil
   260  }