From 31af73410380a0324e0ce8faabe089e1c719a859 Mon Sep 17 00:00:00 2001 From: Amber Brown Date: Mon, 15 Jul 2024 16:32:23 +1000 Subject: [PATCH] [ARO-5368] Try and log the VM info + console log on failure (#3629) Dumps the VM info + console logs on failure so that we don't need to run the Geneva Action or have the control plane still around to get it. Also refactors frontend and geneva action to make use of the same code path. --- cmd/aro/rp.go | 2 +- pkg/cluster/failurediagnostics/diagnostics.go | 34 ++++ .../failurediagnostics/virtualmachines.go | 72 ++++++++ .../virtualmachines_test.go | 168 ++++++++++++++++++ pkg/cluster/gatherlogs.go | 45 +++-- pkg/cluster/graph/manager_test.go | 25 +-- pkg/cluster/install_test.go | 8 + .../admin_hive_clusterdeployment_get_test.go | 4 +- .../admin_openshiftcluster_approvecsr_test.go | 2 +- .../admin_openshiftcluster_cordonnode_test.go | 2 +- ...hiftcluster_delete_managedresource_test.go | 2 +- .../admin_openshiftcluster_drainnode_test.go | 2 +- ...nshiftcluster_etcdcertificaterenew_test.go | 2 + ...dmin_openshiftcluster_etcdrecovery_test.go | 1 + ...openshiftcluster_kubernetesobjects_test.go | 4 +- ...enshiftcluster_kubernetespods_logs_test.go | 2 +- .../admin_openshiftcluster_list_test.go | 2 +- .../admin_openshiftcluster_redeployvm_test.go | 2 +- ...in_openshiftcluster_resources_list_test.go | 2 +- .../admin_openshiftcluster_serialconsole.go | 15 +- .../admin_openshiftcluster_startvm_test.go | 2 +- .../admin_openshiftcluster_stopvm_test.go | 2 +- .../admin_openshiftcluster_vmresize_test.go | 2 +- .../admin_openshiftcluster_vmsizelist_test.go | 2 +- .../admin_openshiftversion_list_test.go | 2 +- .../admin_openshiftversion_put_test.go | 2 +- ...atformworkloadidentityroleset_list_test.go | 2 +- ...latformworkloadidentityroleset_put_test.go | 2 +- pkg/frontend/adminactions/applens.go | 68 +++++++ pkg/frontend/adminactions/azureactions.go | 38 +--- pkg/frontend/adminactions/generate.go | 2 +- pkg/frontend/adminactions/vmserialconsole.go | 68 +------ .../adminactions/vmserialconsole_test.go | 82 +++++++++ pkg/frontend/asyncoperationresult_get_test.go | 2 +- .../asyncoperationsstatus_get_test.go | 2 +- pkg/frontend/clustermanager_delete_test.go | 2 +- pkg/frontend/clustermanager_get_test.go | 2 +- .../clustermanager_putorpatch_test.go | 2 +- pkg/frontend/fixetcd_test.go | 1 + pkg/frontend/frontend.go | 10 +- .../openshiftcluster_applensdetectors.go | 8 +- .../openshiftcluster_applensdetectors_test.go | 10 +- pkg/frontend/openshiftcluster_delete_test.go | 2 +- pkg/frontend/openshiftcluster_get_test.go | 2 +- pkg/frontend/openshiftcluster_list_test.go | 2 +- ...enshiftcluster_preflightvalidation_test.go | 2 +- .../openshiftcluster_putorpatch_test.go | 6 +- .../openshiftclustercredentials_post_test.go | 2 +- ...tclusterkubeconfigcredentials_post_test.go | 2 +- pkg/frontend/openshiftversions_list_test.go | 2 +- ...tformworkloadidentityrolesets_list_test.go | 2 +- pkg/frontend/security_test.go | 2 +- pkg/frontend/subscriptions_put_test.go | 2 +- .../mgmt/compute/virtualmachines_addons.go | 50 ++++++ pkg/util/mocks/adminactions/adminactions.go | 88 +++++---- .../mocks/azureclient/mgmt/compute/compute.go | 15 ++ pkg/util/mocks/storage/storage.go | 65 ++++++- pkg/util/storage/generate.go | 2 +- pkg/util/storage/manager.go | 30 +++- test/e2e/adminapi_serialconsole.go | 51 ++++++ 60 files changed, 806 insertions(+), 230 deletions(-) create mode 100644 pkg/cluster/failurediagnostics/diagnostics.go create mode 100644 pkg/cluster/failurediagnostics/virtualmachines.go create mode 100644 pkg/cluster/failurediagnostics/virtualmachines_test.go create mode 100644 pkg/frontend/adminactions/applens.go create mode 100644 pkg/frontend/adminactions/vmserialconsole_test.go create mode 100644 test/e2e/adminapi_serialconsole.go diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go index d27483aac6a..3eb9a14c3c1 100644 --- a/cmd/aro/rp.go +++ b/cmd/aro/rp.go @@ -182,7 +182,7 @@ func rp(ctx context.Context, log, audit *logrus.Entry) error { if err != nil { return err } - f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, dbPlatformWorkloadIdentityRoleSets, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, clusterdata.NewParallelEnricher(metrics, _env)) + f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, dbPlatformWorkloadIdentityRoleSets, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, adminactions.NewAppLensActions, clusterdata.NewParallelEnricher(metrics, _env)) if err != nil { return err } diff --git a/pkg/cluster/failurediagnostics/diagnostics.go b/pkg/cluster/failurediagnostics/diagnostics.go new file mode 100644 index 00000000000..c38d58effa8 --- /dev/null +++ b/pkg/cluster/failurediagnostics/diagnostics.go @@ -0,0 +1,34 @@ +package failurediagnostics + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute" +) + +type manager struct { + log *logrus.Entry + env env.Interface + doc *api.OpenShiftClusterDocument + + virtualMachines compute.VirtualMachinesClient +} + +func NewFailureDiagnostics(log *logrus.Entry, _env env.Interface, + doc *api.OpenShiftClusterDocument, + + virtualMachines compute.VirtualMachinesClient, + +) *manager { + return &manager{ + log: log, + env: _env, + doc: doc, + virtualMachines: virtualMachines, + } +} diff --git a/pkg/cluster/failurediagnostics/virtualmachines.go b/pkg/cluster/failurediagnostics/virtualmachines.go new file mode 100644 index 00000000000..f69e4a73eb7 --- /dev/null +++ b/pkg/cluster/failurediagnostics/virtualmachines.go @@ -0,0 +1,72 @@ +package failurediagnostics + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "bufio" + "bytes" + "context" + "fmt" + + "github.com/Azure/ARO-RP/pkg/util/stringutils" +) + +// LogVMSerialConsole fetches the serial console from VMs and logs them with +// the associated VM name. +func (m *manager) LogVMSerialConsole(ctx context.Context) (interface{}, error) { + items := make([]interface{}, 0) + + if m.virtualMachines == nil { + items = append(items, "vmclient missing") + return items, nil + } + + resourceGroupName := stringutils.LastTokenByte(m.doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID, '/') + vms, err := m.virtualMachines.List(ctx, resourceGroupName) + if err != nil { + items = append(items, fmt.Sprintf("vm listing error: %s", err)) + return items, nil + } + + if len(vms) == 0 { + items = append(items, "no VMs found") + return items, nil + } + + vmNames := make([]string, 0) + for _, v := range vms { + j, err := v.MarshalJSON() + if err != nil { + items = append(items, fmt.Sprintf("vm marshalling error: %s", err)) + } else { + vmName := "" + if v.Name != nil { + vmName = *v.Name + vmNames = append(vmNames, vmName) + } + items = append(items, fmt.Sprintf("vm %s: %s", vmName, string(j))) + } + } + + // Fetch boot diagnostics URIs for the VMs + for _, vmName := range vmNames { + blob := &bytes.Buffer{} + err := m.virtualMachines.GetSerialConsoleForVM(ctx, resourceGroupName, vmName, blob) + if err != nil { + items = append(items, fmt.Sprintf("vm boot diagnostics retrieval error for %s: %s", vmName, err)) + continue + } + + logForVM := m.log.WithField("failedRoleInstance", vmName) + scanner := bufio.NewScanner(blob) + for scanner.Scan() { + logForVM.Info(scanner.Text()) + } + if err := scanner.Err(); err != nil { + items = append(items, fmt.Sprintf("blob storage scan on %s: %s", vmName, err)) + } + } + + return items, nil +} diff --git a/pkg/cluster/failurediagnostics/virtualmachines_test.go b/pkg/cluster/failurediagnostics/virtualmachines_test.go new file mode 100644 index 00000000000..00f92595bec --- /dev/null +++ b/pkg/cluster/failurediagnostics/virtualmachines_test.go @@ -0,0 +1,168 @@ +package failurediagnostics + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "bytes" + "context" + "errors" + "io" + "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/go-test/deep" + "github.com/golang/mock/gomock" + "github.com/onsi/gomega" + "github.com/onsi/gomega/types" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + mock_compute "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/compute" + testlog "github.com/Azure/ARO-RP/test/util/log" +) + +func TestVirtualMachinesSerialConsole(t *testing.T) { + const ( + key = "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName1" + clusterProfile = "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroupCluster" + ) + + oc := &api.OpenShiftClusterDocument{ + Key: strings.ToLower(key), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: key, + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: clusterProfile, + }, + StorageSuffix: "PrefixHere", + }, + }, + } + + for _, tt := range []struct { + name string + expectedOutput interface{} + mock func(vmClient *mock_compute.MockVirtualMachinesClient) + expectedLogs []map[string]types.GomegaMatcher + }{ + { + name: "failure to fetch VMs", + mock: func(vmClient *mock_compute.MockVirtualMachinesClient) { + vmClient.EXPECT().List(gomock.Any(), "resourceGroupCluster").Return(nil, errors.New("vm explod")) + }, + expectedLogs: []map[string]types.GomegaMatcher{}, + expectedOutput: []interface{}{ + "vm listing error: vm explod", + }, + }, + { + name: "no VMs returned", + mock: func(vmClient *mock_compute.MockVirtualMachinesClient) { + vmClient.EXPECT().List(gomock.Any(), "resourceGroupCluster").Return([]mgmtcompute.VirtualMachine{}, nil) + }, + expectedLogs: []map[string]types.GomegaMatcher{}, + expectedOutput: []interface{}{ + "no VMs found", + }, + }, + { + name: "failure to get VM serial console", + mock: func(vmClient *mock_compute.MockVirtualMachinesClient) { + vmClient.EXPECT().List(gomock.Any(), "resourceGroupCluster").Return([]mgmtcompute.VirtualMachine{ + { + Name: to.StringPtr("somename"), + Location: to.StringPtr("eastus"), + VirtualMachineProperties: &mgmtcompute.VirtualMachineProperties{ + InstanceView: &mgmtcompute.VirtualMachineInstanceView{ + BootDiagnostics: &mgmtcompute.BootDiagnosticsInstanceView{ + SerialConsoleLogBlobURI: to.StringPtr("bogusurl"), + }, + }, + }, + }, + }, nil) + + vmClient.EXPECT().GetSerialConsoleForVM( + gomock.Any(), "resourceGroupCluster", "somename", gomock.Any(), + ).Times(1).Return(errors.New("explod")) + }, + expectedLogs: []map[string]types.GomegaMatcher{}, + expectedOutput: []interface{}{ + `vm somename: {"location":"eastus","properties":{}}`, + "vm boot diagnostics retrieval error for somename: explod", + }, + }, + { + name: "success", + mock: func(vmClient *mock_compute.MockVirtualMachinesClient) { + vmClient.EXPECT().List(gomock.Any(), "resourceGroupCluster").Return([]mgmtcompute.VirtualMachine{ + { + Name: to.StringPtr("somename"), + Location: to.StringPtr("eastus"), + VirtualMachineProperties: &mgmtcompute.VirtualMachineProperties{}, + }, + }, nil) + + iothing := bytes.NewBufferString("hello\nthere :)") + vmClient.EXPECT().GetSerialConsoleForVM( + gomock.Any(), "resourceGroupCluster", "somename", gomock.Any(), + ).Times(1).DoAndReturn(func(ctx context.Context, + rg string, vmName string, target io.Writer) error { + _, err := io.Copy(target, iothing) + return err + }) + }, + expectedLogs: []map[string]types.GomegaMatcher{ + { + "level": gomega.Equal(logrus.InfoLevel), + "msg": gomega.Equal(`hello`), + "failedRoleInstance": gomega.Equal("somename"), + }, + { + "level": gomega.Equal(logrus.InfoLevel), + "msg": gomega.Equal(`there :)`), + "failedRoleInstance": gomega.Equal("somename"), + }, + }, + expectedOutput: []interface{}{ + `vm somename: {"location":"eastus","properties":{}}`, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + hook, entry := testlog.New() + + controller := gomock.NewController(t) + defer controller.Finish() + + vmClient := mock_compute.NewMockVirtualMachinesClient(controller) + + tt.mock(vmClient) + + d := &manager{ + log: entry, + doc: oc, + virtualMachines: vmClient, + } + + out, err := d.LogVMSerialConsole(ctx) + if err != nil { + t.Errorf("returned %s, should never return an error", err) + } + + err = testlog.AssertLoggingOutput(hook, tt.expectedLogs) + if err != nil { + t.Error(err) + } + + for _, e := range deep.Equal(out, tt.expectedOutput) { + t.Error(e) + } + }) + } +} diff --git a/pkg/cluster/gatherlogs.go b/pkg/cluster/gatherlogs.go index 86afc11adb8..31633ce8dad 100644 --- a/pkg/cluster/gatherlogs.go +++ b/pkg/cluster/gatherlogs.go @@ -9,29 +9,50 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/Azure/ARO-RP/pkg/cluster/failurediagnostics" "github.com/Azure/ARO-RP/pkg/util/steps" ) +type diagnosticStep struct { + f func(context.Context) (interface{}, error) + isJSON bool +} + func (m *manager) gatherFailureLogs(ctx context.Context) { - for _, f := range []func(context.Context) (interface{}, error){ - m.logClusterVersion, - m.logNodes, - m.logClusterOperators, - m.logIngressControllers, + d := failurediagnostics.NewFailureDiagnostics(m.log, m.env, m.doc, m.virtualMachines) + + for _, f := range []diagnosticStep{ + {f: m.logClusterVersion, isJSON: true}, + {f: m.logNodes, isJSON: true}, + {f: m.logClusterOperators, isJSON: true}, + {f: m.logIngressControllers, isJSON: true}, + {f: d.LogVMSerialConsole, isJSON: false}, } { - o, err := f(ctx) + o, err := f.f(ctx) if err != nil { m.log.Error(err) continue } - b, err := json.MarshalIndent(o, "", " ") - if err != nil { - m.log.Error(err) - continue + if f.isJSON { + b, err := json.MarshalIndent(o, "", " ") + if err != nil { + m.log.Error(err) + continue + } + + m.log.Printf("%s: %s", steps.FriendlyName(f.f), string(b)) + } else { + entries, ok := o.([]interface{}) + name := steps.FriendlyName(f.f) + if ok { + for _, i := range entries { + m.log.Printf("%s: %v", name, i) + } + } else { + m.log.Printf("%s: %v", steps.FriendlyName(f.f), o) + } } - - m.log.Printf("%s: %s", steps.FriendlyName(f), string(b)) } } diff --git a/pkg/cluster/graph/manager_test.go b/pkg/cluster/graph/manager_test.go index 491aa7087da..edcb0622b16 100644 --- a/pkg/cluster/graph/manager_test.go +++ b/pkg/cluster/graph/manager_test.go @@ -8,7 +8,6 @@ import ( "errors" "testing" - azstorage "github.com/Azure/azure-sdk-for-go/storage" "github.com/golang/mock/gomock" "github.com/sirupsen/logrus" @@ -26,24 +25,17 @@ func TestLoadPersisted(t *testing.T) { mocks func(*mock_storage.MockManager, *mock_env.MockInterface, *mock_keyvault.MockManager) wantErr string }{ - { - name: "get a general error as azstorage not mocked", - mocks: func(storage *mock_storage.MockManager, env *mock_env.MockInterface, kv *mock_keyvault.MockManager) { - storage.EXPECT().BlobService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&azstorage.BlobStorageClient{}, nil) - }, - wantErr: " authentication is not supported yet", - }, { name: "loadPersisted returns an error other than the chacha20poly1305 one", mocks: func(storage *mock_storage.MockManager, env *mock_env.MockInterface, kv *mock_keyvault.MockManager) { - storage.EXPECT().BlobService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&azstorage.BlobStorageClient{}, errors.New("general error")) + storage.EXPECT().BlobService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("general error")) }, wantErr: "general error", }, { name: "loadPersisted returns a chacha20poly1305 error", mocks: func(storage *mock_storage.MockManager, env *mock_env.MockInterface, kv *mock_keyvault.MockManager) { - storage.EXPECT().BlobService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&azstorage.BlobStorageClient{}, errors.New("chacha20poly1305: message authentication failed")) + storage.EXPECT().BlobService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("chacha20poly1305: message authentication failed")) env.EXPECT().ServiceKeyvault().Return(kv) kv.EXPECT().GetBase64Secret(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("random error")) }, @@ -51,18 +43,13 @@ func TestLoadPersisted(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - env_ctrl := gomock.NewController(t) - defer env_ctrl.Finish() - storage_ctrl := gomock.NewController(t) - defer storage_ctrl.Finish() - kv_ctrl := gomock.NewController(t) - defer kv_ctrl.Finish() + ctrl := gomock.NewController(t) rg := "test-rg" account := "TEST-ACCOUNT" - env := mock_env.NewMockInterface(env_ctrl) - storage := mock_storage.NewMockManager(storage_ctrl) - kv := mock_keyvault.NewMockManager(kv_ctrl) + env := mock_env.NewMockInterface(ctrl) + storage := mock_storage.NewMockManager(ctrl) + kv := mock_keyvault.NewMockManager(ctrl) tt.mocks(storage, env, kv) diff --git a/pkg/cluster/install_test.go b/pkg/cluster/install_test.go index 600cd556189..27794c3924f 100644 --- a/pkg/cluster/install_test.go +++ b/pkg/cluster/install_test.go @@ -123,6 +123,10 @@ func TestStepRunnerWithInstaller(t *testing.T) { "level": gomega.Equal(logrus.InfoLevel), "msg": gomega.MatchRegexp(`(?s)pkg/cluster.\(\*manager\).logIngressControllers:.*"name": "ingress-controller"`), }, + { + "level": gomega.Equal(logrus.InfoLevel), + "msg": gomega.Equal(`pkg/cluster/failurediagnostics.(*manager).LogVMSerialConsole: vmclient missing`), + }, }, kubernetescli: fake.NewSimpleClientset(node), configcli: configfake.NewSimpleClientset(clusterVersion, clusterOperator), @@ -159,6 +163,10 @@ func TestStepRunnerWithInstaller(t *testing.T) { "level": gomega.Equal(logrus.InfoLevel), "msg": gomega.Equal(`pkg/cluster.(*manager).logIngressControllers: null`), }, + { + "level": gomega.Equal(logrus.InfoLevel), + "msg": gomega.Equal(`pkg/cluster/failurediagnostics.(*manager).LogVMSerialConsole: vmclient missing`), + }, }, kubernetescli: fake.NewSimpleClientset(), configcli: configfake.NewSimpleClientset(), diff --git a/pkg/frontend/admin_hive_clusterdeployment_get_test.go b/pkg/frontend/admin_hive_clusterdeployment_get_test.go index 4e40e4516d8..2afcadebc8d 100644 --- a/pkg/frontend/admin_hive_clusterdeployment_get_test.go +++ b/pkg/frontend/admin_hive_clusterdeployment_get_test.go @@ -90,10 +90,10 @@ func Test_getAdminHiveClusterDeployment(t *testing.T) { clusterManager := mock_hive.NewMockClusterManager(controller) clusterManager.EXPECT().GetClusterDeployment(gomock.Any(), gomock.Any()).Return(&clusterDeployment, nil).Times(tt.expectedGetClusterDeploymentCallCount) f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil) + ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil, nil) } else { f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) } if err != nil { diff --git a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go index 53b76de1753..d5db5fd7e5c 100644 --- a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go +++ b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go @@ -93,7 +93,7 @@ func TestAdminApproveCSR(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go index 52097ef559c..031d632665b 100644 --- a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go @@ -154,7 +154,7 @@ func TestAdminCordonUncordonNode(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go index 3fd49d5b22a..47f29d16000 100644 --- a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go +++ b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go @@ -115,7 +115,7 @@ func TestAdminDeleteManagedResource(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_drainnode_test.go b/pkg/frontend/admin_openshiftcluster_drainnode_test.go index c23a6b968b8..7d7fdd6b47e 100644 --- a/pkg/frontend/admin_openshiftcluster_drainnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_drainnode_test.go @@ -86,7 +86,7 @@ func TestAdminDrainNode(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go index ab58f9615cd..8ddf335cf01 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go @@ -546,6 +546,7 @@ func TestAdminEtcdCertificateRenew(t *testing.T) { return k, nil }, nil, + nil, nil) if err != nil { t.Fatal(err) @@ -764,6 +765,7 @@ func TestAdminEtcdCertificateRecovery(t *testing.T) { return k, nil }, nil, + nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go index 2e15ccb7e35..2d70d27c727 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go @@ -175,6 +175,7 @@ func TestAdminEtcdRecovery(t *testing.T) { nil, kubeActionsFactory, nil, + nil, ti.enricher) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go index b0b2e06bd75..4481edd1ed9 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go @@ -264,7 +264,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -413,7 +413,7 @@ func TestAdminPostKubernetesObjects(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go index 8dd8c53b563..52b1f44aafa 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go @@ -127,7 +127,7 @@ func TestAdminKubernetesGetPodLogs(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go index 27922d2a433..8608fda37d6 100644 --- a/pkg/frontend/admin_openshiftcluster_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_list_test.go @@ -124,7 +124,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.throwsError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go index 818dcea16e7..2ce909efdf8 100644 --- a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go @@ -86,7 +86,7 @@ func TestAdminRedeployVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_resources_list_test.go b/pkg/frontend/admin_openshiftcluster_resources_list_test.go index bb56f0bebd3..14a21400bf2 100644 --- a/pkg/frontend/admin_openshiftcluster_resources_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_resources_list_test.go @@ -95,7 +95,7 @@ func TestAdminListResourcesList(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) mockResponder := mock_frontend.NewMockStreamResponder(ti.controller) mockResponder.EXPECT().AdminReplyStream(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) f.streamResponder = mockResponder diff --git a/pkg/frontend/admin_openshiftcluster_serialconsole.go b/pkg/frontend/admin_openshiftcluster_serialconsole.go index f109b5b1848..2046dff71ed 100644 --- a/pkg/frontend/admin_openshiftcluster_serialconsole.go +++ b/pkg/frontend/admin_openshiftcluster_serialconsole.go @@ -4,7 +4,9 @@ package frontend // Licensed under the Apache License 2.0. import ( + "bytes" "context" + "io" "net/http" "path/filepath" "strings" @@ -22,12 +24,19 @@ func (f *frontend) getAdminOpenShiftClusterSerialConsole(w http.ResponseWriter, log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) r.URL.Path = filepath.Dir(r.URL.Path) - err := f._getAdminOpenShiftClusterSerialConsole(ctx, w, r, log) + buf := &bytes.Buffer{} + + err := f._getAdminOpenShiftClusterSerialConsole(ctx, r, log, buf) + + if err == nil { + w.Header().Set("Content-Type", "text/plain") + _, err = io.Copy(w, buf) + } adminReply(log, w, nil, nil, err) } -func (f *frontend) _getAdminOpenShiftClusterSerialConsole(ctx context.Context, w http.ResponseWriter, r *http.Request, log *logrus.Entry) error { +func (f *frontend) _getAdminOpenShiftClusterSerialConsole(ctx context.Context, r *http.Request, log *logrus.Entry, w io.Writer) error { resType, resName, resGroupName := chi.URLParam(r, "resourceType"), chi.URLParam(r, "resourceName"), chi.URLParam(r, "resourceGroupName") vmName := r.URL.Query().Get("vmName") @@ -56,5 +65,5 @@ func (f *frontend) _getAdminOpenShiftClusterSerialConsole(ctx context.Context, w return err } - return a.VMSerialConsole(ctx, w, log, vmName) + return a.VMSerialConsole(ctx, log, vmName, w) } diff --git a/pkg/frontend/admin_openshiftcluster_startvm_test.go b/pkg/frontend/admin_openshiftcluster_startvm_test.go index e7a91708bd4..4d2c8251e06 100644 --- a/pkg/frontend/admin_openshiftcluster_startvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_startvm_test.go @@ -86,7 +86,7 @@ func TestAdminStartVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_stopvm_test.go b/pkg/frontend/admin_openshiftcluster_stopvm_test.go index bb35093ed84..f8f2f189db3 100644 --- a/pkg/frontend/admin_openshiftcluster_stopvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_stopvm_test.go @@ -88,7 +88,7 @@ func TestAdminStopVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_vmresize_test.go b/pkg/frontend/admin_openshiftcluster_vmresize_test.go index 6eaa65f9acd..79dc083516f 100644 --- a/pkg/frontend/admin_openshiftcluster_vmresize_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmresize_test.go @@ -205,7 +205,7 @@ func TestAdminVMResize(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go index 1d7fda2cabd..b687aa0efc1 100644 --- a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go @@ -138,7 +138,7 @@ func TestAdminListVMSizeList(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftversion_list_test.go b/pkg/frontend/admin_openshiftversion_list_test.go index ff18ac7aebf..9826b5e07ae 100644 --- a/pkg/frontend/admin_openshiftversion_list_test.go +++ b/pkg/frontend/admin_openshiftversion_list_test.go @@ -110,7 +110,7 @@ func TestOpenShiftVersionList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftversion_put_test.go b/pkg/frontend/admin_openshiftversion_put_test.go index efef5a07b6a..7667207b924 100644 --- a/pkg/frontend/admin_openshiftversion_put_test.go +++ b/pkg/frontend/admin_openshiftversion_put_test.go @@ -266,7 +266,7 @@ func TestOpenShiftVersionPut(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go b/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go index 4f92a3be110..44747871aa1 100644 --- a/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go +++ b/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go @@ -258,7 +258,7 @@ func TestPlatformWorkloadIdentityRoleSetList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go b/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go index 0c632dc93e8..5a6a60a3dcc 100644 --- a/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go +++ b/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go @@ -757,7 +757,7 @@ func TestPlatformWorkloadIdentityRoleSetPut(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/adminactions/applens.go b/pkg/frontend/adminactions/applens.go new file mode 100644 index 00000000000..6773214a4ad --- /dev/null +++ b/pkg/frontend/adminactions/applens.go @@ -0,0 +1,68 @@ +package adminactions + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "encoding/json" + + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/util/azureclient/applens" +) + +type AppLensActions interface { + AppLensGetDetector(ctx context.Context, detectorId string) ([]byte, error) + AppLensListDetectors(ctx context.Context) ([]byte, error) +} + +type appLensActions struct { + log *logrus.Entry + env env.Interface + oc *api.OpenShiftCluster + + appLens applens.AppLensClient +} + +func NewAppLensActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, + subscriptionDoc *api.SubscriptionDocument) (AppLensActions, error) { + fpClientCertCred, err := env.FPNewClientCertificateCredential(env.Environment().AppLensTenantID) + if err != nil { + return nil, err + } + + appLensClient, err := applens.NewAppLensClient(env.Environment(), fpClientCertCred) + if err != nil { + return nil, err + } + + return &appLensActions{ + log: log, + env: env, + oc: oc, + appLens: appLensClient, + }, nil +} + +func (a *appLensActions) AppLensGetDetector(ctx context.Context, detectorId string) ([]byte, error) { + detector, err := a.appLens.GetDetector(ctx, &applens.GetDetectorOptions{ResourceID: a.oc.ID, DetectorID: detectorId, Location: a.oc.Location}) + + if err != nil { + return nil, err + } + + return json.Marshal(detector) +} + +func (a *appLensActions) AppLensListDetectors(ctx context.Context) ([]byte, error) { + detectors, err := a.appLens.ListDetectors(ctx, &applens.ListDetectorsOptions{ResourceID: a.oc.ID, Location: a.oc.Location}) + + if err != nil { + return nil, err + } + + return json.Marshal(detectors) +} diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go index d0061262666..bf807db2582 100644 --- a/pkg/frontend/adminactions/azureactions.go +++ b/pkg/frontend/adminactions/azureactions.go @@ -5,7 +5,6 @@ package adminactions import ( "context" - "encoding/json" "fmt" "io" "net/http" @@ -16,7 +15,6 @@ import ( "github.com/Azure/ARO-RP/pkg/api" "github.com/Azure/ARO-RP/pkg/env" - "github.com/Azure/ARO-RP/pkg/util/azureclient/applens" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features" "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network" @@ -36,9 +34,7 @@ type AzureActions interface { VMSizeList(ctx context.Context) ([]mgmtcompute.ResourceSku, error) VMResize(ctx context.Context, vmName string, vmSize string) error ResourceGroupHasVM(ctx context.Context, vmName string) (bool, error) - VMSerialConsole(ctx context.Context, w http.ResponseWriter, log *logrus.Entry, vmName string) error - AppLensGetDetector(ctx context.Context, detectorId string) ([]byte, error) - AppLensListDetectors(ctx context.Context) ([]byte, error) + VMSerialConsole(ctx context.Context, log *logrus.Entry, vmName string, target io.Writer) error ResourceDeleteAndWait(ctx context.Context, resourceID string) error } @@ -56,7 +52,6 @@ type azureActions struct { storageAccounts storage.AccountsClient networkInterfaces network.InterfacesClient loadBalancers network.LoadBalancersClient - appLens applens.AppLensClient } // NewAzureActions returns an azureActions @@ -68,16 +63,6 @@ func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClus return nil, err } - fpClientCertCred, err := env.FPNewClientCertificateCredential(env.Environment().AppLensTenantID) - if err != nil { - return nil, err - } - - appLensClient, err := applens.NewAppLensClient(env.Environment(), fpClientCertCred) - if err != nil { - return nil, err - } - return &azureActions{ log: log, env: env, @@ -92,7 +77,6 @@ func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClus storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionDoc.ID, fpAuth), networkInterfaces: network.NewInterfacesClient(env.Environment(), subscriptionDoc.ID, fpAuth), loadBalancers: network.NewLoadBalancersClient(env.Environment(), subscriptionDoc.ID, fpAuth), - appLens: appLensClient, }, nil } @@ -150,23 +134,3 @@ func (a *azureActions) ResourceGroupHasVM(ctx context.Context, vmName string) (b return false, nil } - -func (a *azureActions) AppLensGetDetector(ctx context.Context, detectorId string) ([]byte, error) { - detector, err := a.appLens.GetDetector(ctx, &applens.GetDetectorOptions{ResourceID: a.oc.ID, DetectorID: detectorId, Location: a.oc.Location}) - - if err != nil { - return nil, err - } - - return json.Marshal(detector) -} - -func (a *azureActions) AppLensListDetectors(ctx context.Context) ([]byte, error) { - detectors, err := a.appLens.ListDetectors(ctx, &applens.ListDetectorsOptions{ResourceID: a.oc.ID, Location: a.oc.Location}) - - if err != nil { - return nil, err - } - - return json.Marshal(detectors) -} diff --git a/pkg/frontend/adminactions/generate.go b/pkg/frontend/adminactions/generate.go index 0462f2241e3..51fbe48cb6a 100644 --- a/pkg/frontend/adminactions/generate.go +++ b/pkg/frontend/adminactions/generate.go @@ -4,5 +4,5 @@ package adminactions // Licensed under the Apache License 2.0. //go:generate rm -rf ../../util/mocks/$GOPACKAGE -//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE KubeActions,AzureActions +//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE KubeActions,AzureActions,AppLensActions //go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../util/mocks/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/frontend/adminactions/vmserialconsole.go b/pkg/frontend/adminactions/vmserialconsole.go index 06914501ed0..8ce8f51700b 100644 --- a/pkg/frontend/adminactions/vmserialconsole.go +++ b/pkg/frontend/adminactions/vmserialconsole.go @@ -5,78 +5,16 @@ package adminactions import ( "context" - "fmt" "io" - "net/http" - "net/url" - "strings" - "time" - mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" - mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage" - azstorage "github.com/Azure/azure-sdk-for-go/storage" - "github.com/Azure/go-autorest/autorest/date" "github.com/sirupsen/logrus" "github.com/Azure/ARO-RP/pkg/util/stringutils" ) -func (a *azureActions) VMSerialConsole(ctx context.Context, w http.ResponseWriter, - log *logrus.Entry, vmName string) error { +func (a *azureActions) VMSerialConsole(ctx context.Context, + log *logrus.Entry, vmName string, target io.Writer) error { clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') - vm, err := a.virtualMachines.Get(ctx, clusterRGName, vmName, mgmtcompute.InstanceView) - if err != nil { - return err - } - if vm.InstanceView == nil || vm.InstanceView.BootDiagnostics == nil { - return fmt.Errorf("BootDiagnostics not enabled on %s, serial log is not available", vmName) - } - - u, err := url.Parse(*vm.InstanceView.BootDiagnostics.SerialConsoleLogBlobURI) - if err != nil { - return err - } - - parts := strings.Split(u.Path, "/") - if len(parts) != 3 { - return fmt.Errorf("serialConsoleLogBlobURI has %d parts, expected 3", len(parts)) - } - - t := time.Now().UTC().Truncate(time.Second) - res, err := a.storageAccounts.ListAccountSAS( - ctx, clusterRGName, "cluster"+a.oc.Properties.StorageSuffix, mgmtstorage.AccountSasParameters{ - Services: mgmtstorage.ServicesB, - ResourceTypes: mgmtstorage.SignedResourceTypesO, - Permissions: mgmtstorage.PermissionsR, - Protocols: mgmtstorage.HTTPProtocolHTTPS, - SharedAccessStartTime: &date.Time{Time: t}, - SharedAccessExpiryTime: &date.Time{Time: t.Add(24 * time.Hour)}, - }) - if err != nil { - return err - } - - v, err := url.ParseQuery(*res.AccountSasToken) - if err != nil { - return err - } - - blobService := azstorage.NewAccountSASClient( - "cluster"+a.oc.Properties.StorageSuffix, v, (*a.env.Environment()).Environment).GetBlobService() - - c := blobService.GetContainerReference(parts[1]) - - b := c.GetBlobReference(parts[2]) - - rc, err := b.Get(nil) - if err != nil { - return err - } - defer rc.Close() - - w.Header().Add("Content-Type", "text/plain") - - _, err = io.Copy(w, rc) - return err + return a.virtualMachines.GetSerialConsoleForVM(ctx, clusterRGName, vmName, target) } diff --git a/pkg/frontend/adminactions/vmserialconsole_test.go b/pkg/frontend/adminactions/vmserialconsole_test.go new file mode 100644 index 00000000000..d6bbd923574 --- /dev/null +++ b/pkg/frontend/adminactions/vmserialconsole_test.go @@ -0,0 +1,82 @@ +package adminactions + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "bytes" + "context" + "fmt" + "io" + "testing" + + "github.com/go-test/deep" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + mock_compute "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/compute" + mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" + utilerror "github.com/Azure/ARO-RP/test/util/error" +) + +func TestVMSerialConsole(t *testing.T) { + type test struct { + name string + mocks func(*mock_compute.MockVirtualMachinesClient) + wantResponse []byte + wantError string + } + + for _, tt := range []*test{ + { + name: "basic coverage", + mocks: func(vmc *mock_compute.MockVirtualMachinesClient) { + iothing := bytes.NewBufferString("outputhere") + + vmc.EXPECT().GetSerialConsoleForVM(gomock.Any(), clusterRG, "vm1", gomock.Any()).DoAndReturn(func(ctx context.Context, + rg string, vmName string, target io.Writer) error { + _, err := io.Copy(target, iothing) + return err + }) + }, + wantResponse: []byte(`outputhere`), + }, + } { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + env := mock_env.NewMockInterface(controller) + env.EXPECT().Location().AnyTimes().Return(location) + + vmClient := mock_compute.NewMockVirtualMachinesClient(controller) + + tt.mocks(vmClient) + log := logrus.NewEntry(logrus.StandardLogger()) + a := azureActions{ + log: log, + env: env, + oc: &api.OpenShiftCluster{ + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscription, clusterRG), + }, + }, + }, + virtualMachines: vmClient, + } + + ctx := context.Background() + + target := &bytes.Buffer{} + err := a.VMSerialConsole(ctx, log, "vm1", target) + + utilerror.AssertErrorMessage(t, err, tt.wantError) + + for _, errs := range deep.Equal(target.Bytes(), tt.wantResponse) { + t.Error(errs) + } + }) + } +} diff --git a/pkg/frontend/asyncoperationresult_get_test.go b/pkg/frontend/asyncoperationresult_get_test.go index a48eefad21b..adac00420cb 100644 --- a/pkg/frontend/asyncoperationresult_get_test.go +++ b/pkg/frontend/asyncoperationresult_get_test.go @@ -136,7 +136,7 @@ func TestGetAsyncOperationResult(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/asyncoperationsstatus_get_test.go b/pkg/frontend/asyncoperationsstatus_get_test.go index 9533d02db1b..9965f625789 100644 --- a/pkg/frontend/asyncoperationsstatus_get_test.go +++ b/pkg/frontend/asyncoperationsstatus_get_test.go @@ -183,7 +183,7 @@ func TestGetAsyncOperationsStatus(t *testing.T) { ti.asyncOperationsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_delete_test.go b/pkg/frontend/clustermanager_delete_test.go index 91d83e6d8a2..4d592bef272 100644 --- a/pkg/frontend/clustermanager_delete_test.go +++ b/pkg/frontend/clustermanager_delete_test.go @@ -120,7 +120,7 @@ func TestDeleteClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_get_test.go b/pkg/frontend/clustermanager_get_test.go index 0f38667ab22..66f6e1ac95c 100644 --- a/pkg/frontend/clustermanager_get_test.go +++ b/pkg/frontend/clustermanager_get_test.go @@ -123,7 +123,7 @@ func TestGetClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_putorpatch_test.go b/pkg/frontend/clustermanager_putorpatch_test.go index 9bd73948a57..29d661f17c6 100644 --- a/pkg/frontend/clustermanager_putorpatch_test.go +++ b/pkg/frontend/clustermanager_putorpatch_test.go @@ -201,7 +201,7 @@ func TestPutOrPatchClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/fixetcd_test.go b/pkg/frontend/fixetcd_test.go index 43ad982bbb4..e65e33253fc 100644 --- a/pkg/frontend/fixetcd_test.go +++ b/pkg/frontend/fixetcd_test.go @@ -531,6 +531,7 @@ func TestFixEtcd(t *testing.T) { nil, nil, nil, + nil, ti.enricher) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index 9e62d3000cb..b127c381631 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -44,6 +44,7 @@ func (err statusCodeError) Error() string { type kubeActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) type azureActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) +type appLensActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AppLensActions, error) type frontend struct { auditLog *logrus.Entry @@ -76,9 +77,10 @@ type frontend struct { aead encryption.AEAD - hiveClusterManager hive.ClusterManager - kubeActionsFactory kubeActionsFactory - azureActionsFactory azureActionsFactory + hiveClusterManager hive.ClusterManager + kubeActionsFactory kubeActionsFactory + azureActionsFactory azureActionsFactory + appLensActionsFactory appLensActionsFactory skuValidator SkuValidator quotaValidator QuotaValidator @@ -130,6 +132,7 @@ func NewFrontend(ctx context.Context, hiveClusterManager hive.ClusterManager, kubeActionsFactory kubeActionsFactory, azureActionsFactory azureActionsFactory, + appLensActionsFactory appLensActionsFactory, enricher clusterdata.BestEffortEnricher, ) (*frontend, error) { f := &frontend{ @@ -167,6 +170,7 @@ func NewFrontend(ctx context.Context, hiveClusterManager: hiveClusterManager, kubeActionsFactory: kubeActionsFactory, azureActionsFactory: azureActionsFactory, + appLensActionsFactory: appLensActionsFactory, quotaValidator: quotaValidator{}, skuValidator: skuValidator{}, diff --git a/pkg/frontend/openshiftcluster_applensdetectors.go b/pkg/frontend/openshiftcluster_applensdetectors.go index bb33dd77320..f594fb75cba 100644 --- a/pkg/frontend/openshiftcluster_applensdetectors.go +++ b/pkg/frontend/openshiftcluster_applensdetectors.go @@ -29,7 +29,7 @@ func (f *frontend) listAppLensDetectors(w http.ResponseWriter, r *http.Request) } func (f *frontend) _listAppLensDetectors(ctx context.Context, r *http.Request, log *logrus.Entry) ([]byte, error) { - a, err := f._createAzureActionsFactory(ctx, r, log) + a, err := f._createAppLensActionsFactory(ctx, r, log) if err != nil { return nil, err } @@ -48,7 +48,7 @@ func (f *frontend) getAppLensDetector(w http.ResponseWriter, r *http.Request) { } func (f *frontend) _appLensDetectors(ctx context.Context, r *http.Request, log *logrus.Entry) ([]byte, error) { - a, err := f._createAzureActionsFactory(ctx, r, log) + a, err := f._createAppLensActionsFactory(ctx, r, log) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func (f *frontend) _appLensDetectors(ctx context.Context, r *http.Request, log * return a.AppLensGetDetector(ctx, detectorId) } -func (f *frontend) _createAzureActionsFactory(ctx context.Context, r *http.Request, log *logrus.Entry) (adminactions.AzureActions, error) { +func (f *frontend) _createAppLensActionsFactory(ctx context.Context, r *http.Request, log *logrus.Entry) (adminactions.AppLensActions, error) { resType, resName, resGroupName := chi.URLParam(r, "resourceType"), chi.URLParam(r, "resourceName"), chi.URLParam(r, "resourceGroupName") resourceID := strings.TrimSuffix(r.URL.Path, "/detectors") @@ -74,7 +74,7 @@ func (f *frontend) _createAzureActionsFactory(ctx context.Context, r *http.Reque return nil, err } - a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) + a, err := f.appLensActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc) if err != nil { return nil, err } diff --git a/pkg/frontend/openshiftcluster_applensdetectors_test.go b/pkg/frontend/openshiftcluster_applensdetectors_test.go index b1665012e32..dba53e5154b 100644 --- a/pkg/frontend/openshiftcluster_applensdetectors_test.go +++ b/pkg/frontend/openshiftcluster_applensdetectors_test.go @@ -30,7 +30,7 @@ func TestAppLensDetectors(t *testing.T) { resourceID string detectorID string getDetector bool - mocks func(*test, *mock_adminactions.MockAzureActions) + mocks func(*test, *mock_adminactions.MockAppLensActions) method string wantStatusCode int wantResponse []byte @@ -44,7 +44,7 @@ func TestAppLensDetectors(t *testing.T) { resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID), detectorID: "", getDetector: false, - mocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + mocks: func(tt *test, a *mock_adminactions.MockAppLensActions) { a.EXPECT(). AppLensListDetectors(gomock.Any()). Return([]byte(`{"Kind": "test"}`), nil) @@ -58,7 +58,7 @@ func TestAppLensDetectors(t *testing.T) { resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID), detectorID: "testdetector", getDetector: true, - mocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + mocks: func(tt *test, a *mock_adminactions.MockAppLensActions) { a.EXPECT(). AppLensGetDetector(gomock.Any(), tt.detectorID). Return([]byte(`{"Kind": "test"}`), nil) @@ -71,7 +71,7 @@ func TestAppLensDetectors(t *testing.T) { ti := newTestInfra(t).WithSubscriptions().WithOpenShiftClusters() defer ti.done() - a := mock_adminactions.NewMockAzureActions(ti.controller) + a := mock_adminactions.NewMockAppLensActions(ti.controller) tt.mocks(tt, a) ti.fixture.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ @@ -97,7 +97,7 @@ func TestAppLensDetectors(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AppLensActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/openshiftcluster_delete_test.go b/pkg/frontend/openshiftcluster_delete_test.go index 08883437ff1..b10c4dde781 100644 --- a/pkg/frontend/openshiftcluster_delete_test.go +++ b/pkg/frontend/openshiftcluster_delete_test.go @@ -114,7 +114,7 @@ func TestDeleteOpenShiftCluster(t *testing.T) { ti.subscriptionsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_get_test.go b/pkg/frontend/openshiftcluster_get_test.go index 1ac87c137e5..84d776fd940 100644 --- a/pkg/frontend/openshiftcluster_get_test.go +++ b/pkg/frontend/openshiftcluster_get_test.go @@ -95,7 +95,7 @@ func TestGetOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go index 9fc49f4bda7..6938f73a211 100644 --- a/pkg/frontend/openshiftcluster_list_test.go +++ b/pkg/frontend/openshiftcluster_list_test.go @@ -204,7 +204,7 @@ func TestListOpenShiftCluster(t *testing.T) { aead := testdatabase.NewFakeAEAD() - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_preflightvalidation_test.go b/pkg/frontend/openshiftcluster_preflightvalidation_test.go index 1312de21a6e..8e39fda8811 100644 --- a/pkg/frontend/openshiftcluster_preflightvalidation_test.go +++ b/pkg/frontend/openshiftcluster_preflightvalidation_test.go @@ -158,7 +158,7 @@ func TestPreflightValidation(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go index acc6eafbacc..1504c97869b 100644 --- a/pkg/frontend/openshiftcluster_putorpatch_test.go +++ b/pkg/frontend/openshiftcluster_putorpatch_test.go @@ -1728,7 +1728,7 @@ func TestPutOrPatchOpenShiftClusterAdminAPI(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -2801,7 +2801,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -3134,7 +3134,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclustercredentials_post_test.go b/pkg/frontend/openshiftclustercredentials_post_test.go index 14400926f3f..c8451f96900 100644 --- a/pkg/frontend/openshiftclustercredentials_post_test.go +++ b/pkg/frontend/openshiftclustercredentials_post_test.go @@ -267,7 +267,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go index f8461d56679..5795501a66b 100644 --- a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go +++ b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go @@ -242,7 +242,7 @@ func TestPostOpenShiftClusterKubeConfigCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftversions_list_test.go b/pkg/frontend/openshiftversions_list_test.go index 11b90ed9db7..0c6c503311e 100644 --- a/pkg/frontend/openshiftversions_list_test.go +++ b/pkg/frontend/openshiftversions_list_test.go @@ -78,7 +78,7 @@ func TestListInstallVersions(t *testing.T) { ti := newTestInfra(t).WithSubscriptions().WithOpenShiftVersions() defer ti.done() - frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/platformworkloadidentityrolesets_list_test.go b/pkg/frontend/platformworkloadidentityrolesets_list_test.go index 1ab45d9317f..b9b51d1d298 100644 --- a/pkg/frontend/platformworkloadidentityrolesets_list_test.go +++ b/pkg/frontend/platformworkloadidentityrolesets_list_test.go @@ -125,7 +125,7 @@ func TestListPlatformWorkloadIdentityRoleSets(t *testing.T) { ti := newTestInfra(t).WithSubscriptions().WithPlatformWorkloadIdentityRoleSets() defer ti.done() - frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/security_test.go b/pkg/frontend/security_test.go index 1f501074234..f95de709360 100644 --- a/pkg/frontend/security_test.go +++ b/pkg/frontend/security_test.go @@ -77,7 +77,7 @@ func TestSecurity(t *testing.T) { log := logrus.NewEntry(logrus.StandardLogger()) auditHook, auditEntry := testlog.NewAudit() - f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/subscriptions_put_test.go b/pkg/frontend/subscriptions_put_test.go index 7be248dbd9b..cfe1d7cbc63 100644 --- a/pkg/frontend/subscriptions_put_test.go +++ b/pkg/frontend/subscriptions_put_test.go @@ -244,7 +244,7 @@ func TestPutSubscription(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go b/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go index 28d73faa5b5..57ec05ca02a 100644 --- a/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go +++ b/pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go @@ -5,6 +5,9 @@ package compute import ( "context" + "fmt" + "io" + "net/http" mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" "github.com/Azure/go-autorest/autorest/to" @@ -18,6 +21,7 @@ type VirtualMachinesClientAddons interface { StartAndWait(ctx context.Context, resourceGroupName string, VMName string) error StopAndWait(ctx context.Context, resourceGroupName string, VMName string, deallocateVM bool) error List(ctx context.Context, resourceGroupName string) (result []mgmtcompute.VirtualMachine, err error) + GetSerialConsoleForVM(ctx context.Context, resourceGroupName string, VMName string, target io.Writer) error } func (c *virtualMachinesClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName string, VMName string, parameters mgmtcompute.VirtualMachine) error { @@ -95,3 +99,49 @@ func (c *virtualMachinesClient) List(ctx context.Context, resourceGroupName stri return result, nil } + +// retrieveBootDiagnosticsData returns the boot diagnostics data for the given +// VM by RG and VMName. +func (c *virtualMachinesClient) retrieveBootDiagnosticsData(ctx context.Context, resourceGroupName string, VMName string) (serialConsoleURI string, err error) { + resp, err := c.VirtualMachinesClient.RetrieveBootDiagnosticsData(ctx, resourceGroupName, VMName, to.Int32Ptr(60)) + if err != nil { + return "", err + } + + if resp.SerialConsoleLogBlobURI == nil { + return "", fmt.Errorf("no available serial console URI") + } + + return *resp.SerialConsoleLogBlobURI, nil +} + +// GetSerialConsoleForVM will return the serial console log blob as an +// io.ReadCloser, or an error if it cannot be retrieved. +func (c *virtualMachinesClient) GetSerialConsoleForVM(ctx context.Context, resourceGroupName string, vmName string, target io.Writer) error { + serialConsoleLogBlobURI, err := c.retrieveBootDiagnosticsData(ctx, resourceGroupName, vmName) + if err != nil { + return fmt.Errorf("failure getting boot diagnostics URI Azure: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, serialConsoleLogBlobURI, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failure downloading blob URI: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("got %d instead of 200 downloading blob URI", resp.StatusCode) + } + + _, err = io.Copy(target, resp.Body) + if err != nil { + return fmt.Errorf("failure copying blob URI body: %w", err) + } + + return nil +} diff --git a/pkg/util/mocks/adminactions/adminactions.go b/pkg/util/mocks/adminactions/adminactions.go index e5ac3ab3a67..0fb390effa4 100644 --- a/pkg/util/mocks/adminactions/adminactions.go +++ b/pkg/util/mocks/adminactions/adminactions.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: KubeActions,AzureActions) +// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: KubeActions,AzureActions,AppLensActions) // Package mock_adminactions is a generated GoMock package. package mock_adminactions @@ -7,7 +7,6 @@ package mock_adminactions import ( context "context" io "io" - http "net/http" reflect "reflect" compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" @@ -225,36 +224,6 @@ func (m *MockAzureActions) EXPECT() *MockAzureActionsMockRecorder { return m.recorder } -// AppLensGetDetector mocks base method. -func (m *MockAzureActions) AppLensGetDetector(arg0 context.Context, arg1 string) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AppLensGetDetector", arg0, arg1) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AppLensGetDetector indicates an expected call of AppLensGetDetector. -func (mr *MockAzureActionsMockRecorder) AppLensGetDetector(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppLensGetDetector", reflect.TypeOf((*MockAzureActions)(nil).AppLensGetDetector), arg0, arg1) -} - -// AppLensListDetectors mocks base method. -func (m *MockAzureActions) AppLensListDetectors(arg0 context.Context) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AppLensListDetectors", arg0) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AppLensListDetectors indicates an expected call of AppLensListDetectors. -func (mr *MockAzureActionsMockRecorder) AppLensListDetectors(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppLensListDetectors", reflect.TypeOf((*MockAzureActions)(nil).AppLensListDetectors), arg0) -} - // GroupResourceList mocks base method. func (m *MockAzureActions) GroupResourceList(arg0 context.Context) ([]features.GenericResourceExpanded, error) { m.ctrl.T.Helper() @@ -356,7 +325,7 @@ func (mr *MockAzureActionsMockRecorder) VMResize(arg0, arg1, arg2 interface{}) * } // VMSerialConsole mocks base method. -func (m *MockAzureActions) VMSerialConsole(arg0 context.Context, arg1 http.ResponseWriter, arg2 *logrus.Entry, arg3 string) error { +func (m *MockAzureActions) VMSerialConsole(arg0 context.Context, arg1 *logrus.Entry, arg2 string, arg3 io.Writer) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VMSerialConsole", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) @@ -425,3 +394,56 @@ func (mr *MockAzureActionsMockRecorder) WriteToStream(arg0, arg1 interface{}) *g mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteToStream", reflect.TypeOf((*MockAzureActions)(nil).WriteToStream), arg0, arg1) } + +// MockAppLensActions is a mock of AppLensActions interface. +type MockAppLensActions struct { + ctrl *gomock.Controller + recorder *MockAppLensActionsMockRecorder +} + +// MockAppLensActionsMockRecorder is the mock recorder for MockAppLensActions. +type MockAppLensActionsMockRecorder struct { + mock *MockAppLensActions +} + +// NewMockAppLensActions creates a new mock instance. +func NewMockAppLensActions(ctrl *gomock.Controller) *MockAppLensActions { + mock := &MockAppLensActions{ctrl: ctrl} + mock.recorder = &MockAppLensActionsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAppLensActions) EXPECT() *MockAppLensActionsMockRecorder { + return m.recorder +} + +// AppLensGetDetector mocks base method. +func (m *MockAppLensActions) AppLensGetDetector(arg0 context.Context, arg1 string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppLensGetDetector", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AppLensGetDetector indicates an expected call of AppLensGetDetector. +func (mr *MockAppLensActionsMockRecorder) AppLensGetDetector(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppLensGetDetector", reflect.TypeOf((*MockAppLensActions)(nil).AppLensGetDetector), arg0, arg1) +} + +// AppLensListDetectors mocks base method. +func (m *MockAppLensActions) AppLensListDetectors(arg0 context.Context) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppLensListDetectors", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AppLensListDetectors indicates an expected call of AppLensListDetectors. +func (mr *MockAppLensActionsMockRecorder) AppLensListDetectors(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppLensListDetectors", reflect.TypeOf((*MockAppLensActions)(nil).AppLensListDetectors), arg0) +} diff --git a/pkg/util/mocks/azureclient/mgmt/compute/compute.go b/pkg/util/mocks/azureclient/mgmt/compute/compute.go index abbea26f306..0f05283b764 100644 --- a/pkg/util/mocks/azureclient/mgmt/compute/compute.go +++ b/pkg/util/mocks/azureclient/mgmt/compute/compute.go @@ -6,6 +6,7 @@ package mock_compute import ( context "context" + io "io" reflect "reflect" compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" @@ -168,6 +169,20 @@ func (mr *MockVirtualMachinesClientMockRecorder) Get(arg0, arg1, arg2, arg3 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockVirtualMachinesClient)(nil).Get), arg0, arg1, arg2, arg3) } +// GetSerialConsoleForVM mocks base method. +func (m *MockVirtualMachinesClient) GetSerialConsoleForVM(arg0 context.Context, arg1, arg2 string, arg3 io.Writer) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSerialConsoleForVM", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetSerialConsoleForVM indicates an expected call of GetSerialConsoleForVM. +func (mr *MockVirtualMachinesClientMockRecorder) GetSerialConsoleForVM(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSerialConsoleForVM", reflect.TypeOf((*MockVirtualMachinesClient)(nil).GetSerialConsoleForVM), arg0, arg1, arg2, arg3) +} + // List mocks base method. func (m *MockVirtualMachinesClient) List(arg0 context.Context, arg1 string) ([]compute.VirtualMachine, error) { m.ctrl.T.Helper() diff --git a/pkg/util/mocks/storage/storage.go b/pkg/util/mocks/storage/storage.go index 2d73ec11a00..4e316d52942 100644 --- a/pkg/util/mocks/storage/storage.go +++ b/pkg/util/mocks/storage/storage.go @@ -1,18 +1,73 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/Azure/ARO-RP/pkg/util/storage (interfaces: Manager) +// Source: github.com/Azure/ARO-RP/pkg/util/storage (interfaces: BlobStorageClient,Manager) // Package mock_storage is a generated GoMock package. package mock_storage import ( context "context" + io "io" reflect "reflect" - storage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage" - storage0 "github.com/Azure/azure-sdk-for-go/storage" + storage0 "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage" + storage1 "github.com/Azure/azure-sdk-for-go/storage" gomock "github.com/golang/mock/gomock" + + storage "github.com/Azure/ARO-RP/pkg/util/storage" ) +// MockBlobStorageClient is a mock of BlobStorageClient interface. +type MockBlobStorageClient struct { + ctrl *gomock.Controller + recorder *MockBlobStorageClientMockRecorder +} + +// MockBlobStorageClientMockRecorder is the mock recorder for MockBlobStorageClient. +type MockBlobStorageClientMockRecorder struct { + mock *MockBlobStorageClient +} + +// NewMockBlobStorageClient creates a new mock instance. +func NewMockBlobStorageClient(ctrl *gomock.Controller) *MockBlobStorageClient { + mock := &MockBlobStorageClient{ctrl: ctrl} + mock.recorder = &MockBlobStorageClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlobStorageClient) EXPECT() *MockBlobStorageClientMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockBlobStorageClient) Get(arg0 string) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockBlobStorageClientMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockBlobStorageClient)(nil).Get), arg0) +} + +// GetContainerReference mocks base method. +func (m *MockBlobStorageClient) GetContainerReference(arg0 string) *storage1.Container { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContainerReference", arg0) + ret0, _ := ret[0].(*storage1.Container) + return ret0 +} + +// GetContainerReference indicates an expected call of GetContainerReference. +func (mr *MockBlobStorageClientMockRecorder) GetContainerReference(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerReference", reflect.TypeOf((*MockBlobStorageClient)(nil).GetContainerReference), arg0) +} + // MockManager is a mock of Manager interface. type MockManager struct { ctrl *gomock.Controller @@ -37,10 +92,10 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder { } // BlobService mocks base method. -func (m *MockManager) BlobService(arg0 context.Context, arg1, arg2 string, arg3 storage.Permissions, arg4 storage.SignedResourceTypes) (*storage0.BlobStorageClient, error) { +func (m *MockManager) BlobService(arg0 context.Context, arg1, arg2 string, arg3 storage0.Permissions, arg4 storage0.SignedResourceTypes) (storage.BlobStorageClient, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlobService", arg0, arg1, arg2, arg3, arg4) - ret0, _ := ret[0].(*storage0.BlobStorageClient) + ret0, _ := ret[0].(storage.BlobStorageClient) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/pkg/util/storage/generate.go b/pkg/util/storage/generate.go index ceb54ea09a4..5049a5a6621 100644 --- a/pkg/util/storage/generate.go +++ b/pkg/util/storage/generate.go @@ -4,5 +4,5 @@ package storage // Licensed under the Apache License 2.0. //go:generate rm -rf ../mocks/$GOPACKAGE -//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE Manager +//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE BlobStorageClient,Manager //go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../mocks/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/util/storage/manager.go b/pkg/util/storage/manager.go index a364ee1ef7a..8e8a6f99b43 100644 --- a/pkg/util/storage/manager.go +++ b/pkg/util/storage/manager.go @@ -5,8 +5,10 @@ package storage import ( "context" + "io" "net/http" "net/url" + "strings" "time" mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage" @@ -19,8 +21,13 @@ import ( "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/storage" ) +type BlobStorageClient interface { + Get(uri string) (io.ReadCloser, error) + GetContainerReference(name string) *azstorage.Container +} + type Manager interface { - BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (*azstorage.BlobStorageClient, error) + BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (BlobStorageClient, error) } type manager struct { @@ -59,7 +66,7 @@ func getCorrectErrWhenTooManyRequests(err error) error { return cloudError } -func (m *manager) BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (*azstorage.BlobStorageClient, error) { +func (m *manager) BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (BlobStorageClient, error) { t := time.Now().UTC().Truncate(time.Second) res, err := m.storageAccounts.ListAccountSAS(ctx, resourceGroup, account, mgmtstorage.AccountSasParameters{ Services: mgmtstorage.ServicesB, @@ -80,5 +87,22 @@ func (m *manager) BlobService(ctx context.Context, resourceGroup, account string blobcli := azstorage.NewAccountSASClient(account, v, (*m.env.Environment()).Environment).GetBlobService() - return &blobcli, nil + return &wrappedStorageClient{&blobcli}, nil +} + +type wrappedStorageClient struct { + client *azstorage.BlobStorageClient +} + +func (c *wrappedStorageClient) GetContainerReference(name string) *azstorage.Container { + return c.client.GetContainerReference(name) +} + +func (c *wrappedStorageClient) Get(uri string) (io.ReadCloser, error) { + parts := strings.Split(uri, "/") + + container := c.client.GetContainerReference(parts[1]) + b := container.GetBlobReference(parts[2]) + + return b.Get(nil) } diff --git a/test/e2e/adminapi_serialconsole.go b/test/e2e/adminapi_serialconsole.go new file mode 100644 index 00000000000..70d8b060172 --- /dev/null +++ b/test/e2e/adminapi_serialconsole.go @@ -0,0 +1,51 @@ +package e2e + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "net/http" + "net/url" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/Azure/ARO-RP/pkg/util/stringutils" +) + +var _ = Describe("[Admin API] VM serial console action", func() { + BeforeEach(skipIfNotInDevelopmentEnv) + + It("must return the serial console logs", func(ctx context.Context) { + By("getting the resource group where the VM instances live in") + oc, err := clients.OpenshiftClusters.Get(ctx, vnetResourceGroup, clusterName) + Expect(err).NotTo(HaveOccurred()) + clusterResourceGroup := stringutils.LastTokenByte(*oc.OpenShiftClusterProperties.ClusterProfile.ResourceGroupID, '/') + + By("picking a master node to get logs from") + vms, err := clients.VirtualMachines.List(ctx, clusterResourceGroup) + Expect(err).NotTo(HaveOccurred()) + Expect(vms).NotTo(BeEmpty()) + + var vm string + + for _, possibleVM := range vms { + if strings.Contains(*possibleVM.Name, "-master-") { + vm = *possibleVM.Name + } + } + log.Infof("selected vm: %s", vm) + + var logs string + + By("querying the serial console API") + resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/serialconsole", url.Values{"vmName": []string{vm}}, true, nil, &logs) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + + By("we can see Linux serial console") + Expect(logs).To(ContainSubstring("Red Hat Enterprise Linux CoreOS")) + }) +})