github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/network_pool.go (about) 1 /* 2 * Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "fmt" 9 "github.com/vmware/go-vcloud-director/v2/types/v56" 10 "net/url" 11 "strings" 12 ) 13 14 type NetworkPool struct { 15 NetworkPool *types.NetworkPool 16 vcdClient *VCDClient 17 } 18 19 var backingUseErrorMessages = map[types.BackingUseConstraint]string{ 20 types.BackingUseExplicit: "no element named %s found", 21 types.BackingUseWhenOnlyOne: "no single element found for this backing", 22 types.BackingUseFirstAvailable: "no elements found for this backing", 23 } 24 25 // GetOpenApiUrl retrieves the full URL of a network pool 26 func (np *NetworkPool) GetOpenApiUrl() (string, error) { 27 response, err := url.JoinPath(np.vcdClient.sessionHREF.String(), "admin", "extension", "networkPool", np.NetworkPool.Id) 28 if err != nil { 29 return "", err 30 } 31 return response, nil 32 } 33 34 // GetNetworkPoolSummaries retrieves the list of all available network pools 35 func (vcdClient *VCDClient) GetNetworkPoolSummaries(queryParameters url.Values) ([]*types.NetworkPool, error) { 36 client := vcdClient.Client 37 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPoolSummaries 38 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 39 if err != nil { 40 return nil, err 41 } 42 43 urlRef, err := client.OpenApiBuildEndpoint(endpoint) 44 if err != nil { 45 return nil, err 46 } 47 typeResponse := []*types.NetworkPool{{}} 48 err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponse, nil) 49 if err != nil { 50 return nil, err 51 } 52 53 return typeResponse, nil 54 } 55 56 // GetNetworkPoolById retrieves Network Pool with a given ID 57 func (vcdClient *VCDClient) GetNetworkPoolById(id string) (*NetworkPool, error) { 58 if id == "" { 59 return nil, fmt.Errorf("network pool lookup requires ID") 60 } 61 62 client := vcdClient.Client 63 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools 64 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 65 if err != nil { 66 return nil, err 67 } 68 69 urlRef, err := client.OpenApiBuildEndpoint(endpoint, id) 70 if err != nil { 71 return nil, err 72 } 73 74 response := &NetworkPool{ 75 vcdClient: vcdClient, 76 NetworkPool: &types.NetworkPool{}, 77 } 78 79 err = client.OpenApiGetItem(apiVersion, urlRef, nil, response.NetworkPool, nil) 80 if err != nil { 81 return nil, err 82 } 83 84 return response, nil 85 } 86 87 // GetNetworkPoolByName retrieves a network pool with a given name 88 // Note. It will return an error if multiple network pools exist with the same name 89 func (vcdClient *VCDClient) GetNetworkPoolByName(name string) (*NetworkPool, error) { 90 if name == "" { 91 return nil, fmt.Errorf("network pool lookup requires name") 92 } 93 94 queryParameters := url.Values{} 95 queryParameters.Add("filter", "name=="+name) 96 97 filteredNetworkPools, err := vcdClient.GetNetworkPoolSummaries(queryParameters) 98 if err != nil { 99 return nil, fmt.Errorf("error getting network pools: %s", err) 100 } 101 102 if len(filteredNetworkPools) == 0 { 103 return nil, fmt.Errorf("no network pool found with name '%s' - %s", name, ErrorEntityNotFound) 104 } 105 106 if len(filteredNetworkPools) > 1 { 107 return nil, fmt.Errorf("more than one network pool found with name '%s'", name) 108 } 109 110 return vcdClient.GetNetworkPoolById(filteredNetworkPools[0].Id) 111 } 112 113 // CreateNetworkPool creates a network pool using the given configuration 114 // It can create any type of network pool 115 func (vcdClient *VCDClient) CreateNetworkPool(config *types.NetworkPool) (*NetworkPool, error) { 116 client := vcdClient.Client 117 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools 118 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 119 if err != nil { 120 return nil, err 121 } 122 123 urlRef, err := client.OpenApiBuildEndpoint(endpoint) 124 if err != nil { 125 return nil, err 126 } 127 128 result := &NetworkPool{ 129 NetworkPool: &types.NetworkPool{}, 130 vcdClient: vcdClient, 131 } 132 133 err = client.OpenApiPostItem(apiVersion, urlRef, nil, config, result.NetworkPool, nil) 134 if err != nil { 135 return nil, err 136 } 137 138 return result, nil 139 } 140 141 // Update will change all changeable network pool items 142 func (np *NetworkPool) Update() error { 143 if np == nil || np.NetworkPool == nil || np.NetworkPool.Id == "" { 144 return fmt.Errorf("network pool must have ID") 145 } 146 if np.vcdClient == nil || np.vcdClient.Client.APIVersion == "" { 147 return fmt.Errorf("network pool '%s': no client found", np.NetworkPool.Name) 148 } 149 150 client := np.vcdClient.Client 151 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools 152 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 153 if err != nil { 154 return err 155 } 156 157 urlRef, err := client.OpenApiBuildEndpoint(endpoint, np.NetworkPool.Id) 158 if err != nil { 159 return err 160 } 161 162 err = client.OpenApiPutItem(apiVersion, urlRef, nil, np.NetworkPool, np.NetworkPool, nil) 163 if err != nil { 164 return err 165 } 166 167 if err != nil { 168 return fmt.Errorf("error updating network pool '%s': %s", np.NetworkPool.Name, err) 169 } 170 171 return nil 172 } 173 174 // Delete removes a network pool 175 func (np *NetworkPool) Delete() error { 176 if np == nil || np.NetworkPool == nil || np.NetworkPool.Id == "" { 177 return fmt.Errorf("network pool must have ID") 178 } 179 if np.vcdClient == nil || np.vcdClient.Client.APIVersion == "" { 180 return fmt.Errorf("network pool '%s': no client found", np.NetworkPool.Name) 181 } 182 183 client := np.vcdClient.Client 184 endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools 185 apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) 186 if err != nil { 187 return err 188 } 189 190 urlRef, err := client.OpenApiBuildEndpoint(endpoint, np.NetworkPool.Id) 191 if err != nil { 192 return err 193 } 194 195 err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil) 196 if err != nil { 197 return err 198 } 199 200 if err != nil { 201 return fmt.Errorf("error deleting network pool '%s': %s", np.NetworkPool.Name, err) 202 } 203 204 return nil 205 } 206 207 func backingErrorMessage(constraint types.BackingUseConstraint, name string) string { 208 errorMessage := fmt.Sprintf("[constraint: %s] %s", constraint, backingUseErrorMessages[constraint]) 209 if strings.Contains(errorMessage, "%s") { 210 return fmt.Sprintf(errorMessage, name) 211 } 212 return errorMessage 213 } 214 215 type getElementFunc[B any] func(*B) string 216 type validElementFunc[B any] func(*B) bool 217 218 // chooseBackingElement will select a backing element from a list, using the given constraint 219 // * constraint is the type of choice we are looking for 220 // * wantedName is the name of the element we want. If we use a constraint other than types.BackingUseExplicit, it can be empty 221 // * elements is the list of backing elements we want to choose from 222 // * getEl is a function that, given an element, returns its name 223 // * validateEl is an optional function that tells whether a given element is valid or not. If missing, we assume all elements are valid 224 func chooseBackingElement[B any](constraint types.BackingUseConstraint, wantedName string, elements []*B, getEl getElementFunc[B], validateEl validElementFunc[B]) (*B, error) { 225 var searchedElement *B 226 if validateEl == nil { 227 validateEl = func(*B) bool { return true } 228 } 229 numberOfValidElements := 0 230 // We need to pre-calculate the number of valid elements, to use it when constraint == BackingUseWhenOnlyOne 231 for _, element := range elements { 232 if validateEl(element) { 233 numberOfValidElements++ 234 } 235 } 236 // availableElements will contain the list of available elements, to be used in error messages 237 var availableElements []string 238 for _, element := range elements { 239 elementName := getEl(element) 240 if !validateEl(element) { 241 continue 242 } 243 availableElements = append(availableElements, elementName) 244 245 switch constraint { 246 case types.BackingUseExplicit: 247 // When asking for a specific element explicitly, we return it only if the name matches the request) 248 if wantedName == elementName { 249 searchedElement = element 250 } 251 case types.BackingUseWhenOnlyOne: 252 // With BackingUseWhenOnlyOne, we return the element only if there is a single *valid* element in the list 253 if wantedName == "" && numberOfValidElements == 1 { 254 searchedElement = element 255 } 256 case types.BackingUseFirstAvailable: 257 // This is the most permissive constraint: we get the first available element 258 if wantedName == "" { 259 searchedElement = element 260 } 261 } 262 if searchedElement != nil { 263 break 264 } 265 } 266 // If no item was retrieved, we build an error message appropriate for the current constraint, and add 267 // the list of available elements to it 268 if searchedElement == nil { 269 return nil, fmt.Errorf(backingErrorMessage(constraint, wantedName)+" - available elements: %v", availableElements) 270 } 271 272 // When we reach this point, we have found what was requested, and return the element 273 return searchedElement, nil 274 } 275 276 // CreateNetworkPoolGeneve creates a network pool of GENEVE type 277 // The function retrieves the given NSX-T manager and corresponding transport zone names 278 // If the transport zone name is empty, the first available will be used 279 func (vcdClient *VCDClient) CreateNetworkPoolGeneve(name, description, nsxtManagerName, transportZoneName string, constraint types.BackingUseConstraint) (*NetworkPool, error) { 280 managers, err := vcdClient.QueryNsxtManagerByName(nsxtManagerName) 281 if err != nil { 282 return nil, err 283 } 284 285 if len(managers) == 0 { 286 return nil, fmt.Errorf("no manager '%s' found", nsxtManagerName) 287 } 288 if len(managers) > 1 { 289 return nil, fmt.Errorf("more than one manager '%s' found", nsxtManagerName) 290 } 291 manager := managers[0] 292 293 managerId := "urn:vcloud:nsxtmanager:" + extractUuid(managers[0].HREF) 294 transportZones, err := vcdClient.GetAllNsxtTransportZones(managerId, nil) 295 if err != nil { 296 return nil, fmt.Errorf("error retrieving transport zones for manager '%s': %s", manager.Name, err) 297 } 298 transportZone, err := chooseBackingElement[types.TransportZone]( 299 constraint, 300 transportZoneName, 301 transportZones, 302 func(tz *types.TransportZone) string { return tz.Name }, 303 func(tz *types.TransportZone) bool { return !tz.AlreadyImported }, 304 ) 305 306 if err != nil { 307 return nil, err 308 } 309 if transportZone.AlreadyImported { 310 return nil, fmt.Errorf("transport zone '%s' is already imported", transportZone.Name) 311 } 312 313 // Note: in this type of network pool, the managing owner is the NSX-T manager 314 managingOwner := types.OpenApiReference{ 315 Name: manager.Name, 316 ID: managerId, 317 } 318 var config = &types.NetworkPool{ 319 Name: name, 320 Description: description, 321 PoolType: types.NetworkPoolGeneveType, 322 ManagingOwnerRef: managingOwner, 323 Backing: types.NetworkPoolBacking{ 324 TransportZoneRef: types.OpenApiReference{ 325 ID: transportZone.Id, 326 Name: transportZone.Name, 327 }, 328 ProviderRef: managingOwner, 329 }, 330 } 331 return vcdClient.CreateNetworkPool(config) 332 } 333 334 // CreateNetworkPoolPortGroup creates a network pool of PORTGROUP_BACKED type 335 // The function retrieves the given vCenter and corresponding port group names 336 // If the port group name is empty, the first available will be used 337 func (vcdClient *VCDClient) CreateNetworkPoolPortGroup(name, description, vCenterName string, portgroupNames []string, constraint types.BackingUseConstraint) (*NetworkPool, error) { 338 vCenter, err := vcdClient.GetVCenterByName(vCenterName) 339 if err != nil { 340 return nil, fmt.Errorf("error retrieving vCenter '%s': %s", vCenterName, err) 341 } 342 var params = make(url.Values) 343 params.Set("filter", "virtualCenter.id=="+vCenter.VSphereVCenter.VcId) 344 portgroups, err := vcdClient.GetAllVcenterImportableDvpgs(params) 345 if err != nil { 346 return nil, fmt.Errorf("error retrieving portgroups for vCenter '%s': %s", vCenterName, err) 347 } 348 349 var chosenPortgroups []*VcenterImportableDvpg 350 var chosenReferences []types.OpenApiReference 351 for _, portgroupName := range portgroupNames { 352 portGroup, err := chooseBackingElement[VcenterImportableDvpg]( 353 constraint, 354 portgroupName, 355 portgroups, 356 func(v *VcenterImportableDvpg) string { 357 return v.VcenterImportableDvpg.BackingRef.Name 358 }, 359 nil, 360 ) 361 362 if err != nil { 363 return nil, err 364 } 365 chosenPortgroups = append(chosenPortgroups, portGroup) 366 chosenReferences = append(chosenReferences, types.OpenApiReference{ 367 Name: portGroup.VcenterImportableDvpg.BackingRef.Name, 368 ID: portGroup.VcenterImportableDvpg.BackingRef.ID, 369 }) 370 } 371 372 if len(chosenPortgroups) == 0 { 373 return nil, fmt.Errorf("no suitable portgroups found for names %v", portgroupNames) 374 } 375 if len(chosenPortgroups) > 1 { 376 if !chosenPortgroups[0].UsableWith(chosenPortgroups...) { 377 return nil, fmt.Errorf("portgroups %v should all belong to the same host", portgroupNames) 378 } 379 } 380 381 // Note: in this type of network pool, the managing owner is the vCenter 382 managingOwner := types.OpenApiReference{ 383 Name: vCenter.VSphereVCenter.Name, 384 ID: vCenter.VSphereVCenter.VcId, 385 } 386 config := types.NetworkPool{ 387 Name: name, 388 Description: description, 389 PoolType: types.NetworkPoolPortGroupType, 390 ManagingOwnerRef: managingOwner, 391 Backing: types.NetworkPoolBacking{ 392 PortGroupRefs: chosenReferences, 393 ProviderRef: managingOwner, 394 }, 395 } 396 return vcdClient.CreateNetworkPool(&config) 397 } 398 399 // CreateNetworkPoolVlan creates a network pool of VLAN type 400 // The function retrieves the given vCenter and corresponding distributed switch names 401 // If the distributed switch name is empty, the first available will be used 402 func (vcdClient *VCDClient) CreateNetworkPoolVlan(name, description, vCenterName, dsName string, ranges []types.VlanIdRange, constraint types.BackingUseConstraint) (*NetworkPool, error) { 403 vCenter, err := vcdClient.GetVCenterByName(vCenterName) 404 if err != nil { 405 return nil, fmt.Errorf("error retrieving vCenter '%s': %s", vCenterName, err) 406 } 407 408 dswitches, err := vcdClient.GetAllVcenterDistributedSwitches(vCenter.VSphereVCenter.VcId, nil) 409 if err != nil { 410 return nil, fmt.Errorf("error retrieving distributed switches for vCenter '%s': %s", vCenterName, err) 411 } 412 413 dswitch, err := chooseBackingElement[types.VcenterDistributedSwitch]( 414 constraint, 415 dsName, 416 dswitches, 417 func(t *types.VcenterDistributedSwitch) string { return t.BackingRef.Name }, 418 nil, 419 ) 420 if err != nil { 421 return nil, err 422 } 423 424 // Note: in this type of network pool, the managing owner is the vCenter 425 managingOwner := types.OpenApiReference{ 426 Name: vCenter.VSphereVCenter.Name, 427 ID: vCenter.VSphereVCenter.VcId, 428 } 429 config := types.NetworkPool{ 430 Name: name, 431 Description: description, 432 PoolType: types.NetworkPoolVlanType, 433 ManagingOwnerRef: managingOwner, 434 Backing: types.NetworkPoolBacking{ 435 VlanIdRanges: types.VlanIdRanges{ 436 Values: ranges, 437 }, 438 VdsRefs: []types.OpenApiReference{ 439 { 440 Name: dswitch.BackingRef.Name, 441 ID: dswitch.BackingRef.ID, 442 }, 443 }, 444 ProviderRef: managingOwner, 445 }, 446 } 447 return vcdClient.CreateNetworkPool(&config) 448 }