Skip to content

Commit

Permalink
[FEATURE] Operator: CAPTenantOutput introduced
Browse files Browse the repository at this point in the history
Introduce a new custom resource that can be used to provide additional
data to saas provisioning callbacks.
This is a data only resource that can be created via Tenant Operation(s)
and will be merged eventually into the additional data during
subscription callback handling in the subscription server.
  • Loading branch information
Pavan-SAP committed Sep 2, 2024
1 parent fcfc681 commit 6dfcf9d
Show file tree
Hide file tree
Showing 27 changed files with 1,204 additions and 19 deletions.
49 changes: 44 additions & 5 deletions cmd/server/internal/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ type OAuthResponse struct {
AccessToken string `json:"access_token"`
}

type tenantInfo struct {
tenantId string
tenantSubDomain string
}

func (s *SubscriptionHandler) CreateTenant(req *http.Request) *Result {
util.LogInfo("Create Tenant triggered", TenantProvisioning, "CreateTenant", nil)
var created = false
Expand Down Expand Up @@ -181,7 +186,8 @@ func (s *SubscriptionHandler) CreateTenant(req *http.Request) *Result {

// TODO: consider retrying tenant creation if it is in Error state
if tenant != nil {
s.initializeCallback(tenant.Name, ca, saasData, req, reqType["subscribedSubdomain"].(string), true)
tenantIn := tenantInfo{tenantId: reqType["subscribedTenantId"].(string), tenantSubDomain: reqType["subscribedSubdomain"].(string)}
s.initializeCallback(tenant.Name, ca, saasData, req, tenantIn, true)
}

// Tenant created/exists
Expand Down Expand Up @@ -277,8 +283,8 @@ func (s *SubscriptionHandler) DeleteTenant(req *http.Request) *Result {
return &Result{Tenant: nil, Message: err.Error()}
}
}

s.initializeCallback(tenantName, ca, saasData, req, reqType["subscribedSubdomain"].(string), false)
tenantIn := tenantInfo{tenantId: reqType["subscribedTenantId"].(string), tenantSubDomain: reqType["subscribedSubdomain"].(string)}
s.initializeCallback(tenantName, ca, saasData, req, tenantIn, false)

return &Result{Tenant: tenant, Message: ResourceDeleted}
}
Expand Down Expand Up @@ -321,12 +327,12 @@ func (s *SubscriptionHandler) checkAuthorization(authHeader string, saasData *ut
return nil
}

func (s *SubscriptionHandler) initializeCallback(tenantName string, ca *v1alpha1.CAPApplication, saasData *util.SaasRegistryCredentials, req *http.Request, tenantSubDomain string, isProvisioning bool) {
func (s *SubscriptionHandler) initializeCallback(tenantName string, ca *v1alpha1.CAPApplication, saasData *util.SaasRegistryCredentials, req *http.Request, tenantIn tenantInfo, isProvisioning bool) {
subscriptionDomain := ca.Annotations[AnnotationSubscriptionDomain]
if subscriptionDomain == "" {
subscriptionDomain = ca.Spec.Domains.Primary
}
appUrl := "https://" + tenantSubDomain + "." + subscriptionDomain
appUrl := "https://" + tenantIn.tenantSubDomain + "." + subscriptionDomain
asyncCallbackPath := req.Header.Get("STATUS_CALLBACK")
util.LogInfo("Callback initialized", TenantProvisioning, ca, nil, "subscription URL", appUrl, "async callback path", asyncCallbackPath, "tenantName", tenantName)

Expand Down Expand Up @@ -356,6 +362,11 @@ func (s *SubscriptionHandler) initializeCallback(tenantName string, ca *v1alpha1
additionalOutput = nil
}
}
// Add tenant data to the additional output if it exists
err := s.enrichAdditionalOutput(ca.Namespace, tenantIn.tenantId, additionalOutput)
if err != nil {
util.LogError(err, "Error updating tenant data", step, ca, nil, "tenantId", tenantIn.tenantId)
}
} else {
additionalOutput = nil
}
Expand All @@ -365,6 +376,34 @@ func (s *SubscriptionHandler) initializeCallback(tenantName string, ca *v1alpha1
util.LogInfo("Waiting for async saas callback after checks...", step, ca, nil, "tenantName", tenantName)
}

