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:
68
providers/azure/aadMock.go
Normal file
68
providers/azure/aadMock.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
83
providers/azure/aciMock.go
Normal file
83
providers/azure/aciMock.go
Normal 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
183
providers/azure/aci_test.go
Normal 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user