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 }