Fill in Default Values for CPU/Memory (#130)

Update k8s client and the dependencies
ACI client change for Mocking
Add ACI Provider Mock Tests
Add the Windows development environment
Add UT for Default Resource Requests
Enable the make test in Docker file
Update the vendors
This commit is contained in:
Robbie Zhang
2018-04-16 10:31:16 -07:00
committed by GitHub
parent 88bafc701b
commit 2b85b0d1df
862 changed files with 61483 additions and 16781 deletions

View File

@@ -0,0 +1,68 @@
package azure
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"time"
"github.com/Azure/go-autorest/autorest/adal"
)
// AADMock implements a AAD mock server .
type AADMock struct {
server *httptest.Server
OnAcquireToken func(http.ResponseWriter,*http.Request)
}
// NewAADMock creates a new AAD server mocker.
func NewAADMock() *AADMock {
aadServer := new(AADMock)
aadServer.start()
return aadServer
}
// Start the AAD server mocker.
func (mock *AADMock)start() {
if mock.server != nil {
return
}
mock.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if mock.OnAcquireToken != nil {
mock.OnAcquireToken(w, r)
return
}
w.WriteHeader(http.StatusOK)
token := adal.Token{
AccessToken: "Test Token",
NotBefore: strconv.FormatInt(time.Now().UnixNano(), 10),
ExpiresIn: strconv.FormatInt(int64(time.Minute), 10),
}
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(token)
w.Write(b.Bytes())
}))
}
// GetServerURL returns the mock server URL.
func (mock *AADMock)GetServerURL() string {
if mock.server != nil {
return mock.server.URL
}
panic("Mock server is not initialized.")
}
// Close terminates the AAD server mocker.
func (mock *AADMock)Close() {
if mock.server != nil {
mock.server.Close()
mock.server = nil
}
}

View File

