Skip to content

Commit

Permalink
figured it out :)
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkowl committed Jul 5, 2024
1 parent 4b551b7 commit d65328a
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 117 deletions.
9 changes: 4 additions & 5 deletions pkg/cluster/failurediagnostics/virtualmachines.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ package failurediagnostics

import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"

"github.com/Azure/ARO-RP/pkg/util/stringutils"
Expand Down Expand Up @@ -49,16 +49,15 @@ func (m *manager) LogAzureInformation(ctx context.Context) (interface{}, error)

// Fetch boot diagnostics URIs for the VMs
for _, vmName := range vmNames {
serialConsoleBlob, err := m.virtualMachines.GetSerialConsoleForVM(ctx, resourceGroupName, vmName)
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)

b64Reader := base64.NewDecoder(base64.StdEncoding, serialConsoleBlob)
scanner := bufio.NewScanner(b64Reader)
scanner := bufio.NewScanner(blob)
for scanner.Scan() {
logForVM.Info(scanner.Text())
}
Expand Down
55 changes: 9 additions & 46 deletions pkg/cluster/failurediagnostics/virtualmachines_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,55 +87,15 @@ func TestVirtualMachines(t *testing.T) {
}, nil)

vmClient.EXPECT().GetSerialConsoleForVM(
gomock.Any(), "resourceGroupCluster", "somename",
).Times(1).Return(nil, errors.New("explod"))
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: "failed blob decoding",
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/boguscontainer/bogusblob"),
},
},
},
},
}, nil)

out := io.NopCloser(bytes.NewBufferString("aGVsbG8KdGhlcmUgOikKZ"))

vmClient.EXPECT().GetSerialConsoleForVM(
gomock.Any(), "resourceGroupCluster", "somename",
).Times(1).Return(out, nil)
},
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":{}}`,
`blob storage scan on somename: unexpected EOF`,
},
},
{
name: "success",
mock: func(vmClient *mock_compute.MockVirtualMachinesClient) {
Expand All @@ -147,11 +107,14 @@ func TestVirtualMachines(t *testing.T) {
},
}, nil)

out := io.NopCloser(bytes.NewBufferString("aGVsbG8KdGhlcmUgOikK"))

iothing := bytes.NewBufferString("hello\nthere :)")
vmClient.EXPECT().GetSerialConsoleForVM(
gomock.Any(), "resourceGroupCluster", "somename",
).Times(1).Return(out, nil)
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{
{
Expand Down
23 changes: 14 additions & 9 deletions pkg/frontend/admin_openshiftcluster_serialconsole.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package frontend
// Licensed under the Apache License 2.0.

import (
"bytes"
"context"
"io"
"net/http"
"path/filepath"
"strings"
Expand All @@ -22,43 +24,46 @@ func (f *frontend) getAdminOpenShiftClusterSerialConsole(w http.ResponseWriter,
log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
r.URL.Path = filepath.Dir(r.URL.Path)

b, err := f._getAdminOpenShiftClusterSerialConsole(ctx, 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, b, err)
adminReply(log, w, nil, nil, err)
}

func (f *frontend) _getAdminOpenShiftClusterSerialConsole(ctx context.Context, r *http.Request, log *logrus.Entry) ([]byte, 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")
err := validateAdminVMName(vmName)
if err != nil {
return nil, err
return err
}

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.", resType, resName, resGroupName)
return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", resType, resName, resGroupName)
case err != nil:
return nil, err
return err
}

subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
if err != nil {
return nil, err
return err
}

a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
if err != nil {
return nil, err
return err
}

return a.VMSerialConsole(ctx, log, vmName)
return a.VMSerialConsole(ctx, log, vmName, w)
}
2 changes: 1 addition & 1 deletion pkg/frontend/adminactions/azureactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +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, log *logrus.Entry, vmName string) ([]byte, error)
VMSerialConsole(ctx context.Context, log *logrus.Entry, vmName string, target io.Writer) error
ResourceDeleteAndWait(ctx context.Context, resourceID string) error
}

Expand Down
9 changes: 2 additions & 7 deletions pkg/frontend/adminactions/vmserialconsole.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,8 @@ import (
)

