Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inspect-api): retrieve full XDS config #3768

Merged
merged 15 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -19,6 +19,7 @@ import (
"github.com/kumahq/kuma/pkg/core/resources/store"
"github.com/kumahq/kuma/pkg/core/runtime"
"github.com/kumahq/kuma/pkg/dns/vips"
"github.com/kumahq/kuma/pkg/envoy/admin"
core_metrics "github.com/kumahq/kuma/pkg/metrics"
"github.com/kumahq/kuma/pkg/plugins/authn/api-server/certs"
"github.com/kumahq/kuma/pkg/test"
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,
},
api_server.ConfigDumpFunc(admin.ConfigDump),
)
Expect(err).ToNot(HaveOccurred())
return apiServer
Expand Down
108 changes: 108 additions & 0 deletions pkg/api-server/inspect_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,42 @@ 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/validators"
core_xds "github.com/kumahq/kuma/pkg/core/xds"
"github.com/kumahq/kuma/pkg/envoy/admin"
xds_context "github.com/kumahq/kuma/pkg/xds/context"
"github.com/kumahq/kuma/pkg/xds/envoy"
"github.com/kumahq/kuma/pkg/xds/server/callbacks"
"github.com/kumahq/kuma/pkg/xds/sync"
)

type ConfigDumper interface {
ConfigDump(addresser admin.Addresser, defaultAdminPort uint32) ([]byte, error)
}

type ConfigDumpFunc func(addresser admin.Addresser, defaultAdminPort uint32) ([]byte, error)

func (f ConfigDumpFunc) ConfigDump(addresser admin.Addresser, defaultAdminPort uint32) ([]byte, error) {
return f(addresser, defaultAdminPort)
}

type fakeDataSourceLoader struct {
}

Expand All @@ -46,6 +63,8 @@ func addInspectEndpoints(
ws *restful.WebService,
cfg *kuma_cp.Config,
builder xds_context.MeshContextBuilder,
rm manager.ResourceManager,
cd ConfigDumper,
) {
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/policies").To(inspectDataplane(cfg, builder)).
Expand All @@ -55,6 +74,29 @@ func addInspectEndpoints(
Returns(200, "OK", nil),
)

if cfg.Mode != config_core.Global {
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(inspectDataplaneXDS(cd, 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(cd, rm, cfg.Multizone.Zone.Name, cfg.GetEnvoyAdminPort())).
Doc("inspect zone ingresses XDS configuration").
Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")),
)
} else {
jakubdyszkiewicz marked this conversation as resolved.
Show resolved Hide resolved
ws.Route(
ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(methodNotAllowedOnGlobal).
Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")))
ws.Route(
ws.GET("/zoneingresses/{zoneingress}/xds").To(methodNotAllowedOnGlobal).
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 All @@ -66,6 +108,14 @@ func addInspectEndpoints(
}
}

func methodNotAllowedOnGlobal(_ *restful.Request, response *restful.Response) {
lobkovilya marked this conversation as resolved.
Show resolved Hide resolved
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)
}

func inspectDataplane(cfg *kuma_cp.Config, builder xds_context.MeshContextBuilder) restful.RouteFunction {
return func(request *restful.Request, response *restful.Response) {
meshName := request.PathParameter("mesh")
Expand Down Expand Up @@ -150,6 +200,64 @@ func inspectPolicies(
}
}

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

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 := dumper.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
}
jakubdyszkiewicz marked this conversation as resolved.
Show resolved Hide resolved
}
}

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

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 := dumper.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
}
}
}

lobkovilya marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -561,6 +625,57 @@ var _ = Describe("Inspect WS", func() {
build(),
},
}),
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
18 changes: 17 additions & 1 deletion pkg/api-server/resource_api_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api_server_test
import (
"bytes"
"context"
"fmt"
"net"
"net/http"
"path/filepath"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/kumahq/kuma/pkg/core/resources/store"
"github.com/kumahq/kuma/pkg/core/runtime"
"github.com/kumahq/kuma/pkg/dns/vips"
"github.com/kumahq/kuma/pkg/envoy/admin"
core_metrics "github.com/kumahq/kuma/pkg/metrics"
"github.com/kumahq/kuma/pkg/plugins/authn/api-server/certs"
"github.com/kumahq/kuma/pkg/test"
Expand Down Expand Up @@ -108,7 +110,15 @@ func putSampleResourceIntoStore(resourceStore store.ResourceStore, name string,
Expect(err).NotTo(HaveOccurred())
}

func createTestApiServer(store store.ResourceStore, config *config_api_server.ApiServerConfig, enableGUI bool, metrics core_metrics.Metrics) *api_server.ApiServer {
type configModifier func(config *kuma_cp.Config)

func createTestApiServer(
store store.ResourceStore,
config *config_api_server.ApiServerConfig,
enableGUI bool,
metrics core_metrics.Metrics,
modifiers ...configModifier,
) *api_server.ApiServer {
// we have to manually search for port and put it into config. There is no way to retrieve port of running
// http.Server and we need it later for the client
port, err := test.GetFreePort()
Expand All @@ -126,6 +136,9 @@ func createTestApiServer(store store.ResourceStore, config *config_api_server.Ap

cfg := kuma_cp.DefaultConfig()
cfg.ApiServer = config
for _, modifier := range modifiers {
modifier(&cfg)
}

apiServer, err := api_server.NewApiServer(
manager.NewResourceManager(store),
Expand All @@ -149,6 +162,9 @@ func createTestApiServer(store store.ResourceStore, config *config_api_server.Ap
ResourceAccess: resources_access.NewAdminResourceAccess(cfg.Access.Static.AdminResources),
DataplaneTokenAccess: nil,
},
api_server.ConfigDumpFunc(func(addresser admin.Addresser, defaultAdminAddress uint32) ([]byte, error) {
return []byte(fmt.Sprintf(`{"envoyAdminAddress": "%s"}`, addresser.AdminAddress(defaultAdminAddress))), nil
}),
)
Expect(err).ToNot(HaveOccurred())
return apiServer
Expand Down
Loading