@@ -54,12 +54,13 @@ type AuthConfig struct {
RegistryToken string `json:"registrytoken,omitempty"`
}
// See https://docs.microsoft.com/en-us/azure/container-instances/container-instances-quotas for valid regions.
// See https://azure.microsoft.com/en-us/status/ for valid regions.
var validAciRegions = []string{
"westeurope",
"westus",
"eastus",
"southeastasia",
"westus2",
}
// isValidACIRegion checks to make sure we're using a valid ACI region
@@ -562,10 +563,41 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) {
})
}
cpuLimit := float64(container.Resources.Limits.Cpu().Value())
memoryLimit := float64(container.Resources.Limits.Memory().Value()) / 1000000000.00
cpuRequest := float64(container.Resources.Requests.Cpu().Value())
memoryRequest := float64(container.Resources.Requests.Memory().Value()) / 1000000000.00
// NOTE(robbiezhang): ACI CPU limit must be times of 10m
cpuLimit := 1.00
if _, ok := container.Resources.Limits[v1.ResourceCPU]; ok {
cpuLimit = float64(container.Resources.Limits.Cpu().MilliValue() / 10.00) / 100.00
if cpuLimit < 0.01 {
cpuLimit = 0.01
}
}
// NOTE(robbiezhang): ACI Memory limit must be times of 0.1 GB
memoryLimit := 1.50
if _, ok := container.Resources.Limits[v1.ResourceMemory]; ok {
memoryLimit = float64(container.Resources.Limits.Memory().Value() / 100000000.00) / 10.00
if memoryLimit < 0.10 {
memoryLimit = 0.10
}
}
// NOTE(robbiezhang): ACI CPU request must be times of 10m
cpuRequest := 1.00
if _, ok := container.Resources.Requests[v1.ResourceCPU]; ok {
cpuRequest = float64(container.Resources.Requests.Cpu().MilliValue() / 10.00) / 100.00
if cpuRequest < 0.01 {
cpuRequest = 0.01
}
}
// NOTE(robbiezhang): ACI memory request must be times of 0.1 GB
memoryRequest := 1.50
if _, ok := container.Resources.Requests[v1.ResourceMemory]; ok {
memoryRequest = float64(container.Resources.Requests.Memory().Value() / 100000000.00) / 10.00
if memoryRequest < 0.10 {
memoryRequest = 0.10
}
}
c.Resources = aci.ResourceRequirements{
Limits: aci.ResourceLimits{

View File

@@ -0,0 +1,83 @@
package azure
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"github.com/gorilla/mux"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
)
// ACIMock implements a Azure Container Instance mock server.
type ACIMock struct {
server *httptest.Server
OnCreate func(string, string, string, *aci.ContainerGroup) (int, interface{})
}
const (
containerGroupsRoute = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.ContainerInstance/containerGroups"
containerGroupRoute = containerGroupsRoute + "/{containerGroup}"
containerGroupLogRoute = containerGroupRoute + "/containers/{containerName}/logs"
)
// NewACIMock creates a new Azure Container Instance mock server.
func NewACIMock() *ACIMock {
mock := new(ACIMock)
mock.start()
return mock
}
// Start the Azure Container Instance mock service.
func (mock *ACIMock) start() {
if mock.server != nil {
return
}
router := mux.NewRouter()
router.HandleFunc(
containerGroupRoute,
func(w http.ResponseWriter, r *http.Request) {
subscription, _ := mux.Vars(r)["subscriptionId"]
resourceGroup, _ := mux.Vars(r)["resourceGroup"]
containerGroup, _ := mux.Vars(r)["containerGroup"]
var cg aci.ContainerGroup
if err := json.NewDecoder(r.Body).Decode(&cg); err != nil {
panic(err)
}
if mock.OnCreate != nil {
statusCode, response := mock.OnCreate(subscription, resourceGroup, containerGroup, &cg)
w.WriteHeader(statusCode)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(response)
w.Write(b.Bytes())
return
}
w.WriteHeader(http.StatusNotImplemented)
}).Methods("PUT")
mock.server = httptest.NewServer(router)
}
// GetServerURL returns the mock server URL.
func (mock *ACIMock) GetServerURL() string {
if mock.server != nil {
return mock.server.URL
}
panic("Mock server is not initialized.")
}
// Close terminates the Azure Container Instance mock server.
func (mock *ACIMock) Close() {
if mock.server != nil {
mock.server.Close()
mock.server = nil
}
}

183
providers/azure/aci_test.go Normal file
View File

@@ -0,0 +1,183 @@
/**
* Copyright (c) Microsoft. All rights reserved.
*/
package azure
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/virtual-kubelet/virtual-kubelet/manager"
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/client-go/kubernetes/fake"
)
const (
fakeSubscription = "a88d9e8f-3cb3-456f-8f10-27395c1e122a"
fakeResourceGroup = "vk-rg"
fakeClientID = "f14193ad-4c4c-4876-a18a-c0badb3bbd40"
fakeClientSecret = "VGhpcyBpcyBhIHNlY3JldAo="
fakeTenantID = "8cb81aca-83fe-4c6f-b667-4ec09c45a8bf"
)
// Tests create pod without resource spec
func TestCreatePodWithoutResourceSpec(t *testing.T) {
_, aciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
podName := "pod-" + uuid.New().String()
podNamespace := "ns-" + uuid.New().String()
aciServerMocker.OnCreate = func(subscription, resourceGroup, containerGroup string, cg *aci.ContainerGroup) (int, interface{}) {
assert.Equal(t, fakeSubscription, subscription, "Subscription doesn't match")
assert.Equal(t, fakeResourceGroup, resourceGroup, "Resource group doesn't match")
assert.NotNil(t, cg, "Container group is nil")
assert.Equal(t, podNamespace + "-" + podName, containerGroup, "Container group name is not expected")
assert.NotNil(t, cg.ContainerGroupProperties, "Container group properties should not be nil")
assert.NotNil(t, cg.ContainerGroupProperties.Containers, "Containers should not be nil")
assert.Equal(t, 1, len(cg.ContainerGroupProperties.Containers), "1 Container is expected")
assert.Equal(t, "nginx", cg.ContainerGroupProperties.Containers[0].Name, "Container nginx is expected")
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources, "Container resources should not be nil")
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil")
assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected")
assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected")
assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected")
assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected")
return http.StatusOK, cg
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: podNamespace,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
v1.Container{
Name: "nginx",
},
},
},
}
if err := provider.CreatePod(pod); err != nil {
t.Fatal("Failed to create pod", err)
}
}
// Tests create pod without resource spec
func TestCreatePodWithResourceSpec(t *testing.T) {
_, aciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
podName := "pod-" + uuid.New().String()
podNamespace := "ns-" + uuid.New().String()
aciServerMocker.OnCreate = func(subscription, resourceGroup, containerGroup string, cg *aci.ContainerGroup) (int, interface{}) {
assert.Equal(t, fakeSubscription, subscription, "Subscription doesn't match")
assert.Equal(t, fakeResourceGroup, resourceGroup, "Resource group doesn't match")
assert.NotNil(t, cg, "Container group is nil")
assert.Equal(t, podNamespace + "-" + podName, containerGroup, "Container group name is not expected")
assert.NotNil(t, cg.ContainerGroupProperties, "Container group properties should not be nil")
assert.NotNil(t, cg.ContainerGroupProperties.Containers, "Containers should not be nil")
assert.Equal(t, 1, len(cg.ContainerGroupProperties.Containers), "1 Container is expected")
assert.Equal(t, "nginx", cg.ContainerGroupProperties.Containers[0].Name, "Container nginx is expected")
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources, "Container resources should not be nil")
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil")
assert.Equal(t, 1.98, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected")
assert.Equal(t, 3.4, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected")
assert.Equal(t, 3.99, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected")
assert.Equal(t, 8.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected")
return http.StatusOK, cg
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: podNamespace,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
v1.Container{
Name: "nginx",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1.98"),
"memory": resource.MustParse("3.4G"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("3999m"),
"memory": resource.MustParse("8010M"),
},
},
},
},
},
}
if err := provider.CreatePod(pod); err != nil {
t.Fatal("Failed to create pod", err)
}
}
func prepareMocks() (*AADMock, *ACIMock, *ACIProvider, error) {
aadServerMocker := NewAADMock()
aciServerMocker := NewACIMock()
auth := azure.NewAuthentication(
azure.PublicCloud.Name,
fakeClientID,
fakeClientSecret,
fakeSubscription,
fakeTenantID)
auth.ActiveDirectoryEndpoint = aadServerMocker.GetServerURL()
auth.ResourceManagerEndpoint = aciServerMocker.GetServerURL()
file, err := ioutil.TempFile("", "auth.json")
if err != nil {
return nil, nil, nil, err
}
defer os.Remove(file.Name())
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(auth)
if _, err := file.Write(b.Bytes()); err != nil {
return nil, nil, nil, err
}
os.Setenv("AZURE_AUTH_LOCATION", file.Name())
os.Setenv("ACI_RESOURCE_GROUP", fakeResourceGroup)
clientset := fake.NewSimpleClientset()
rm := manager.NewResourceManager(clientset)
provider, err := NewACIProvider("example.toml", rm, "vk", "Linux", "0.0.0.0", 10250)
if err != nil {
return nil, nil, nil, err
}
return aadServerMocker, aciServerMocker, provider, nil
}