func (s *SubscriptionHandler) enrichAdditionalOutput(namespace string, tenantId string, additionalOutput *map[string]any) error {
labelSelector, err := labels.ValidatedSelectorFromSet(map[string]string{
LabelTenantId: tenantId,
})
if err != nil {
return err
}

tenantDataList, err := s.Clientset.SmeV1alpha1().CAPTenantOutputs(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()})
if err != nil {
return err
}

for _, tenantData := range tenantDataList.Items {
// Update relevant data from each CAPTenantOutput to saas callback additional output
tenantDataOutput := &map[string]any{}
err = json.Unmarshal([]byte(tenantData.Spec.SubscriptionCallbackData), tenantDataOutput)
if err != nil {
return err
}
// merge tenant data output into additional output
for k, v := range *tenantDataOutput {
(*additionalOutput)[k] = v
}
}
return nil
}

func (s *SubscriptionHandler) checkCAPTenantStatus(ctx context.Context, tenantNamespace string, tenantName string, provisioning bool, callbackTimeoutMs string) bool {
asyncCallbackTimeout := 15 * time.Minute
if callbackTimeoutMs != "" {
Expand Down
39 changes: 30 additions & 9 deletions cmd/server/internal/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ const (
tenantId = "012012012-1234-1234-123456"
)

func setup(ca *v1alpha1.CAPApplication, cat *v1alpha1.CAPTenant, client *http.Client) *SubscriptionHandler {
func setup(ca *v1alpha1.CAPApplication, cat *v1alpha1.CAPTenant, ctout *v1alpha1.CAPTenantOutput, client *http.Client) *SubscriptionHandler {
crdObjects := []runtime.Object{}
if ca != nil {
crdObjects = append(crdObjects, ca)
}
if cat != nil {
crdObjects = append(crdObjects, cat)
}
if ctout != nil {
crdObjects = append(crdObjects, ctout)
}

subHandler := NewSubscriptionHandler(fake.NewSimpleClientset(crdObjects...), k8sfake.NewSimpleClientset(createSecrets()...))
if client != nil {
Expand Down Expand Up @@ -221,7 +224,7 @@ func TestMain(m *testing.M) {
func Test_IncorrectMethod(t *testing.T) {
res := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPatch, RequestPath, strings.NewReader(`{"foo": "bar"}`))
subHandler := setup(nil, nil, nil)
subHandler := setup(nil, nil, nil, nil)
subHandler.HandleRequest(res, req)
if res.Code != http.StatusMethodNotAllowed {
t.Errorf("Expected status '%d', received '%d'", http.StatusMethodNotAllowed, res.Code)
Expand Down Expand Up @@ -251,6 +254,7 @@ func Test_provisioning(t *testing.T) {
invalidAdditionalData bool
withSecretKey bool
existingTenant bool
existingTenantOutput bool
expectedStatusCode int
expectedResponse Result
}{
Expand Down Expand Up @@ -304,6 +308,19 @@ func Test_provisioning(t *testing.T) {
Message: ResourceCreated,
},
},
{
name: "Provisioning Request valid with additional data and existing tenant and existing tenant output",
method: http.MethodPut,
body: `{"subscriptionAppName":"` + appName + `","globalAccountGUID":"` + globalAccountId + `","subscribedTenantId":"` + tenantId + `","subscribedSubdomain":"` + subDomain + `"}`,
createCROs: true,
withAdditionalData: true,
existingTenant: true,
existingTenantOutput: true,
expectedStatusCode: http.StatusAccepted,
expectedResponse: Result{
Message: ResourceCreated,
},
},
{
name: "Provisioning Request valid with invalid additional data and existing tenant",
method: http.MethodPut,
Expand Down Expand Up @@ -334,6 +351,7 @@ func Test_provisioning(t *testing.T) {
t.Run(testData.name, func(t *testing.T) {
var ca *v1alpha1.CAPApplication
var cat *v1alpha1.CAPTenant
var ctout *v1alpha1.CAPTenantOutput
if testData.createCROs {
ca = createCA()
if testData.withAdditionalData {
Expand All @@ -347,11 +365,14 @@ func Test_provisioning(t *testing.T) {
if testData.existingTenant {
cat = createCAT(testData.withAdditionalData)
}
if testData.existingTenantOutput {
ctout = &v1alpha1.CAPTenantOutput{ObjectMeta: v1.ObjectMeta{Name: caName + "-provider", Namespace: v1.NamespaceDefault, Labels: map[string]string{LabelTenantId: tenantId}}, Spec: v1alpha1.CAPTenantOutputSpec{SubscriptionCallbackData: "{\"foo3\":\"bar3\"}"}}
}
client, tokenString, err := SetupValidTokenAndIssuerForSubscriptionTests("appname!b14")
if err != nil {
t.Fatal(err.Error())
}
subHandler := setup(ca, cat, client)
subHandler := setup(ca, cat, ctout, client)

res := httptest.NewRecorder()
req := httptest.NewRequest(testData.method, RequestPath, strings.NewReader(testData.body))
Expand Down Expand Up @@ -446,7 +467,7 @@ func Test_deprovisioning(t *testing.T) {
if err != nil {
t.Fatal(err.Error())
}
subHandler := setup(ca, cat, client)
subHandler := setup(ca, cat, nil, client)

res := httptest.NewRecorder()
req := httptest.NewRequest(testData.method, RequestPath, strings.NewReader(testData.body))
Expand Down Expand Up @@ -614,7 +635,7 @@ func TestAsyncCallback(t *testing.T) {
saasData.CredentialType = p.useCredentialType
t.Run(p.testName, func(t *testing.T) {
client := createCallbackTestServer(context.TODO(), t, &p)
subHandler := setup(nil, nil, client)
subHandler := setup(nil, nil, nil, client)
subHandler.handleAsyncCallback(
ctx,
saasData,
Expand All @@ -631,7 +652,7 @@ func TestAsyncCallback(t *testing.T) {
func TestCheckTenantStatusContextCancellationAsyncTimeout(t *testing.T) {
execTestsWithBLI(t, "Check Tenant Status Context Cancellation AsyncTimeout", []string{"ERP4SMEPREPWORKAPPPLAT-2240"}, func(t *testing.T) {
// test context cancellation (like deadline)
subHandler := setup(nil, nil, nil)
subHandler := setup(nil, nil, nil, nil)
notify := make(chan bool)
go func() {
r := subHandler.checkCAPTenantStatus(context.Background(), "default", "test-cat", true, "4000")
Expand All @@ -654,7 +675,7 @@ func TestCheckTenantStatusContextCancellationAsyncTimeout(t *testing.T) {
func TestCheckTenantStatusTenantReady(t *testing.T) {
// test context cancellation (like deadline)
cat := createCAT(true)
subHandler := setup(nil, cat, nil)
subHandler := setup(nil, cat, nil, nil)
r := subHandler.checkCAPTenantStatus(context.TODO(), cat.Namespace, cat.Name, true, "")

if r != true {
Expand All @@ -666,7 +687,7 @@ func TestCheckTenantStatusWithCallbacktimeout(t *testing.T) {
execTestsWithBLI(t, "Check Tenant Status With Callback timeout", []string{"ERP4SMEPREPWORKAPPPLAT-2240"}, func(t *testing.T) {
// test context cancellation (like deadline)
cat := createCAT(false)
subHandler := setup(nil, cat, nil)
subHandler := setup(nil, cat, nil, nil)
r := subHandler.checkCAPTenantStatus(context.TODO(), cat.Namespace, cat.Name, true, "4000")

if r != false {
Expand All @@ -680,7 +701,7 @@ func TestMultiXSUAA(t *testing.T) {
// CA without "sme.sap.com/primary-xsuaa" annotation
ca := createCA()

subHandler := setup(ca, nil, nil)
subHandler := setup(ca, nil, nil, nil)
uaaCreds := subHandler.getXSUAADetails(ca, "Test")

if uaaCreds.AuthUrl != "https://app-domain.auth.service.local" {
Expand Down
2 changes: 1 addition & 1 deletion crds/sme.sap.com_capapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.15.0
controller-gen.kubebuilder.io/version: v0.16.1
name: capapplications.sme.sap.com
spec:
group: sme.sap.com
Expand Down
Loading

0 comments on commit 6dfcf9d

Please sign in to comment.