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:
@@ -10,6 +10,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -23,6 +25,7 @@ import (
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
client "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/network"
|
||||
"k8s.io/api/core/v1"
|
||||
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@@ -32,10 +35,16 @@ import (
|
||||
stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
)
|
||||
|
||||
// The service account secret mount path.
|
||||
const serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
const (
|
||||
// The service account secret mount path.
|
||||
serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
|
||||
const virtualKubeletDNSNameLabel = "virtualkubelet.io/dnsnamelabel"
|
||||
virtualKubeletDNSNameLabel = "virtualkubelet.io/dnsnamelabel"
|
||||
|
||||
subnetsAction = "Microsoft.Network/virtualNetworks/subnets/action"
|
||||
subnetDelegationService = "Microsoft.ContainerInstance/containerGroups"
|
||||
networkProfileType = "Microsoft.Network/networkProfiles"
|
||||
)
|
||||
|
||||
// ACIProvider implements the virtual-kubelet provider interface and communicates with Azure's ACI APIs.
|
||||
type ACIProvider struct {
|
||||
@@ -51,6 +60,10 @@ type ACIProvider struct {
|
||||
internalIP string
|
||||
daemonEndpointPort int32
|
||||
diagnostics *aci.ContainerGroupDiagnostics
|
||||
subnetName string
|
||||
subnetCIDR string
|
||||
vnetName string
|
||||
networkProfile string
|
||||
|
||||
metricsSync sync.Mutex
|
||||
metricsSyncTime time.Time
|
||||
@@ -146,6 +159,8 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
p.resourceGroup = acsCredential.ResourceGroup
|
||||
p.region = acsCredential.Region
|
||||
}
|
||||
|
||||
p.vnetName = acsCredential.VNetName
|
||||
}
|
||||
|
||||
if clientID := os.Getenv("AZURE_CLIENT_ID"); clientID != "" {
|
||||
@@ -225,6 +240,25 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
p.pods = podsQuota
|
||||
}
|
||||
|
||||
if subnetName := os.Getenv("ACI_SUBNET_NAME"); subnetName != "" {
|
||||
p.subnetName = subnetName
|
||||
}
|
||||
if subnetCIDR := os.Getenv("ACI_SUBNET_CIDR"); subnetCIDR != "" {
|
||||
if p.subnetName == "" {
|
||||
return nil, fmt.Errorf("subnet CIDR defined but no subnet name, subnet name is required to set a subnet CIDR")
|
||||
}
|
||||
if _, _, err := net.ParseCIDR(subnetCIDR); err != nil {
|
||||
return nil, fmt.Errorf("error parsing provided subnet range: %v", err)
|
||||
}
|
||||
p.subnetCIDR = subnetCIDR
|
||||
}
|
||||
|
||||
if p.subnetName != "" {
|
||||
if err := p.setupNetworkProfile(azAuth); err != nil {
|
||||
return nil, fmt.Errorf("error setting up network profile: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
p.operatingSystem = operatingSystem
|
||||
p.nodeName = nodeName
|
||||
p.internalIP = internalIP
|
||||
@@ -233,6 +267,103 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
return &p, err
|
||||
}
|
||||
|
||||
func (p *ACIProvider) setupNetworkProfile(auth *client.Authentication) error {
|
||||
c, err := network.NewClient(auth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating azure networking client: %v", err)
|
||||
}
|
||||
|
||||
createSubnet := true
|
||||
subnet, err := c.GetSubnet(p.resourceGroup, p.vnetName, p.subnetName)
|
||||
if err != nil && !network.IsNotFound(err) {
|
||||
return fmt.Errorf("error while looking up subnet: %v", err)
|
||||
}
|
||||
if err == nil {
|
||||
if p.subnetCIDR != subnet.Properties.AddressPrefix {
|
||||
return fmt.Errorf("found existing subnet with different CIDR")
|
||||
}
|
||||
for _, d := range subnet.Properties.Delegations {
|
||||
if d.Properties.ServiceName == subnetDelegationService {
|
||||
createSubnet = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if createSubnet {
|
||||
if subnet == nil {
|
||||
subnet = &network.Subnet{Name: p.subnetName}
|
||||
}
|
||||
populateSubnet(subnet, p.subnetCIDR)
|
||||
if err = c.CreateOrUpdateSubnet(p.resourceGroup, p.vnetName, subnet); err != nil {
|
||||
return fmt.Errorf("error creating subnet: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
profile, err := c.GetProfile(p.resourceGroup, p.nodeName)
|
||||
if err != nil && !network.IsNotFound(err) {
|
||||
return fmt.Errorf("error while looking up network profile: %v", err)
|
||||
}
|
||||
if err == nil {
|
||||
for _, config := range profile.Properties.ContainerNetworkInterfaceConfigurations {
|
||||
for _, ipConfig := range config.Properties.IPConfigurations {
|
||||
if ipConfig.Properties.Subnet.ID == subnet.ID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("found existing network profile but the profile is not linked to the subnet")
|
||||
}
|
||||
|
||||
// at this point, profile should be nil
|
||||
profile = &network.Profile{
|
||||
Name: p.nodeName,
|
||||
Type: networkProfileType,
|
||||
}
|
||||
|
||||
populateNetworkProfile(profile, subnet)
|
||||
if err := c.CreateOrUpdateProfile(p.resourceGroup, profile); err != nil {
|
||||
return err
|
||||
}
|
||||
p.networkProfile = profile.ID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateSubnet(s *network.Subnet, cidr string) {
|
||||
if s.Properties == nil {
|
||||
s.Properties = &network.SubnetProperties{
|
||||
AddressPrefix: cidr,
|
||||
}
|
||||
}
|
||||
|
||||
s.Properties.Delegations = append(s.Properties.Delegations, network.Delegation{
|
||||
Name: "aciDelegation",
|
||||
Properties: network.DelegationProperties{
|
||||
ServiceName: subnetDelegationService,
|
||||
Actions: []string{subnetsAction},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func populateNetworkProfile(p *network.Profile, subnet *network.Subnet) {
|
||||
p.Properties.ContainerNetworkInterfaceConfigurations = append(p.Properties.ContainerNetworkInterfaceConfigurations, network.InterfaceConfiguration{
|
||||
Name: "eth0",
|
||||
Properties: network.InterfaceConfigurationProperties{
|
||||
IPConfigurations: []network.IPConfiguration{
|
||||
{
|
||||
Name: "ipconfigprofile1",
|
||||
Properties: network.IPConfigurationProperties{
|
||||
Subnet: network.ID{
|
||||
ID: subnet.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreatePod accepts a Pod definition and creates
|
||||
// an ACI deployment
|
||||
func (p *ACIProvider) CreatePod(pod *v1.Pod) error {
|
||||
@@ -240,6 +371,7 @@ func (p *ACIProvider) CreatePod(pod *v1.Pod) error {
|
||||
containerGroup.Location = p.region
|
||||
containerGroup.RestartPolicy = aci.ContainerGroupRestartPolicy(pod.Spec.RestartPolicy)
|
||||
containerGroup.ContainerGroupProperties.OsType = aci.OperatingSystemTypes(p.OperatingSystem())
|
||||
containerGroup.NetworkProfile = &aci.NetworkProfileDefinition{ID: p.networkProfile}
|
||||
|
||||
// get containers
|
||||
containers, err := p.getContainers(pod)
|
||||
|
||||
@@ -18,6 +18,7 @@ type AcsCredential struct {
|
||||
ClientSecret string `json:"aadClientSecret"`
|
||||
ResourceGroup string `json:"resourceGroup"`
|
||||
Region string `json:"location"`
|
||||
VNetName string `json:"vnetName"`
|
||||
}
|
||||
|
||||
// NewAcsCredential returns an AcsCredential struct from file path
|
||||
|
||||
63
providers/azure/client/network/client.go
Normal file
63
providers/azure/client/network/client.go
Normal 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
|
||||
}
|
||||
}
|
||||
45
providers/azure/client/network/client_test.go
Normal file
45
providers/azure/client/network/client_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
76
providers/azure/client/network/main_test.go
Normal file
76
providers/azure/client/network/main_test.go
Normal 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
|
||||
}
|
||||
149
providers/azure/client/network/profile.go
Normal file
149
providers/azure/client/network/profile.go
Normal 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")
|
||||
}
|
||||
78
providers/azure/client/network/profile_test.go
Normal file
78
providers/azure/client/network/profile_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
154
providers/azure/client/network/subnet.go
Normal file
154
providers/azure/client/network/subnet.go
Normal 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")
|
||||
}
|
||||
42
providers/azure/client/network/subnet_test.go
Normal file
42
providers/azure/client/network/subnet_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package azure
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
@@ -16,6 +17,8 @@ type providerConfig struct {
|
||||
CPU string
|
||||
Memory string
|
||||
Pods string
|
||||
SubnetName string
|
||||
SubnetCIDR string
|
||||
}
|
||||
|
||||
func (p *ACIProvider) loadConfig(r io.Reader) error {
|
||||
@@ -53,6 +56,19 @@ func (p *ACIProvider) loadConfig(r io.Reader) error {
|
||||
}
|
||||
}
|
||||
|
||||
// default subnet name
|
||||
if config.SubnetName != "" {
|
||||
p.subnetName = config.SubnetName
|
||||
}
|
||||
if config.SubnetCIDR != "" {
|
||||
if config.SubnetName == "" {
|
||||
return fmt.Errorf("subnet CIDR is set but no subnet name provided, must provide a subnet name in order to set a subnet CIDR")
|
||||
}
|
||||
if _, _, err := net.ParseCIDR(config.SubnetCIDR); err != nil {
|
||||
return fmt.Errorf("error parsing provided subnet CIDR: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
p.operatingSystem = config.OperatingSystem
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user