github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/orgvdcnetwork.go (about) 1 /* 2 * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "bytes" 9 "encoding/xml" 10 "fmt" 11 "net/http" 12 "net/url" 13 "os" 14 "regexp" 15 "strings" 16 "time" 17 18 "github.com/vmware/go-vcloud-director/v2/types/v56" 19 "github.com/vmware/go-vcloud-director/v2/util" 20 ) 21 22 // OrgVDCNetwork an org vdc network client 23 type OrgVDCNetwork struct { 24 OrgVDCNetwork *types.OrgVDCNetwork 25 client *Client 26 } 27 28 var reErrorBusy2 = regexp.MustCompile("is busy, cannot proceed with the operation.$") 29 30 // NewOrgVDCNetwork creates an org vdc network client 31 func NewOrgVDCNetwork(cli *Client) *OrgVDCNetwork { 32 return &OrgVDCNetwork{ 33 OrgVDCNetwork: new(types.OrgVDCNetwork), 34 client: cli, 35 } 36 } 37 38 func (orgVdcNet *OrgVDCNetwork) Refresh() error { 39 if orgVdcNet.OrgVDCNetwork.HREF == "" { 40 return fmt.Errorf("cannot refresh, Object is empty") 41 } 42 43 refreshUrl := orgVdcNet.OrgVDCNetwork.HREF 44 45 // Empty struct before a new unmarshal, otherwise we end up with duplicate 46 // elements in slices. 47 orgVdcNet.OrgVDCNetwork = &types.OrgVDCNetwork{} 48 49 _, err := orgVdcNet.client.ExecuteRequest(refreshUrl, http.MethodGet, 50 "", "error retrieving vDC network: %s", nil, orgVdcNet.OrgVDCNetwork) 51 52 return err 53 } 54 55 // Delete a network. Fails if the network is busy. 56 // Returns a task to monitor the deletion. 57 func (orgVdcNet *OrgVDCNetwork) Delete() (Task, error) { 58 err := orgVdcNet.Refresh() 59 if err != nil { 60 return Task{}, fmt.Errorf("error refreshing network: %s", err) 61 } 62 pathArr := strings.Split(orgVdcNet.OrgVDCNetwork.HREF, "/") 63 apiEndpoint := urlParseRequestURI(orgVdcNet.OrgVDCNetwork.HREF) 64 apiEndpoint.Path = "/api/admin/network/" + pathArr[len(pathArr)-1] 65 66 var resp *http.Response 67 for { 68 req := orgVdcNet.client.NewRequest(map[string]string{}, http.MethodDelete, *apiEndpoint, nil) 69 resp, err = checkResp(orgVdcNet.client.Http.Do(req)) 70 if err != nil { 71 if reErrorBusy2.MatchString(err.Error()) { 72 time.Sleep(3 * time.Second) 73 continue 74 } 75 return Task{}, fmt.Errorf("error deleting Network: %s", err) 76 } 77 break 78 } 79 80 task := NewTask(orgVdcNet.client) 81 82 if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil { 83 return Task{}, fmt.Errorf("error decoding Task response: %s", err) 84 } 85 86 // The request was successful 87 return *task, nil 88 } 89 90 // RemoveOrgVdcNetworkIfExists looks for an Org Vdc network and, if found, will delete it. 91 func RemoveOrgVdcNetworkIfExists(vdc Vdc, networkName string) error { 92 network, err := vdc.GetOrgVdcNetworkByName(networkName, true) 93 94 if IsNotFound(err) { 95 // Network not found. No action needed 96 return nil 97 } 98 if err != nil { 99 // Some other error happened during retrieval. We pass it along 100 return err 101 } 102 // The network was found. We attempt deletion 103 task, err := network.Delete() 104 if err != nil { 105 return fmt.Errorf("error deleting network '%s' [phase 1]: %s", networkName, err) 106 } 107 err = task.WaitTaskCompletion() 108 if err != nil { 109 return fmt.Errorf("error deleting network '%s' [task]: %s", networkName, err) 110 } 111 return nil 112 } 113 114 // A wrapper call around CreateOrgVDCNetwork. 115 // Creates a network and then uses the associated task to monitor its configuration 116 func (vdc *Vdc) CreateOrgVDCNetworkWait(networkConfig *types.OrgVDCNetwork) error { 117 118 task, err := vdc.CreateOrgVDCNetwork(networkConfig) 119 if err != nil { 120 return fmt.Errorf("error creating the network: %s", err) 121 } 122 if task == (Task{}) { 123 return fmt.Errorf("NULL task retrieved after network creation") 124 125 } 126 err = task.WaitTaskCompletion() 127 // err = task.WaitInspectTaskCompletion(InspectTask, 10) 128 if err != nil { 129 return fmt.Errorf("error performing task: %s", err) 130 } 131 return nil 132 } 133 134 // Fine tuning network creation function. 135 // Return an error (the result of the network creation) and a task (used to monitor 136 // the network configuration) 137 // This function can create any type of Org Vdc network. The exact type is determined by 138 // the combination of properties given with the network configuration structure. 139 func (vdc *Vdc) CreateOrgVDCNetwork(networkConfig *types.OrgVDCNetwork) (Task, error) { 140 for _, av := range vdc.Vdc.Link { 141 if av.Rel == "add" && av.Type == "application/vnd.vmware.vcloud.orgVdcNetwork+xml" { 142 createUrl, err := url.ParseRequestURI(av.HREF) 143 144 if err != nil { 145 return Task{}, fmt.Errorf("error decoding vdc response: %s", err) 146 } 147 148 networkConfig.Xmlns = types.XMLNamespaceVCloud 149 150 output, err := xml.MarshalIndent(networkConfig, " ", " ") 151 if err != nil { 152 return Task{}, fmt.Errorf("error marshaling OrgVDCNetwork compose: %s", err) 153 } 154 155 var resp *http.Response 156 for { 157 b := bytes.NewBufferString(xml.Header + string(output)) 158 util.Logger.Printf("[DEBUG] VCD Client configuration: %s", b) 159 req := vdc.client.NewRequest(map[string]string{}, http.MethodPost, *createUrl, b) 160 req.Header.Add("Content-Type", av.Type) 161 resp, err = checkResp(vdc.client.Http.Do(req)) 162 if err != nil { 163 if reErrorBusy2.MatchString(err.Error()) { 164 time.Sleep(3 * time.Second) 165 continue 166 } 167 return Task{}, fmt.Errorf("error instantiating a new OrgVDCNetwork: %s", err) 168 } 169 break 170 } 171 orgVDCNetwork := NewOrgVDCNetwork(vdc.client) 172 if err = decodeBody(types.BodyTypeXML, resp, orgVDCNetwork.OrgVDCNetwork); err != nil { 173 return Task{}, fmt.Errorf("error decoding orgvdcnetwork response: %s", err) 174 } 175 activeTasks := 0 176 // Makes sure that there is only one active task for this network. 177 for _, taskItem := range orgVDCNetwork.OrgVDCNetwork.Tasks.Task { 178 if taskItem.HREF != "" { 179 activeTasks += 1 180 if os.Getenv("GOVCD_DEBUG") != "" { 181 fmt.Printf("task %s (%s) is active\n", taskItem.HREF, taskItem.Status) 182 } 183 } 184 } 185 if activeTasks > 1 { 186 // By my understanding of the implementation, there should not be more than one task for this operation. 187 // If there is, we will need to change the logic of this function, as we can only return one task. (GM) 188 return Task{}, fmt.Errorf("found %d active tasks instead of one", activeTasks) 189 } 190 for _, taskItem := range orgVDCNetwork.OrgVDCNetwork.Tasks.Task { 191 return Task{taskItem, vdc.client}, nil 192 } 193 return Task{}, fmt.Errorf("[%s] no suitable task found", util.CurrentFuncName()) 194 } 195 } 196 return Task{}, fmt.Errorf("network creation failed: no operational link found") 197 } 198 199 // GetNetworkList returns a list of networks for the VDC 200 func (vdc *Vdc) GetNetworkList() ([]*types.QueryResultOrgVdcNetworkRecordType, error) { 201 // Find the list of networks with the wanted name 202 result, err := vdc.client.cumulativeQuery(types.QtOrgVdcNetwork, nil, map[string]string{ 203 "type": types.QtOrgVdcNetwork, 204 "filter": fmt.Sprintf("vdc==%s", url.QueryEscape(vdc.Vdc.ID)), 205 "filterEncoded": "true", 206 }) 207 if err != nil { 208 return nil, fmt.Errorf("[findEdgeGatewayConnection] error returning the list of networks for VDC: %s", err) 209 } 210 return result.Results.OrgVdcNetworkRecord, nil 211 } 212 213 // FindEdgeGatewayNameByNetwork searches the VDC for a connection between an edge gateway and a given network. 214 // On success, returns the name of the edge gateway 215 func (vdc *Vdc) FindEdgeGatewayNameByNetwork(networkName string) (string, error) { 216 217 // Find the list of networks with the wanted name 218 result, err := vdc.client.cumulativeQuery(types.QtOrgVdcNetwork, nil, map[string]string{ 219 "type": types.QtOrgVdcNetwork, 220 "filter": fmt.Sprintf("name==%s;vdc==%s", url.QueryEscape(networkName), url.QueryEscape(vdc.Vdc.ID)), 221 "filterEncoded": "true", 222 }) 223 if err != nil { 224 return "", fmt.Errorf("[findEdgeGatewayConnection] error returning the list of networks for VDC: %s", err) 225 } 226 netList := result.Results.OrgVdcNetworkRecord 227 228 for _, net := range netList { 229 if net.Name == networkName { 230 // linkType is not well documented, but empiric tests show that: 231 // 0 = direct 232 // 1 = routed 233 // 2 = isolated 234 if net.ConnectedTo != "" && net.LinkType == 1 { // We only want routed networks 235 return net.ConnectedTo, nil 236 } 237 } 238 } 239 return "", ErrorEntityNotFound 240 } 241 242 // getParentVdc retrieves the VDC to which the network is attached 243 func (orgVdcNet *OrgVDCNetwork) getParentVdc() (*Vdc, error) { 244 for _, link := range orgVdcNet.OrgVDCNetwork.Link { 245 if link.Type == "application/vnd.vmware.vcloud.vdc+xml" { 246 247 vdc := NewVdc(orgVdcNet.client) 248 249 _, err := orgVdcNet.client.ExecuteRequest(link.HREF, http.MethodGet, 250 "", "error retrieving parent vdc: %s", nil, vdc.Vdc) 251 if err != nil { 252 return nil, err 253 } 254 255 return vdc, nil 256 } 257 } 258 return nil, fmt.Errorf("could not find a parent Vdc for network %s", orgVdcNet.OrgVDCNetwork.Name) 259 } 260 261 // getEdgeGateway retrieves the edge gateway connected to a routed network 262 func (orgVdcNet *OrgVDCNetwork) getEdgeGateway() (*EdgeGateway, error) { 263 // If it is not routed, returns an error 264 if orgVdcNet.OrgVDCNetwork.Configuration.FenceMode != types.FenceModeNAT { 265 return nil, fmt.Errorf("network %s is not routed", orgVdcNet.OrgVDCNetwork.Name) 266 } 267 vdc, err := orgVdcNet.getParentVdc() 268 if err != nil { 269 return nil, err 270 } 271 272 // Since this function can be called from Update(), we must take into account the 273 // possibility of a name change. If this is happening, we need to retrieve the original 274 // name, which is still stored in the VDC. 275 oldNetwork, err := vdc.GetOrgVdcNetworkById(orgVdcNet.OrgVDCNetwork.ID, false) 276 if err != nil { 277 return nil, err 278 } 279 networkName := oldNetwork.OrgVDCNetwork.Name 280 281 edgeGatewayName, err := vdc.FindEdgeGatewayNameByNetwork(networkName) 282 if err != nil { 283 return nil, err 284 } 285 286 return vdc.GetEdgeGatewayByName(edgeGatewayName, false) 287 } 288 289 // UpdateAsync will change the contents of a network using the information in the 290 // receiver data structure. 291 func (orgVdcNet *OrgVDCNetwork) UpdateAsync() (Task, error) { 292 if orgVdcNet.OrgVDCNetwork.HREF == "" { 293 return Task{}, fmt.Errorf("cannot update Org VDC network: HREF is empty") 294 } 295 if orgVdcNet.OrgVDCNetwork.Name == "" { 296 return Task{}, fmt.Errorf("cannot update Org VDC network: name is empty") 297 } 298 if orgVdcNet.OrgVDCNetwork.Configuration == nil { 299 return Task{}, fmt.Errorf("cannot update Org VDC network: configuration is empty") 300 } 301 302 href := orgVdcNet.OrgVDCNetwork.HREF 303 304 if !strings.Contains(href, "/api/admin/") { 305 href = strings.ReplaceAll(href, "/api/", "/api/admin/") 306 } 307 308 // Routed networks need to have edge gateway information for both creation and update. 309 // Since the network data structure doesn't return edge gateway information, 310 // we fetch it explicitly. 311 if orgVdcNet.OrgVDCNetwork.Configuration.FenceMode == types.FenceModeNAT { 312 edgeGateway, err := orgVdcNet.getEdgeGateway() 313 if err != nil { 314 return Task{}, fmt.Errorf("error retrieving edge gateway for Org VDC network %s : %s", orgVdcNet.OrgVDCNetwork.Name, err) 315 } 316 orgVdcNet.OrgVDCNetwork.EdgeGateway = &types.Reference{ 317 HREF: edgeGateway.EdgeGateway.HREF, 318 ID: edgeGateway.EdgeGateway.ID, 319 Type: edgeGateway.EdgeGateway.Type, 320 Name: edgeGateway.EdgeGateway.Name, 321 } 322 } 323 return orgVdcNet.client.ExecuteTaskRequest(href, http.MethodPut, 324 types.MimeOrgVdcNetwork, "error updating Org VDC network: %s", orgVdcNet.OrgVDCNetwork) 325 } 326 327 // Update is a wrapper around UpdateAsync, where we 328 // explicitly wait for the task to finish. 329 // The pointer receiver is refreshed after update 330 func (orgVdcNet *OrgVDCNetwork) Update() error { 331 task, err := orgVdcNet.UpdateAsync() 332 if err != nil { 333 return err 334 } 335 err = task.WaitTaskCompletion() 336 if err != nil { 337 return err 338 } 339 340 return orgVdcNet.Refresh() 341 } 342 343 // Rename is a wrapper around Update(), where we only change the name of the network 344 // Since the purpose is explicitly changing the name, the function will fail if the new name 345 // is not different from the existing one 346 func (orgVdcNet *OrgVDCNetwork) Rename(newName string) error { 347 if orgVdcNet.OrgVDCNetwork.Name == newName { 348 return fmt.Errorf("new name is the same ase the existing name") 349 } 350 orgVdcNet.OrgVDCNetwork.Name = newName 351 return orgVdcNet.Update() 352 }