Skip to content

Commit

Permalink
kuma-cp: generate HTTP-specific configuration of access log
Browse files Browse the repository at this point in the history
  • Loading branch information
yskopets committed Feb 21, 2020
1 parent 01ecff1 commit d293d3f
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 96 deletions.
97 changes: 97 additions & 0 deletions pkg/xds/envoy/listeners/access_log_configurer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package listeners

import (
"fmt"
"strings"

"github.com/pkg/errors"

"github.com/golang/protobuf/ptypes"

envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v2"
filter_accesslog "github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2"
envoy_wellknown "github.com/envoyproxy/go-control-plane/pkg/wellknown"

"github.com/Kong/kuma/api/mesh/v1alpha1"
core_xds "github.com/Kong/kuma/pkg/core/xds"
)

const accessLogSink = "access_log_sink"

type AccessLogConfigurer struct {
sourceService string
destinationService string
backend *v1alpha1.LoggingBackend
proxy *core_xds.Proxy
}

func convertLoggingBackend(sourceService string, destinationService string, backend *v1alpha1.LoggingBackend, proxy *core_xds.Proxy, defaultFormat string) (*filter_accesslog.AccessLog, error) {
if backend == nil {
return nil, nil
}
format := defaultFormat
if backend.Format != "" {
format = backend.Format
}
iface, _ := proxy.Dataplane.Spec.Networking.GetInboundInterface(sourceService)
sourceAddress := ""
if iface != nil {
sourceAddress = iface.DataplaneIP
}
format = strings.ReplaceAll(format, "%KUMA_SOURCE_ADDRESS%", fmt.Sprintf("%s:0", sourceAddress))
format = strings.ReplaceAll(format, "%KUMA_SOURCE_SERVICE%", sourceService)
format = strings.ReplaceAll(format, "%KUMA_DESTINATION_SERVICE%", destinationService)

if file, ok := backend.GetType().(*v1alpha1.LoggingBackend_File_); ok {
return fileAccessLog(format, file)
} else if tcp, ok := backend.GetType().(*v1alpha1.LoggingBackend_Tcp_); ok {
return tcpAccessLog(format, tcp)
} else {
return nil, errors.Errorf("could not convert LoggingBackend of type %T to AccessLog", backend.GetType())
}
}

func tcpAccessLog(format string, tcp *v1alpha1.LoggingBackend_Tcp_) (*filter_accesslog.AccessLog, error) {
fileAccessLog := &accesslog.HttpGrpcAccessLogConfig{
CommonConfig: &accesslog.CommonGrpcAccessLogConfig{
LogName: fmt.Sprintf("%s;%s", tcp.Tcp.Address, format),
GrpcService: &envoy_core.GrpcService{
TargetSpecifier: &envoy_core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_core.GrpcService_EnvoyGrpc{
ClusterName: accessLogSink,
},
},
},
},
}
marshalled, err := ptypes.MarshalAny(fileAccessLog)
if err != nil {
return nil, errors.Wrap(err, "could not marshall FileAccessLog")
}
return &filter_accesslog.AccessLog{
Name: envoy_wellknown.HTTPGRPCAccessLog,
ConfigType: &filter_accesslog.AccessLog_TypedConfig{
TypedConfig: marshalled,
},
}, nil
}

func fileAccessLog(format string, file *v1alpha1.LoggingBackend_File_) (*filter_accesslog.AccessLog, error) {
fileAccessLog := &accesslog.FileAccessLog{
AccessLogFormat: &accesslog.FileAccessLog_Format{
Format: format,
},
Path: file.File.Path,
}
marshalled, err := ptypes.MarshalAny(fileAccessLog)
if err != nil {
return nil, errors.Wrap(err, "could not marshall FileAccessLog")
}
return &filter_accesslog.AccessLog{
Name: envoy_wellknown.FileAccessLog,
ConfigType: &filter_accesslog.AccessLog_TypedConfig{
TypedConfig: marshalled,
},
}, nil
}
49 changes: 49 additions & 0 deletions pkg/xds/envoy/listeners/http_access_log_configurer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package listeners

import (
"github.com/golang/protobuf/proto"

envoy_listener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
envoy_hcm "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
envoy_wellknown "github.com/envoyproxy/go-control-plane/pkg/wellknown"

"github.com/Kong/kuma/api/mesh/v1alpha1"
core_xds "github.com/Kong/kuma/pkg/core/xds"
)

const defaultHttpAccessLogFormat = `[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%KUMA_SOURCE_SERVICE%" "%KUMA_DESTINATION_SERVICE%" "%KUMA_SOURCE_ADDRESS%" "%UPSTREAM_HOST%"\n`

func HttpAccessLog(sourceService string, destinationService string, backend *v1alpha1.LoggingBackend, proxy *core_xds.Proxy) FilterChainBuilderOpt {
return FilterChainBuilderOptFunc(func(config *FilterChainBuilderConfig) {
if backend != nil {
config.Add(&HttpAccessLogConfigurer{
AccessLogConfigurer: AccessLogConfigurer{
sourceService: sourceService,
destinationService: destinationService,
backend: backend,
proxy: proxy,
},
})
}
})
}

type HttpAccessLogConfigurer struct {
AccessLogConfigurer
}