View File

@@ -9,7 +9,7 @@ import (
const (
// BaseURI is the default URI used for compute services.
BaseURI = "https://management.azure.com"
baseURI = "https://management.azure.com"
userAgent = "virtual-kubelet/azure-arm-aci/2018-02-01"
apiVersion = "2018-02-01-preview"
@@ -34,7 +34,7 @@ func NewClient(auth *azure.Authentication) (*Client, error) {
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
}
client, err := azure.NewClient(auth, BaseURI, userAgent)
client, err := azure.NewClient(auth, baseURI, userAgent)
if err != nil {
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
}

View File

@@ -20,7 +20,7 @@ func (c *Client) CreateContainerGroup(resourceGroup, containerGroupName string,
}
// Create the url.
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
uri += "?" + url.Values(urlParams).Encode()
// Create the body for the request.

View File

@@ -17,7 +17,7 @@ func (c *Client) DeleteContainerGroup(resourceGroup, containerGroupName string)
}
// Create the url.
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.

View File

@@ -19,7 +19,7 @@ func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*C
}
// Create the url.
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.

View File

@@ -22,10 +22,10 @@ func (c *Client) ListContainerGroups(resourceGroup string) (*ContainerGroupListR
}
// Create the url.
uri := api.ResolveRelative(BaseURI, containerGroupListURLPath)
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListURLPath)
// List by resource group if they passed one.
if resourceGroup != "" {
uri = api.ResolveRelative(BaseURI, containerGroupListByResourceGroupURLPath)
uri = api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListByResourceGroupURLPath)
}
uri += "?" + url.Values(urlParams).Encode()

View File

@@ -20,7 +20,7 @@ func (c *Client) GetContainerLogs(resourceGroup, containerGroupName, containerNa
}
// Create the url.
uri := api.ResolveRelative(BaseURI, containerLogsURLPath)
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerLogsURLPath)
uri += "?" + url.Values(urlParams).Encode()
// Create the request.