Restructure config and setup of handlers

- move initialization and dependency resolving to registration of protocol handlers
- replace file opening in HTTP mock with fs.FS - depends on having a FS that returns an io.ReadSeeker - could be worked around in the future
- update config for new routing rules to see how they look like in production
- extend grammar tests
This commit is contained in:
Peter 2021-04-26 22:35:28 +02:00
parent 45a6d11d5b
commit 9f53b01e49
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
19 changed files with 248 additions and 215 deletions

View file

@ -10,27 +10,18 @@ import (
"gitlab.com/inetmock/inetmock/internal/app"
"gitlab.com/inetmock/inetmock/internal/endpoint"
dns "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
http "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
"gitlab.com/inetmock/inetmock/pkg/cert"
)
var (
serverApp app.App
cfg appConfig
registrations = []endpoint.Registration{
http.AddHTTPMock,
dns.AddDNSMock,
proxy.AddHTTPProxy,
metrics.AddMetricsExporter,
}
serverApp app.App
cfg appConfig
)
type Data struct {
PCAP string
Audit string
PCAP string
Audit string
FakeFiles string
}
func (d *Data) setup() (err error) {

View file

@ -1,10 +1,17 @@
package main
import (
"io/fs"
"os"
"github.com/spf13/cobra"
"go.uber.org/zap"
"gitlab.com/inetmock/inetmock/internal/endpoint"
dnsmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
"gitlab.com/inetmock/inetmock/internal/pcap"
audit2 "gitlab.com/inetmock/inetmock/internal/pcap/consumers/audit"
"gitlab.com/inetmock/inetmock/internal/rpc"
@ -39,12 +46,7 @@ func startINetMock(_ *cobra.Command, _ []string) error {
return err
}
for _, registration := range registrations {
if err = registration(registry); err != nil {
appLogger.Error("Failed to run registration", zap.Error(err))
return err
}
}
fakeFileFS := os.DirFS(cfg.Data.FakeFiles)
var certStore cert.Store
if certStore, err = cert.NewDefaultStore(cfg.TLS, appLogger.Named("CertStore")); err != nil {
@ -57,8 +59,12 @@ func startINetMock(_ *cobra.Command, _ []string) error {
return err
}
if err = setupEndpointHandlers(registry, appLogger, eventStream, certStore, fakeFileFS); err != nil {
appLogger.Error("Failed to run registration", zap.Error(err))
return err
}
var endpointOrchestrator = endpoint.NewOrchestrator(
serverApp.Context(),
certStore,
registry,
eventStream,
@ -86,7 +92,7 @@ func startINetMock(_ *cobra.Command, _ []string) error {
}
}
errChan := endpointOrchestrator.StartEndpoints()
errChan := endpointOrchestrator.StartEndpoints(serverApp.Context())
if err := rpcAPI.StartServer(); err != nil {
serverApp.Shutdown()
appLogger.Error(
@ -148,6 +154,22 @@ func setupEventStream(appLogger logging.Logger) (audit.EventStream, error) {
return evenStream, nil
}
func setupEndpointHandlers(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, store cert.Store, fakeFileFS fs.FS) (err error) {
if err = mock.AddHTTPMock(registry, logger, emitter, fakeFileFS); err != nil {
return
}
if err = proxy.AddHTTPProxy(registry, logger, emitter, store); err != nil {
return
}
if err = dnsmock.AddDNSMock(registry, logger, emitter); err != nil {
return
}
if err = metrics.AddMetricsExporter(registry, logger); err != nil {
return
}
return nil
}
//nolint:deadcode
func startAuditConsumer(eventStream audit.EventStream) error {
recorder := pcap.NewRecorder()

View file

@ -1,45 +1,19 @@
x-response-rules: &httpResponseRules
rules:
- pattern: ".*\\.(?i)exe"
matcher: Path
- pattern: "^application/octet-stream$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/sample.exe
- pattern: "^image/jpeg$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.jpg
- pattern: ".*\\.(?i)(jpg|jpeg)"
matcher: Path
response: ./assets/fakeFiles/default.jpg
- pattern: "^image/png$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.png
- pattern: ".*\\.(?i)png"
matcher: Path
response: ./assets/fakeFiles/default.png
- pattern: ".*\\.(?i)gif"
matcher: Path
response: ./assets/fakeFiles/default.gif
- pattern: ".*\\.(?i)ico"
matcher: Path
response: ./assets/fakeFiles/default.ico
- pattern: "^text/plain$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.txt
- pattern: ".*\\.(?i)txt"
matcher: Path
response: ./assets/fakeFiles/default.txt
- pattern: "^text/html$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.html
- pattern: ".*"
matcher: Path
response: ./assets/fakeFiles/default.html
- PathPattern("") => File("sample.exe")
- Header("Accept", "application/octet-stream") => File("sample.exe")
- Header("Accept", "image/jpeg") => File("default.jpg")
- PathPattern(".*\\.(?i)(jpg|jpeg)") => File("default.jpg")
- Header("Accept", "image/png") => File("default.png")
- PathPattern(".*\\.(?i)png") => File("default.png")
- Header("Accept", "image/gif") => File("default.gif")
- PathPattern(".*\\.(?i)gif") => File("default.gif")
- Header("Accept", "image/x-icon") => File("default.ico")
- PathPattern(".*\\.(?i)ico") => File("default.ico")
- Header("Accept", "text/plain") => File("default.txt")
- PathPattern(".*\\.(?i)txt") => File("default.txt")
- Header("Accept", "text/html") => File("default.html")
- PathPattern(".*") => File("default.html")
x-http-handlers: &httpHandlers
endpoints:
@ -57,6 +31,7 @@ x-http-handlers: &httpHandlers
data:
pcap: /var/lib/inetmock/data/pcap
audit: /var/lib/inetmock/data/audit
fakeFiles: ./assets/fakeFiles
api:
listen: unix:///var/run/inetmock.sock

View file

@ -6,24 +6,16 @@ import (
"context"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
type Lifecycle interface {
Name() string
Logger() logging.Logger
CertStore() cert.Store
Audit() audit.Emitter
Context() context.Context
Uplink() Uplink
UnmarshalOptions(cfg interface{}) error
}
type ProtocolHandler interface {
Start(ctx Lifecycle) error
Start(ctx context.Context, lifecycle Lifecycle) error
}
type MultiplexHandler interface {

View file

@ -5,6 +5,8 @@ import (
"time"
"go.uber.org/zap"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
const (
@ -17,19 +19,19 @@ type Endpoint struct {
uplink Uplink
}
func (e *Endpoint) Start(lifecycle Lifecycle) (err error) {
func (e *Endpoint) Start(ctx context.Context, logger logging.Logger, lifecycle Lifecycle) (err error) {
startupResult := make(chan error)
ctx, cancel := context.WithTimeout(lifecycle.Context(), startupTimeoutDuration)
ctx, cancel := context.WithTimeout(ctx, startupTimeoutDuration)
defer cancel()
go func() {
defer func() {
if r := recover(); r != nil {
lifecycle.Logger().Fatal("Startup error recovered", zap.Any("recovered", r))
logger.Fatal("Startup error recovered", zap.Any("recovered", r))
}
}()
startupResult <- e.Handler.Start(lifecycle)
startupResult <- e.Handler.Start(ctx, lifecycle)
}()
select {

View file

@ -8,6 +8,7 @@ import (
"go.uber.org/zap"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
@ -15,17 +16,18 @@ const shutdownTimeout = 100 * time.Millisecond
type dnsHandler struct {
logger logging.Logger
emitter audit.Emitter
dnsServer *dns.Server
}
func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
func (d *dnsHandler) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
var err error
var options dnsOptions
if options, err = loadFromConfig(lifecycle); err != nil {
return err
}
d.logger = lifecycle.Logger().With(
d.logger = d.logger.With(
zap.String("handler_name", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
@ -33,8 +35,8 @@ func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
handler := &regexHandler{
handlerName: lifecycle.Name(),
fallback: options.Fallback,
logger: lifecycle.Logger(),
auditEmitter: lifecycle.Audit(),
logger: d.logger,
auditEmitter: d.emitter,
}
for _, rule := range options.Rules {
@ -59,6 +61,7 @@ func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
}
go d.startServer()
go d.shutdownOnEnd(ctx)
return nil
}

View file

@ -6,6 +6,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
@ -21,7 +23,7 @@ var (
initLock sync.Locker = new(sync.Mutex)
)
func AddDNSMock(registry endpoint.HandlerRegistry) error {
func AddDNSMock(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter) error {
initLock.Lock()
defer initLock.Unlock()
@ -61,7 +63,10 @@ func AddDNSMock(registry endpoint.HandlerRegistry) error {
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &dnsHandler{}
return &dnsHandler{
logger: logger,
emitter: emitter,
}
})
return nil

View file

@ -3,6 +3,7 @@ package mock
import (
"context"
"errors"
"io/fs"
"net"
"net/http"
@ -11,6 +12,7 @@ import (
"gitlab.com/inetmock/inetmock/internal/endpoint"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
@ -21,16 +23,18 @@ const (
)
type httpHandler struct {
logger logging.Logger
server *http.Server
logger logging.Logger
fakeFileFS fs.FS
server *http.Server
emitter audit.Emitter
}
func (p *httpHandler) Matchers() []cmux.Matcher {
return []cmux.Matcher{cmux.HTTP1()}
}
func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
p.logger = lifecycle.Logger().With(
func (p *httpHandler) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
p.logger = p.logger.With(
zap.String("protocol_handler", name),
)
@ -45,9 +49,10 @@ func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
)
router := &RegexHandler{
logger: p.logger,
emitter: lifecycle.Audit(),
handlerName: lifecycle.Name(),
logger: p.logger,
emitter: p.emitter,
fakeFileFS: p.fakeFileFS,
}
p.server = &http.Server{
Handler: router,
@ -59,7 +64,7 @@ func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
}
go p.startServer(lifecycle.Uplink().Listener)
go p.shutdownOnCancel(lifecycle.Context())
go p.shutdownOnCancel(ctx)
return nil
}

View file

@ -1,9 +1,10 @@
package mock
import (
"io"
"io/fs"
"math/rand"
"net/http"
"os"
"path"
"strconv"
"time"
@ -27,13 +28,15 @@ type RegexHandler struct {
logger logging.Logger
routes []*route
emitter audit.Emitter
fakeFileFS fs.FS
}
func NewRegexHandler(name string, logger logging.Logger, emitter audit.Emitter) *RegexHandler {
func NewRegexHandler(name string, logger logging.Logger, emitter audit.Emitter, fakeFileFS fs.FS) *RegexHandler {
return &RegexHandler{
handlerName: name,
logger: logger,
emitter: emitter,
fakeFileFS: fakeFileFS,
}
}
@ -68,23 +71,32 @@ func (h *RegexHandler) AddRouteRule(rule TargetRule) {
h.Handler(rule, emittingFileHandler{
emitter: h.emitter,
targetPath: rule.response,
fs: h.fakeFileFS,
})
}
type emittingFileHandler struct {
emitter audit.Emitter
targetPath string
fs fs.FS
}
func (f emittingFileHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
f.emitter.Emit(imHttp.EventFromRequest(request, v1.AppProtocol_APP_PROTOCOL_HTTP))
file, err := os.Open(f.targetPath)
file, err := f.fs.Open(f.targetPath)
if err != nil {
http.Error(writer, err.Error(), 500)
}
var rs io.ReadSeeker
var ok bool
if rs, ok = file.(io.ReadSeeker); !ok {
http.Error(writer, "internal server error", 500)
}
defer func() {
_ = file.Close()
}()
//nolint:gosec
http.ServeContent(writer, request, path.Base(request.RequestURI), time.Now().Add(-(time.Duration(rand.Int()) * time.Millisecond)), file)
http.ServeContent(writer, request, path.Base(request.RequestURI), time.Now().Add(-(time.Duration(rand.Int()) * time.Millisecond)), rs)
}

View file

@ -1,13 +1,14 @@
package mock_test
import (
"crypto/sha256"
"encoding/hex"
"io"
"io/fs"
"net/http"
"net/url"
"path/filepath"
"strings"
"testing"
"testing/fstest"
"time"
"github.com/golang/mock/gomock"
"github.com/maxatome/go-testdeep/td"
@ -19,6 +20,19 @@ import (
"gitlab.com/inetmock/inetmock/pkg/logging"
)
var (
defaultHTMLContent = `<html>
<head>
<title>INetSim default HTML page</title>
</head>
<body>
<p></p>
<p align="center">This is the default HTML page for INetMock HTTP mock handler.</p>
<p align="center">This file is an HTML document.</p>
</body>
</html>`
)
//nolint:funlen
func TestRegexpHandler_ServeHTTP(t *testing.T) {
t.Parallel()
@ -28,25 +42,32 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
type fields struct {
rules []mock.TargetRule
emitterSetup func(tb testing.TB, ctrl *gomock.Controller) audit.Emitter
fakeFileFS fs.FS
}
type args struct {
req *http.Request
}
tests := []struct {
name string
fields fields
args args
wantErr bool
wantStatus interface{}
wantRespHash string
name string
fields fields
args args
wantErr bool
wantStatus interface{}
want string
}{
{
name: "GET /index.html",
fields: fields{
rules: []mock.TargetRule{
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
},
emitterSetup: defaultEmitter,
fakeFileFS: fstest.MapFS{
"default.html": &fstest.MapFile{
Data: []byte(defaultHTMLContent),
ModTime: time.Now().Add(-1337 * time.Second),
},
},
},
args: args{
req: &http.Request{
@ -54,16 +75,22 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
Method: http.MethodGet,
},
},
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
wantStatus: td.Between(200, 299),
want: defaultHTMLContent,
wantStatus: td.Between(200, 299),
},
{
name: "GET /profile.htm",
fields: fields{
rules: []mock.TargetRule{
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
},
emitterSetup: defaultEmitter,
fakeFileFS: fstest.MapFS{
"default.html": &fstest.MapFile{
Data: []byte(defaultHTMLContent),
ModTime: time.Now().Add(-1337 * time.Second),
},
},
},
args: args{
req: &http.Request{
@ -71,17 +98,23 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
Method: http.MethodGet,
},
},
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
wantStatus: td.Between(200, 299),
want: defaultHTMLContent,
wantStatus: td.Between(200, 299),
},
{
name: "GET with Accept: text/html",
fields: fields{
rules: []mock.TargetRule{
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
mock.MustHeaderTargetRule("Accept", "(?i)text/html", filepath.Join("testdata", "default.html")),
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
mock.MustHeaderTargetRule("Accept", "(?i)text/html", "default.html"),
},
emitterSetup: defaultEmitter,
fakeFileFS: fstest.MapFS{
"default.html": &fstest.MapFile{
Data: []byte(defaultHTMLContent),
ModTime: time.Now().Add(-1337 * time.Second),
},
},
},
args: args{
req: &http.Request{
@ -92,8 +125,8 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
Method: http.MethodGet,
},
},
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
wantStatus: td.Between(200, 299),
want: defaultHTMLContent,
wantStatus: td.Between(200, 299),
},
}
for _, tc := range tests {
@ -102,7 +135,7 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
t.Parallel()
logger := logging.CreateTestLogger(t)
ctrl := gomock.NewController(t)
h := mock.NewRegexHandler(t.Name(), logger, tt.fields.emitterSetup(t, ctrl))
h := mock.NewRegexHandler(t.Name(), logger, tt.fields.emitterSetup(t, ctrl), tt.fields.fakeFileFS)
for _, rule := range tt.fields.rules {
h.AddRouteRule(rule)
@ -121,11 +154,10 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
td.Cmp(t, resp.StatusCode, tt.wantStatus)
sha256Hash := sha256.New()
_, err = io.Copy(sha256Hash, resp.Body)
builder := new(strings.Builder)
_, err = io.Copy(builder, resp.Body)
td.CmpNoError(t, err)
computedHash := hex.EncodeToString(sha256Hash.Sum(nil))
td.Cmp(t, computedHash, tt.wantRespHash)
td.Cmp(t, builder.String(), tt.want)
})
}
}

View file

@ -1,11 +1,14 @@
package mock
import (
"io/fs"
"sync"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
@ -46,13 +49,16 @@ func InitMetrics() error {
return nil
}
func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) {
func AddHTTPMock(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, fakeFileFS fs.FS) (err error) {
if err := InitMetrics(); err != nil {
return err
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpHandler{}
return &httpHandler{
emitter: emitter,
logger: logger,
}
})
return

View file

@ -12,6 +12,8 @@ import (
"gitlab.com/inetmock/inetmock/internal/endpoint"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
@ -20,16 +22,18 @@ const (
)
type httpProxy struct {
logger logging.Logger
proxy *goproxy.ProxyHttpServer
server *http.Server
logger logging.Logger
proxy *goproxy.ProxyHttpServer
certStore cert.Store
emitter audit.Emitter
server *http.Server
}
func (h *httpProxy) Matchers() []cmux.Matcher {
return []cmux.Matcher{cmux.HTTP1()}
}
func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) error {
func (h *httpProxy) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
var opts httpProxyOptions
if err := lifecycle.UnmarshalOptions(&opts); err != nil {
return err
@ -44,25 +48,25 @@ func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) error {
zap.String("address", lifecycle.Uplink().Addr().String()),
)
tlsConfig := lifecycle.CertStore().TLSConfig()
tlsConfig := h.certStore.TLSConfig()
proxyHandler := &proxyHTTPHandler{
handlerName: lifecycle.Name(),
options: opts,
logger: h.logger,
emitter: lifecycle.Audit(),
emitter: h.emitter,
}
proxyHTTPSHandler := &proxyHTTPSHandler{
options: opts,
tlsConfig: tlsConfig,
emitter: lifecycle.Audit(),
emitter: h.emitter,
}
h.proxy.OnRequest().Do(proxyHandler)
h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler)
go h.startProxy(lifecycle.Uplink().Listener)
go h.shutdownOnContextDone(lifecycle.Context())
go h.shutdownOnContextDone(ctx)
return nil
}

View file

@ -6,6 +6,8 @@ import (
"go.uber.org/zap"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
@ -15,11 +17,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec
)
func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
}
func AddHTTPProxy(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, store cert.Store) (err error) {
logger = logger.With(
zap.String("protocol_handler", name),
)
@ -30,8 +28,10 @@ func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpProxy{
logger: logger,
proxy: goproxy.NewProxyHttpServer(),
logger: logger,
emitter: emitter,
certStore: store,
proxy: goproxy.NewProxyHttpServer(),
}
})

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"errors"
"net/http"
@ -20,7 +21,7 @@ type metricsExporter struct {
server *http.Server
}
func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) error {
func (m *metricsExporter) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
var exporterOptions metricsExporterOptions
if err := lifecycle.UnmarshalOptions(&exporterOptions); err != nil {
return err
@ -47,7 +48,7 @@ func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) error {
}()
go func() {
<-lifecycle.Context().Done()
<-ctx.Done()
if err := m.server.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) {
m.logger.Error("failed to stop metrics server", zap.Error(err))
}

View file

@ -7,11 +7,7 @@ import (
"gitlab.com/inetmock/inetmock/pkg/logging"
)
func AddMetricsExporter(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
}
func AddMetricsExporter(registry endpoint.HandlerRegistry, logger logging.Logger) (err error) {
logger = logger.With(
zap.String("protocol_handler", name),
)

View file

@ -1,40 +1,22 @@
package endpoint
import (
"context"
"github.com/mitchellh/mapstructure"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
type endpointLifecycle struct {
endpointName string
ctx context.Context
logger logging.Logger
certStore cert.Store
emitter audit.Emitter
uplink Uplink
opts map[string]interface{}
}
func NewEndpointLifecycleFromContext(
func NewEndpointLifecycle(
endpointName string,
ctx context.Context,
logger logging.Logger,
certStore cert.Store,
emitter audit.Emitter,
uplink Uplink,
opts map[string]interface{},
) Lifecycle {
return &endpointLifecycle{
endpointName: endpointName,
ctx: ctx,
logger: logger,
certStore: certStore,
emitter: emitter,
uplink: uplink,
opts: opts,
}
@ -48,22 +30,6 @@ func (e *endpointLifecycle) Uplink() Uplink {
return e.uplink
}
func (e *endpointLifecycle) Logger() logging.Logger {
return e.logger
}
func (e *endpointLifecycle) CertStore() cert.Store {
return e.certStore
}
func (e *endpointLifecycle) Audit() audit.Emitter {
return e.emitter
}
func (e *endpointLifecycle) Context() context.Context {
return e.ctx
}
func (e *endpointLifecycle) UnmarshalOptions(cfg interface{}) error {
return mapstructure.Decode(e.opts, cfg)
}

View file

@ -18,18 +18,16 @@ var (
type Orchestrator interface {
RegisterListener(spec ListenerSpec) error
StartEndpoints() (errChan chan error)
StartEndpoints(ctx context.Context) (errChan chan error)
}
func NewOrchestrator(
appCtx context.Context,
certStore cert.Store,
registry HandlerRegistry,
emitter audit.Emitter,
logger logging.Logger,
) Orchestrator {
return &orchestrator{
appCtx: appCtx,
registry: registry,
logger: logger,
certStore: certStore,
@ -38,7 +36,6 @@ func NewOrchestrator(
}
type orchestrator struct {
appCtx context.Context
registry HandlerRegistry
logger logging.Logger
certStore cert.Store
@ -68,24 +65,20 @@ func (e *orchestrator) RegisterListener(spec ListenerSpec) (err error) {
return
}
func (e *orchestrator) StartEndpoints() chan error {
func (e *orchestrator) StartEndpoints(ctx context.Context) chan error {
var errChan = make(chan error)
for _, epListener := range e.endpointListeners {
endpointLogger := e.logger.With(
zap.String("epListener", epListener.name),
)
endpointLogger.Info("Starting epListener")
lifecycle := NewEndpointLifecycleFromContext(
lifecycle := NewEndpointLifecycle(
epListener.name,
e.appCtx,
e.logger.With(zap.String("epListener", epListener.name)),
e.certStore,
e.emitter,
epListener.uplink,
epListener.Options,
)
if err := epListener.Start(lifecycle); err == nil {
if err := epListener.Start(ctx, e.logger.With(zap.String("epListener", epListener.name)), lifecycle); err == nil {
endpointLogger.Info("successfully started epListener")
} else {
endpointLogger.Error("error occurred during epListener startup - will be skipped for now")

View file

@ -2,43 +2,61 @@ package endpoint_test
import (
"testing"
"testing/fstest"
"github.com/golang/mock/gomock"
"github.com/maxatome/go-testdeep/td"
"gitlab.com/inetmock/inetmock/internal/endpoint"
dnsmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
httpmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
t.Parallel()
tests := []struct {
name string
handlerRegistry endpoint.HandlerRegistry
handlerRegistrySetup func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry
wantAvailableHandlers interface{}
}{
{
name: "Empty registry",
handlerRegistry: endpoint.NewHandlerRegistry(),
name: "Empty registry",
handlerRegistrySetup: func(testing.TB, *gomock.Controller) endpoint.HandlerRegistry {
return endpoint.NewHandlerRegistry()
},
wantAvailableHandlers: td.Nil(),
},
{
name: "Single handler registered",
handlerRegistry: func() endpoint.HandlerRegistry {
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
tb.Helper()
registry := endpoint.NewHandlerRegistry()
_ = httpmock.AddHTTPMock(registry)
logger := logging.CreateTestLogger(tb)
emitter := audit_mock.NewMockEmitter(ctrl)
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
tb.Fatalf("AddHTTPMock() error = %v", err)
}
return registry
}(),
},
wantAvailableHandlers: td.Set(endpoint.HandlerReference("http_mock")),
},
{
name: "Multiple handler registered",
handlerRegistry: func() endpoint.HandlerRegistry {
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
tb.Helper()
registry := endpoint.NewHandlerRegistry()
_ = httpmock.AddHTTPMock(registry)
_ = dnsmock.AddDNSMock(registry)
logger := logging.CreateTestLogger(tb)
emitter := audit_mock.NewMockEmitter(ctrl)
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
tb.Fatalf("AddHTTPMock() error = %v", err)
}
if err := dnsmock.AddDNSMock(registry, logger, emitter); err != nil {
tb.Fatalf("AddHTTPMock() error = %v", err)
}
return registry
}(),
},
wantAvailableHandlers: td.Set(
endpoint.HandlerReference("dns_mock"),
endpoint.HandlerReference("http_mock"),
@ -49,7 +67,8 @@ func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
tt := tc
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotAvailableHandlers := tt.handlerRegistry.AvailableHandlers()
ctrl := gomock.NewController(t)
gotAvailableHandlers := tt.handlerRegistrySetup(t, ctrl).AvailableHandlers()
td.Cmp(t, gotAvailableHandlers, tt.wantAvailableHandlers)
})
}
@ -58,26 +77,34 @@ func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
func Test_handlerRegistry_HandlerForName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
handlerRegistry endpoint.HandlerRegistry
handlerRef endpoint.HandlerReference
wantInstance interface{}
wantOk bool
name string
handlerRegistrySetup func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry
handlerRef endpoint.HandlerReference
wantInstance interface{}
wantOk bool
}{
{
name: "Empty registry",
handlerRegistry: endpoint.NewHandlerRegistry(),
handlerRef: "http_mock",
wantInstance: nil,
wantOk: false,
name: "Empty registry",
handlerRegistrySetup: func(tb testing.TB, _ *gomock.Controller) endpoint.HandlerRegistry {
tb.Helper()
return endpoint.NewHandlerRegistry()
},
handlerRef: "http_mock",
wantInstance: nil,
wantOk: false,
},
{
name: "Registry with HTTP mock registered",
handlerRegistry: func() endpoint.HandlerRegistry {
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
tb.Helper()
registry := endpoint.NewHandlerRegistry()
_ = httpmock.AddHTTPMock(registry)
logger := logging.CreateTestLogger(tb)
emitter := audit_mock.NewMockEmitter(ctrl)
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
tb.Fatalf("AddHTTPMock() error = %v", err)
}
return registry
}(),
},
handlerRef: "http_mock",
wantInstance: td.NotNil(),
wantOk: true,
@ -87,7 +114,8 @@ func Test_handlerRegistry_HandlerForName(t *testing.T) {
tt := tc
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotInstance, gotOk := tt.handlerRegistry.HandlerForName(tt.handlerRef)
ctrl := gomock.NewController(t)
gotInstance, gotOk := tt.handlerRegistrySetup(t, ctrl).HandlerForName(tt.handlerRef)
td.Cmp(t, gotInstance, tt.wantInstance)
td.Cmp(t, gotOk, tt.wantOk)
})

View file

@ -22,11 +22,11 @@ func TestParse(t *testing.T) {
{
name: "Terminator only - string argument",
args: args{
rule: `=> ReturnFile("default.html")`,
rule: `=> File("default.html")`,
},
want: &Routing{
Terminator: &Method{
Name: "ReturnFile",
Name: "File",
Params: []Param{
{
String: stringRef("default.html"),
@ -73,7 +73,7 @@ func TestParse(t *testing.T) {
{
name: "path pattern and terminator",
args: args{
rule: `PathPattern("/index.html") => ReturnFile("default.html")`,
rule: `PathPattern(".*\\.(?i)png") => ReturnFile("default.html")`,
},
want: &Routing{
Terminator: &Method{
@ -90,7 +90,7 @@ func TestParse(t *testing.T) {
Name: "PathPattern",
Params: []Param{
{
String: stringRef("/index.html"),
String: stringRef(`.*\.(?i)png`),
},
},
},