зеркало из https://github.com/Azure/ARO-RP.git
Merge pull request #2554 from anshulvermapatel/azure-side-information
Adding VM's allocation status column to the machines table
This commit is contained in:
Коммит
eb1b604233
|
@ -26,7 +26,8 @@ import (
|
|||
)
|
||||
|
||||
func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error {
|
||||
_env, err := env.NewCore(ctx, log)
|
||||
_env, err := env.NewEnv(ctx, log)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -115,6 +116,11 @@ func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error {
|
|||
return err
|
||||
}
|
||||
|
||||
dbSubscriptions, err := database.NewSubscriptions(ctx, _env.IsLocalDevelopmentMode(), dbc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
portalKeyvaultURI, err := keyvault.URI(_env, env.PortalKeyvaultSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -187,7 +193,7 @@ func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error {
|
|||
|
||||
log.Printf("listening %s", address)
|
||||
|
||||
p := pkgportal.NewPortal(_env, audit, log.WithField("component", "portal"), log.WithField("component", "portal-access"), l, sshl, verifier, hostname, servingKey, servingCerts, clientID, clientKey, clientCerts, sessionKey, sshKey, groupIDs, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, dialer, m)
|
||||
p := pkgportal.NewPortal(_env, audit, log.WithField("component", "portal"), log.WithField("component", "portal-access"), l, sshl, verifier, hostname, servingKey, servingCerts, clientID, clientKey, clientCerts, sessionKey, sshKey, groupIDs, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, dbSubscriptions, dialer, m)
|
||||
|
||||
return p.Run(ctx)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.61fbb3a0.js",
|
||||
"main.js": "/static/js/main.ae7987cb.js",
|
||||
"index.html": "/index.html",
|
||||
"main.61fbb3a0.js.map": "/static/js/main.61fbb3a0.js.map"
|
||||
"main.ae7987cb.js.map": "/static/js/main.ae7987cb.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/main.61fbb3a0.js"
|
||||
"static/js/main.ae7987cb.js"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>ARO Portal</title><script defer="defer" src="/static/js/main.61fbb3a0.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>ARO Portal</title><script defer="defer" src="/static/js/main.ae7987cb.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -88,7 +88,11 @@ func (p *portal) clusters(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portal) clusterOperators(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -113,7 +117,11 @@ func (p *portal) clusterOperators(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portal) nodes(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -138,7 +146,11 @@ func (p *portal) nodes(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portal) machines(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -163,7 +175,40 @@ func (p *portal) machines(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portal) VMAllocationStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
azurefetcher, err := p.makeAzureFetcher(ctx, r)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
machineVMAllocationStatus, err := azurefetcher.VMAllocationStatus(ctx)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(machineVMAllocationStatus, "", " ")
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portal) machineSets(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -188,5 +233,9 @@ func (p *portal) machineSets(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,30 @@ package cluster
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
machineclient "github.com/openshift/client-go/machine/clientset/versioned"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/api"
|
||||
"github.com/Azure/ARO-RP/pkg/env"
|
||||
"github.com/Azure/ARO-RP/pkg/proxy"
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient"
|
||||
"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/restconfig"
|
||||
"github.com/Azure/ARO-RP/pkg/util/stringutils"
|
||||
)
|
||||
|
||||
type ResourceClientFactory interface {
|
||||
NewResourcesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) features.ResourcesClient
|
||||
}
|
||||
|
||||
type VirtualMachinesClientFactory interface {
|
||||
NewVirtualMachinesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) compute.VirtualMachinesClient
|
||||
}
|
||||
|
||||
// FetchClient is the interface that the Admin Portal Frontend uses to gather
|
||||
// information about clusters. It returns frontend-suitable data structures.
|
||||
type FetchClient interface {
|
||||
|
@ -25,6 +39,10 @@ type FetchClient interface {
|
|||
MachineSets(context.Context) (*MachineSetListInformation, error)
|
||||
}
|
||||
|
||||
type AzureFetchClient interface {
|
||||
VMAllocationStatus(context.Context) (map[string]string, error)
|
||||
}
|
||||
|
||||
// client is an implementation of FetchClient. It currently contains a "fetcher"
|
||||
// which is responsible for fetching information from the k8s clusters. The
|
||||
// mechanism of fetching the data from the cluster and returning it to the
|
||||
|
@ -47,6 +65,45 @@ type realFetcher struct {
|
|||
machineClient machineclient.Interface
|
||||
}
|
||||
|
||||
// azureClient is the same implementation as client's, the only difference is that it will be used to fetch something from azure regarding a cluster.
|
||||
type azureClient struct {
|
||||
log *logrus.Entry
|
||||
fetcher *azureSideFetcher
|
||||
}
|
||||
|
||||
// azureSideFetcher is responsible for fetching information about azure resources of a k8s cluster. It
|
||||
// contains azure related authentication/authorization data and returns the frontend-suitable data
|
||||
// structures. The concrete implementation of AzureFetchClient wraps this.
|
||||
type azureSideFetcher struct {
|
||||
log *logrus.Entry
|
||||
resourceGroupName string
|
||||
subscriptionDoc *api.SubscriptionDocument
|
||||
env env.Interface
|
||||
resourceClientFactory ResourceClientFactory
|
||||
virtualMachinesClientFactory VirtualMachinesClientFactory
|
||||
}
|
||||
|
||||
type clientFactory struct{}
|
||||
|
||||
func (cf clientFactory) NewResourcesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) features.ResourcesClient {
|
||||
return features.NewResourcesClient(environment, subscriptionID, authorizer)
|
||||
}
|
||||
|
||||
func (cf clientFactory) NewVirtualMachinesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) compute.VirtualMachinesClient {
|
||||
return compute.NewVirtualMachinesClient(environment, subscriptionID, authorizer)
|
||||
}
|
||||
|
||||
func newAzureSideFetcher(log *logrus.Entry, resourceGroupName string, subscriptionDoc *api.SubscriptionDocument, env env.Interface, resourceClientFactory ResourceClientFactory, virtualMachinesClientFactory VirtualMachinesClientFactory) azureSideFetcher {
|
||||
return azureSideFetcher{
|
||||
log: log,
|
||||
resourceGroupName: resourceGroupName,
|
||||
subscriptionDoc: subscriptionDoc,
|
||||
env: env,
|
||||
resourceClientFactory: resourceClientFactory,
|
||||
virtualMachinesClientFactory: virtualMachinesClientFactory,
|
||||
}
|
||||
}
|
||||
|
||||
func newRealFetcher(log *logrus.Entry, dialer proxy.Dialer, doc *api.OpenShiftClusterDocument) (*realFetcher, error) {
|
||||
restConfig, err := restconfig.RestConfig(dialer, doc.OpenShiftCluster)
|
||||
if err != nil {
|
||||
|
@ -91,3 +148,13 @@ func NewFetchClient(log *logrus.Entry, dialer proxy.Dialer, cluster *api.OpenShi
|
|||
fetcher: fetcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAzureFetchClient(log *logrus.Entry, cluster *api.OpenShiftClusterDocument, subscriptionDoc *api.SubscriptionDocument, env env.Interface) AzureFetchClient {
|
||||
resourceGroupName := stringutils.LastTokenByte(cluster.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID, '/')
|
||||
cf := clientFactory{}
|
||||
azureSideFetcher := newAzureSideFetcher(log, resourceGroupName, subscriptionDoc, env, cf, cf)
|
||||
return &azureClient{
|
||||
log: log,
|
||||
fetcher: &azureSideFetcher,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,15 @@ package cluster
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
|
||||
mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
|
||||
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute"
|
||||
)
|
||||
|
||||
type MachinesInformation struct {
|
||||
|
@ -59,7 +65,52 @@ func (c *client) Machines(ctx context.Context) (*MachineListInformation, error)
|
|||
return c.fetcher.Machines(ctx)
|
||||
}
|
||||
|
||||
func (c *azureClient) VMAllocationStatus(ctx context.Context) (map[string]string, error) {
|
||||
return c.fetcher.vmAllocationStatus(ctx)
|
||||
}
|
||||
|
||||
func (f *azureSideFetcher) vmAllocationStatus(ctx context.Context) (map[string]string, error) {
|
||||
env := f.env
|
||||
subscriptionDoc := f.subscriptionDoc
|
||||
clusterRGName := f.resourceGroupName
|
||||
aroEnvironment := env.Environment()
|
||||
fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID, aroEnvironment.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Getting Virtual Machine resources through the Cluster's Resource Group
|
||||
computeResources, err := f.resourceClientFactory.NewResourcesClient(aroEnvironment, subscriptionDoc.ID, fpAuth).ListByResourceGroup(ctx, clusterRGName, "resourceType eq 'Microsoft.Compute/virtualMachines'", "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vmAllocationStatus := make(map[string]string)
|
||||
virtualMachineClient := f.virtualMachinesClientFactory.NewVirtualMachinesClient(aroEnvironment, subscriptionDoc.ID, fpAuth)
|
||||
for _, res := range computeResources {
|
||||
putAllocationStatusToMap(ctx, clusterRGName, vmAllocationStatus, res, virtualMachineClient, f.log)
|
||||
}
|
||||
|
||||
return vmAllocationStatus, nil
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
func putAllocationStatusToMap(ctx context.Context, clusterRGName string, vmAllocationStatus map[string]string, res mgmtfeatures.GenericResourceExpanded, virtualMachineClient compute.VirtualMachinesClient, log *logrus.Entry) {
|
||||
vm, err := virtualMachineClient.Get(ctx, clusterRGName, *res.Name, mgmtcompute.InstanceView)
|
||||
if err != nil {
|
||||
log.Warn(err) // can happen when the ARM cache is lagging
|
||||
return
|
||||
}
|
||||
|
||||
vmName := *vm.Name
|
||||
instanceViewStatuses := vm.InstanceView.Statuses
|
||||
for _, status := range *instanceViewStatuses {
|
||||
if strings.HasPrefix(*status.Code, "PowerState/") {
|
||||
vmAllocationStatus[vmName] = *status.Code
|
||||
return
|
||||
}
|
||||
}
|
||||
vmAllocationStatus[vmName] = ""
|
||||
}
|
||||
|
||||
func getLastOperation(machine machinev1beta1.Machine) string {
|
||||
lastOperation := "Unknown"
|
||||
if machine.Status.LastOperation != nil &&
|
||||
|
|
|
@ -6,15 +6,31 @@ package cluster
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
|
||||
mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/golang/mock/gomock"
|
||||
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
|
||||
machinefake "github.com/openshift/client-go/machine/clientset/versioned/fake"
|
||||
kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/api"
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient"
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute"
|
||||
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
|
||||
mock_compute "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/compute"
|
||||
mock_features "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/features"
|
||||
mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env"
|
||||
mock_refreshable "github.com/Azure/ARO-RP/pkg/util/mocks/refreshable"
|
||||
"github.com/Azure/ARO-RP/pkg/util/refreshable"
|
||||
testlog "github.com/Azure/ARO-RP/test/util/log"
|
||||
)
|
||||
|
||||
|
@ -97,3 +113,133 @@ func TestMachines(t *testing.T) {
|
|||
t.Fatal(r)
|
||||
}
|
||||
}
|
||||
|
||||
type MockClientFactory struct {
|
||||
mockResourcesClient *mock_features.MockResourcesClient
|
||||
mockVirtualMachinesClient *mock_compute.MockVirtualMachinesClient
|
||||
}
|
||||
|
||||
func newMockClientFactory(mockResourcesClient *mock_features.MockResourcesClient, mockVirtualMachinesClient *mock_compute.MockVirtualMachinesClient) MockClientFactory {
|
||||
return MockClientFactory{mockResourcesClient: mockResourcesClient, mockVirtualMachinesClient: mockVirtualMachinesClient}
|
||||
}
|
||||
|
||||
func (mcf MockClientFactory) NewResourcesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) features.ResourcesClient {
|
||||
return mcf.mockResourcesClient
|
||||
}
|
||||
|
||||
func (mcf MockClientFactory) NewVirtualMachinesClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) compute.VirtualMachinesClient {
|
||||
return mcf.mockVirtualMachinesClient
|
||||
}
|
||||
|
||||
func TestVMAllocationStatus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
subscriptionDoc := &api.SubscriptionDocument{
|
||||
ID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
ResourceID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
Timestamp: 1668689726,
|
||||
Self: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
ETag: "\"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"",
|
||||
Attachments: "attachments/",
|
||||
Subscription: &api.Subscription{
|
||||
State: "Registered",
|
||||
Properties: &api.SubscriptionProperties{
|
||||
TenantID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
RegisteredFeatures: []api.RegisteredFeatureProfile{
|
||||
{Name: "Microsoft.RedHatOpenShift/RedHatEngineering", State: "Registered"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
controller := gomock.NewController(t)
|
||||
mockEnv := mock_env.NewMockInterface(controller)
|
||||
mockRefreshable := mock_refreshable.NewMockAuthorizer(controller)
|
||||
mockResourcesClient := mock_features.NewMockResourcesClient(controller)
|
||||
mockVirtualMachinesClient := mock_compute.NewMockVirtualMachinesClient(controller)
|
||||
type test struct {
|
||||
name string
|
||||
returnedRefreshableAuthorizer refreshable.Authorizer
|
||||
returnedVM mgmtcompute.VirtualMachine
|
||||
returnedGenericResourceExpanded []mgmtfeatures.GenericResourceExpanded
|
||||
wantOutput map[string]string
|
||||
wantErr error
|
||||
}
|
||||
for _, tt := range []*test{
|
||||
{
|
||||
name: "Successfully fetching VMs allocation status. Calling all the required methods.",
|
||||
returnedRefreshableAuthorizer: mockRefreshable,
|
||||
returnedGenericResourceExpanded: []mgmtfeatures.GenericResourceExpanded{
|
||||
{
|
||||
Kind: func(v string) *string { return &v }("something"),
|
||||
Type: func(v string) *string { return &v }("Microsoft.Compute/virtualMachines"),
|
||||
Name: func(v string) *string { return &v }("master-x"),
|
||||
},
|
||||
},
|
||||
returnedVM: mgmtcompute.VirtualMachine{
|
||||
Name: func(v string) *string { return &v }("master-x"),
|
||||
VirtualMachineProperties: &mgmtcompute.VirtualMachineProperties{
|
||||
InstanceView: &mgmtcompute.VirtualMachineInstanceView{
|
||||
Statuses: &[]mgmtcompute.InstanceViewStatus{
|
||||
{
|
||||
Code: func() *string {
|
||||
s := new(string)
|
||||
*s = "PowerState/running"
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutput: map[string]string{"master-x": "PowerState/running"},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "No VM resource found",
|
||||
returnedRefreshableAuthorizer: mockRefreshable,
|
||||
returnedGenericResourceExpanded: []mgmtfeatures.GenericResourceExpanded{},
|
||||
wantOutput: map[string]string{},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Empty FP Authorizer",
|
||||
returnedRefreshableAuthorizer: nil,
|
||||
returnedGenericResourceExpanded: nil,
|
||||
wantOutput: nil,
|
||||
wantErr: errors.New("Empty Athorizer"),
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockEnv.EXPECT().Environment().Return(&azureclient.AROEnvironment{
|
||||
Environment: azure.Environment{
|
||||
ResourceManagerEndpoint: "temp",
|
||||
},
|
||||
})
|
||||
mockEnv.EXPECT().FPAuthorizer(gomock.Any(), gomock.Any()).Return(tt.returnedRefreshableAuthorizer, tt.wantErr)
|
||||
if tt.returnedRefreshableAuthorizer != nil {
|
||||
mockResourcesClient.EXPECT().ListByResourceGroup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
|
||||
Return(tt.returnedGenericResourceExpanded, tt.wantErr)
|
||||
}
|
||||
if len(tt.returnedGenericResourceExpanded) > 0 {
|
||||
mockVirtualMachinesClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.returnedVM, tt.wantErr)
|
||||
}
|
||||
_, log := testlog.New()
|
||||
mcf := newMockClientFactory(mockResourcesClient, mockVirtualMachinesClient)
|
||||
azureSideFetcher := &azureSideFetcher{
|
||||
log: log,
|
||||
resourceGroupName: "someResourceGroup",
|
||||
subscriptionDoc: subscriptionDoc,
|
||||
env: mockEnv,
|
||||
resourceClientFactory: mcf,
|
||||
virtualMachinesClientFactory: mcf,
|
||||
}
|
||||
azureClient := &azureClient{fetcher: azureSideFetcher, log: log}
|
||||
vmAllocationStatuses, err := azureClient.VMAllocationStatus(ctx)
|
||||
if !reflect.DeepEqual(vmAllocationStatuses, tt.wantOutput) {
|
||||
t.Error("Expected output", tt.wantOutput, "Got", vmAllocationStatuses)
|
||||
}
|
||||
if err != nil && err != tt.wantErr || err == nil && tt.wantErr != nil {
|
||||
t.Error("Expected", tt.wantErr, "Got", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@ type testPortal struct {
|
|||
portalLogHook *test.Hook
|
||||
}
|
||||
|
||||
func NewTestPortal(_env env.Core, dbOpenShiftClusters database.OpenShiftClusters, dbPortal database.Portal) *testPortal {
|
||||
func NewTestPortal(_env env.Interface, dbOpenShiftClusters database.OpenShiftClusters, dbPortal database.Portal, dbSubscription database.Subscriptions) *testPortal {
|
||||
_, portalAccessLog := testlog.New()
|
||||
portalLogHook, portalLog := testlog.New()
|
||||
auditHook, portalAuditLog := testlog.NewAudit()
|
||||
|
||||
l := listener.NewListener()
|
||||
p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, nil, nil, "", nil, nil, "", nil, nil, make([]byte, 32), nil, nonElevatedGroupIDs, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, nil, nil).(*portal)
|
||||
p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, nil, nil, "", nil, nil, "", nil, nil, make([]byte, 32), nil, nonElevatedGroupIDs, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, dbSubscription, nil, nil).(*portal)
|
||||
|
||||
return &testPortal{
|
||||
p: p,
|
||||
|
|
|
@ -23,7 +23,7 @@ func TestInfo(t *testing.T) {
|
|||
controller := gomock.NewController(t)
|
||||
defer controller.Finish()
|
||||
|
||||
_env := mock_env.NewMockCore(controller)
|
||||
_env := mock_env.NewMockInterface(controller)
|
||||
_env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false)
|
||||
_env.EXPECT().Location().AnyTimes().Return("eastus")
|
||||
_env.EXPECT().TenantID().AnyTimes().Return("00000000-0000-0000-0000-000000000001")
|
||||
|
@ -32,8 +32,9 @@ func TestInfo(t *testing.T) {
|
|||
|
||||
dbOpenShiftClusters, _ := testdatabase.NewFakeOpenShiftClusters()
|
||||
dbPortal, _ := testdatabase.NewFakePortal()
|
||||
dbSubscription, _ := testdatabase.NewFakeSubscriptions()
|
||||
|
||||
p := NewTestPortal(_env, dbOpenShiftClusters, dbPortal)
|
||||
p := NewTestPortal(_env, dbOpenShiftClusters, dbPortal, dbSubscription)
|
||||
defer p.Cleanup()
|
||||
err := p.Run(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -19,12 +19,15 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/gorilla/csrf"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/api"
|
||||
"github.com/Azure/ARO-RP/pkg/api/validate"
|
||||
"github.com/Azure/ARO-RP/pkg/database"
|
||||
"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
|
||||
"github.com/Azure/ARO-RP/pkg/env"
|
||||
frontendmiddleware "github.com/Azure/ARO-RP/pkg/frontend/middleware"
|
||||
"github.com/Azure/ARO-RP/pkg/metrics"
|
||||
|
@ -44,7 +47,7 @@ type Runnable interface {
|
|||
}
|
||||
|
||||
type portal struct {
|
||||
env env.Core
|
||||
env env.Interface
|
||||
audit *logrus.Entry
|
||||
log *logrus.Entry
|
||||
baseAccessLog *logrus.Entry
|
||||
|
@ -66,6 +69,7 @@ type portal struct {
|
|||
|
||||
dbPortal database.Portal
|
||||
dbOpenShiftClusters database.OpenShiftClusters
|
||||
dbSubscriptions database.Subscriptions
|
||||
|
||||
dialer proxy.Dialer
|
||||
|
||||
|
@ -77,7 +81,7 @@ type portal struct {
|
|||
m metrics.Emitter
|
||||
}
|
||||
|
||||
func NewPortal(env env.Core,
|
||||
func NewPortal(env env.Interface,
|
||||
audit *logrus.Entry,
|
||||
log *logrus.Entry,
|
||||
baseAccessLog *logrus.Entry,
|
||||
|
@ -96,6 +100,7 @@ func NewPortal(env env.Core,
|
|||
elevatedGroupIDs []string,
|
||||
dbOpenShiftClusters database.OpenShiftClusters,
|
||||
dbPortal database.Portal,
|
||||
dbSubscriptions database.Subscriptions,
|
||||
dialer proxy.Dialer,
|
||||
m metrics.Emitter,
|
||||
) Runnable {
|
||||
|
@ -122,6 +127,7 @@ func NewPortal(env env.Core,
|
|||
|
||||
dbOpenShiftClusters: dbOpenShiftClusters,
|
||||
dbPortal: dbPortal,
|
||||
dbSubscriptions: dbSubscriptions,
|
||||
|
||||
dialer: dialer,
|
||||
|
||||
|
@ -304,6 +310,7 @@ func (p *portal) aadAuthenticatedRoutes(r *mux.Router, prom *prometheus.Promethe
|
|||
r.Methods(http.MethodGet).Path("/api/{subscription}/{resourceGroup}/{clusterName}").HandlerFunc(p.clusterInfo)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/nodes").HandlerFunc(p.nodes)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/machines").HandlerFunc(p.machines)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/vmallocationstatus").HandlerFunc(p.VMAllocationStatus)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/machine-sets").HandlerFunc(p.machineSets)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}").HandlerFunc(p.clusterInfo)
|
||||
|
||||
|
@ -390,10 +397,39 @@ func (p *portal) makeFetcher(ctx context.Context, r *http.Request) (cluster.Fetc
|
|||
} else {
|
||||
dialer = p.dialer
|
||||
}
|
||||
|
||||
return cluster.NewFetchClient(p.log, dialer, doc)
|
||||
}
|
||||
|
||||
// makeAzureFetcher creates a cluster.AzureFetchClient suitable for use by the Portal REST API to fetch anything directly from Azure like VM Details etc.
|
||||
func (p *portal) makeAzureFetcher(ctx context.Context, r *http.Request) (cluster.AzureFetchClient, error) {
|
||||
apiVars := mux.Vars(r)
|
||||
|
||||
subscription := apiVars["subscription"]
|
||||
resourceGroup := apiVars["resourceGroup"]
|
||||
clusterName := apiVars["clusterName"]
|
||||
|
||||
resourceID :=
|
||||
strings.ToLower(
|
||||
fmt.Sprintf(
|
||||
"/subscriptions/%s/resourceGroups/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters/%s",
|
||||
subscription, resourceGroup, clusterName))
|
||||
if !validate.RxClusterID.MatchString(resourceID) {
|
||||
return nil, fmt.Errorf("invalid resource ID")
|
||||
}
|
||||
|
||||
doc, err := p.dbOpenShiftClusters.Get(ctx, resourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriptionDoc, err := p.getSubscriptionDocument(ctx, doc.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cluster.NewAzureFetchClient(p.log, doc, subscriptionDoc, p.env), nil
|
||||
}
|
||||
|
||||
func (p *portal) serve(path string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
asset, err := assets.EmbeddedFiles.ReadFile(path)
|
||||
|
@ -406,6 +442,19 @@ func (p *portal) serve(path string) func(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
}
|
||||
|
||||
func (p *portal) getSubscriptionDocument(ctx context.Context, key string) (*api.SubscriptionDocument, error) {
|
||||
r, err := azure.ParseResourceID(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc, err := p.dbSubscriptions.Get(ctx, r.SubscriptionID)
|
||||
if cosmosdb.IsErrorStatusCode(err, http.StatusNotFound) {
|
||||
return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidSubscriptionState, "", "Request is not allowed in unregistered subscription '%s'.", r.SubscriptionID)
|
||||
}
|
||||
|
||||
return doc, err
|
||||
}
|
||||
|
||||
func (p *portal) internalServerError(w http.ResponseWriter, err error) {
|
||||
p.log.Warn(err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestSecurity(t *testing.T) {
|
|||
controller := gomock.NewController(t)
|
||||
defer controller.Finish()
|
||||
|
||||
_env := mock_env.NewMockCore(controller)
|
||||
_env := mock_env.NewMockInterface(controller)
|
||||
_env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false)
|
||||
_env.EXPECT().Location().AnyTimes().Return("eastus")
|
||||
_env.EXPECT().TenantID().AnyTimes().Return("00000000-0000-0000-0000-000000000001")
|
||||
|
@ -74,6 +74,7 @@ func TestSecurity(t *testing.T) {
|
|||
|
||||
dbOpenShiftClusters, _ := testdatabase.NewFakeOpenShiftClusters()
|
||||
dbPortal, _ := testdatabase.NewFakePortal()
|
||||
dbSubscription, _ := testdatabase.NewFakeSubscriptions()
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(servercerts[0])
|
||||
|
@ -90,7 +91,7 @@ func TestSecurity(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, sshl, nil, "", serverkey, servercerts, "", nil, nil, make([]byte, 32), sshkey, nil, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, nil, &noop.Noop{})
|
||||
p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, sshl, nil, "", serverkey, servercerts, "", nil, nil, make([]byte, 32), sshkey, nil, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, dbSubscription, nil, &noop.Noop{})
|
||||
go func() {
|
||||
err := p.Run(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -6735,7 +6735,7 @@
|
|||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
|
@ -7309,8 +7309,7 @@
|
|||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"version": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
|
@ -19679,7 +19679,7 @@
|
|||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -19871,7 +19871,7 @@
|
|||
"requires": {
|
||||
"@humanwhocodes/object-schema": "^1.2.1",
|
||||
"debug": "^4.1.1",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/module-importer": {
|
||||
|
@ -20569,7 +20569,7 @@
|
|||
"error-stack-parser": "^2.0.6",
|
||||
"find-up": "^5.0.0",
|
||||
"html-entities": "^2.1.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.3",
|
||||
"schema-utils": "^3.0.0",
|
||||
"source-map": "^0.7.3"
|
||||
}
|
||||
|
@ -21626,7 +21626,7 @@
|
|||
"integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"regex-parser": "^2.2.11"
|
||||
}
|
||||
},
|
||||
|
@ -21938,7 +21938,7 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"schema-utils": "^2.6.5"
|
||||
},
|
||||
|
@ -23534,7 +23534,7 @@
|
|||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.4.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.1",
|
||||
"regexpp": "^3.2.0",
|
||||
|
@ -23781,7 +23781,7 @@
|
|||
"has": "^1.0.3",
|
||||
"is-core-module": "^2.8.1",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.values": "^1.1.5",
|
||||
"resolve": "^1.22.0",
|
||||
"tsconfig-paths": "^3.14.1"
|
||||
|
@ -23839,7 +23839,7 @@
|
|||
"has": "^1.0.3",
|
||||
"jsx-ast-utils": "^3.3.2",
|
||||
"language-tags": "^1.0.5",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -23867,7 +23867,7 @@
|
|||
"eslint-utils": "^3.0.0",
|
||||
"ignore": "^5.1.1",
|
||||
"is-core-module": "^2.11.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.1",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
|
@ -23903,7 +23903,7 @@
|
|||
"doctrine": "^2.1.0",
|
||||
"estraverse": "^5.3.0",
|
||||
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.entries": "^1.1.5",
|
||||
"object.fromentries": "^2.0.5",
|
||||
"object.hasown": "^1.1.1",
|
||||
|
@ -24330,7 +24330,7 @@
|
|||
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -24340,7 +24340,7 @@
|
|||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"filesize": {
|
||||
|
@ -24454,7 +24454,7 @@
|
|||
"fs-extra": "^9.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
|
@ -24701,7 +24701,7 @@
|
|||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
|
@ -25521,7 +25521,7 @@
|
|||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
"filelist": "^1.0.1",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
|
@ -29055,7 +29055,7 @@
|
|||
"gzip-size": "^6.0.0",
|
||||
"immer": "^9.0.7",
|
||||
"is-root": "^2.1.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^3.2.0",
|
||||
"open": "^8.4.0",
|
||||
"pkg-up": "^3.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
|
@ -29158,7 +29158,7 @@
|
|||
"requires": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"babel-jest": "^27.4.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.8",
|
||||
|
@ -29252,7 +29252,7 @@
|
|||
"integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
|
@ -29421,7 +29421,7 @@
|
|||
"requires": {
|
||||
"adjust-sourcemap-loader": "^4.0.0",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"postcss": "^7.0.35",
|
||||
"source-map": "0.6.1"
|
||||
},
|
||||
|
@ -30296,7 +30296,7 @@
|
|||
"requires": {
|
||||
"@istanbuljs/schema": "^0.1.2",
|
||||
"glob": "^7.1.4",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
|
|
|
@ -53,12 +53,13 @@ export class MachinesComponent extends Component<MachinesComponentProps, IMachin
|
|||
}
|
||||
|
||||
private extractCurrentMachine = (machineName: string): IMachine => {
|
||||
let currentMachine = this.state.machines[0]
|
||||
this.state.machines.forEach((machine: IMachine) => {
|
||||
if (machine.name === machineName) {
|
||||
return machine
|
||||
currentMachine = machine
|
||||
}
|
||||
})
|
||||
return this.state.machines[0]
|
||||
return currentMachine
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -13,16 +13,19 @@ export declare interface IMachinesList {
|
|||
name?: string;
|
||||
status: string;
|
||||
createdTime: string;
|
||||
allocationStatus?: string;
|
||||
}
|
||||
|
||||
interface MachinesListComponentProps {
|
||||
machines: any
|
||||
clusterName: string
|
||||
vmAllocationStatus: Map<string, string>
|
||||
}
|
||||
|
||||
export interface IMachinesListState {
|
||||
machines: IMachine[]
|
||||
clusterName: string
|
||||
vmAllocationStatus: Map<string, string>
|
||||
}
|
||||
|
||||
export class MachinesListComponent extends React.Component<MachinesListComponentProps, IMachinesListState> {
|
||||
|
@ -33,20 +36,22 @@ export class MachinesListComponent extends React.Component<MachinesListComponent
|
|||
this.state = {
|
||||
machines: this.props.machines,
|
||||
clusterName: this.props.clusterName,
|
||||
vmAllocationStatus: this.props.vmAllocationStatus
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<MachinesListHelperComponent machines={this.state.machines} clusterName={this.state.clusterName}/>
|
||||
<MachinesListHelperComponent vmAllocationStatus={this.state.vmAllocationStatus} machines={this.state.machines} clusterName={this.state.clusterName}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function MachinesListHelperComponent(props: {
|
||||
machines: any,
|
||||
clusterName: string
|
||||
clusterName: string,
|
||||
vmAllocationStatus: Map<string, string>
|
||||
}) {
|
||||
const [columns, setColumns] = useState<IColumn[]>([
|
||||
{
|
||||
|
@ -74,6 +79,17 @@ export function MachinesListHelperComponent(props: {
|
|||
isSortedDescending: false,
|
||||
showSortIconWhenUnsorted: true,
|
||||
},
|
||||
{
|
||||
key: "allocationStatus",
|
||||
name: "Allocation State",
|
||||
fieldName: "allocationStatus",
|
||||
minWidth: 120,
|
||||
maxWidth: 120,
|
||||
isResizable: true,
|
||||
isSorted: true,
|
||||
isSortedDescending: false,
|
||||
showSortIconWhenUnsorted: true,
|
||||
},
|
||||
{
|
||||
key: "createdTime",
|
||||
name: "Created Time",
|
||||
|
@ -97,15 +113,52 @@ export function MachinesListHelperComponent(props: {
|
|||
setMachinesList(createMachinesList(props.machines))
|
||||
}, [props.machines] );
|
||||
|
||||
|
||||
// For updating machinesList with VM Allocation Status
|
||||
useEffect(() => {
|
||||
const initialMachineLength: number = machinesList.length
|
||||
const fetchError: string = "FetchError"
|
||||
if (initialMachineLength > 0) {
|
||||
let localMachineList = machinesList
|
||||
for (let i=0; i < localMachineList.length; i++) {
|
||||
let allocationStatus: string = props.vmAllocationStatus.get(localMachineList[i].name!)!
|
||||
let r: string = allocationStatus.slice(11, 12).toUpperCase() + allocationStatus.slice(12, allocationStatus.length)
|
||||
localMachineList[i].allocationStatus = r
|
||||
}
|
||||
setMachinesList(localMachineList)
|
||||
} else {
|
||||
let localMachineList: IMachinesList[] = []
|
||||
props.vmAllocationStatus.forEach((allocationStatus, machineName) => {
|
||||
let allocationStatusShort: string = allocationStatus.slice(11, 12).toUpperCase() + allocationStatus.slice(12, allocationStatus.length)
|
||||
localMachineList.push({name: machineName, status: fetchError, allocationStatus: allocationStatusShort, createdTime: fetchError,})
|
||||
})
|
||||
setMachinesList(localMachineList)
|
||||
}
|
||||
|
||||
const newColumns: IColumn[] = columns.slice();
|
||||
|
||||
newColumns.forEach(col => {
|
||||
col.onColumnClick = _onColumnClick
|
||||
if (col.key == "machineName" && initialMachineLength == 0) {
|
||||
col.onRender = undefined
|
||||
} else if (col.key == "machineName") {
|
||||
col.onRender = (item: IMachinesList) => (
|
||||
<Link onClick={() => _onMachineInfoLinkClick(item.name!)}>{item.name}</Link>
|
||||
)
|
||||
}
|
||||
})
|
||||
setColumns(newColumns)
|
||||
|
||||
}, [props.vmAllocationStatus])
|
||||
|
||||
// For Shimmer
|
||||
useEffect(() => {
|
||||
const newColumns: IColumn[] = columns.slice();
|
||||
newColumns.forEach(col => {
|
||||
col.onColumnClick = _onColumnClick
|
||||
})
|
||||
setColumns(newColumns)
|
||||
|
||||
if (machinesList.length > 0) {
|
||||
if (machinesList.length > 0 || props.vmAllocationStatus.keys.length > 0) {
|
||||
SetShimmerVisibility(false)
|
||||
}
|
||||
|
||||
|
@ -146,12 +199,11 @@ export function MachinesListHelperComponent(props: {
|
|||
})
|
||||
|
||||
setColumns(newColumns)
|
||||
//setMachinesList(machineLocal)
|
||||
}
|
||||
|
||||
function createMachinesList(machines: IMachine[]): IMachinesList[] {
|
||||
return machines.map(machine => {
|
||||
return {name: machine.name, status: machine.status, createdTime: machine.createdTime}
|
||||
return {name: machine.name, status: machine.status, allocationStatus: "Loading...", createdTime: machine.createdTime,}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { FetchMachines } from '../Request';
|
||||
import { FetchMachines, FetchVMAllocationStatus } from '../Request';
|
||||
import { ICluster } from "../App"
|
||||
import { MachinesListComponent } from './MachinesList';
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react';
|
||||
import { machinesKey } from "../ClusterDetail";
|
||||
|
||||
|
||||
export interface IMachine {
|
||||
name?: string,
|
||||
createdTime: string,
|
||||
|
@ -23,6 +24,7 @@ export function MachinesWrapper(props: {
|
|||
loaded: boolean
|
||||
}) {
|
||||
const [data, setData] = useState<any>([])
|
||||
const [vmAllocationstatus, setVmallocationstatus] = useState<any>([])
|
||||
const [error, setError] = useState<AxiosResponse | null>(null)
|
||||
const state = useRef<MachinesListComponent>(null)
|
||||
const [fetching, setFetching] = useState("")
|
||||
|
@ -74,6 +76,17 @@ export function MachinesWrapper(props: {
|
|||
}
|
||||
}
|
||||
|
||||
const updateVMAllocationStatusData = (newData: any) => {
|
||||
let map = new Map<string, string>()
|
||||
for (var key in newData) {
|
||||
map.set(key, newData[key])
|
||||
}
|
||||
setVmallocationstatus(map)
|
||||
if (state && state.current) {
|
||||
state.current.setState({ vmAllocationStatus: map })
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onData = (result: AxiosResponse | null) => {
|
||||
if (result?.status === 200) {
|
||||
|
@ -84,20 +97,29 @@ export function MachinesWrapper(props: {
|
|||
setFetching(props.currentCluster.name)
|
||||
}
|
||||
|
||||
const onVMAllocationStatusData = (result: AxiosResponse | null) => {
|
||||
if (result?.status === 200) {
|
||||
updateVMAllocationStatusData(result.data)
|
||||
} else {
|
||||
setError(result)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.detailPanelSelected.toLowerCase() == machinesKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != "") {
|
||||
setFetching("FETCHING")
|
||||
FetchMachines(props.currentCluster).then(onData)
|
||||
FetchVMAllocationStatus(props.currentCluster).then(onVMAllocationStatusData)
|
||||
}
|
||||
}, [data, props.loaded, props.detailPanelSelected])
|
||||
}, [data, props.loaded, props.detailPanelSelected, vmAllocationstatus])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<MachinesListComponent machines={data!} ref={state} clusterName={props.currentCluster != null ? props.currentCluster.name : ""} />
|
||||
<MachinesListComponent vmAllocationStatus={vmAllocationstatus} machines={data!} ref={state} clusterName={props.currentCluster != null ? props.currentCluster.name : ""} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
|
|
|
@ -64,6 +64,17 @@ export const FetchMachines = async (cluster: ICluster): Promise<AxiosResponse |
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchVMAllocationStatus = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name + "/vmallocationstatus")
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
return OnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
export const FetchMachineSets = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
|
|
Загрузка…
Ссылка в новой задаче