From 61601929c556c22cc3b574c4c15dd0d62dacb528 Mon Sep 17 00:00:00 2001 From: Antony Natale Date: Tue, 21 May 2024 20:27:17 +0000 Subject: [PATCH] adds db calls for subscriptions --- .../configurations/svc-cluster.bicepparam | 2 +- frontend/cluster.json | 31 +++++++++ frontend/database.go | 65 +++++++++++++++++-- frontend/document.go | 32 +++++++++ frontend/frontend.go | 55 +++++++++++++++- frontend/hcpclusterdocument.go | 16 ----- 6 files changed, 178 insertions(+), 23 deletions(-) create mode 100644 frontend/cluster.json create mode 100644 frontend/document.go delete mode 100644 frontend/hcpclusterdocument.go diff --git a/dev-infrastructure/configurations/svc-cluster.bicepparam b/dev-infrastructure/configurations/svc-cluster.bicepparam index 8a941650e0..3d765d69d5 100644 --- a/dev-infrastructure/configurations/svc-cluster.bicepparam +++ b/dev-infrastructure/configurations/svc-cluster.bicepparam @@ -8,7 +8,7 @@ param podSubnetPrefix = '10.128.64.0/18' param enablePrivateCluster = false param persist = false param disableLocalAuth = false -param deployFrontendCosmos = false +param deployFrontendCosmos = true param deployMaestroInfra = false param maestroNamespace = 'maestro' param workloadIdentities = items({ diff --git a/frontend/cluster.json b/frontend/cluster.json new file mode 100644 index 0000000000..8b4a3dc2c2 --- /dev/null +++ b/frontend/cluster.json @@ -0,0 +1,31 @@ +{ + "properties": { + "spec": { + "version": { + "id": "1.19.0", + "channelGroup": "stable" + }, + "dns": { + "baseDomainPrefix": "xyz" + }, + "network": { + "podCidr": "10.10.0.0/24", + "serviceCidr": "10.10.0.0/24", + "machineCidr": "10.10.0.0/24" + }, + "console": {}, + "api": { + "visibility": "public" + }, + "proxy": {}, + "platform": { + "managedResourceGroup": "xyz", + "subnetId": "/subscriptions/xyz/resourceGroups/xyz/providers/Microsoft.Network/virtualNetworks/xyz/subnets/xyz", + "outboundType": "loadBalancer", + "networkSecurityGroupId": "/subscriptions/xyz/resourceGroups/xyz/providers/Microsoft.Network/networkSecurityGroups/xyz", + "etcdEncryptionSetId": "/subscriptions/xyz/resourceGroups/xyz/providers/Microsoft.Compute/encryptionSets/xyz" + }, + "externalAuth": {} + } + } +} \ No newline at end of file diff --git a/frontend/database.go b/frontend/database.go index a1b99e0b21..d38cd47332 100644 --- a/frontend/database.go +++ b/frontend/database.go @@ -12,6 +12,9 @@ import ( const ( clustersContainer = "Clusters" + subsContainer = "Subscriptions" + billingContainer = "Billing" + asyncContainer = "AsyncOperations" ) // DBClient defines the needed values to perform CRUD operations against the async DB @@ -75,7 +78,7 @@ func (d *DBClient) DBConnectionTest(ctx context.Context) (string, error) { return result.DatabaseProperties.ID, nil } -// GetCluster retreives a cluster document from async DB using resource ID +// GetClusterDoc retreives a cluster document from async DB using resource ID func (d *DBClient) GetClusterDoc(ctx context.Context, resourceID string, partitionKey string) (*HCPOpenShiftClusterDocument, bool, error) { container, err := d.client.NewContainer(d.config.DBName, clustersContainer) if err != nil { @@ -109,10 +112,9 @@ func (d *DBClient) GetClusterDoc(ctx context.Context, resourceID string, partiti return doc, true, nil } return nil, false, nil - } -// SetCluster creates/updates a cluster document in the async DB during cluster creation/patching +// SetClusterDoc creates/updates a cluster document in the async DB during cluster creation/patching func (d *DBClient) SetClusterDoc(ctx context.Context, doc *HCPOpenShiftClusterDocument) error { data, err := json.Marshal(doc) if err != nil { @@ -132,7 +134,7 @@ func (d *DBClient) SetClusterDoc(ctx context.Context, doc *HCPOpenShiftClusterDo return nil } -// DeleteCluster removes a cluter document from the async DB using resource ID +// DeleteClusterDoc removes a cluter document from the async DB using resource ID func (d *DBClient) DeleteClusterDoc(ctx context.Context, resourceID string, partitionKey string) error { doc, found, err := d.GetClusterDoc(ctx, resourceID, partitionKey) if !found { @@ -153,3 +155,58 @@ func (d *DBClient) DeleteClusterDoc(ctx context.Context, resourceID string, part } return nil } + +// GetSubscriptionDoc retreives a subscription document from async DB using the subscription ID +func (d *DBClient) GetSubscriptionDoc(ctx context.Context, partitionKey string) (*SubscriptionDocument, bool, error) { + container, err := d.client.NewContainer(d.config.DBName, subsContainer) + if err != nil { + return nil, false, err + } + + query := "SELECT * FROM c WHERE c.partitionKey = @partitionKey" + opt := azcosmos.QueryOptions{ + PageSizeHint: 1, + QueryParameters: []azcosmos.QueryParameter{{Name: "@partitionKey", Value: partitionKey}}, + } + + pk := azcosmos.NewPartitionKeyString(partitionKey) + queryPager := container.NewQueryItemsPager(query, pk, &opt) + + var doc *SubscriptionDocument + for queryPager.More() { + queryResponse, err := queryPager.NextPage(ctx) + if err != nil { + return nil, false, err + } + + for _, item := range queryResponse.Items { + err = json.Unmarshal(item, &doc) + if err != nil { + return nil, false, err + } + } + } + if doc != nil { + return doc, true, nil + } + return nil, false, nil +} + +// SetClusterDoc creates/updates a subscription document in the async DB during cluster creation/patching +func (d *DBClient) SetSubscriptionDoc(ctx context.Context, doc *SubscriptionDocument) error { + data, err := json.Marshal(doc) + if err != nil { + return err + } + + container, err := d.client.NewContainer(d.config.DBName, subsContainer) + if err != nil { + return err + } + + _, err = container.UpsertItem(ctx, azcosmos.NewPartitionKeyString(doc.PartitionKey), data, nil) + if err != nil { + return err + } + return nil +} diff --git a/frontend/document.go b/frontend/document.go new file mode 100644 index 0000000000..665d5b790b --- /dev/null +++ b/frontend/document.go @@ -0,0 +1,32 @@ +package main + +import "github.com/Azure/ARO-HCP/internal/api/arm" + +// HCPOpenShiftClusterDocument represents an HCP OpenShift cluster document. +type HCPOpenShiftClusterDocument struct { + ID string `json:"id,omitempty"` + Key string `json:"key,omitempty"` + PartitionKey string `json:"partitionKey,omitempty"` + ClusterID string `json:"clusterid,omitempty"` + + // Values provided by Cosmos after doc creation + ResourceID string `json:"_rid,omitempty"` + Self string `json:"_self,omitempty"` + ETag string `json:"_etag,omitempty"` + Attachments string `json:"_attachments,omitempty"` + Timestamp int `json:"_ts,omitempty"` +} + +// SubscriptionDocument represents an Azure Subscription document. +type SubscriptionDocument struct { + ID string `json:"id,omitempty"` + PartitionKey string `json:"partitionKey,omitempty"` + Subscription *arm.Subscription `json:"subscription,omitempty"` + + // Values provided by Cosmos after doc creation + ResourceID string `json:"_rid,omitempty"` + Self string `json:"_self,omitempty"` + ETag string `json:"_etag,omitempty"` + Attachments string `json:"_attachments,omitempty"` + Timestamp int `json:"_ts,omitempty"` +} diff --git a/frontend/frontend.go b/frontend/frontend.go index fbd69b0a2e..ac4384b4c1 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -88,7 +88,8 @@ func NewFrontend(logger *slog.Logger, listener net.Listener, emitter metrics.Emi mux.HandleFunc("/", f.NotFound) mux.HandleFunc(MuxPattern(http.MethodGet, "healthz", "ready"), f.HealthzReady) // TODO: determine where in the auth chain we should allow for this endpoint to be called by ARM - mux.HandleFunc(MuxPattern(http.MethodPut, PatternSubscriptions), f.ArmSubscriptionAction) + mux.HandleFunc(MuxPattern(http.MethodGet, PatternSubscriptions), f.ArmSubscriptionGet) + mux.HandleFunc(MuxPattern(http.MethodPut, PatternSubscriptions), f.ArmSubscriptionPut) // Expose Prometheus metrics endpoint mux.Handle(MuxPattern(http.MethodGet, "metrics"), promhttp.Handler()) @@ -411,7 +412,33 @@ func (f *Frontend) ArmResourceAction(writer http.ResponseWriter, request *http.R writer.WriteHeader(http.StatusOK) } -func (f *Frontend) ArmSubscriptionAction(writer http.ResponseWriter, request *http.Request) { +func (f *Frontend) ArmSubscriptionGet(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() + subId := request.PathValue(PathSegmentSubscriptionID) + + doc, found, err := f.dbClient.GetSubscriptionDoc(ctx, subId) + if err != nil { + f.logger.Error("failed to get document for subscription %s: %v", subId, err) + } + if !found { + f.logger.Error(fmt.Sprintf("document not found for subscription %s", subId)) + } + + resp, err := json.Marshal(&doc) + if err != nil { + f.logger.Error(err.Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + _, err = writer.Write(resp) + if err != nil { + f.logger.Error(err.Error()) + } + + writer.WriteHeader(http.StatusOK) +} + +func (f *Frontend) ArmSubscriptionPut(writer http.ResponseWriter, request *http.Request) { ctx := request.Context() body, err := BodyFromContext(ctx) @@ -432,6 +459,28 @@ func (f *Frontend) ArmSubscriptionAction(writer http.ResponseWriter, request *ht subId := request.PathValue(PathSegmentSubscriptionID) f.cache.SetSubscription(subId, &subscription) + var doc *SubscriptionDocument + doc, found, err := f.dbClient.GetSubscriptionDoc(ctx, subId) + if err != nil { + f.logger.Error("failed to fetch document for %s: %v", subId, err) + arm.WriteInternalServerError(writer) + return + } + if !found { + f.logger.Info(fmt.Sprintf("existing document not found for subscription - creating one for %s", subId)) + doc = &SubscriptionDocument{ + ID: uuid.New().String(), + PartitionKey: subId, + Subscription: &subscription, + } + } else { + doc.Subscription = &subscription + } + err = f.dbClient.SetSubscriptionDoc(ctx, doc) + if err != nil { + f.logger.Error("failed to create document for subscription %s: %v", subId, err) + } + resp, err := json.Marshal(subscription) if err != nil { f.logger.Error(err.Error()) @@ -442,6 +491,8 @@ func (f *Frontend) ArmSubscriptionAction(writer http.ResponseWriter, request *ht if err != nil { f.logger.Error(err.Error()) } + + writer.WriteHeader(http.StatusCreated) } func (f *Frontend) ArmDeploymentPreflight(writer http.ResponseWriter, request *http.Request) { diff --git a/frontend/hcpclusterdocument.go b/frontend/hcpclusterdocument.go deleted file mode 100644 index 22ce5b7ce8..0000000000 --- a/frontend/hcpclusterdocument.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -// HCPOpenShiftClusterDocument represents an HCP OpenShift cluster document. -type HCPOpenShiftClusterDocument struct { - ID string `json:"id,omitempty"` - Key string `json:"key,omitempty"` - PartitionKey string `json:"partitionKey,omitempty"` - ClusterID string `json:"clusterid,omitempty"` - - // Values provided by Cosmos after doc creation - ResourceID string `json:"_rid,omitempty"` - Self string `json:"_self,omitempty"` - ETag string `json:"_etag,omitempty"` - Attachments string `json:"_attachments,omitempty"` - Timestamp int `json:"_ts,omitempty"` -}