func (c *HttpAccessLogConfigurer) Configure(filterChain *envoy_listener.FilterChain) error {
accessLog, err := convertLoggingBackend(c.AccessLogConfigurer.sourceService, c.AccessLogConfigurer.destinationService, c.AccessLogConfigurer.backend, c.AccessLogConfigurer.proxy, defaultHttpAccessLogFormat)
if err != nil {
return err
}

return UpdateFilterConfig(filterChain, envoy_wellknown.HTTPConnectionManager, func(filterConfig proto.Message) error {
hcm, ok := filterConfig.(*envoy_hcm.HttpConnectionManager)
if !ok {
return NewUnexpectedFilterConfigTypeError(filterConfig, &envoy_hcm.HttpConnectionManager{})
}
hcm.AccessLog = append(hcm.AccessLog, accessLog)
return nil
})
}
186 changes: 186 additions & 0 deletions pkg/xds/envoy/listeners/http_access_log_configurer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package listeners_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

. "github.com/Kong/kuma/pkg/xds/envoy/listeners"

mesh_proto "github.com/Kong/kuma/api/mesh/v1alpha1"
mesh_core "github.com/Kong/kuma/pkg/core/resources/apis/mesh"
"github.com/Kong/kuma/pkg/core/xds"
core_xds "github.com/Kong/kuma/pkg/core/xds"
util_proto "github.com/Kong/kuma/pkg/util/proto"
)

var _ = Describe("HttpAccessLogConfigurer", func() {

type testCase struct {
listenerName string
listenerAddress string
listenerPort uint32
statsName string
routeName string
backend *mesh_proto.LoggingBackend
expected string
}

DescribeTable("should generate proper Envoy config",
func(given testCase) {
// given
sourceService := "web"
destinationService := "backend"
proxy := &core_xds.Proxy{
Id: xds.ProxyId{
Name: "web",
Mesh: "example",
},
Dataplane: &mesh_core.DataplaneResource{
Spec: mesh_proto.Dataplane{
Networking: &mesh_proto.Dataplane_Networking{
Inbound: []*mesh_proto.Dataplane_Networking_Inbound{{
Interface: "192.168.0.1:80:8080",
Tags: map[string]string{
"service": "web",
},
}},
Outbound: []*mesh_proto.Dataplane_Networking_Outbound{{
Interface: ":27070",
Service: "backend",
}},
},
},
},
}

// when
listener, err := NewListenerBuilder().
Configure(OutboundListener(given.listenerName, given.listenerAddress, given.listenerPort)).
Configure(FilterChain(NewFilterChainBuilder().
Configure(HttpConnectionManager(given.statsName)).
Configure(HttpOutboundRoute(given.routeName)).
Configure(HttpAccessLog(sourceService, destinationService, given.backend, proxy)))).
Build()
// then
Expect(err).ToNot(HaveOccurred())

// when
actual, err := util_proto.ToYAML(listener)
Expect(err).ToNot(HaveOccurred())
// and
Expect(actual).To(MatchYAML(given.expected))
},
Entry("basic http_connection_manager without access log", testCase{
listenerName: "outbound:127.0.0.1:27070",
listenerAddress: "127.0.0.1",
listenerPort: 27070,
statsName: "backend",
routeName: "outbound:backend",
backend: nil,
expected: `
name: outbound:127.0.0.1:27070
address:
socketAddress:
address: 127.0.0.1
portValue: 27070
filterChains:
- filters:
- name: envoy.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
httpFilters:
- name: envoy.router
rds:
configSource:
ads: {}
routeConfigName: outbound:backend
statPrefix: backend
`,
}),
Entry("basic http_connection_manager with file access log", testCase{
listenerName: "outbound:127.0.0.1:27070",
listenerAddress: "127.0.0.1",
listenerPort: 27070,
statsName: "backend",
routeName: "outbound:backend",
backend: &mesh_proto.LoggingBackend{
Name: "file",
Type: &mesh_proto.LoggingBackend_File_{
File: &mesh_proto.LoggingBackend_File{
Path: "/tmp/log",
},
},
},
expected: `
name: outbound:127.0.0.1:27070
address:
socketAddress:
address: 127.0.0.1
portValue: 27070
filterChains:
- filters:
- name: envoy.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
accessLog:
- name: envoy.file_access_log
typedConfig:
'@type': type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
format: '[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "web" "backend" "192.168.0.1:0" "%UPSTREAM_HOST%"\n'
path: /tmp/log
httpFilters:
- name: envoy.router
rds:
configSource:
ads: {}
routeConfigName: outbound:backend
statPrefix: backend
`,
}),
Entry("basic http_connection_manager with tcp access log", testCase{
listenerName: "outbound:127.0.0.1:27070",
listenerAddress: "127.0.0.1",
listenerPort: 27070,
statsName: "backend",
routeName: "outbound:backend",
backend: &mesh_proto.LoggingBackend{
Name: "tcp",
Format: "custom format",
Type: &mesh_proto.LoggingBackend_Tcp_{
Tcp: &mesh_proto.LoggingBackend_Tcp{
Address: "127.0.0.1:1234",
},
},
},
expected: `
name: outbound:127.0.0.1:27070
address:
socketAddress:
address: 127.0.0.1
portValue: 27070
filterChains:
- filters:
- name: envoy.http_connection_manager
typedConfig:
'@type': type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
accessLog:
- name: envoy.http_grpc_access_log
typedConfig:
'@type': type.googleapis.com/envoy.config.accesslog.v2.HttpGrpcAccessLogConfig
commonConfig:
grpcService:
envoyGrpc:
clusterName: access_log_sink
logName: 127.0.0.1:1234;custom format
httpFilters:
- name: envoy.router
rds:
configSource:
ads: {}
routeConfigName: outbound:backend
statPrefix: backend
`,
}),
)
})
Loading

0 comments on commit d293d3f

Please sign in to comment.