func (a *azureActions) VMSerialConsole(ctx context.Context,
log *logrus.Entry, vmName string) ([]byte, error) {
log *logrus.Entry, vmName string, target io.Writer) error {
clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/')

blob, err := a.virtualMachines.GetSerialConsoleForVM(ctx, clusterRGName, vmName)
if err != nil {
return nil, err
}

return io.ReadAll(blob)
return a.virtualMachines.GetSerialConsoleForVM(ctx, clusterRGName, vmName, target)
}
12 changes: 9 additions & 3 deletions pkg/frontend/adminactions/vmserialconsole_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bytes"
"context"
"fmt"
"io"
"testing"

"github.com/go-test/deep"
Expand All @@ -33,7 +34,11 @@ func TestVMSerialConsole(t *testing.T) {
mocks: func(vmc *mock_compute.MockVirtualMachinesClient) {
iothing := bytes.NewBufferString("outputhere")

vmc.EXPECT().GetSerialConsoleForVM(gomock.Any(), clusterRG, "vm1").Return(iothing, nil)
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`),
},
Expand Down Expand Up @@ -64,11 +69,12 @@ func TestVMSerialConsole(t *testing.T) {

ctx := context.Background()

resp, err := a.VMSerialConsole(ctx, log, "vm1")
target := &bytes.Buffer{}
err := a.VMSerialConsole(ctx, log, "vm1", target)

utilerror.AssertErrorMessage(t, err, tt.wantError)

for _, errs := range deep.Equal(resp, tt.wantResponse) {
for _, errs := range deep.Equal(target.Bytes(), tt.wantResponse) {
t.Error(errs)
}
})
Expand Down
20 changes: 9 additions & 11 deletions pkg/util/azureclient/mgmt/compute/virtualmachines_addons.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package compute
// Licensed under the Apache License 2.0.

import (
"bytes"
"context"
"fmt"
"io"
Expand All @@ -22,7 +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) (blob io.Reader, 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 {
Expand Down Expand Up @@ -118,32 +117,31 @@ func (c *virtualMachinesClient) retrieveBootDiagnosticsData(ctx context.Context,

// 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) (io.Reader, error) {
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 nil, fmt.Errorf("failure getting boot diagnostics URI Azure: %w", err)
return fmt.Errorf("failure getting boot diagnostics URI Azure: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, serialConsoleLogBlobURI, nil)
if err != nil {
return nil, err
return err
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failure downloading blob URI: %w", err)
return fmt.Errorf("failure downloading blob URI: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("got %d instead of 200 downloading blob URI", resp.StatusCode)
return fmt.Errorf("got %d instead of 200 downloading blob URI", resp.StatusCode)
}

buf := &bytes.Buffer{}
_, err = io.Copy(buf, resp.Body)
_, err = io.Copy(target, resp.Body)
if err != nil {
return nil, fmt.Errorf("failure downloading blob URI body: %w", err)
return fmt.Errorf("failure copying blob URI body: %w", err)
}

return buf, nil
return nil
}
13 changes: 6 additions & 7 deletions pkg/util/mocks/adminactions/adminactions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 6 additions & 7 deletions pkg/util/mocks/azureclient/mgmt/compute/compute.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 2 additions & 21 deletions test/e2e/adminapi_serialconsole.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ package e2e
// Licensed under the Apache License 2.0.

import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -49,22 +45,7 @@ var _ = Describe("[Admin API] VM serial console action", func() {
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusOK))

By("decoding the logs, we can see Linux serial console")
log.Infof("got logs: %s", logs)
foundLogs := false
b64Reader := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(logs))
scanner := bufio.NewScanner(b64Reader)
output := ""
for scanner.Scan() {
output = output + scanner.Text()
}
Expect(scanner.Err()).NotTo(HaveOccurred())

if strings.Contains(output, "Red Hat Enterprise Linux CoreOS") {
foundLogs = true
}

Expect(foundLogs).To(BeTrue(), fmt.Sprintf("expected to find serial console logs in b64: %s", logs))

By("we can see Linux serial console")
Expect(logs).To(ContainSubstring("Red Hat Enterprise Linux CoreOS"))
})
})

0 comments on commit d65328a

Please sign in to comment.