diff --git a/deploy/manifest/built-in-providers/applications_core.yaml b/deploy/manifest/built-in-providers/dev/applications_core.yaml similarity index 68% rename from deploy/manifest/built-in-providers/applications_core.yaml rename to deploy/manifest/built-in-providers/dev/applications_core.yaml index d105acaf01..6e8d34f65e 100644 --- a/deploy/manifest/built-in-providers/applications_core.yaml +++ b/deploy/manifest/built-in-providers/dev/applications_core.yaml @@ -1,37 +1,40 @@ name: Applications.Core +locations: + global: + address: "http://localhost:8080" types: containers: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] applications: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] environments: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] gateways: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] secretStores: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] extenders: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] volumes: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] diff --git a/deploy/manifest/built-in-providers/applications_dapr.yaml b/deploy/manifest/built-in-providers/dev/applications_dapr.yaml similarity index 70% rename from deploy/manifest/built-in-providers/applications_dapr.yaml rename to deploy/manifest/built-in-providers/dev/applications_dapr.yaml index 3738e87643..11cd13567e 100644 --- a/deploy/manifest/built-in-providers/applications_dapr.yaml +++ b/deploy/manifest/built-in-providers/dev/applications_dapr.yaml @@ -1,22 +1,25 @@ name: Applications.Dapr +locations: + global: + address: "http://localhost:8080" types: configurationStores: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] pubSubBrokers: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] secretStores: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] stateStores: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/applications_datastores.yaml b/deploy/manifest/built-in-providers/dev/applications_datastores.yaml similarity index 68% rename from deploy/manifest/built-in-providers/applications_datastores.yaml rename to deploy/manifest/built-in-providers/dev/applications_datastores.yaml index b8eb7d65b7..767ed81ca1 100644 --- a/deploy/manifest/built-in-providers/applications_datastores.yaml +++ b/deploy/manifest/built-in-providers/dev/applications_datastores.yaml @@ -1,17 +1,20 @@ name: Applications.Datastores +locations: + global: + address: "http://localhost:8080" types: mongoDatabases: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] sqlDatabases: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] redisCaches: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/applications_messaging.yaml b/deploy/manifest/built-in-providers/dev/applications_messaging.yaml similarity index 59% rename from deploy/manifest/built-in-providers/applications_messaging.yaml rename to deploy/manifest/built-in-providers/dev/applications_messaging.yaml index cac03e8aa4..10db9242da 100644 --- a/deploy/manifest/built-in-providers/applications_messaging.yaml +++ b/deploy/manifest/built-in-providers/dev/applications_messaging.yaml @@ -1,7 +1,10 @@ name: Applications.Messaging +locations: + global: + address: "http://localhost:8080" types: rabbitMQQueues: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/microsoft_resources.yaml b/deploy/manifest/built-in-providers/dev/microsoft_resources.yaml similarity index 54% rename from deploy/manifest/built-in-providers/microsoft_resources.yaml rename to deploy/manifest/built-in-providers/dev/microsoft_resources.yaml index b2c24733bb..f5f13a7b9f 100644 --- a/deploy/manifest/built-in-providers/microsoft_resources.yaml +++ b/deploy/manifest/built-in-providers/dev/microsoft_resources.yaml @@ -1,8 +1,10 @@ name: Microsoft.Resources +locations: + global: + address: "http://localhost:5017" types: deployments: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: [] - diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml new file mode 100644 index 0000000000..8700134816 --- /dev/null +++ b/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml @@ -0,0 +1,40 @@ +name: Applications.Core +locations: + global: + address: "http://applications-rp.radius-system:5443" +types: + containers: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] + applications: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] + environments: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] + gateways: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] + secretStores: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] + extenders: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + volumes: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml new file mode 100644 index 0000000000..daaa4fd33d --- /dev/null +++ b/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml @@ -0,0 +1,25 @@ +name: Applications.Dapr +locations: + global: + address: "http://applications-rp.radius-system:5443" +types: + configurationStores: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + pubSubBrokers: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + secretStores: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + stateStores: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml new file mode 100644 index 0000000000..5d40939fe6 --- /dev/null +++ b/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml @@ -0,0 +1,20 @@ +name: Applications.Datastores +locations: + global: + address: "http://applications-rp.radius-system:5443" +types: + mongoDatabases: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + sqlDatabases: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] + redisCaches: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml new file mode 100644 index 0000000000..8cb7c16206 --- /dev/null +++ b/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml @@ -0,0 +1,10 @@ +name: Applications.Messaging +locations: + global: + address: "http://applications-rp.radius-system:5443" +types: + rabbitMQQueues: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: ["Recipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml b/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml new file mode 100644 index 0000000000..f041b89f90 --- /dev/null +++ b/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml @@ -0,0 +1,10 @@ +name: Microsoft.Resources +locations: + global: + address: "http://bicep-de.radius-system:6443" +types: + deployments: + apiVersions: + "2023-10-01-preview": + schema: {} + capabilities: [] diff --git a/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go b/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go index 9ee2929d5f..b36c7471fb 100644 --- a/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go +++ b/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go @@ -24,6 +24,7 @@ import ( v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" "github.com/radius-project/radius/pkg/armrpc/asyncoperation/controller" "github.com/radius-project/radius/pkg/components/database" + "github.com/radius-project/radius/pkg/to" "github.com/radius-project/radius/pkg/ucp/datamodel" "github.com/radius-project/radius/pkg/ucp/resources" "github.com/radius-project/radius/pkg/ucp/trackedresource" @@ -47,10 +48,15 @@ func Test_Run(t *testing.T) { id := resources.MustParse("/planes/test/local/resourceGroups/test-rg/providers/Applications.Test/testResources/my-resource") trackingID := trackedresource.IDFor(id) + data := datamodel.GenericResourceFromID(id, trackingID) + data.Properties.APIVersion = "2025-01-01" resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) require.NoError(t, err) + locationID, err := datamodel.ResourceProviderLocationIDFromResourceID(id, "global") + require.NoError(t, err) + plane := datamodel.RadiusPlane{ Properties: datamodel.RadiusPlaneProperties{ ResourceProviders: map[string]string{ @@ -58,8 +64,42 @@ func Test_Run(t *testing.T) { }, }, } - resourceGroup := datamodel.ResourceGroup{} - data := datamodel.GenericResourceFromID(id, trackingID) + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + resourceTypeResource := &datamodel.ResourceType{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "testResources", + ID: resourceTypeID.String(), + }, + }, + Properties: datamodel.ResourceTypeProperties{}, + } + + locationResource := &datamodel.Location{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "global", + ID: locationID.String(), + }, + }, + Properties: datamodel.LocationProperties{ + Address: to.Ptr("https://localhost:1234"), + ResourceTypes: map[string]datamodel.LocationResourceTypeConfiguration{ + "testResources": { + APIVersions: map[string]datamodel.LocationAPIVersionConfiguration{ + "2025-01-01": {}, + }, + }, + }, + }, + } // Most of the heavy lifting is done by the updater. We just need to test that we're calling it correctly. t.Run("Success", func(t *testing.T) { @@ -70,17 +110,21 @@ func Test_Run(t *testing.T) { Return(&database.Object{Data: data}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+trackingID.PlaneNamespace(), gomock.Any()). + Get(gomock.Any(), trackingID.PlaneScope(), gomock.Any()). Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), trackingID.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + result, err := pc.Run(testcontext.New(t), &controller.Request{ResourceID: trackingID.String()}) require.Equal(t, controller.Result{}, result) require.NoError(t, err) @@ -94,17 +138,21 @@ func Test_Run(t *testing.T) { Return(&database.Object{Data: data}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+trackingID.PlaneNamespace(), gomock.Any()). + Get(gomock.Any(), trackingID.PlaneScope(), gomock.Any()). Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), trackingID.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + // Force a retry. updater.Result = &trackedresource.InProgressErr{} @@ -155,6 +203,7 @@ func Test_Run(t *testing.T) { require.Equal(t, expected, result) require.NoError(t, err) }) + } type mockUpdater struct { diff --git a/pkg/ucp/datamodel/radiusplane.go b/pkg/ucp/datamodel/radiusplane.go index 4c47a3d2a8..08243b1989 100644 --- a/pkg/ucp/datamodel/radiusplane.go +++ b/pkg/ucp/datamodel/radiusplane.go @@ -17,8 +17,6 @@ limitations under the License. package datamodel import ( - "strings" - v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" ) @@ -46,15 +44,3 @@ type RadiusPlane struct { func (p RadiusPlane) ResourceTypeName() string { return p.Type } - -// LookupResourceProvider checks if the input provider is in the list of configured providers. -func (plane RadiusPlane) LookupResourceProvider(key string) string { - var value string - for k, v := range plane.Properties.ResourceProviders { - if strings.EqualFold(k, key) { - value = v - break - } - } - return value -} diff --git a/pkg/ucp/frontend/controller/radius/proxy_test.go b/pkg/ucp/frontend/controller/radius/proxy_test.go index 063105e599..4629db95fd 100644 --- a/pkg/ucp/frontend/controller/radius/proxy_test.go +++ b/pkg/ucp/frontend/controller/radius/proxy_test.go @@ -29,6 +29,7 @@ import ( "github.com/radius-project/radius/pkg/armrpc/frontend/controller" "github.com/radius-project/radius/pkg/armrpc/rest" "github.com/radius-project/radius/pkg/components/database" + "github.com/radius-project/radius/pkg/to" "github.com/radius-project/radius/pkg/ucp/datamodel" "github.com/radius-project/radius/pkg/ucp/resources" "github.com/radius-project/radius/pkg/ucp/trackedresource" @@ -68,11 +69,12 @@ func createController(t *testing.T) (*ProxyController, *database.MockClient, *mo func Test_Run(t *testing.T) { id := resources.MustParse("/planes/test/local/resourceGroups/test-rg/providers/Applications.Test/testResources/my-resource") - // This test covers the legacy (pre-UDT) behavior for looking up the downstream URL. Update - // this when the old behavior is removed. resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) require.NoError(t, err) + locationID, err := datamodel.ResourceProviderLocationIDFromResourceID(id, "global") + require.NoError(t, err) + plane := datamodel.RadiusPlane{ Properties: datamodel.RadiusPlaneProperties{ ResourceProviders: map[string]string{ @@ -80,7 +82,42 @@ func Test_Run(t *testing.T) { }, }, } - resourceGroup := datamodel.ResourceGroup{} + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + resourceTypeResource := &datamodel.ResourceType{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "testResources", + ID: resourceTypeID.String(), + }, + }, + Properties: datamodel.ResourceTypeProperties{}, + } + + locationResource := &datamodel.Location{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "global", + ID: locationID.String(), + }, + }, + Properties: datamodel.LocationProperties{ + Address: to.Ptr("https://localhost:1234"), + ResourceTypes: map[string]datamodel.LocationResourceTypeConfiguration{ + "testResources": { + APIVersions: map[string]datamodel.LocationAPIVersionConfiguration{ + "2025-01-01": {}, + }, + }, + }, + }, + } t.Run("success (non-tracked)", func(t *testing.T) { p, databaseClient, _, roundTripper, _ := createController(t) @@ -98,17 +135,21 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodGet, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Get(gomock.Any(), id.PlaneScope(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + downstreamResponse := httptest.NewRecorder() downstreamResponse.WriteHeader(http.StatusOK) roundTripper.Response = downstreamResponse.Result() @@ -134,17 +175,21 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Get(gomock.Any(), id.PlaneScope(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + downstreamResponse := httptest.NewRecorder() downstreamResponse.WriteHeader(http.StatusOK) roundTripper.Response = downstreamResponse.Result() @@ -173,17 +218,21 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Get(gomock.Any(), id.PlaneScope(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + // Tracking entry created databaseClient.EXPECT(). Get(gomock.Any(), gomock.Any(), gomock.Any()). @@ -224,17 +273,21 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(nil, &database.ErrNotFound{}).Times(1) + Get(gomock.Any(), id.PlaneScope(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT(). + Get(gomock.Any(), locationResource.ID). + Return(&database.Object{Data: locationResource}, nil).Times(1) + // Tracking entry created existingEntry := &database.Object{ Data: &datamodel.GenericResource{ @@ -343,6 +396,7 @@ func Test_ProxyController_PrepareProxyRequest(t *testing.T) { require.Equal(t, "failed to parse downstream URL: parse \"\\ninvalid\": net/url: invalid control character in URL", err.Error()) require.Nil(t, proxyReq) }) + } type mockUpdater struct { diff --git a/pkg/ucp/frontend/controller/resourcegroups/util.go b/pkg/ucp/frontend/controller/resourcegroups/util.go index 6c9ab112a3..5d41849821 100644 --- a/pkg/ucp/frontend/controller/resourcegroups/util.go +++ b/pkg/ucp/frontend/controller/resourcegroups/util.go @@ -123,12 +123,7 @@ func ValidateResourceType(ctx context.Context, client database.Client, id resour _, err = database.GetResource[datamodel.ResourceType](ctx, client, resourceTypeID.String()) if errors.Is(err, &database.ErrNotFound{}) { - - // Return the error as-is to fallback to the legacy routing behavior. - return nil, err - - // Uncomment this when we remove the legacy routing behavior. - // return nil, &InvalidError{Message: fmt.Sprintf("resource type %q not found", id.Type())} + return nil, &InvalidError{Message: fmt.Sprintf("resource type %q not found", id.Type())} } else if err != nil { return nil, fmt.Errorf("failed to fetch resource type %q: %w", id.Type(), err) } @@ -141,12 +136,7 @@ func ValidateResourceType(ctx context.Context, client database.Client, id resour location, err := database.GetResource[datamodel.Location](ctx, client, locationID.String()) if errors.Is(err, &database.ErrNotFound{}) { - - // Return the error as-is to fallback to the legacy routing behavior. - return nil, err - - // Uncomment this when we remove the legacy routing behavior. - // return nil, &InvalidError{Message: fmt.Sprintf("location %q not found for resource provider %q", locationName, id.ProviderNamespace())} + return nil, &InvalidError{Message: fmt.Sprintf("location %q not found for resource provider %q", locationName, id.ProviderNamespace())} } else if err != nil { return nil, fmt.Errorf("failed to fetch location %q: %w", locationID.String(), err) } @@ -230,22 +220,6 @@ func isOperationResourceType(id resources.ID) bool { return false } -// ValidateLegacyResourceProvider validates that the resource provider specified in the id exists. Returns InvalidError if the plane -// contains invalid data. -func ValidateLegacyResourceProvider(ctx context.Context, client database.Client, id resources.ID, plane *datamodel.RadiusPlane) (*url.URL, error) { - downstream := plane.LookupResourceProvider(id.ProviderNamespace()) - if downstream == "" { - return nil, &InvalidError{Message: fmt.Sprintf("resource provider %s not configured", id.ProviderNamespace())} - } - - downstreamURL, err := url.Parse(downstream) - if err != nil { - return nil, &InvalidError{Message: fmt.Sprintf("failed to parse downstream URL: %v", err.Error())} - } - - return downstreamURL, nil -} - // ValidateDownstream can be used to find and validate the downstream URL for a resource. // Returns NotFoundError for the case where the plane or resource group does not exist. // Returns InvalidError for cases where the data is invalid, like when the resource provider is not configured. @@ -255,12 +229,11 @@ func ValidateDownstream(ctx context.Context, client database.Client, id resource // - The plane exists // - The resource group exists // - The resource provider is configured .. either: - // - As part of the plane (legacy routing) // - As part of a resource provider resource (System.Resources/resourceProviders) (new/UDT routing) // // The plane exists. - plane, err := ValidateRadiusPlane(ctx, client, id) + _, err := ValidateRadiusPlane(ctx, client, id) if err != nil { return nil, err } @@ -273,10 +246,7 @@ func ValidateDownstream(ctx context.Context, client database.Client, id resource // If this returns success, it means the resource type is configured using new/UDT routing. downstreamURL, err := ValidateResourceType(ctx, client, id, location, apiVersion) - if errors.Is(err, &database.ErrNotFound{}) { - // If the resource provider is not found, treat it like a legacy provider. - return ValidateLegacyResourceProvider(ctx, client, id, plane) - } else if err != nil { + if err != nil { return nil, err } diff --git a/pkg/ucp/frontend/controller/resourcegroups/util_test.go b/pkg/ucp/frontend/controller/resourcegroups/util_test.go index 3b80d21bc3..085f1de9b2 100644 --- a/pkg/ucp/frontend/controller/resourcegroups/util_test.go +++ b/pkg/ucp/frontend/controller/resourcegroups/util_test.go @@ -409,211 +409,3 @@ func Test_ValidateDownstream(t *testing.T) { require.Nil(t, downstreamURL) }) } - -// This test validates the pre-UDT before where resource providers are registered as part of the plane. -// This can be deleted once the legacy routing behavior is removed. -func Test_ValidateDownstream_Legacy(t *testing.T) { - id, err := resources.ParseResource("/planes/radius/local/resourceGroups/test-group/providers/System.TestRP/testResources/name") - require.NoError(t, err) - - idWithoutResourceGroup, err := resources.Parse("/planes/radius/local/providers/System.TestRP/testResources") - require.NoError(t, err) - - resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) - require.NoError(t, err) - - downstream := "http://localhost:7443" - - plane := &datamodel.RadiusPlane{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.PlaneScope(), - }, - }, - Properties: datamodel.RadiusPlaneProperties{ - ResourceProviders: map[string]string{ - "System.TestRP": downstream, - }, - }, - } - - setup := func(t *testing.T) *database.MockClient { - ctrl := gomock.NewController(t) - return database.NewMockClient(ctrl) - } - - t.Run("success (resource group)", func(t *testing.T) { - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) - - expectedURL, err := url.Parse(downstream) - require.NoError(t, err) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.NoError(t, err) - require.Equal(t, expectedURL, downstreamURL) - }) - - t.Run("success (non resource group)", func(t *testing.T) { - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), idWithoutResourceGroup.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) - - expectedURL, err := url.Parse(downstream) - require.NoError(t, err) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, idWithoutResourceGroup, location, apiVersion) - require.NoError(t, err) - require.Equal(t, expectedURL, downstreamURL) - }) - - t.Run("plane not found", func(t *testing.T) { - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(nil, &database.ErrNotFound{}).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, &NotFoundError{Message: "plane \"/planes/radius/local\" not found"}, err) - require.Nil(t, downstreamURL) - }) - - t.Run("plane retrieval failure", func(t *testing.T) { - databaseClient := setup(t) - - expected := fmt.Errorf("failed to fetch plane \"/planes/radius/local\": %w", errors.New("test error")) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(nil, errors.New("test error")).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, expected, err) - require.Nil(t, downstreamURL) - }) - - t.Run("resource group not found", func(t *testing.T) { - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(nil, &database.ErrNotFound{}).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, &NotFoundError{Message: "resource group \"/planes/radius/local/resourceGroups/test-group\" not found"}, err) - require.Nil(t, downstreamURL) - }) - - t.Run("resource group err", func(t *testing.T) { - databaseClient := setup(t) - - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(nil, errors.New("test error")).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, "failed to fetch resource group \"/planes/radius/local/resourceGroups/test-group\": test error", err.Error()) - require.Nil(t, downstreamURL) - }) - - t.Run("legacy resource provider not configured", func(t *testing.T) { - plane := &datamodel.RadiusPlane{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.PlaneScope(), - }, - }, - Properties: datamodel.RadiusPlaneProperties{ - ResourceProviders: map[string]string{}, - }, - } - - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, &InvalidError{Message: "resource provider System.TestRP not configured"}, err) - require.Nil(t, downstreamURL) - }) - - t.Run("location not found", func(t *testing.T) { - plane := &datamodel.RadiusPlane{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.PlaneScope(), - }, - }, - Properties: datamodel.RadiusPlaneProperties{ - ResourceProviders: map[string]string{}, - }, - } - - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, &InvalidError{Message: "resource provider System.TestRP not configured"}, err) - require.Nil(t, downstreamURL) - }) - - t.Run("resource provider invalid URL", func(t *testing.T) { - plane := &datamodel.RadiusPlane{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.PlaneScope(), - }, - }, - Properties: datamodel.RadiusPlaneProperties{ - ResourceProviders: map[string]string{ - "System.TestRP": "\ninvalid", - }, - }, - } - - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - databaseClient := setup(t) - databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) - - downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) - require.Error(t, err) - require.Equal(t, &InvalidError{Message: "failed to parse downstream URL: parse \"\\ninvalid\": net/url: invalid control character in URL"}, err) - require.Nil(t, downstreamURL) - }) -} diff --git a/pkg/ucp/integrationtests/radius/proxy_test.go b/pkg/ucp/integrationtests/radius/proxy_test.go index 5275034f31..b98dec935f 100644 --- a/pkg/ucp/integrationtests/radius/proxy_test.go +++ b/pkg/ucp/integrationtests/radius/proxy_test.go @@ -36,11 +36,14 @@ import ( const ( apiVersionParameter = "api-version=2023-10-01-preview" - testRadiusPlaneID = "/planes/radius/test" + testRadiusPlaneID = "/planes/radius/local" testResourceNamespace = "System.Test" testResourceGroupID = testRadiusPlaneID + "/resourceGroups/test-rg" + testResourceProviderID = "/planes/radius/local/providers/System.Resources/resourceproviders/System.Test" testResourceCollectionID = testResourceGroupID + "/providers/System.Test/testResources" testResourceID = testResourceCollectionID + "/test-resource" + resourceTypeURL = "/planes/radius/local/providers/System.Resources/resourceproviders/System.Test/resourcetypes/testResources" + locationID = "/planes/radius/local/providers/System.Resources/resourceproviders/System.Test/locations/global" assertTimeout = time.Second * 10 assertRetry = time.Second * 2 @@ -57,21 +60,27 @@ func Test_RadiusPlane_Proxy_ResourceGroupDoesNotExist(t *testing.T) { response := ucp.MakeRequest(http.MethodGet, testResourceID+"?api-version="+testrp.Version, nil) response.EqualsErrorCode(http.StatusNotFound, "NotFound") - require.Equal(t, "the resource with id '/planes/radius/test/resourceGroups/test-rg/providers/System.Test/testResources/test-resource' was not found: resource group \"/planes/radius/test/resourceGroups/test-rg\" not found", response.Error.Error.Message) + require.Equal(t, "the resource with id '/planes/radius/local/resourceGroups/test-rg/providers/System.Test/testResources/test-resource' was not found: resource group \"/planes/radius/local/resourceGroups/test-rg\" not found", response.Error.Error.Message) } func Test_RadiusPlane_ResourceSync(t *testing.T) { ucp := testhost.Start(t) rp := testrp.Start(t) rp.Handler = testrp.SyncResource(t, ucp, testResourceGroupID) - + address := to.Ptr("http://" + rp.Address()) rps := map[string]*string{ - testResourceNamespace: to.Ptr("http://" + rp.Address()), + testResourceNamespace: address, } createRadiusPlane(ucp, rps) createResourceGroup(ucp, testResourceGroupID) + createResourceProvider(ucp) + + createResourceType(ucp, resourceTypeURL) + + createLocation(ucp, address) + message := "here is some test data" expectedTrackedResource := v20231001preview.GenericResource{ @@ -89,7 +98,10 @@ func Test_RadiusPlane_ResourceSync(t *testing.T) { body, err := json.Marshal(data) require.NoError(t, err) - response := ucp.MakeRequest(http.MethodPut, testResourceID+"?api-version="+testrp.Version, body) + response := ucp.MakeRequest(http.MethodGet, resourceTypeURL+"?api-version="+testrp.Version, nil) + response.EqualsStatusCode(http.StatusOK) + + response = ucp.MakeRequest(http.MethodPut, testResourceID+"?api-version="+testrp.Version, body) response.EqualsStatusCode(http.StatusOK) resource := &testrp.TestResource{} @@ -184,14 +196,21 @@ func Test_RadiusPlane_ResourceAsync(t *testing.T) { } rp.Handler = testrp.AsyncResource(t, ucp, testResourceGroupID, onPut, onDelete) - + address := to.Ptr("http://" + rp.Address()) rps := map[string]*string{ - testResourceNamespace: to.Ptr("http://" + rp.Address()), + testResourceNamespace: address, } + createRadiusPlane(ucp, rps) createResourceGroup(ucp, testResourceGroupID) + createResourceProvider(ucp) + + createResourceType(ucp, resourceTypeURL) + + createLocation(ucp, address) + message := "here is some test data" expectedTrackedResource := v20231001preview.GenericResource{ @@ -400,3 +419,44 @@ func createResourceGroup(ucp *testhost.TestHost, id string) { response := ucp.MakeTypedRequest(http.MethodPut, id+"?"+apiVersionParameter, body) response.EqualsStatusCode(http.StatusOK) } + +func createResourceProvider(ucp *testhost.TestHost) { + body := v20231001preview.ResourceProviderResource{ + Location: to.Ptr(v1.LocationGlobal), + Properties: &v20231001preview.ResourceProviderProperties{}, + } + response := ucp.MakeTypedRequest("PUT", testResourceProviderID+"?"+apiVersionParameter, body) + response.WaitForOperationComplete(nil) + response.EqualsStatusCode(http.StatusCreated) +} + +func createResourceType(ucp *testhost.TestHost, id string) { + body := v20231001preview.ResourceTypeResource{ + Properties: &v20231001preview.ResourceTypeProperties{ + DefaultAPIVersion: to.Ptr("2023-10-01-preview"), + }, + } + + response := ucp.MakeTypedRequest(http.MethodPut, id+"?"+apiVersionParameter, body) + response.WaitForOperationComplete(nil) + response.EqualsStatusCode(http.StatusCreated) +} + +func createLocation(server *testhost.TestHost, address *string) { + body := v20231001preview.LocationResource{ + Properties: &v20231001preview.LocationProperties{ + Address: address, + ResourceTypes: map[string]*v20231001preview.LocationResourceType{ + "testResources": { + APIVersions: map[string]map[string]any{ + "2023-10-01-preview": {}, + }, + }, + }, + }, + } + + response := server.MakeTypedRequest("PUT", locationID+"?"+apiVersionParameter, body) + response.WaitForOperationComplete(nil) + response.EqualsStatusCode(http.StatusCreated) +} diff --git a/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml b/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml index c3348b0229..84dbc04431 100644 --- a/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml +++ b/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml @@ -2,6 +2,6 @@ name: MyCompany.Resources types: testResources: apiVersions: - "2025-01-01-preview": + "2023-10-01-preview": schema: {} capabilities: ["SupportsRecipes"]