feat: validate VHD availability on azurestack (#2342)

* added image api

* images api test

* fix: final pr for review

* wip including test

* tests working

* fixed variables in test

* fixed tests and code complete

* generated templates

* changed file formats

* added coryright headers

* added test validation

* reverted kubeconfig for azurestack

* Pull request changes

* fix validation if version == latest

* context timeout

* added image api

* images api test

* fix: final pr for review

* wip including test

* tests working

* fixed variables in test

* fixed tests and code complete

* generated templates

* changed file formats

* added coryright headers

* added test validation

* reverted kubeconfig for azurestack

* Pull request changes

* fix validation if version == latest

* context timeout

* Improved code coverage for failure cases

Co-authored-by: Javier Darsie <44655727+jadarsie@users.noreply.github.com>
This commit is contained in:
deaborch 2020-01-27 11:07:52 -08:00 коммит произвёл Azure Kubernetes Service Bot
Родитель 4f6e477034
Коммит d46d6e5207
16 изменённых файлов: 605 добавлений и 1 удалений

Просмотреть файл

@ -404,6 +404,10 @@ func (dc *deployCmd) run() error {
return errors.Wrapf(err, "in SetPropertiesDefaults template %s", dc.apimodelPath)
}
if err = dc.validateOSBaseImage(); err != nil {
return errors.Wrapf(err, "validating OS base images required by %s", dc.apimodelPath)
}
template, parameters, err := templateGenerator.GenerateTemplateV2(dc.containerService, engine.DefaultGeneratorCode, BuildTag)
if err != nil {
return errors.Wrapf(err, "generating template %s", dc.apimodelPath)
@ -437,10 +441,11 @@ func (dc *deployCmd) run() error {
return err
}
deploymentSuffix := dc.random.Int31()
cx, cancel := context.WithTimeout(context.Background(), armhelpers.DefaultARMOperationTimeout)
defer cancel()
deploymentSuffix := dc.random.Int31()
if res, err := dc.client.DeployTemplate(
cx,
dc.resourceGroup,
@ -531,3 +536,15 @@ func (dc *deployCmd) configureContainerMonitoringAddon(ctx context.Context, k8sC
}
return nil
}
// validateOSBaseImage checks if the OS image is available on the target cloud (ATM, Azure Stack only)
func (dc *deployCmd) validateOSBaseImage() error {
if dc.containerService.Properties.IsAzureStackCloud() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := armhelpers.ValidateRequiredImages(ctx, dc.location, dc.containerService.Properties, dc.client); err != nil {
return errors.Wrap(err, "OS base image not available in target cloud")
}
}
return nil
}

Просмотреть файл

@ -70,6 +70,7 @@ type AzureClient struct {
disksClient compute.DisksClient
availabilitySetsClient compute.AvailabilitySetsClient
workspacesClient operationalinsights.WorkspacesClient
virtualMachineImagesClient compute.VirtualMachineImagesClient
applicationsClient graphrbac.ApplicationsClient
servicePrincipalsClient graphrbac.ServicePrincipalsClient
@ -353,6 +354,7 @@ func getClient(env azure.Environment, subscriptionID, tenantID string, armAuthor
disksClient: compute.NewDisksClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
availabilitySetsClient: compute.NewAvailabilitySetsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
workspacesClient: operationalinsights.NewWorkspacesClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
virtualMachineImagesClient: compute.NewVirtualMachineImagesClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
applicationsClient: graphrbac.NewApplicationsClientWithBaseURI(env.GraphEndpoint, tenantID),
servicePrincipalsClient: graphrbac.NewServicePrincipalsClientWithBaseURI(env.GraphEndpoint, tenantID),
@ -373,6 +375,7 @@ func getClient(env azure.Environment, subscriptionID, tenantID string, armAuthor
c.disksClient.Authorizer = armAuthorizer
c.availabilitySetsClient.Authorizer = armAuthorizer
c.workspacesClient.Authorizer = armAuthorizer
c.virtualMachineImagesClient.Authorizer = armAuthorizer
c.deploymentsClient.PollingDelay = time.Second * 5
c.resourcesClient.PollingDelay = time.Second * 5

Просмотреть файл

@ -66,6 +66,7 @@ type AzureClient struct {
disksClient compute.DisksClient
availabilitySetsClient compute.AvailabilitySetsClient
workspacesClient operationalinsights.WorkspacesClient
virtualMachineImagesClient compute.VirtualMachineImagesClient
applicationsClient graphrbac.ApplicationsClient
servicePrincipalsClient graphrbac.ServicePrincipalsClient
@ -218,6 +219,7 @@ func getClient(env azure.Environment, subscriptionID, tenantID string, armAuthor
disksClient: compute.NewDisksClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
availabilitySetsClient: compute.NewAvailabilitySetsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
workspacesClient: operationalinsights.NewWorkspacesClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
virtualMachineImagesClient: compute.NewVirtualMachineImagesClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID),
applicationsClient: graphrbac.NewApplicationsClientWithBaseURI(env.GraphEndpoint, tenantID),
servicePrincipalsClient: graphrbac.NewServicePrincipalsClientWithBaseURI(env.GraphEndpoint, tenantID),
@ -238,6 +240,7 @@ func getClient(env azure.Environment, subscriptionID, tenantID string, armAuthor
c.disksClient.Authorizer = armAuthorizer
c.availabilitySetsClient.Authorizer = armAuthorizer
c.workspacesClient.Authorizer = armAuthorizer
c.virtualMachineImagesClient.Authorizer = armAuthorizer
c.deploymentsClient.PollingDelay = time.Second * 5
c.resourcesClient.PollingDelay = time.Second * 5

Просмотреть файл

@ -37,6 +37,10 @@ const (
virutalDiskName = "testVirtualdickName"
location = "local"
operationID = "7184adda-13fc-4d49-b941-fbbc3b08ed64"
publisher = "DefaultPublisher"
sku = "DefaultSku"
offer = "DefaultOffer"
version = "DefaultVersion"
filePathTokenResponse = "httpMockClientData/tokenResponse.json"
filePathListVirtualMachineScaleSets = "httpMockClientData/listVirtualMachineScaleSets.json"
filePathListVirtualMachineScaleSetVMs = "httpMockClientData/listVirtualMachineScaleSetVMs.json"
@ -49,6 +53,8 @@ const (
filePathGetLogAnalyticsWorkspaceSharedKeys = "httpMockClientData/getLogAnalyticsWorkspaceSharedKeys.json"
filePathListWorkspacesByResourceGroup = "httpMockClientData/getListWorkspacesByResourceGroup.json"
filePathCreateOrUpdateWorkspace = "httpMockClientData/createOrUpdateWorkspace.json"
filePathGetVirtualMachineImage = "httpMockClientData/getVirtualMachineImage.json"
filePathListVirtualMachineImages = "httpMockClientData/listVirtualMachineImages.json"
)
//HTTPMockClient is an wrapper of httpmock
@ -76,6 +82,10 @@ type HTTPMockClient struct {
Location string
OperationID string
TokenResponse string
Publisher string
Sku string
Offer string
Version string
ResponseListVirtualMachineScaleSets string
ResponseListVirtualMachineScaleSetVMs string
ResponseListVirtualMachines string
@ -87,6 +97,8 @@ type HTTPMockClient struct {
ResponseGetLogAnalyticsWorkspaceSharedKeys string
ResponseListWorkspacesByResourceGroup string
ResponseCreateOrUpdateWorkspace string
ResponseGetVirtualMachineImage string
ResponseListVirtualMachineImages string
mux *http.ServeMux
server *testserver.TestServer
}
@ -132,6 +144,10 @@ func NewHTTPMockClient() (HTTPMockClient, error) {
VirutalDiskName: virutalDiskName,
Location: location,
OperationID: operationID,
Publisher: publisher,
Offer: offer,
Sku: sku,
Version: version,
mux: http.NewServeMux(),
}
var err error
@ -183,6 +199,15 @@ func NewHTTPMockClient() (HTTPMockClient, error) {
if err != nil {
return client, err
}
client.ResponseGetVirtualMachineImage, err = readFromFile(filePathGetVirtualMachineImage)
if err != nil {
return client, err
}
client.ResponseListVirtualMachineImages, err = readFromFile(filePathListVirtualMachineImages)
if err != nil {
return client, err
}
return client, nil
}
@ -541,6 +566,27 @@ func (mc HTTPMockClient) RegisterEnsureDefaultLogAnalyticsWorkspaceCreateNew() {
}
// RegisterVMImageFetcherInterface registers the mock response for VMImageFetcherInterface methods.
func (mc *HTTPMockClient) RegisterVMImageFetcherInterface() {
pattern := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", mc.SubscriptionID, mc.Location, mc.Publisher, mc.Offer, mc.Sku, mc.Version)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseGetVirtualMachineImage)
}
})
pattern = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions", mc.SubscriptionID, mc.Location, mc.Publisher, mc.Offer, mc.Sku)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseListVirtualMachineImages)
}
})
}
func readFromFile(filePath string) (string, error) {
bytes, err := ioutil.ReadFile(filePath)
if err != nil {

Просмотреть файл

@ -0,0 +1,11 @@
{
"id": "/Subscriptions/14faff93-96b5-428e-8eaa-868d3c7b9397/Providers/Microsoft.Compute/Locations/redmond/Publishers/microsoft-aks/ArtifactTypes/VMImage/Offers/aks/Skus/aks-ubuntu-1604-201910/Versions/2019.10.24",
"location": "redmond",
"name": "2019.10.24",
"properties": {
"osDiskImage": {
"operatingSystem": "Linux"
},
"dataDiskImages": []
}
}

Просмотреть файл

@ -0,0 +1,7 @@
[
{
"id": "/Subscriptions/14faff93-96b5-428e-8eaa-868d3c7b9397/Providers/Microsoft.Compute/Locations/redmond/Publishers/microsoft-aks/ArtifactTypes/VMImage/Offers/aks/Skus/aks-ubuntu-1604-201910/Versions/2019.10.24",
"location": "redmond",
"name": "2019.10.24"
}
]

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package azurestack
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
)
// ListVirtualMachineImages returns the list of images available in the current environment
func (az *AzureClient) ListVirtualMachineImages(ctx context.Context, location, publisherName, offer, skus string) (compute.ListVirtualMachineImageResource, error) {
// random value
top := int32(10)
list, err := az.virtualMachineImagesClient.List(ctx, location, publisherName, offer, skus, "", &top, "")
if err != nil {
return compute.ListVirtualMachineImageResource{}, fmt.Errorf("failed to list virtual machine images, %s", err)
}
r := compute.ListVirtualMachineImageResource{}
if err = DeepCopy(&r, list); err != nil {
return r, fmt.Errorf("failed to deep copy virtual machine images, %s", err)
}
return r, err
}
// GetVirtualMachineImage returns an image or an error if the image is not found
func (az *AzureClient) GetVirtualMachineImage(ctx context.Context, location, publisherName, offer, skus, version string) (compute.VirtualMachineImage, error) {
image, err := az.virtualMachineImagesClient.Get(ctx, location, publisherName, offer, skus, version)
if err != nil {
return compute.VirtualMachineImage{}, fmt.Errorf("failed to get virtual machine image, %s", err)
}
r := compute.VirtualMachineImage{}
if err = DeepCopy(&r, image); err != nil {
return r, fmt.Errorf("failed to deep copy virtual machine image, %s", err)
}
return r, err
}

Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package azurestack
import (
"context"
"testing"
)
const (
badlocation = "badlocation"
badPublisher = "badPublisher"
badOffer = "badOffer"
badSku = "badSku"
)
func TestVMImageFetcherInterface(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
_, err = azureClient.GetVirtualMachineImage(context.Background(), location, publisher, offer, sku, version)
if err != nil {
t.Error(err)
}
_, err = azureClient.ListVirtualMachineImages(context.Background(), location, publisher, offer, sku)
if err != nil {
t.Error(err)
}
}
func TestVMImageFetcherInterfaceBadInput(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
_, err = azureClient.GetVirtualMachineImage(context.Background(), badlocation, badPublisher, badOffer, badSku, version)
if err == nil {
t.Fatal("GetVirtualMachineImage did not fail with bad input")
}
_, err = azureClient.ListVirtualMachineImages(context.Background(), badlocation, badPublisher, badOffer, badSku)
if err == nil {
t.Fatal("ListVirtualMachineImages did not fail with bad input")
}
}

Просмотреть файл

@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"github.com/Azure/aks-engine/pkg/api"
"github.com/Azure/aks-engine/pkg/armhelpers/testserver"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
@ -40,6 +41,10 @@ const (
virutalDiskName = "testVirtualdickName"
location = "local"
operationID = "7184adda-13fc-4d49-b941-fbbc3b08ed64"
publisher = "DefaultPublisher"
sku = "DefaultSku"
offer = "DefaultOffer"
version = "DefaultVersion"
filePathTokenResponse = "httpMockClientData/tokenResponse.json"
filePathListVirtualMachineScaleSets = "httpMockClientData/listVirtualMachineScaleSets.json"
filePathListVirtualMachineScaleSetVMs = "httpMockClientData/listVirtualMachineScaleSetVMs.json"
@ -54,6 +59,8 @@ const (
filePathCreateOrUpdateWorkspace = "httpMockClientData/createOrUpdateWorkspace.json"
filePathListWorkspacesByResourceGroupInMC = "httpMockClientData/getListWorkspacesByResourceGroup.json"
filePathCreateOrUpdateWorkspaceInMC = "httpMockClientData/createOrUpdateWorkspace.json"
filePathGetVirtualMachineImage = "httpMockClientData/getVirtualMachineImage.json"
filePathListVirtualMachineImages = "httpMockClientData/listVirtualMachineImages.json"
)
//HTTPMockClient is an wrapper of httpmock
@ -84,6 +91,10 @@ type HTTPMockClient struct {
Location string
OperationID string
TokenResponse string
Publisher string
Sku string
Offer string
Version string
ResponseListVirtualMachineScaleSets string
ResponseListVirtualMachineScaleSetVMs string
ResponseListVirtualMachines string
@ -97,6 +108,8 @@ type HTTPMockClient struct {
ResponseCreateOrUpdateWorkspace string
ResponseListWorkspacesByResourceGroupInMC string
ResponseCreateOrUpdateWorkspaceInMC string
ResponseGetVirtualMachineImage string
ResponseListVirtualMachineImages string
mux *http.ServeMux
server *testserver.TestServer
}
@ -145,6 +158,10 @@ func NewHTTPMockClient() (HTTPMockClient, error) {
VirutalDiskName: virutalDiskName,
Location: location,
OperationID: operationID,
Publisher: publisher,
Offer: offer,
Sku: sku,
Version: version,
mux: http.NewServeMux(),
}
var err error
@ -206,6 +223,16 @@ func NewHTTPMockClient() (HTTPMockClient, error) {
return client, err
}
client.ResponseGetVirtualMachineImage, err = readFromFile(filePathGetVirtualMachineImage)
if err != nil {
return client, err
}
client.ResponseListVirtualMachineImages, err = readFromFile(filePathListVirtualMachineImages)
if err != nil {
return client, err
}
return client, nil
}
@ -594,6 +621,54 @@ func (mc HTTPMockClient) RegisterEnsureDefaultLogAnalyticsWorkspaceCreateNewInMC
}
// RegisterVMImageFetcherInterface registers the mock response for VMImageFetcherInterface methods.
func (mc *HTTPMockClient) RegisterVMImageFetcherInterface() {
pattern := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", mc.SubscriptionID, mc.Location, mc.Publisher, mc.Offer, mc.Sku, mc.Version)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseGetVirtualMachineImage)
}
})
pattern = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", mc.SubscriptionID, mc.Location, api.Ubuntu1604OSImageConfig.ImagePublisher, api.Ubuntu1604OSImageConfig.ImageOffer, api.Ubuntu1604OSImageConfig.ImageSku, api.Ubuntu1604OSImageConfig.ImageVersion)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseGetVirtualMachineImage)
}
})
pattern = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", mc.SubscriptionID, mc.Location, api.WindowsServer2019OSImageConfig.ImagePublisher, api.WindowsServer2019OSImageConfig.ImageOffer, api.WindowsServer2019OSImageConfig.ImageSku, api.WindowsServer2019OSImageConfig.ImageVersion)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseGetVirtualMachineImage)
}
})
pattern = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", mc.SubscriptionID, mc.Location, api.AKSUbuntu1604OSImageConfig.ImagePublisher, api.AKSUbuntu1604OSImageConfig.ImageOffer, api.AKSUbuntu1604OSImageConfig.ImageSku, api.AKSUbuntu1604OSImageConfig.ImageVersion)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseGetVirtualMachineImage)
}
})
pattern = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions", mc.SubscriptionID, mc.Location, mc.Publisher, mc.Offer, mc.Sku)
mc.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("api-version") != mc.ComputeAPIVersion {
w.WriteHeader(http.StatusNotFound)
} else {
_, _ = fmt.Fprint(w, mc.ResponseListVirtualMachineImages)
}
})
}
func readFromFile(filePath string) (string, error) {
bytes, err := ioutil.ReadFile(filePath)
if err != nil {

Просмотреть файл

@ -0,0 +1,11 @@
{
"id": "/Subscriptions/14faff93-96b5-428e-8eaa-868d3c7b9397/Providers/Microsoft.Compute/Locations/redmond/Publishers/microsoft-aks/ArtifactTypes/VMImage/Offers/aks/Skus/aks-ubuntu-1604-201910/Versions/2019.10.24",
"location": "redmond",
"name": "2019.10.24",
"properties": {
"osDiskImage": {
"operatingSystem": "Linux"
},
"dataDiskImages": []
}
}

Просмотреть файл

@ -0,0 +1,7 @@
[
{
"id": "/Subscriptions/14faff93-96b5-428e-8eaa-868d3c7b9397/Providers/Microsoft.Compute/Locations/redmond/Publishers/microsoft-aks/ArtifactTypes/VMImage/Offers/aks/Skus/aks-ubuntu-1604-201910/Versions/2019.10.24",
"location": "redmond",
"name": "2019.10.24"
}
]

Просмотреть файл

@ -79,6 +79,16 @@ type DiskListPage interface {
Values() []compute.Disk
}
//VMImageFetcher is an extension of AKSEngine client allows us to operate on the virtual machine images in the environment
type VMImageFetcher interface {
// ListVirtualMachineImages return a list of images
ListVirtualMachineImages(ctx context.Context, location, publisherName, offer, skus string) (compute.ListVirtualMachineImageResource, error)
// GetVirtualMachineImage return a virtual machine image
GetVirtualMachineImage(ctx context.Context, location, publisherName, offer, skus, version string) (compute.VirtualMachineImage, error)
}
// AKSEngineClient is the interface used to talk to an Azure environment.
// This interface exposes just the subset of Azure APIs and clients needed for
// AKS Engine.

Просмотреть файл

@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package armhelpers
import (
"context"
"github.com/Azure/aks-engine/pkg/api"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
type validationResult struct {
image api.AzureOSImageConfig
errorData error
}
// ValidateRequiredImages checks that the OS images required by both
// master and agent pools are available on the target cloud
func ValidateRequiredImages(ctx context.Context, location string, p *api.Properties, client AKSEngineClient) error {
if fetcher, ok := client.(VMImageFetcher); ok {
missingImages := make(map[api.Distro]validationResult)
for distro, i := range requiredImages(p) {
if i.ImageVersion == "latest" {
list, err := fetcher.ListVirtualMachineImages(ctx, location, i.ImagePublisher, i.ImageOffer, i.ImageSku)
if err != nil || len(*list.Value) == 0 {
missingImages[distro] = validationResult{
image: i,
errorData: err,
}
}
} else {
if _, err := fetcher.GetVirtualMachineImage(ctx, location, i.ImagePublisher, i.ImageOffer, i.ImageSku, i.ImageVersion); err != nil {
missingImages[distro] = validationResult{
image: i,
errorData: err,
}
}
}
}
if len(missingImages) == 0 {
return nil
}
return printErrorIfAny(missingImages)
}
return errors.New("parameter client is not a VMImageFetcher")
}
func requiredImages(p *api.Properties) map[api.Distro]api.AzureOSImageConfig {
images := make(map[api.Distro]api.AzureOSImageConfig)
images[p.MasterProfile.Distro] = toImageConfig(p.MasterProfile.Distro)
for _, app := range p.AgentPoolProfiles {
if app.OSType == api.Windows {
images[app.Distro] = api.WindowsServer2019OSImageConfig
} else {
images[app.Distro] = toImageConfig(app.Distro)
}
}
return images
}
func printErrorIfAny(missingImages map[api.Distro]validationResult) error {
for _, value := range missingImages {
i := value.image
log.Errorf("error: %+v", value.errorData)
log.Errorf("Image Publisher: %s, Offer: %s, SKU: %s, Version: %s", i.ImagePublisher, i.ImageOffer, i.ImageSku, i.ImageVersion)
}
return errors.New("some VM images are missing on the target cloud")
}
func toImageConfig(distro api.Distro) api.AzureOSImageConfig {
if distro == "" {
return api.Ubuntu1604OSImageConfig
}
switch distro {
case api.Ubuntu:
return api.Ubuntu1604OSImageConfig
case api.Ubuntu1804:
return api.Ubuntu1804OSImageConfig
case api.RHEL:
return api.RHELOSImageConfig
case api.CoreOS:
return api.CoreOSImageConfig
case api.AKSUbuntu1604:
return api.AKSUbuntu1604OSImageConfig
case api.AKSUbuntu1804:
return api.AKSUbuntu1804OSImageConfig
case api.ACC1604:
return api.ACC1604OSImageConfig
default:
return api.Ubuntu1604OSImageConfig
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package armhelpers
import (
"context"
"testing"
"github.com/Azure/aks-engine/pkg/api"
)
func TestValidateRequiredImages(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
testProperties := api.Properties{}
masterProfile := api.MasterProfile{}
masterProfile.Distro = api.AKSUbuntu1604
profile := api.AgentPoolProfile{
OSType: api.Linux,
Distro: api.AKSUbuntu1604,
}
winprofile := api.AgentPoolProfile{
OSType: api.Windows,
}
agentProfiles := []*api.AgentPoolProfile{}
agentProfiles = append(agentProfiles, &profile)
agentProfiles = append(agentProfiles, &winprofile)
testProperties.AgentPoolProfiles = agentProfiles
testProperties.MasterProfile = &masterProfile
if err := ValidateRequiredImages(context.Background(), location, &testProperties, azureClient); err != nil {
t.Fatalf("can not validate required images %s", err)
}
}
func TestValidateRequiredImagesMissingImageCase(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
testProperties := api.Properties{}
masterProfile := api.MasterProfile{}
masterProfile.Distro = api.AKSUbuntu1804
profile := api.AgentPoolProfile{
OSType: api.Linux,
Distro: api.AKSUbuntu1804,
}
agentProfiles := []*api.AgentPoolProfile{}
agentProfiles = append(agentProfiles, &profile)
testProperties.AgentPoolProfiles = agentProfiles
testProperties.MasterProfile = &masterProfile
if err := ValidateRequiredImages(context.Background(), location, &testProperties, azureClient); err == nil {
t.Fatal("could not fail fast for missing images")
}
}

24
pkg/armhelpers/vmImage.go Normal file
Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package armhelpers
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
)
// ListVirtualMachineImages returns the list of images available in the current environment
func (az *AzureClient) ListVirtualMachineImages(ctx context.Context, location, publisherName, offer, skus string) (compute.ListVirtualMachineImageResource, error) {
// random value
top := int32(10)
list, err := az.virtualMachineImagesClient.List(ctx, location, publisherName, offer, skus, "", &top, "")
return list, err
}
// GetVirtualMachineImage returns an image or an error where there is no image
func (az *AzureClient) GetVirtualMachineImage(ctx context.Context, location, publisherName, offer, skus, version string) (compute.VirtualMachineImage, error) {
image, err := az.virtualMachineImagesClient.Get(ctx, location, publisherName, offer, skus, version)
return image, err
}

Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package armhelpers
import (
"context"
"testing"
)
const (
badlocation = "badlocation"
badPublisher = "badPublisher"
badOffer = "badOffer"
badSku = "badSku"
)
func TestVMImageFetcherInterface(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
_, err = azureClient.GetVirtualMachineImage(context.Background(), location, publisher, offer, sku, version)
if err != nil {
t.Error(err)
}
_, err = azureClient.ListVirtualMachineImages(context.Background(), location, publisher, offer, sku)
if err != nil {
t.Error(err)
}
}
func TestVMImageFetcherInterfaceBadInput(t *testing.T) {
mc, err := NewHTTPMockClient()
if err != nil {
t.Fatalf("failed to create HttpMockClient - %s", err)
}
mc.RegisterLogin()
mc.RegisterVMImageFetcherInterface()
err = mc.Activate()
if err != nil {
t.Fatalf("failed to activate HttpMockClient - %s", err)
}
defer mc.DeactivateAndReset()
env := mc.GetEnvironment()
azureClient, err := NewAzureClientWithClientSecret(env, subscriptionID, "clientID", "secret")
if err != nil {
t.Fatalf("can not get client %s", err)
}
_, err = azureClient.GetVirtualMachineImage(context.Background(), badlocation, badPublisher, badOffer, badSku, version)
if err == nil {
t.Fatal("GetVirtualMachineImage did not fail with bad input")
}
_, err = azureClient.ListVirtualMachineImages(context.Background(), badlocation, badPublisher, badOffer, badSku)
if err == nil {
t.Fatal("ListVirtualMachineImages did not fail with bad input")
}
}