From 4084266a6521f2071de883e367bbbeb59dc5a6d8 Mon Sep 17 00:00:00 2001 From: Jory Horeman Date: Tue, 3 May 2022 10:54:59 -0700 Subject: [PATCH] Master resize * master resize GA --- .../admin_openshiftcluster_startvm.go | 60 +++++ .../admin_openshiftcluster_startvm_test.go | 110 ++++++++ pkg/frontend/admin_openshiftcluster_stopvm.go | 60 +++++ .../admin_openshiftcluster_stopvm_test.go | 110 ++++++++ .../admin_openshiftcluster_vmresize.go | 111 ++++++++ .../admin_openshiftcluster_vmresize_test.go | 240 ++++++++++++++++++ .../admin_openshiftcluster_vmsizelist.go | 78 ++++++ .../admin_openshiftcluster_vmsizelist_test.go | 162 ++++++++++++ pkg/frontend/adminactions/azureactions.go | 34 +++ pkg/frontend/adminactions/kubeactions.go | 16 +- pkg/frontend/frontend.go | 24 ++ pkg/frontend/validate.go | 6 + .../mgmt/compute/virtualmachines_addons.go | 11 + pkg/util/mocks/adminactions/adminactions.go | 58 +++++ .../mocks/azureclient/mgmt/compute/compute.go | 14 + 15 files changed, 1090 insertions(+), 4 deletions(-) create mode 100644 pkg/frontend/admin_openshiftcluster_startvm.go create mode 100644 pkg/frontend/admin_openshiftcluster_startvm_test.go create mode 100644 pkg/frontend/admin_openshiftcluster_stopvm.go create mode 100644 pkg/frontend/admin_openshiftcluster_stopvm_test.go create mode 100644 pkg/frontend/admin_openshiftcluster_vmresize.go create mode 100644 pkg/frontend/admin_openshiftcluster_vmresize_test.go create mode 100644 pkg/frontend/admin_openshiftcluster_vmsizelist.go create mode 100644 pkg/frontend/admin_openshiftcluster_vmsizelist_test.go diff --git a/pkg/frontend/admin_openshiftcluster_startvm.go b/pkg/frontend/admin_openshiftcluster_startvm.go new file mode 100644 index 00000000000..be43f238326 --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_startvm.go @@ -0,0 +1,60 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "net/http" + "path/filepath" + "strings" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +func (f *frontend) postAdminOpenShiftClusterStartVM(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + err := f._postAdminOpenShiftClusterStartVM(ctx, r, log) + + adminReply(log, w, nil, nil, err) +} + +func (f *frontend) _postAdminOpenShiftClusterStartVM(ctx context.Context, r *http.Request, log *logrus.Entry) error { + vars := mux.Vars(r) + + vmName := r.URL.Query().Get("vmName") + err := validateAdminVMName(vmName) + if err != nil { + return err + } + + resourceID := strings.TrimPrefix(r.URL.Path, "/admin") + + doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID) + switch { + case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound): + return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"]) + case err != nil: + return err + } + + subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key) + if err != nil { + return err + } + + a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) + if err != nil { + return err + } + + return a.VMStartAndWait(ctx, vmName) +} diff --git a/pkg/frontend/admin_openshiftcluster_startvm_test.go b/pkg/frontend/admin_openshiftcluster_startvm_test.go new file mode 100644 index 00000000000..cc3553bd43e --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_startvm_test.go @@ -0,0 +1,110 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestAdminStartVM(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + mockTenantID := "00000000-0000-0000-0000-000000000000" + + ctx := context.Background() + + type test struct { + name string + resourceID string + fixture func(*testdatabase.Fixture) + vmName string + mocks func(*test, *mock_adminactions.MockAzureActions) + wantStatusCode int + wantResponse []byte + wantError string + } + + for _, tt := range []*test{ + { + name: "basic coverage", + vmName: "aro-worker-australiasoutheast-7tcq7", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + a.EXPECT().VMStartAndWait(gomock.Any(), tt.vmName).Return(nil) + }, + wantStatusCode: http.StatusOK, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions() + defer ti.done() + + a := mock_adminactions.NewMockAzureActions(ti.controller) + tt.mocks(tt, a) + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + return a, nil + }, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodPost, + fmt.Sprintf("https://server/admin%s/startvm?vmName=%s", tt.resourceID, tt.vmName), + nil, nil) + if err != nil { + t.Error(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/admin_openshiftcluster_stopvm.go b/pkg/frontend/admin_openshiftcluster_stopvm.go new file mode 100644 index 00000000000..e2a0139791e --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_stopvm.go @@ -0,0 +1,60 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "net/http" + "path/filepath" + "strings" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +func (f *frontend) postAdminOpenShiftClusterStopVM(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + err := f._postAdminOpenShiftClusterStopVM(ctx, r, log) + + adminReply(log, w, nil, nil, err) +} + +func (f *frontend) _postAdminOpenShiftClusterStopVM(ctx context.Context, r *http.Request, log *logrus.Entry) error { + vars := mux.Vars(r) + + vmName := r.URL.Query().Get("vmName") + err := validateAdminVMName(vmName) + if err != nil { + return err + } + + resourceID := strings.TrimPrefix(r.URL.Path, "/admin") + + doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID) + switch { + case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound): + return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"]) + case err != nil: + return err + } + + subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key) + if err != nil { + return err + } + + a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) + if err != nil { + return err + } + + return a.VMStopAndWait(ctx, vmName) +} diff --git a/pkg/frontend/admin_openshiftcluster_stopvm_test.go b/pkg/frontend/admin_openshiftcluster_stopvm_test.go new file mode 100644 index 00000000000..4f7ac59707a --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_stopvm_test.go @@ -0,0 +1,110 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestAdminStopVM(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + mockTenantID := "00000000-0000-0000-0000-000000000000" + + ctx := context.Background() + + type test struct { + name string + resourceID string + fixture func(*testdatabase.Fixture) + vmName string + mocks func(*test, *mock_adminactions.MockAzureActions) + wantStatusCode int + wantResponse []byte + wantError string + } + + for _, tt := range []*test{ + { + name: "basic coverage", + vmName: "aro-worker-australiasoutheast-7tcq7", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + a.EXPECT().VMStopAndWait(gomock.Any(), tt.vmName).Return(nil) + }, + wantStatusCode: http.StatusOK, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions() + defer ti.done() + + a := mock_adminactions.NewMockAzureActions(ti.controller) + tt.mocks(tt, a) + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + return a, nil + }, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodPost, + fmt.Sprintf("https://server/admin%s/stopvm?vmName=%s", tt.resourceID, tt.vmName), + nil, nil) + if err != nil { + t.Error(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/admin_openshiftcluster_vmresize.go b/pkg/frontend/admin_openshiftcluster_vmresize.go new file mode 100644 index 00000000000..e04a7e08be8 --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_vmresize.go @@ -0,0 +1,111 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "encoding/json" + "net/http" + "path/filepath" + "strings" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kruntime "k8s.io/apimachinery/pkg/runtime" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +func (f *frontend) postAdminOpenShiftClusterVMResize(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + err := f._postAdminOpenShiftClusterVMResize(ctx, r, log) + + adminReply(log, w, nil, nil, err) +} + +func (f *frontend) _postAdminOpenShiftClusterVMResize(ctx context.Context, r *http.Request, log *logrus.Entry) error { + vars := mux.Vars(r) + + vmName := r.URL.Query().Get("vmName") + err := validateAdminVMName(vmName) + if err != nil { + return err + } + + resourceID := strings.TrimPrefix(r.URL.Path, "/admin") + + doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID) + switch { + case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound): + return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", + "The Resource '%s/%s' under resource group '%s' was not found.", + vars["resourceType"], vars["resourceName"], vars["resourceGroupName"]) + case err != nil: + return err + } + + subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key) + if err != nil { + return err + } + + vmSize := r.URL.Query().Get("vmSize") + err = validateAdminVMSize(vmSize) + if err != nil { + return err + } + + a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) + if err != nil { + return err + } + + k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster) + if err != nil { + return err + } + + nodeList, err := k.KubeList(ctx, "node", "") + if err != nil { + return err + } + + var u unstructured.Unstructured + var nodes corev1.NodeList + if err = json.Unmarshal(nodeList, &u); err != nil { + return err + } + + err = kruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &nodes) + if err != nil { + return err + } + + nodeExists := false + for _, node := range nodes.Items { + if _, ok := node.ObjectMeta.Labels["node-role.kubernetes.io/master"]; !ok { + continue + } + + if strings.EqualFold(vmName, node.ObjectMeta.Name) { + nodeExists = true + break + } + } + + if !nodeExists { + return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeNotFound, "", + `"The master node '%s' under resource group '%s' was not found."`, + vmName, vars["resourceGroupName"]) + } + + return a.VMResize(ctx, vmName, vmSize) +} diff --git a/pkg/frontend/admin_openshiftcluster_vmresize_test.go b/pkg/frontend/admin_openshiftcluster_vmresize_test.go new file mode 100644 index 00000000000..97897c6a837 --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_vmresize_test.go @@ -0,0 +1,240 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestAdminVMResize(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + mockTenantID := "00000000-0000-0000-0000-000000000000" + ctx := context.Background() + + type test struct { + name string + resourceID string + vmName string + vmSize string + fixture func(f *testdatabase.Fixture) + mocks func(*test, *mock_adminactions.MockAzureActions, *mock_adminactions.MockKubeActions) + wantStatusCode int + wantResponse []byte + wantError string + } + + for _, tt := range []*test{ + { + name: "basic coverage", + vmName: "aro-fake-node-0", + vmSize: "Standard_D8s_v3", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions, k *mock_adminactions.MockKubeActions) { + node := corev1.Node{ + TypeMeta: metav1.TypeMeta{ + Kind: "node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "aro-fake-node-0", + + Labels: map[string]string{ + "node-role.kubernetes.io/master": "true", + }, + }, + } + nodeList := corev1.NodeList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + }, + Items: []corev1.Node{node}, + } + marsh, _ := json.Marshal(nodeList) + k.EXPECT().KubeList(gomock.Any(), "node", "").Return([]byte(marsh), nil) + a.EXPECT().VMResize(gomock.Any(), tt.vmName, tt.vmSize).Return(nil) + }, + wantStatusCode: http.StatusOK, + }, + { + name: "cluster not found", + vmName: "aro-fake-node-0", + vmSize: "Standard_D8s_v3", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions, k *mock_adminactions.MockKubeActions) {}, + wantStatusCode: http.StatusNotFound, + wantError: `404: ResourceNotFound: : The Resource 'openshiftclusters/resourcename' under resource group 'resourcegroup' was not found.`, + }, + { + name: "subscription doc not found", + vmName: "aro-fake-node-0", + vmSize: "Standard_D8s_v3", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions, k *mock_adminactions.MockKubeActions) {}, + wantStatusCode: http.StatusBadRequest, + wantError: fmt.Sprintf(`400: InvalidSubscriptionState: : Request is not allowed in unregistered subscription '%s'.`, mockSubID), + }, + { + name: "master node not found", + vmName: "aro-fake-node-0", + vmSize: "Standard_D8s_v3", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions, k *mock_adminactions.MockKubeActions) { + node := corev1.Node{ + TypeMeta: metav1.TypeMeta{ + Kind: "node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "aro-fake-node-0", + + Labels: map[string]string{ + "node-role.kubernetes.io/worker": "true", + }, + }, + } + nodeList := corev1.NodeList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + }, + Items: []corev1.Node{node}, + } + marsh, _ := json.Marshal(nodeList) + k.EXPECT().KubeList(gomock.Any(), "node", "").Return([]byte(marsh), nil) + }, + wantStatusCode: http.StatusNotFound, + wantError: `404: NotFound: : "The master node 'aro-fake-node-0' under resource group 'resourcegroup' was not found."`, + }, + { + name: "invalid vmname", + vmName: "%26&ersandvmname", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + vmSize: "Standard_D8s_v3", + mocks: func(tt *test, a *mock_adminactions.MockAzureActions, k *mock_adminactions.MockKubeActions) {}, + wantStatusCode: http.StatusBadRequest, + wantError: `400: InvalidParameter: : The provided vmName '&' is invalid.`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithSubscriptions().WithOpenShiftClusters() + defer ti.done() + + a := mock_adminactions.NewMockAzureActions(ti.controller) + k := mock_adminactions.NewMockKubeActions(ti.controller) + tt.mocks(tt, a, k) + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, + func(e *logrus.Entry, i env.Interface, osc *api.OpenShiftCluster) (adminactions.KubeActions, error) { + return k, nil + }, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + return a, nil + }, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodPost, + fmt.Sprintf("https://server/admin%s/resize?vmName=%s&vmSize=%s", tt.resourceID, tt.vmName, tt.vmSize), + nil, nil) + if err != nil { + t.Fatal(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist.go b/pkg/frontend/admin_openshiftcluster_vmsizelist.go new file mode 100644 index 00000000000..5a712b363f9 --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_vmsizelist.go @@ -0,0 +1,78 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "encoding/json" + "net/http" + "path/filepath" + "strings" + + mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +// /admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/skus +func (f *frontend) getAdminOpenShiftClusterVMResizeOptions(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + b, err := f._getAdminOpenShiftClusterVMResizeOptions(ctx, r, log) + + adminReply(log, w, nil, b, err) +} + +func (f *frontend) _getAdminOpenShiftClusterVMResizeOptions(ctx context.Context, r *http.Request, log *logrus.Entry) ([]byte, error) { + vars := mux.Vars(r) + + resourceID := strings.TrimPrefix(r.URL.Path, "/admin") + + doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID) + switch { + case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound): + return nil, api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", + "The Resource '%s/%s' under resource group '%s' was not found.", + vars["resourceType"], vars["resourceName"], vars["resourceGroupName"]) + case err != nil: + return nil, err + } + + subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key) + if err != nil { + return nil, err + } + + a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) + if err != nil { + return nil, err + } + + skus, err := a.VMSizeList(ctx) + if err != nil { + return nil, err + } + + return json.Marshal(f.filterVMSkus(skus)) +} + +func (f *frontend) filterVMSkus(skus []mgmtcompute.ResourceSku) []string { + filteredSkus := []string{} + + for _, sku := range skus { + if sku.Restrictions != nil && len(*sku.Restrictions) == 0 { + if sku.Name != nil { + filteredSkus = append(filteredSkus, *sku.Name) + } + } + } + + return filteredSkus +} diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go new file mode 100644 index 00000000000..4347e3436fb --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go @@ -0,0 +1,162 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + + mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" + "github.com/Azure/go-autorest/autorest/to" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestAdminListVMSizeList(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + mockTenantID := "00000000-0000-0000-0000-000000000000" + ctx := context.Background() + + type test struct { + name string + resourceID string + fixture func(f *testdatabase.Fixture) + mocks func(*test, *mock_adminactions.MockAzureActions) + wantStatusCode int + wantResponse []byte + wantError string + } + + for _, tt := range []*test{ + { + name: "happy path", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + a.EXPECT(). + VMSizeList(gomock.Any()). + Return([]mgmtcompute.ResourceSku{ + { + Name: to.StringPtr("Standard_D8s_v90"), + Restrictions: &[]mgmtcompute.ResourceSkuRestrictions{ + { + Type: mgmtcompute.Location, + }, + }, + }, + { + Name: to.StringPtr("Standard_D8s_v9001"), + Restrictions: &[]mgmtcompute.ResourceSkuRestrictions{}, + }, + }, nil) + }, + wantStatusCode: http.StatusOK, + wantResponse: []byte(`["Standard_D8s_v9001"]` + "\n"), + }, + { + name: "cluster not found", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions) {}, + wantStatusCode: http.StatusNotFound, + wantError: `404: ResourceNotFound: : The Resource 'openshiftclusters/resourcename' under resource group 'resourcegroup' was not found.`, + }, + { + name: "subscription doc not found", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + }, + mocks: func(tt *test, a *mock_adminactions.MockAzureActions) {}, + wantStatusCode: http.StatusBadRequest, + wantError: fmt.Sprintf(`400: InvalidSubscriptionState: : Request is not allowed in unregistered subscription '%s'.`, mockSubID), + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithSubscriptions().WithOpenShiftClusters() + defer ti.done() + + a := mock_adminactions.NewMockAzureActions(ti.controller) + tt.mocks(tt, a) + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + return a, nil + }, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodGet, + fmt.Sprintf("https://server/admin%s/skus", tt.resourceID), + nil, nil) + if err != nil { + t.Fatal(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go index d0e9bf73783..e9ba404c91d 100644 --- a/pkg/frontend/adminactions/azureactions.go +++ b/pkg/frontend/adminactions/azureactions.go @@ -5,8 +5,10 @@ package adminactions import ( "context" + "fmt" "net/http" + mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" "github.com/sirupsen/logrus" "github.com/Azure/ARO-RP/pkg/api" @@ -23,6 +25,10 @@ type AzureActions interface { ResourcesList(ctx context.Context) ([]byte, error) NICReconcileFailedState(ctx context.Context, nicName string) error VMRedeployAndWait(ctx context.Context, vmName string) error + VMStartAndWait(ctx context.Context, vmName string) error + VMStopAndWait(ctx context.Context, vmName string) error + VMSizeList(ctx context.Context) ([]mgmtcompute.ResourceSku, error) + VMResize(ctx context.Context, vmName string, vmSize string) error VMSerialConsole(ctx context.Context, w http.ResponseWriter, log *logrus.Entry, vmName string) error } @@ -32,6 +38,7 @@ type azureActions struct { oc *api.OpenShiftCluster resources features.ResourcesClient + resourceSkus compute.ResourceSkusClient virtualMachines compute.VirtualMachinesClient virtualNetworks network.VirtualNetworksClient diskEncryptionSets compute.DiskEncryptionSetsClient @@ -55,6 +62,7 @@ func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClus oc: oc, resources: features.NewResourcesClient(env.Environment(), subscriptionDoc.ID, fpAuth), + resourceSkus: compute.NewResourceSkusClient(env.Environment(), subscriptionDoc.ID, fpAuth), virtualMachines: compute.NewVirtualMachinesClient(env.Environment(), subscriptionDoc.ID, fpAuth), virtualNetworks: network.NewVirtualNetworksClient(env.Environment(), subscriptionDoc.ID, fpAuth), diskEncryptionSets: compute.NewDiskEncryptionSetsClient(env.Environment(), subscriptionDoc.ID, fpAuth), @@ -68,3 +76,29 @@ func (a *azureActions) VMRedeployAndWait(ctx context.Context, vmName string) err clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') return a.virtualMachines.RedeployAndWait(ctx, clusterRGName, vmName) } + +func (a *azureActions) VMStartAndWait(ctx context.Context, vmName string) error { + clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') + return a.virtualMachines.StartAndWait(ctx, clusterRGName, vmName) +} + +func (a *azureActions) VMStopAndWait(ctx context.Context, vmName string) error { + clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') + return a.virtualMachines.StopAndWait(ctx, clusterRGName, vmName) +} + +func (a *azureActions) VMSizeList(ctx context.Context) ([]mgmtcompute.ResourceSku, error) { + filter := fmt.Sprintf("location eq '%s'", a.env.Location()) + return a.resourceSkus.List(ctx, filter) +} + +func (a *azureActions) VMResize(ctx context.Context, vmName string, size string) error { + clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') + vm, err := a.virtualMachines.Get(ctx, clusterRGName, vmName, mgmtcompute.InstanceView) + if err != nil { + return err + } + + vm.HardwareProfile.VMSize = mgmtcompute.VirtualMachineSizeTypes(size) + return a.virtualMachines.CreateOrUpdateAndWait(ctx, clusterRGName, vmName, vm) +} diff --git a/pkg/frontend/adminactions/kubeactions.go b/pkg/frontend/adminactions/kubeactions.go index b0f51bc7c56..a7cef211b7a 100644 --- a/pkg/frontend/adminactions/kubeactions.go +++ b/pkg/frontend/adminactions/kubeactions.go @@ -13,6 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" "github.com/Azure/ARO-RP/pkg/api" "github.com/Azure/ARO-RP/pkg/env" @@ -35,8 +36,9 @@ type kubeActions struct { gvrResolver dynamichelper.GVRResolver - dyn dynamic.Interface - configcli configclient.Interface + kubernetescli kubernetes.Interface + dyn dynamic.Interface + configcli configclient.Interface } // NewKubeActions returns a kubeActions @@ -56,6 +58,11 @@ func NewKubeActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClust return nil, err } + kubernetescli, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + configcli, err := configclient.NewForConfig(restConfig) if err != nil { return nil, err @@ -67,8 +74,9 @@ func NewKubeActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClust gvrResolver: gvrResolver, - dyn: dyn, - configcli: configcli, + kubernetescli: kubernetescli, + dyn: dyn, + configcli: configcli, }, nil } diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index e020eb84a50..29d24f96261 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -244,6 +244,18 @@ func (f *frontend) authenticatedRoutes(r *mux.Router) { s.Methods(http.MethodPost).HandlerFunc(f.postAdminOpenShiftClusterRedeployVM).Name("postAdminOpenShiftClusterRedeployVM") + s = r. + Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/stopvm"). + Subrouter() + + s.Methods(http.MethodPost).HandlerFunc(f.postAdminOpenShiftClusterStopVM).Name("postAdminOpenShiftClusterStopVM") + + s = r. + Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/startvm"). + Subrouter() + + s.Methods(http.MethodPost).HandlerFunc(f.postAdminOpenShiftClusterStartVM).Name("postAdminOpenShiftClusterStartVM") + s = r. Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/upgrade"). Subrouter() @@ -256,6 +268,18 @@ func (f *frontend) authenticatedRoutes(r *mux.Router) { s.Methods(http.MethodGet).HandlerFunc(f.getAdminOpenShiftClusters).Name("getAdminOpenShiftClusters") + s = r. + Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/skus"). + Subrouter() + + s.Methods(http.MethodGet).HandlerFunc(f.getAdminOpenShiftClusterVMResizeOptions).Name("getAdminOpenShiftClusterVMResizeOptions") + + s = r. + Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/resize"). + Subrouter() + + s.Methods(http.MethodPost).HandlerFunc(f.postAdminOpenShiftClusterVMResize).Name("postAdminOpenShiftClusterVMResize") + s = r. Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/reconcilefailednic"). Subrouter() diff --git a/pkg/frontend/validate.go b/pkg/frontend/validate.go index 71c4defb808..3f63af9da77 100644 --- a/pkg/frontend/validate.go +++ b/pkg/frontend/validate.go @@ -127,6 +127,12 @@ func validateNetworkInterfaceName(nicName string) error { if nicName == "" || !rxNetworkInterfaceName.MatchString(nicName) { return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "", "The provided nicName '%s' is invalid.", nicName) } + return nil +} +func validateAdminVMSize(vmSize string) error { + if vmSize == "" { + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "", "The provided vmSize '%s' is invalid.", vmSize) + } return nil } diff --git a/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go b/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go index 8617d2cc3eb..5b61138607b 100644 --- a/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go +++ b/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go @@ -7,6 +7,7 @@ import ( "context" mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" + "github.com/Azure/go-autorest/autorest/to" ) // VirtualMachinesClientAddons contains addons for VirtualMachinesClient @@ -15,6 +16,7 @@ type VirtualMachinesClientAddons interface { DeleteAndWait(ctx context.Context, resourceGroupName string, VMName string, forceDeletion *bool) error RedeployAndWait(ctx context.Context, resourceGroupName string, VMName string) error StartAndWait(ctx context.Context, resourceGroupName string, VMName string) error + StopAndWait(ctx context.Context, resourceGroupName string, VMName string) error List(ctx context.Context, resourceGroupName string) (result []mgmtcompute.VirtualMachine, err error) } @@ -54,6 +56,15 @@ func (c *virtualMachinesClient) StartAndWait(ctx context.Context, resourceGroupN return future.WaitForCompletionRef(ctx, c.Client) } +func (c *virtualMachinesClient) StopAndWait(ctx context.Context, resourceGroupName string, VMName string) error { + future, err := c.PowerOff(ctx, resourceGroupName, VMName, to.BoolPtr(false)) + if err != nil { + return err + } + + return future.WaitForCompletionRef(ctx, c.Client) +} + func (c *virtualMachinesClient) List(ctx context.Context, resourceGroupName string) (result []mgmtcompute.VirtualMachine, err error) { page, err := c.VirtualMachinesClient.List(ctx, resourceGroupName) if err != nil { diff --git a/pkg/util/mocks/adminactions/adminactions.go b/pkg/util/mocks/adminactions/adminactions.go index f8c83279f52..21501dc6a85 100644 --- a/pkg/util/mocks/adminactions/adminactions.go +++ b/pkg/util/mocks/adminactions/adminactions.go @@ -9,6 +9,7 @@ import ( http "net/http" reflect "reflect" + compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" gomock "github.com/golang/mock/gomock" logrus "github.com/sirupsen/logrus" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -175,6 +176,20 @@ func (mr *MockAzureActionsMockRecorder) VMRedeployAndWait(arg0, arg1 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMRedeployAndWait", reflect.TypeOf((*MockAzureActions)(nil).VMRedeployAndWait), arg0, arg1) } +// VMResize mocks base method. +func (m *MockAzureActions) VMResize(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VMResize", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// VMResize indicates an expected call of VMResize. +func (mr *MockAzureActionsMockRecorder) VMResize(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMResize", reflect.TypeOf((*MockAzureActions)(nil).VMResize), arg0, arg1, arg2) +} + // VMSerialConsole mocks base method. func (m *MockAzureActions) VMSerialConsole(arg0 context.Context, arg1 http.ResponseWriter, arg2 *logrus.Entry, arg3 string) error { m.ctrl.T.Helper() @@ -188,3 +203,46 @@ func (mr *MockAzureActionsMockRecorder) VMSerialConsole(arg0, arg1, arg2, arg3 i mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMSerialConsole", reflect.TypeOf((*MockAzureActions)(nil).VMSerialConsole), arg0, arg1, arg2, arg3) } + +// VMSizeList mocks base method. +func (m *MockAzureActions) VMSizeList(arg0 context.Context) ([]compute.ResourceSku, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VMSizeList", arg0) + ret0, _ := ret[0].([]compute.ResourceSku) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VMSizeList indicates an expected call of VMSizeList. +func (mr *MockAzureActionsMockRecorder) VMSizeList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMSizeList", reflect.TypeOf((*MockAzureActions)(nil).VMSizeList), arg0) +} + +// VMStartAndWait mocks base method. +func (m *MockAzureActions) VMStartAndWait(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VMStartAndWait", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// VMStartAndWait indicates an expected call of VMStartAndWait. +func (mr *MockAzureActionsMockRecorder) VMStartAndWait(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMStartAndWait", reflect.TypeOf((*MockAzureActions)(nil).VMStartAndWait), arg0, arg1) +} + +// VMStopAndWait mocks base method. +func (m *MockAzureActions) VMStopAndWait(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VMStopAndWait", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// VMStopAndWait indicates an expected call of VMStopAndWait. +func (mr *MockAzureActionsMockRecorder) VMStopAndWait(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMStopAndWait", reflect.TypeOf((*MockAzureActions)(nil).VMStopAndWait), arg0, arg1) +} diff --git a/pkg/util/mocks/azureclient/mgmt/compute/compute.go b/pkg/util/mocks/azureclient/mgmt/compute/compute.go index f06be48d1f8..2992076136a 100644 --- a/pkg/util/mocks/azureclient/mgmt/compute/compute.go +++ b/pkg/util/mocks/azureclient/mgmt/compute/compute.go @@ -211,6 +211,20 @@ func (mr *MockVirtualMachinesClientMockRecorder) StartAndWait(arg0, arg1, arg2 i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartAndWait", reflect.TypeOf((*MockVirtualMachinesClient)(nil).StartAndWait), arg0, arg1, arg2) } +// StopAndWait mocks base method. +func (m *MockVirtualMachinesClient) StopAndWait(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StopAndWait", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// StopAndWait indicates an expected call of StopAndWait. +func (mr *MockVirtualMachinesClientMockRecorder) StopAndWait(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopAndWait", reflect.TypeOf((*MockVirtualMachinesClient)(nil).StopAndWait), arg0, arg1, arg2) +} + // MockUsageClient is a mock of UsageClient interface. type MockUsageClient struct { ctrl *gomock.Controller