Skip to content

Commit

Permalink
feat(meshservices): api for listing matching DPPs (kumahq#11850)
Browse files Browse the repository at this point in the history
## 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 kumahq#10244

Signed-off-by: Jakub Dyszkiewicz <jakub.dyszkiewicz@gmail.com>
  • Loading branch information
jakubdyszkiewicz authored Oct 28, 2024
1 parent b0fc6be commit e02c7b4
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 67 deletions.
45 changes: 45 additions & 0 deletions pkg/api-server/inspect_mesh_service.go
Original file line number Diff line number Diff line change
@@ -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))
}
},
)
}
}
155 changes: 88 additions & 67 deletions pkg/api-server/resource_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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")
}
}

Expand Down
1 change: 1 addition & 0 deletions pkg/api-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"items": [
{
"labels": {},
"mesh": "default",
"name": "ts-01",
"type": "Dataplane"
},
{
"labels": {},
"mesh": "default",
"name": "ts-02",
"type": "Dataplane"
}
],
"total": 2
}
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions pkg/core/resources/apis/meshservice/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e02c7b4

Please sign in to comment.