Skip to content

Commit

Permalink
feat(inspect-api): retrieve full XDS config (#3768)
Browse files Browse the repository at this point in the history
  • Loading branch information
lobkovilya authored Feb 7, 2022
1 parent 1e26f60 commit a38f344
Show file tree
Hide file tree
Showing 37 changed files with 6,599 additions and 41 deletions.
4 changes: 4 additions & 0 deletions pkg/api-server/config_ws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ var _ = Describe("Config WS", func() {
"generateZoneToken": {
"users": ["mesh-system:admin"],
"groups": ["mesh-system:admin"]
},
"viewConfigDump": {
"users": [ ],
"groups": ["mesh-system:unauthenticated","mesh-system:authenticated"]
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions pkg/api-server/customization/customization_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
core_metrics "github.com/kumahq/kuma/pkg/metrics"
"github.com/kumahq/kuma/pkg/plugins/authn/api-server/certs"
"github.com/kumahq/kuma/pkg/test"
test_runtime "github.com/kumahq/kuma/pkg/test/runtime"
xds_context "github.com/kumahq/kuma/pkg/xds/context"
"github.com/kumahq/kuma/pkg/xds/server"
)
Expand Down Expand Up @@ -73,6 +74,7 @@ func createTestApiServer(store store.ResourceStore, config *config_api_server.Ap
ResourceAccess: resources_access.NewAdminResourceAccess(cfg.Access.Static.AdminResources),
DataplaneTokenAccess: nil,
},
&test_runtime.DummyEnvoyAdminClient{},
)
Expect(err).ToNot(HaveOccurred())
return apiServer
Expand Down
120 changes: 120 additions & 0 deletions pkg/api-server/inspect_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@ package api_server
import (
"context"
"fmt"
"net/http"
"sort"

"github.com/emicklei/go-restful"

system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
api_server_types "github.com/kumahq/kuma/pkg/api-server/types"
kuma_cp "github.com/kumahq/kuma/pkg/config/app/kuma-cp"
config_core "github.com/kumahq/kuma/pkg/config/core"
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
"github.com/kumahq/kuma/pkg/core/resources/manager"
core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/resources/model/rest"
"github.com/kumahq/kuma/pkg/core/resources/registry"
"github.com/kumahq/kuma/pkg/core/resources/store"
rest_errors "github.com/kumahq/kuma/pkg/core/rest/errors"
"github.com/kumahq/kuma/pkg/core/rest/errors/types"
"github.com/kumahq/kuma/pkg/core/user"
"github.com/kumahq/kuma/pkg/core/validators"
core_xds "github.com/kumahq/kuma/pkg/core/xds"
"github.com/kumahq/kuma/pkg/envoy/admin"
"github.com/kumahq/kuma/pkg/envoy/admin/access"
xds_context "github.com/kumahq/kuma/pkg/xds/context"
"github.com/kumahq/kuma/pkg/xds/envoy"
"github.com/kumahq/kuma/pkg/xds/server/callbacks"
Expand Down Expand Up @@ -46,6 +55,9 @@ func addInspectEndpoints(
ws *restful.WebService,
cfg *kuma_cp.Config,
builder xds_context.MeshContextBuilder,
rm manager.ResourceManager,
configDumpAccess access.ConfigDumpAccess,
envoyAdminClient admin.EnvoyAdminClient,
) {
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/policies").To(inspectDataplane(cfg, builder)).
Expand All @@ -55,6 +67,36 @@ func addInspectEndpoints(
Returns(200, "OK", nil),
)

if cfg.Mode != config_core.Global {
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(inspectDataplaneXDS(envoyAdminClient, configDumpAccess, rm, cfg.GetEnvoyAdminPort())).
Doc("inspect dataplane XDS configuration").
Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")),
)

ws.Route(
ws.GET("/zoneingresses/{zoneingress}/xds").To(inspectZoneIngressXDS(envoyAdminClient, configDumpAccess, rm, cfg.Multizone.Zone.Name, cfg.GetEnvoyAdminPort())).
Doc("inspect zone ingresses XDS configuration").
Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")),
)
} else {
methodNotAllowed := func(_ *restful.Request, response *restful.Response) {
kumaErr := types.Error{
Title: "Method is not allowed",
Details: "It it not possible to inspect envoy config dump on Global CP. Please consider using Zone CP of the corresponding zone",
}
rest_errors.WriteError(response, http.StatusMethodNotAllowed, kumaErr)
}
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(methodNotAllowed).
Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")))
ws.Route(
ws.GET("/zoneingresses/{zoneingress}/xds").To(methodNotAllowed).
Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")))
}

for _, desc := range registry.Global().ObjectDescriptors(core_model.AllowedToInspect()) {
ws.Route(
ws.GET(fmt.Sprintf("/meshes/{mesh}/%s/{name}/dataplanes", desc.WsPath)).To(inspectPolicies(desc.Name, builder, cfg)).
Expand Down Expand Up @@ -148,6 +190,84 @@ func inspectPolicies(
}
}

func inspectDataplaneXDS(
envoyAdminClient admin.EnvoyAdminClient,
access access.ConfigDumpAccess,
rm manager.ResourceManager,
defaultAdminPort uint32,
) restful.RouteFunction {
return func(request *restful.Request, response *restful.Response) {
meshName := request.PathParameter("mesh")
dataplaneName := request.PathParameter("dataplane")

ctx := request.Request.Context()

if err := access.ValidateViewConfigDump(user.FromCtx(ctx)); err != nil {
rest_errors.HandleError(response, err, "Could not get config_dump")
return
}

dp := core_mesh.NewDataplaneResource()
if err := rm.Get(context.Background(), dp, store.GetByKey(dataplaneName, meshName)); err != nil {
rest_errors.HandleError(response, err, "Could not get dataplane resource")
return
}

configDump, err := envoyAdminClient.ConfigDump(dp, defaultAdminPort)
if err != nil {
rest_errors.HandleError(response, err, "Could not get config_dump")
return
}

if _, err := response.Write(configDump); err != nil {
rest_errors.HandleError(response, err, "Could not write response")
return
}
}
}

func inspectZoneIngressXDS(
envoyAdminClient admin.EnvoyAdminClient,
access access.ConfigDumpAccess,
rm manager.ResourceManager,
localZone string,
defaultAdminPort uint32,
) restful.RouteFunction {
return func(request *restful.Request, response *restful.Response) {
zoneIngressName := request.PathParameter("zoneingress")

ctx := request.Request.Context()

if err := access.ValidateViewConfigDump(user.FromCtx(ctx)); err != nil {
rest_errors.HandleError(response, err, "Could not get config_dump")
return
}

zi := core_mesh.NewZoneIngressResource()
if err := rm.Get(context.Background(), zi, store.GetByKey(zoneIngressName, core_model.NoMesh)); err != nil {
rest_errors.HandleError(response, err, "Could not get zone ingress resource")
return
}

if zi.IsRemoteIngress(localZone) {
rest_errors.HandleError(response, &validators.ValidationError{},
"Could not connect to zone ingress that resides in another zone")
return
}

configDump, err := envoyAdminClient.ConfigDump(zi, defaultAdminPort)
if err != nil {
rest_errors.HandleError(response, err, "Could not get config_dump")
return
}

if _, err := response.Write(configDump); err != nil {
rest_errors.HandleError(response, err, "Could not write response")
return
}
}
}

func newDataplaneInspectResponse(matchedPolicies *core_xds.MatchedPolicies, dp *core_mesh.DataplaneResource) []*api_server_types.DataplaneInspectEntry {
attachmentMap := core_xds.GroupByAttachment(matchedPolicies, dp.Spec.Networking)

Expand Down
117 changes: 116 additions & 1 deletion pkg/api-server/inspect_endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (

mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
config "github.com/kumahq/kuma/pkg/config/api-server"
kuma_cp "github.com/kumahq/kuma/pkg/config/app/kuma-cp"
config_core "github.com/kumahq/kuma/pkg/config/core"
"github.com/kumahq/kuma/pkg/core"
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
"github.com/kumahq/kuma/pkg/core/resources/manager"
Expand All @@ -29,6 +31,7 @@ import (
)

type dataplaneBuilder core_mesh.DataplaneResource
type zoneIngressBuilder core_mesh.ZoneIngressResource

func newMesh(name string) *core_mesh.MeshResource {
return &core_mesh.MeshResource{
Expand All @@ -37,6 +40,53 @@ func newMesh(name string) *core_mesh.MeshResource {
}
}

func newZoneIngress() *zoneIngressBuilder {
return &zoneIngressBuilder{
Spec: &mesh_proto.ZoneIngress{
Networking: &mesh_proto.ZoneIngress_Networking{},
},
}
}

func (b *zoneIngressBuilder) meta(name string) *zoneIngressBuilder {
b.Meta = &test_model.ResourceMeta{Name: name, Mesh: core_model.NoMesh}
return b
}

func (b *zoneIngressBuilder) zone(name string) *zoneIngressBuilder {
b.Spec.Zone = name
return b
}

func (b *zoneIngressBuilder) address(address string) *zoneIngressBuilder {
b.Spec.Networking.Address = address
return b
}

func (b *zoneIngressBuilder) port(port uint32) *zoneIngressBuilder {
b.Spec.Networking.Port = port
return b
}

func (b *zoneIngressBuilder) advertisedAddress(address string) *zoneIngressBuilder {
b.Spec.Networking.AdvertisedAddress = address
return b
}

func (b *zoneIngressBuilder) advertisedPort(port uint32) *zoneIngressBuilder {
b.Spec.Networking.AdvertisedPort = port
return b
}

func (b *zoneIngressBuilder) admin(port uint32) *zoneIngressBuilder {
b.Spec.Networking.Admin = &mesh_proto.EnvoyAdmin{Port: port}
return b
}

func (b *zoneIngressBuilder) build() *core_mesh.ZoneIngressResource {
return (*core_mesh.ZoneIngressResource)(b)
}

func newDataplane() *dataplaneBuilder {
return &dataplaneBuilder{
Spec: &mesh_proto.Dataplane{
Expand Down Expand Up @@ -80,6 +130,11 @@ func (b *dataplaneBuilder) outbound(service, ip string, port uint32) *dataplaneB
return b
}

func (b *dataplaneBuilder) admin(port uint32) *dataplaneBuilder {
b.Spec.Networking.Admin = &mesh_proto.EnvoyAdmin{Port: port}
return b
}

type selectors []*mesh_proto.Selector

func anyService() []*mesh_proto.Selector {
Expand Down Expand Up @@ -115,6 +170,7 @@ var _ = Describe("Inspect WS", func() {
path string
goldenFile string
resources []core_model.Resource
global bool
}

DescribeTable("should return valid response",
Expand All @@ -133,7 +189,15 @@ var _ = Describe("Inspect WS", func() {
Expect(err).ToNot(HaveOccurred())
}

apiServer := createTestApiServer(resourceStore, config.DefaultApiServerConfig(), true, metrics)
cfg := config.DefaultApiServerConfig()
apiServer := createTestApiServer(resourceStore, cfg, true, metrics, func(config *kuma_cp.Config) {
if given.global {
config.Mode = config_core.Global
} else {
config.Mode = config_core.Zone
config.Multizone.Zone.Name = "local"
}
})

stop := make(chan struct{})
defer close(stop)
Expand Down Expand Up @@ -590,6 +654,57 @@ var _ = Describe("Inspect WS", func() {
},
},
}),
Entry("inspect xds for dataplane", testCase{
path: "/meshes/mesh-1/dataplanes/backend-1/xds",
goldenFile: "inspect_xds_dataplane.json",
resources: []core_model.Resource{
newMesh("mesh-1"),
newDataplane().
meta("backend-1", "mesh-1").
admin(3301).
inbound("backend", "192.168.0.1", 80, 81).
outbound("redis", "192.168.0.2", 8080).
outbound("gateway", "192.168.0.3", 8080).
outbound("web", "192.168.0.4", 8080).
build(),
},
}),
Entry("inspect xds for local zone ingress", testCase{
path: "/zoneingresses/zi-1/xds",
goldenFile: "inspect_xds_local_zoneingress.json",
resources: []core_model.Resource{
newZoneIngress().
meta("zi-1").
zone(""). // local zone ingress has empty "zone" field
admin(2201).
address("2.2.2.2").port(8080).
advertisedAddress("3.3.3.3").advertisedPort(80).
build(),
},
}),
Entry("inspect xds for zone ingress from another zone", testCase{
path: "/zoneingresses/zi-1/xds",
goldenFile: "inspect_xds_remote_zoneingress.json",
resources: []core_model.Resource{
newZoneIngress().
meta("zi-1").
zone("not-local-zone").
admin(2201).
address("2.2.2.2").port(8080).
advertisedAddress("3.3.3.3").advertisedPort(80).
build(),
},
}),
Entry("inspect xds for zone ingress on global", testCase{
global: true,
path: "/zoneingresses/zi-1/xds",
goldenFile: "inspect_xds_global_zoneingress.json",
}),
Entry("inspect xds for dataplane on global", testCase{
global: true,
path: "/meshes/default/dataplanes/dp-1/xds",
goldenFile: "inspect_xds_global_dataplane.json",
}),
)

It("should change response if state changed", func() {
Expand Down
Loading

0 comments on commit a38f344

Please sign in to comment.