Add API client for Azure custom vnet (#271)

* Update vendor for azure vent support

* Add support for Azure custom vnets.

Use pointers intead of values. This allows the client to pass back
returned data from Azure.
This commit is contained in:
Brian Goff
2018-07-25 16:49:39 -07:00
committed by Robbie Zhang
parent 36eb3db8a9
commit 25e454d18f
56 changed files with 36373 additions and 3 deletions

View File

@@ -0,0 +1,63 @@
package network
import (
"fmt"
"net/http"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-05-01/network"
"github.com/Azure/go-autorest/autorest/azure/auth"
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
)
const (
baseURI = "https://management.azure.com"
userAgent = "virtual-kubelet/azure-arm-networking/2018-05-01"
apiVersion = "2018-05-01"
)
// Client is a client for interacting with Azure networking
type Client struct {
sc network.SubnetsClient
hc *http.Client
auth *azure.Authentication
}
// NewClient creates a new client for interacting with azure networking
func NewClient(azAuth *azure.Authentication) (*Client, error) {
if azAuth == nil {
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
}
client, err := azure.NewClient(azAuth, baseURI, userAgent)
if err != nil {
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
}
authorizer, err := auth.NewClientCredentialsConfig(azAuth.ClientID, azAuth.ClientSecret, azAuth.TenantID).Authorizer()
if err != nil {
return nil, err
}
sc := network.NewSubnetsClient(azAuth.SubscriptionID)
sc.Authorizer = authorizer
return &Client{
sc: sc,
hc: client.HTTPClient,
auth: azAuth,
}, nil
}
// IsNotFound determines if the passed in error is a not found error from the API.
func IsNotFound(err error) bool {
switch e := err.(type) {
case nil:
return false
case *api.Error:
return e.StatusCode == http.StatusNotFound
default:
return false
}
}

View File

@@ -0,0 +1,45 @@
package network
import (
"context"
"sync"
"testing"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-05-01/network"
"github.com/Azure/go-autorest/autorest/azure/auth"
)
var vnetAuthOnce sync.Once
var azAuth autorest.Authorizer
func ensureVnet(t *testing.T, name string) {
vnetAuthOnce.Do(func() {
var err error
azAuth, err = auth.NewClientCredentialsConfig(testAuth.ClientID, testAuth.ClientSecret, testAuth.TenantID).Authorizer()
if err != nil {
t.Fatalf("error setting up client auth for vnet create: %v", err)
}
})
client := network.NewVirtualNetworksClient(testAuth.SubscriptionID)
client.Authorizer = azAuth
prefixes := []string{"10.0.0.0/24"}
result, err := client.CreateOrUpdate(context.Background(), resourceGroup, name, network.VirtualNetwork{
Name: &name,
Location: &location,
VirtualNetworkPropertiesFormat: &network.VirtualNetworkPropertiesFormat{
AddressSpace: &network.AddressSpace{
AddressPrefixes: &prefixes,
},
},
})
if err != nil {
t.Fatal(err)
}
if err := result.WaitForCompletion(context.Background(), client.Client); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,76 @@
package network
import (
"fmt"
"os"
"sync"
"testing"
"github.com/google/uuid"
"github.com/pkg/errors"
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/resourcegroups"
)
var (
location = "eastus2euap"
resourceGroup = "virtual-kubelet-tests"
testAuth *azure.Authentication
)
func TestMain(m *testing.M) {
uid := uuid.New()
resourceGroup += "-" + uid.String()[0:6]
if err := setupAuth(); err != nil {
fmt.Fprintln(os.Stderr, "Error setting up auth:", err)
os.Exit(1)
}
c, err := resourcegroups.NewClient(testAuth)
if err != nil {
os.Exit(1)
}
_, err = c.CreateResourceGroup(resourceGroup, resourcegroups.Group{
Name: resourceGroup,
Location: location,
})
if err != nil {
os.Exit(1)
}
code := m.Run()
if err := c.DeleteResourceGroup(resourceGroup); err != nil {
fmt.Fprintln(os.Stderr, "error removing resource group:", err)
}
os.Exit(code)
}
var authOnce sync.Once
func setupAuth() error {
var err error
authOnce.Do(func() {
testAuth, err = azure.NewAuthenticationFromFile(os.Getenv("AZURE_AUTH_LOCATION"))
if err != nil {
testAuth, err = azure.NewAuthenticationFromFile("../../../../credentials.json")
}
if err != nil {
err = errors.Wrap(err, "failed to load Azure authentication file")
}
})
return err
}
func newTestClient(t *testing.T) *Client {
if err := setupAuth(); err != nil {
t.Fatal(err)
}
c, err := NewClient(testAuth)
if err != nil {
t.Fatal(err)
}
return c
}

View File

@@ -0,0 +1,149 @@
package network
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"github.com/pkg/errors"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
)
const (
profilePath = "subscriptions/{{.subscriptionId}}/resourcegroups/{{.resourceGroupName}}/providers/Microsoft.Network/networkProfiles/{{.profileName}}"
)
// Profile represents an Azure network profile
type Profile struct {
Name string
ID string
ETag string `json:"etag"`
Type string
Location string
Properties ProfileProperties
}
// ProfileProperties stores the properties for network profiles
type ProfileProperties struct {
ContainerNetworkInterfaceConfigurations []InterfaceConfiguration
}
// InterfaceConfiguration is a configuration for a network interface
type InterfaceConfiguration struct {
Name string
Properties InterfaceConfigurationProperties
}
// InterfaceConfigurationProperties is the properties for a network interface configuration
type InterfaceConfigurationProperties struct {
IPConfigurations []IPConfiguration
}
// IPConfiguration stores the configuration for an IP on a network profile
type IPConfiguration struct {
Name string
Properties IPConfigurationProperties
}
// IPConfigurationProperties stores the subnet for an IP configuration
type IPConfigurationProperties struct {
Subnet ID
}
// ID is a generic struct for objets with an ID
type ID struct {
ID string
}
// GetProfile gets the network profile with the provided name
func (c *Client) GetProfile(resourceGroup, name string) (*Profile, error) {
urlParams := url.Values{
"api-version": []string{apiVersion},
}
// Create the url.
uri := api.ResolveRelative(baseURI, profilePath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, "creating network profile get uri request failed")
}
// Add the parameters to the url.
if err := api.ExpandURL(req.URL, map[string]string{
"subscriptionId": c.auth.SubscriptionID,
"resourceGroupName": resourceGroup,
"profileName": name,
}); err != nil {
return nil, errors.Wrap(err, "expanding URL with parameters failed")
}
// Send the request.
resp, err := c.hc.Do(req)
if err != nil {
return nil, errors.Wrap(err, "sending get network profile request failed")
}
defer resp.Body.Close()
// 200 (OK) is a success response.
if err := api.CheckResponse(resp); err != nil {
return nil, err
}
// Decode the body from the response.
if resp.Body == nil {
return nil, errors.New("get network profile returned an empty body in the response")
}
var p Profile
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
return nil, errors.Wrap(err, "decoding get network profile response body failed")
}
return &p, nil
}
// CreateOrUpdateProfile creates or updates an Azure network profile
func (c *Client) CreateOrUpdateProfile(resourceGroup string, p *Profile) error {
urlParams := url.Values{
"api-version": []string{apiVersion},
}
// Create the url.
uri := api.ResolveRelative(baseURI, profilePath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.
b, err := json.Marshal(p)
if err != nil {
return errors.Wrap(err, "marshalling networking profile failed")
}
req, err := http.NewRequest("PUT", uri, bytes.NewReader(b))
if err != nil {
return errors.Wrap(err, "creating network profile create uri request failed")
}
// Add the parameters to the url.
if err := api.ExpandURL(req.URL, map[string]string{
"subscriptionId": c.auth.SubscriptionID,
"resourceGroupName": resourceGroup,
"profileName": p.Name,
}); err != nil {
return errors.Wrap(err, "expanding URL with parameters failed")
}
// Send the request.
resp, err := c.hc.Do(req)
if err != nil {
return errors.Wrap(err, "sending get network profile request failed")
}
defer resp.Body.Close()
// 200 (OK) is a success response.
if err := api.CheckResponse(resp); err != nil {
return err
}
return errors.Wrap(json.NewDecoder(resp.Body).Decode(p), "error decoding network profile create response")
}

View File

@@ -0,0 +1,78 @@
package network
import (
"path"
"testing"
)
func TestCreateGetProfile(t *testing.T) {
c := newTestClient(t)
ensureVnet(t, t.Name())
subnet := &Subnet{
Name: t.Name(),
Properties: &SubnetProperties{
AddressPrefix: "10.0.0.0/24",
},
}
if err := c.CreateOrUpdateSubnet(resourceGroup, t.Name(), subnet); err != nil {
t.Fatal(err)
}
p := &Profile{
Name: t.Name(),
Type: "Microsoft.Network/networkProfiles",
Location: location,
Properties: ProfileProperties{
ContainerNetworkInterfaceConfigurations: []InterfaceConfiguration{
{
Name: "eth0",
Properties: InterfaceConfigurationProperties{
IPConfigurations: []IPConfiguration{
{
Name: "ipconfigprofile1",
Properties: IPConfigurationProperties{
Subnet: ID{
ID: subnet.ID,
},
},
},
},
},
},
},
},
}
if err := c.CreateOrUpdateProfile(resourceGroup, p); err != nil {
t.Fatal(err)
}
p2, err := c.GetProfile(resourceGroup, p.Name)
if err != nil {
t.Fatal(err)
}
if len(p2.Properties.ContainerNetworkInterfaceConfigurations) != 1 {
t.Fatalf("got unexpected profile properties: %+v", p2.Properties)
}
if len(p2.Properties.ContainerNetworkInterfaceConfigurations[0].Properties.IPConfigurations) != 1 {
t.Fatalf("got unexpected profile IP configuration: %+v", p2.Properties.ContainerNetworkInterfaceConfigurations[0].Properties.IPConfigurations)
}
if p2.Properties.ContainerNetworkInterfaceConfigurations[0].Properties.IPConfigurations[0].Properties.Subnet.ID != subnet.ID {
t.Fatal("got unexpected subnet")
}
subnet, err = c.GetSubnet(resourceGroup, t.Name(), t.Name())
if err != nil {
t.Fatal(err)
}
if len(subnet.Properties.IPConfigurationProfiles) != 1 {
t.Fatalf("got unexpected subnet IP configuration profiles: %+v", subnet.Properties.IPConfigurationProfiles)
}
expected := path.Join(p2.ID, "containerNetworkInterfaceConfigurations/eth0/ipConfigurations/ipconfigprofile1")
if subnet.Properties.IPConfigurationProfiles[0].ID != expected {
t.Fatalf("got unexpected profile, expected:\n\t%s, got:\n\t%s", expected, subnet.Properties.IPConfigurationProfiles[0].ID)
}
}

View File

@@ -0,0 +1,154 @@
package network
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-05-01/network"
"github.com/pkg/errors"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
)
const (
subnetPath = "subscriptions/{{.subscriptionId}}/resourcegroups/{{.resourceGroupName}}/providers/Microsoft.Network/virtualNetworks/{{.vnetName}}/subnets/{{.subnetName}}"
)
// Subnet represents an Azure subnet
type Subnet struct {
Name string
ID string
Properties *SubnetProperties
}
// SubnetProperties are the properties for a subne
type SubnetProperties struct {
AddressPrefix string `json:"addressPrefix,omitempty"`
// IPConfigurationProfiles and Delegations are new fields not available in the SDK yet
IPConfigurationProfiles []SubnetIPConfigurationProfile `json:"ipConfigurationProfiles"`
Delegations []Delegation
// copied from official go SDK, none of these are used here except to make sure we don't nil out some data on fetched objects.
// NetworkSecurityGroup - The reference of the NetworkSecurityGroup resource.
NetworkSecurityGroup *network.SecurityGroup `json:"networkSecurityGroup,omitempty"`
// RouteTable - The reference of the RouteTable resource.
RouteTable *network.RouteTable `json:"routeTable,omitempty"`
// ServiceEndpoints - An array of service endpoints.
ServiceEndpoints *[]network.ServiceEndpointPropertiesFormat `json:"serviceEndpoints,omitempty"`
// IPConfigurations - Gets an array of references to the network interface IP configurations using subnet.
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
// ResourceNavigationLinks - Gets an array of references to the external resources using subnet.
ResourceNavigationLinks *[]network.ResourceNavigationLink `json:"resourceNavigationLinks,omitempty"`
// ProvisioningState - The provisioning state of the resource.
ProvisioningState *string `json:"provisioningState,omitempty"`
}
// SubnetIPConfigurationProfile stores the ID for an assigned network profile
type SubnetIPConfigurationProfile struct {
ID string
}
// Delegation stores the subnet delegation details
type Delegation struct {
Name string
ID string
ETag string
Properties DelegationProperties
}
// DelegationProperties stores the properties for a delegation
type DelegationProperties struct {
ServiceName string
Actions []string
}
// GetSubnet gets the subnet from the specified resourcegroup/vnet
func (c *Client) GetSubnet(resourceGroup, vnet, name string) (*Subnet, error) {
urlParams := url.Values{
"api-version": []string{apiVersion},
}
// Create the url.
uri := api.ResolveRelative(baseURI, subnetPath)
uri += "?" + url.Values(urlParams).Encode()
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, errors.Wrap(err, "creating subnet get uri request failed")
}
if err := api.ExpandURL(req.URL, map[string]string{
"subscriptionId": c.auth.SubscriptionID,
"resourceGroupName": resourceGroup,
"subnetName": name,
"vnetName": vnet,
}); err != nil {
return nil, errors.Wrap(err, "expanding URL with parameters failed")
}
// Send the request.
resp, err := c.hc.Do(req)
if err != nil {
return nil, errors.Wrap(err, "sending subnet get request failed")
}
defer resp.Body.Close()
// 200 (OK) is a success response.
if err := api.CheckResponse(resp); err != nil {
return nil, err
}
var subnet Subnet
if err := json.NewDecoder(resp.Body).Decode(&subnet); err != nil {
return nil, err
}
return &subnet, nil
}
// CreateOrUpdateSubnet creates a new or updates an existing subnet in the defined resourcegroup/vnet
func (c *Client) CreateOrUpdateSubnet(resourceGroup, vnet string, subnet *Subnet) error {
urlParams := url.Values{
"api-version": []string{apiVersion},
}
// Create the url.
uri := api.ResolveRelative(baseURI, subnetPath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.
b, err := json.Marshal(subnet)
if err != nil {
return errors.Wrap(err, "marshallig networking profile failed")
}
req, err := http.NewRequest("PUT", uri, bytes.NewReader(b))
if err != nil {
return errors.New("creating subnet create uri request failed")
}
// Add the parameters to the url.
if err := api.ExpandURL(req.URL, map[string]string{
"subscriptionId": c.auth.SubscriptionID,
"resourceGroupName": resourceGroup,
"subnetName": subnet.Name,
"vnetName": vnet,
}); err != nil {
return errors.Wrap(err, "expanding URL with parameters failed")
}
// Send the request.
resp, err := c.hc.Do(req)
if err != nil {
return errors.Wrap(err, "sending create subnet request failed")
}
defer resp.Body.Close()
// 200 (OK) is a success response.
if err := api.CheckResponse(resp); err != nil {
return err
}
return errors.Wrap(json.NewDecoder(resp.Body).Decode(subnet), "error decoding create subnet response")
}

View File

@@ -0,0 +1,42 @@
package network
import "testing"
func TestCreateGetSubnet(t *testing.T) {
c := newTestClient(t)
subnet := &Subnet{
Name: t.Name(),
Properties: &SubnetProperties{
AddressPrefix: "10.0.0.0/24",
Delegations: []Delegation{
{Name: "aciDelegation", Properties: DelegationProperties{
ServiceName: "Microsoft.ContainerInstance/containerGroups",
Actions: []string{"Microsoft.Network/virtualNetworks/subnets/action"},
}},
},
},
}
ensureVnet(t, t.Name())
if err := c.CreateOrUpdateSubnet(resourceGroup, t.Name(), subnet); err != nil {
t.Fatal(err)
}
s, err := c.GetSubnet(resourceGroup, t.Name(), subnet.Name)
if err != nil {
t.Fatal(err)
}
if s.Name != subnet.Name {
t.Fatal("got unexpected subnet")
}
if s.Properties.AddressPrefix != subnet.Properties.AddressPrefix {
t.Fatalf("got unexpected address prefix: %s", s.Properties.AddressPrefix)
}
if len(s.Properties.Delegations) != 1 {
t.Fatalf("got unexpected delgations: %v", s.Properties.Delegations)
}
if s.Properties.Delegations[0].Name != subnet.Properties.Delegations[0].Name {
t.Fatalf("got unexpected delegation: %v", s.Properties.Delegations[0])
}
}