From e02c7b4a0d920062ffdd075d37d1580175fa386b Mon Sep 17 00:00:00 2001 From: Jakub Dyszkiewicz Date: Mon, 28 Oct 2024 11:15:00 +0100 Subject: [PATCH] feat(meshservices): api for listing matching DPPs (#11850) ## Motivation We want to list matching DPPs in the UI ## Implementation information We already had a code for it for policies. In the case of MeshServices, the matching is different. I extracted "boilerplate" of parsing, marshaling request and validating the access to "generic function", so won't duplicate the logic and whether we have a change it matches both. When it comes to OAPI, we already have a definition for it https://github.com/kumahq/kuma/blob/2adb7f53026f3ab38f5967b21f507e6674ce893d/api/openapi/specs/api.yaml#L122 not sure we can do anything more here ## Supporting documentation Fix #10244 Signed-off-by: Jakub Dyszkiewicz --- pkg/api-server/inspect_mesh_service.go | 45 +++++ pkg/api-server/resource_endpoints.go | 155 ++++++++++-------- pkg/api-server/server.go | 1 + .../dataplanes/meshservice.golden.json | 17 ++ .../dataplanes/meshservice.input.yaml | 49 ++++++ pkg/core/resources/apis/meshservice/match.go | 11 ++ 6 files changed, 211 insertions(+), 67 deletions(-) create mode 100644 pkg/api-server/inspect_mesh_service.go create mode 100644 pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.golden.json create mode 100644 pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.input.yaml diff --git a/pkg/api-server/inspect_mesh_service.go b/pkg/api-server/inspect_mesh_service.go new file mode 100644 index 000000000000..254b4b173b06 --- /dev/null +++ b/pkg/api-server/inspect_mesh_service.go @@ -0,0 +1,45 @@ +package api_server + +import ( + "github.com/emicklei/go-restful/v3" + + "github.com/kumahq/kuma/pkg/core/resources/access" + core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" + "github.com/kumahq/kuma/pkg/core/resources/apis/meshservice" + meshservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshservice/api/v1alpha1" + "github.com/kumahq/kuma/pkg/core/resources/manager" + "github.com/kumahq/kuma/pkg/core/resources/model" + "github.com/kumahq/kuma/pkg/core/resources/store" +) + +func addInspectMeshServiceEndpoints( + ws *restful.WebService, + rm manager.ResourceManager, + resourceAccess access.ResourceAccess, +) { + ws.Route( + ws.GET("/meshes/{mesh}/meshservices/{name}/_resources/dataplanes"). + To(matchingDataplanesForMeshServices(rm, resourceAccess)). + Doc("inspect dataplane configuration and stats"). + Param(ws.PathParameter("name", "mesh service name").DataType("string")). + Param(ws.PathParameter("mesh", "mesh name").DataType("string")), + ) +} + +func matchingDataplanesForMeshServices(resManager manager.ResourceManager, resourceAccess access.ResourceAccess) restful.RouteFunction { + return func(request *restful.Request, response *restful.Response) { + matchingDataplanesForFilter( + request, + response, + meshservice_api.MeshServiceResourceTypeDescriptor, + resManager, + resourceAccess, + func(resource model.Resource) store.ListFilterFunc { + meshService := resource.(*meshservice_api.MeshServiceResource) + return func(rs model.Resource) bool { + return meshservice.MatchesDataplane(meshService.Spec, rs.(*core_mesh.DataplaneResource)) + } + }, + ) + } +} diff --git a/pkg/api-server/resource_endpoints.go b/pkg/api-server/resource_endpoints.go index 772ed4a035c4..4254512b3efe 100644 --- a/pkg/api-server/resource_endpoints.go +++ b/pkg/api-server/resource_endpoints.go @@ -628,34 +628,12 @@ func (r *resourceEndpoints) readOnlyMessage() string { func (r *resourceEndpoints) matchingDataplanesForPolicy() restful.RouteFunction { return func(request *restful.Request, response *restful.Response) { - policyName := request.PathParameter("name") - page, err := pagination(request) - if err != nil { - rest_errors.HandleError(request.Request.Context(), response, err, "Could not retrieve policy") - return - } - nameContains := request.QueryParameter("name") meshName, err := r.meshFromRequest(request) if err != nil { rest_errors.HandleError(request.Request.Context(), response, err, "Failed to retrieve Mesh") return } - if err := r.resourceAccess.ValidateGet( - request.Request.Context(), - model.ResourceKey{Mesh: meshName, Name: policyName}, - r.descriptor, - user.FromCtx(request.Request.Context()), - ); err != nil { - rest_errors.HandleError(request.Request.Context(), response, err, "Access Denied") - return - } - policyResource := r.descriptor.NewObject() - if err := r.resManager.Get(request.Request.Context(), policyResource, store.GetByKey(policyName, meshName)); err != nil { - rest_errors.HandleError(request.Request.Context(), response, err, "Could not retrieve policy") - return - } - var dependentTypes []model.ResourceType if r.descriptor.IsTargetRefBased { dependentTypes = []model.ResourceType{meshhttproute_api.MeshHTTPRouteType, core_mesh.MeshGatewayType} @@ -675,54 +653,97 @@ func (r *resourceEndpoints) matchingDataplanesForPolicy() restful.RouteFunction } dependentResources.MeshLocalResources[dependentType] = hl } - filter := func(rs model.Resource) bool { - dpp := rs.(*core_mesh.DataplaneResource) - if r.descriptor.IsTargetRefBased { - res, _ := matchers.PolicyMatches(policyResource, dpp, dependentResources) - return res - } else if dpPolicy, ok := policyResource.(policy.DataplanePolicy); ok { - for _, s := range dpPolicy.Selectors() { - if dpp.Spec.Matches(s.GetMatch()) { - return true - } - } - } else if connPolicy, ok := policyResource.(policy.ConnectionPolicy); ok { - for _, s := range connPolicy.Sources() { - if dpp.Spec.Matches(s.GetMatch()) { - return true - } - } - for _, s := range connPolicy.Destinations() { - if dpp.Spec.Matches(s.GetMatch()) { - return true + matchingDataplanesForFilter( + request, + response, + r.descriptor, + r.resManager, + r.resourceAccess, + func(policyResource core_model.Resource) store.ListFilterFunc { + return func(rs core_model.Resource) bool { + dpp := rs.(*core_mesh.DataplaneResource) + if r.descriptor.IsTargetRefBased { + res, _ := matchers.PolicyMatches(policyResource, dpp, dependentResources) + return res + } else if dpPolicy, ok := policyResource.(policy.DataplanePolicy); ok { + for _, s := range dpPolicy.Selectors() { + if dpp.Spec.Matches(s.GetMatch()) { + return true + } + } + } else if connPolicy, ok := policyResource.(policy.ConnectionPolicy); ok { + for _, s := range connPolicy.Sources() { + if dpp.Spec.Matches(s.GetMatch()) { + return true + } + } + for _, s := range connPolicy.Destinations() { + if dpp.Spec.Matches(s.GetMatch()) { + return true + } + } } + return false } - } - return false - } - dppList := registry.Global().MustNewList(core_mesh.DataplaneType) - err = r.resManager.List(request.Request.Context(), dppList, - store.ListByMesh(meshName), - store.ListByNameContains(nameContains), - store.ListByFilterFunc(filter), - store.ListByPage(page.size, page.offset), + }, ) - if err != nil { - rest_errors.HandleError(request.Request.Context(), response, err, "failed inspect") - return - } - items := make([]api_common.Meta, len(dppList.GetItems())) - for i, elt := range dppList.GetItems() { - items[i] = oapi_helpers.ResourceToMeta(elt) - } - out := api_types.InspectDataplanesForPolicyResponse{ - Total: int(dppList.GetPagination().Total), - Items: items, - Next: nextLink(request, dppList.GetPagination().NextOffset), - } - if err := response.WriteAsJson(out); err != nil { - rest_errors.HandleError(request.Request.Context(), response, err, "Failed writing response") - } + } +} + +func matchingDataplanesForFilter( + request *restful.Request, + response *restful.Response, + descriptor core_model.ResourceTypeDescriptor, + resManager manager.ResourceManager, + resourceAccess access.ResourceAccess, + dpFilterForResource func(resource model.Resource) store.ListFilterFunc, +) { + policyName := request.PathParameter("name") + page, err := pagination(request) + if err != nil { + rest_errors.HandleError(request.Request.Context(), response, err, "Could not retrieve policy") + return + } + nameContains := request.QueryParameter("name") + meshName := request.PathParameter("mesh") + + if err := resourceAccess.ValidateGet( + request.Request.Context(), + model.ResourceKey{Mesh: meshName, Name: policyName}, + descriptor, + user.FromCtx(request.Request.Context()), + ); err != nil { + rest_errors.HandleError(request.Request.Context(), response, err, "Access Denied") + return + } + policyResource := descriptor.NewObject() + if err := resManager.Get(request.Request.Context(), policyResource, store.GetByKey(policyName, meshName)); err != nil { + rest_errors.HandleError(request.Request.Context(), response, err, "Could not retrieve policy") + return + } + + dppList := registry.Global().MustNewList(core_mesh.DataplaneType) + err = resManager.List(request.Request.Context(), dppList, + store.ListByMesh(meshName), + store.ListByNameContains(nameContains), + store.ListByFilterFunc(dpFilterForResource(policyResource)), + store.ListByPage(page.size, page.offset), + ) + if err != nil { + rest_errors.HandleError(request.Request.Context(), response, err, "failed inspect") + return + } + items := make([]api_common.Meta, len(dppList.GetItems())) + for i, elt := range dppList.GetItems() { + items[i] = oapi_helpers.ResourceToMeta(elt) + } + out := api_types.InspectDataplanesForPolicyResponse{ + Total: int(dppList.GetPagination().Total), + Items: items, + Next: nextLink(request, dppList.GetPagination().NextOffset), + } + if err := response.WriteAsJson(out); err != nil { + rest_errors.HandleError(request.Request.Context(), response, err, "Failed writing response") } } diff --git a/pkg/api-server/server.go b/pkg/api-server/server.go index da6f1c7da988..aebe63cb98fb 100644 --- a/pkg/api-server/server.go +++ b/pkg/api-server/server.go @@ -150,6 +150,7 @@ func NewApiServer( addPoliciesWsEndpoints(ws, cfg.IsFederatedZoneCP(), cfg.ApiServer.ReadOnly, defs) addInspectEndpoints(ws, cfg, meshContextBuilder, rt.ResourceManager()) addInspectEnvoyAdminEndpoints(ws, cfg, rt.ResourceManager(), rt.Access().EnvoyAdminAccess, rt.EnvoyAdminClient()) + addInspectMeshServiceEndpoints(ws, rt.ResourceManager(), rt.Access().ResourceAccess) addZoneEndpoints(ws, rt.ResourceManager()) guiUrl := "" if cfg.ApiServer.GUI.Enabled && !cfg.IsFederatedZoneCP() { diff --git a/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.golden.json b/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.golden.json new file mode 100644 index 000000000000..141a2bddc815 --- /dev/null +++ b/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.golden.json @@ -0,0 +1,17 @@ +{ + "items": [ + { + "labels": {}, + "mesh": "default", + "name": "ts-01", + "type": "Dataplane" + }, + { + "labels": {}, + "mesh": "default", + "name": "ts-02", + "type": "Dataplane" + } + ], + "total": 2 +} diff --git a/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.input.yaml b/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.input.yaml new file mode 100644 index 000000000000..028ad209405e --- /dev/null +++ b/pkg/api-server/testdata/resources/inspect/policies/_resources/dataplanes/meshservice.input.yaml @@ -0,0 +1,49 @@ +#/meshes/default/meshservices/test-server/_resources/dataplanes 200 +type: Mesh +name: default +--- +type: MeshService +name: test-server +mesh: default +labels: + kuma.io/origin: zone + kuma.io/env: universal +spec: + selector: + dataplaneTags: + kuma.io/service: test-server + ports: + - port: 80 + targetPort: 80 + appProtocol: http + name: main-port +--- +type: Dataplane +name: ts-01 +mesh: default +networking: + address: 127.0.0.1 + inbound: + - port: 80 + tags: + kuma.io/service: test-server +--- +type: Dataplane +name: ts-02 +mesh: default +networking: + address: 127.0.0.2 + inbound: + - port: 80 + tags: + kuma.io/service: test-server +--- +type: Dataplane +name: not-ts-01 +mesh: default +networking: + address: 127.0.0.2 + inbound: + - port: 80 + tags: + kuma.io/service: not-test-server diff --git a/pkg/core/resources/apis/meshservice/match.go b/pkg/core/resources/apis/meshservice/match.go index 06ebf7b4d001..5888bb9ecee5 100644 --- a/pkg/core/resources/apis/meshservice/match.go +++ b/pkg/core/resources/apis/meshservice/match.go @@ -43,6 +43,17 @@ func MatchDataplanesWithMeshServices( return result } +func MatchesDataplane(meshService *meshservice_api.MeshService, dpp *core_mesh.DataplaneResource) bool { + switch { + case meshService.Selector.DataplaneRef != nil: + return meshService.Selector.DataplaneRef.Name == dpp.GetMeta().GetName() + case meshService.Selector.DataplaneTags != nil: + return dpp.Spec.Matches(mesh_proto.TagSelector(meshService.Selector.DataplaneTags)) + default: + return false + } +} + func indexDpsForMatching( dpps []*core_mesh.DataplaneResource, matchOnlyHealthy bool,