From c95e42367fac5f112feb968d99b74a7ff22ea18b Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 2 Feb 2015 13:20:28 -0500 Subject: [PATCH] Add a client for Virtual Network management This commit adds entities to generate the (unavailable) XML schema for virtual network specification, and a client with Get and Set operations for the currently active subscription. The pattern for using the client is the same as the powershell version - get the running config, merge in any desired changes and set the configuration to the new state. There is no documentation on what the concurrency constraints are with the underlying Azure REST API, I suspect it is not safe to call concurrently though can't verify this. --- clients/vnetClient/entities.go | 81 ++++++++++++++++++++++++++++++++ clients/vnetClient/vnetClient.go | 49 +++++++++++++++++++ common.go | 54 ++++++++++++++------- 3 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 clients/vnetClient/entities.go create mode 100644 clients/vnetClient/vnetClient.go diff --git a/clients/vnetClient/entities.go b/clients/vnetClient/entities.go new file mode 100644 index 000000000000..cfa5a94d958e --- /dev/null +++ b/clients/vnetClient/entities.go @@ -0,0 +1,81 @@ +package vnetClient + +import ( + "encoding/xml" +) + +const xmlNamespace = "http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration" +const xmlNamespaceXsd = "http://www.w3.org/2001/XMLSchema" +const xmlNamespaceXsi = "http://www.w3.org/2001/XMLSchema-instance" + +//NetworkConfiguration represents the network configuration for an entire Azure +//subscription. TODO: Nicer builder methods for these that abstract away the +//underlying structure +type NetworkConfiguration struct { + XMLName xml.Name `xml:"NetworkConfiguration"` + XmlNamespaceXsd string `xml:"xmlns:xsd,attr"` + XmlNamespaceXsi string `xml:"xmlns:xsi,attr"` + Xmlns string `xml:"xmlns,attr"` + Configuration VirtualNetworkConfiguration `xml:"VirtualNetworkConfiguration"` +} + +//NewNetworkConfiguration creates a new empty NetworkConfiguration structure for +//further configuration. The XML namespaces are set correctly. +func NewNetworkConfiguration() NetworkConfiguration { + networkConfiguration := NetworkConfiguration{} + networkConfiguration.setXmlNamespaces() + return networkConfiguration +} + +//setXmlNamespaces ensure that all of the required namespaces are set. It should +//be called prior to marshalling the structure to XML for use with the Azure REST +//endpoint. It is used internally prior to submitting requests, but since it is +//idempotent there is no harm in repeat calls. +func (self *NetworkConfiguration) setXmlNamespaces() { + self.XmlNamespaceXsd = xmlNamespaceXsd + self.XmlNamespaceXsi = xmlNamespaceXsi + self.Xmlns = xmlNamespace +} + +type VirtualNetworkConfiguration struct { + Dns Dns `xml:"Dns,omitempty"` + LocalNetworkSites []LocalNetworkSite `xml:"LocalNetworkSites>LocalNetworkSite"` + VirtualNetworkSites []VirtualNetworkSite `xml:"VirtualNetworkSites>VirtualNetworkSite"` +} + +type Dns struct { + DnsServers []DnsServer `xml:"DnsServers,omitempty>DnsServer,omitempty"` +} + +type DnsServer struct { + XMLName xml.Name `xml:"DnsServer"` + Name string `xml:"name,attr"` + IPAddress string `xml:"IPAddress,attr"` +} + +type DnsServerRef struct { + Name string `xml:"name,attr"` +} + +type VirtualNetworkSite struct { + Name string `xml:"name,attr"` + Location string `xml:"Location,attr"` + AddressSpace AddressSpace `xml:"AddressSpace"` + Subnets []Subnet `xml:"Subnets>Subnet"` + DnsServersRef []DnsServerRef `xml:"DnsServersRef,omitempty>DnsServerRef"` +} + +type LocalNetworkSite struct { + Name string `xml:"name,attr"` + VPNGatewayAddress string + AddressSpace AddressSpace +} + +type AddressSpace struct { + AddressPrefix []string +} + +type Subnet struct { + Name string `xml:"name,attr"` + AddressPrefix string +} diff --git a/clients/vnetClient/vnetClient.go b/clients/vnetClient/vnetClient.go new file mode 100644 index 000000000000..f9638e1d3f6e --- /dev/null +++ b/clients/vnetClient/vnetClient.go @@ -0,0 +1,49 @@ +package vnetClient + +import ( + "encoding/xml" + azure "github.com/MSOpenTech/azure-sdk-for-go" +) + +const ( + azureNetworkConfigurationURL = "services/networking/media" +) + +//GetVirtualNetworkConfiguration retreives the current virtual network +//configuration for the currently active subscription. Note that the +//underlying Azure API means that network related operations are not safe +//for running concurrently. +func GetVirtualNetworkConfiguration() (NetworkConfiguration, error) { + networkConfiguration := NewNetworkConfiguration() + response, err := azure.SendAzureGetRequest(azureNetworkConfigurationURL) + if err != nil { + return networkConfiguration, err + } + + err = xml.Unmarshal(response, &networkConfiguration) + if err != nil { + return networkConfiguration, err + } + + return networkConfiguration, nil +} + +//SetVirtualNetworkConfiguration configures the virtual networks for the +//currently active subscription according to the NetworkConfiguration given. +//Note that the underlying Azure API means that network related operations +//are not safe for running concurrently. +func SetVirtualNetworkConfiguration(networkConfiguration NetworkConfiguration) error { + networkConfiguration.setXmlNamespaces() + networkConfigurationBytes, err := xml.Marshal(networkConfiguration) + if err != nil { + return err + } + + requestId, err := azure.SendAzurePutRequest(azureNetworkConfigurationURL, "text/plain", networkConfigurationBytes) + if err != nil { + return err + } + + err = azure.WaitAsyncOperation(requestId) + return err +} diff --git a/common.go b/common.go index 6808f016a42f..248f035fd3c8 100644 --- a/common.go +++ b/common.go @@ -17,12 +17,12 @@ import ( const ( paramNotSpecifiedError = "Parameter %s is not specified." - azureManagementDnsName = "https://management.core.windows.net" - msVersionHeader = "x-ms-version" - msVersionHeaderValue = "2014-05-01" - contentHeader = "Content-Type" - contentHeaderValue = "application/xml" - requestIdHeader = "X-Ms-Request-Id" + azureManagementDnsName = "https://management.core.windows.net" + msVersionHeader = "x-ms-version" + msVersionHeaderValue = "2014-05-01" + contentHeader = "Content-Type" + defaultContentHeaderValue = "application/xml" + requestIdHeader = "X-Ms-Request-Id" ) //Region public methods starts @@ -32,7 +32,7 @@ func SendAzureGetRequest(url string) ([]byte, error) { return nil, fmt.Errorf(paramNotSpecifiedError, "url") } - response, err := SendAzureRequest(url, "GET", nil) + response, err := SendAzureRequest(url, "GET", "", nil) if err != nil { return nil, err } @@ -46,7 +46,21 @@ func SendAzurePostRequest(url string, data []byte) (string, error) { return "", fmt.Errorf(paramNotSpecifiedError, "url") } - response, err := SendAzureRequest(url, "POST", data) + response, err := SendAzureRequest(url, "POST", "", data) + if err != nil { + return "", err + } + + requestId := response.Header[requestIdHeader] + return requestId[0], nil +} + +func SendAzurePutRequest(url string, contentType string, data []byte) (string, error) { + if len(url) == 0 { + return "", fmt.Errorf(paramNotSpecifiedError, contentType, "url") + } + + response, err := SendAzureRequest(url, "PUT", contentType, data) if err != nil { return "", err } @@ -60,7 +74,7 @@ func SendAzureDeleteRequest(url string) (string, error) { return "", fmt.Errorf(paramNotSpecifiedError, "url") } - response, err := SendAzureRequest(url, "DELETE", nil) + response, err := SendAzureRequest(url, "DELETE", "", nil) if err != nil { return "", err } @@ -69,7 +83,7 @@ func SendAzureDeleteRequest(url string) (string, error) { return requestId[0], nil } -func SendAzureRequest(url string, requestType string, data []byte) (*http.Response, error) { +func SendAzureRequest(url string, requestType string, contentType string, data []byte) (*http.Response, error) { if len(url) == 0 { return nil, fmt.Errorf(paramNotSpecifiedError, "url") } @@ -79,7 +93,7 @@ func SendAzureRequest(url string, requestType string, data []byte) (*http.Respon client := createHttpClient() - response, err := sendRequest(client, url, requestType, data, 7) + response, err := sendRequest(client, url, requestType, contentType, data, 7) if err != nil { return nil, err } @@ -159,7 +173,7 @@ func CheckStringParams(url string) ([]byte, error) { return nil, fmt.Errorf(paramNotSpecifiedError, "url") } - response, err := SendAzureRequest(url, "GET", nil) + response, err := SendAzureRequest(url, "GET", "", nil) if err != nil { return nil, err } @@ -188,8 +202,8 @@ func NewUUID() (string, error) { //Region private methods starts -func sendRequest(client *http.Client, url string, requestType string, data []byte, numberOfRetries int) (*http.Response, error) { - request, reqErr := createAzureRequest(url, requestType, data) +func sendRequest(client *http.Client, url string, requestType string, contentType string, data []byte, numberOfRetries int) (*http.Response, error) { + request, reqErr := createAzureRequest(url, requestType, contentType, data) if reqErr != nil { return nil, reqErr } @@ -200,7 +214,7 @@ func sendRequest(client *http.Client, url string, requestType string, data []byt return nil, err } - return sendRequest(client, url, requestType, data, numberOfRetries-1) + return sendRequest(client, url, requestType, contentType, data, numberOfRetries-1) } if response.StatusCode > 299 { @@ -211,7 +225,7 @@ func sendRequest(client *http.Client, url string, requestType string, data []byt return nil, azureErr } - return sendRequest(client, url, requestType, data, numberOfRetries-1) + return sendRequest(client, url, requestType, contentType, data, numberOfRetries-1) } } @@ -228,7 +242,7 @@ func getAzureError(responseBody []byte) error { return error } -func createAzureRequest(url string, requestType string, data []byte) (*http.Request, error) { +func createAzureRequest(url string, requestType string, contentType string, data []byte) (*http.Request, error) { var request *http.Request var err error @@ -245,7 +259,11 @@ func createAzureRequest(url string, requestType string, data []byte) (*http.Requ } request.Header.Add(msVersionHeader, msVersionHeaderValue) - request.Header.Add(contentHeader, contentHeaderValue) + if len(contentType) > 0 { + request.Header.Add(contentHeader, contentType) + } else { + request.Header.Add(contentHeader, defaultContentHeaderValue) + } return request, nil }