diff --git a/Makefile b/Makefile index fd9fa8b48..2c7066904 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,9 @@ SCHEMA_FILE = schema.yml TEST_COUNT = 1 TEST_FLAGS = +GEN_API_TS = "gen-ts-api" +GEN_API_GO = "api" + ci--env: echo "sha=${GITHUB_SHA}" >> ${GITHUB_OUTPUT} echo "build=${GITHUB_RUN_ID}" >> ${GITHUB_OUTPUT} @@ -22,7 +25,7 @@ docker-build: internal/resources/macoui internal/resources/blocky internal/resou go build \ -ldflags "${LD_FLAGS} -X beryju.io/gravity/pkg/extconfig.BuildHash=${GIT_BUILD_HASH}" \ ${GO_BUILD_FLAGS} \ - -v -a -o gravity ${PWD} + -v -a -o gravity ${PWD}/cmd/server/main clean: rm -rf ${PWD}/data/ @@ -34,7 +37,10 @@ run: internal/resources/macoui internal/resources/blocky internal/resources/tftp export DEBUG=true export LISTEN_ONLY=true $(eval LD_FLAGS := -X beryju.io/gravity/pkg/extconfig.Version=${VERSION} -X beryju.io/gravity/pkg/extconfig.BuildHash=dev-$(shell git rev-parse HEAD)) - go run ${GO_FLAGS} ${PWD} server + go run \ + ${GO_FLAGS} \ + ${PWD}/cmd/server/main \ + server # Web web: web-lint web-build @@ -90,21 +96,25 @@ internal/resources/tftp: curl -L https://boot.netboot.xyz/ipxe/netboot.xyz.efi -o ${PWD}/internal/resources/tftp/netboot.xyz.efi gen-build: - DEBUG=true go run ${GO_FLAGS} ${PWD} generateSchema ${SCHEMA_FILE} + export DEBUG=true + go run \ + ${GO_FLAGS} \ + ${PWD}/cmd/server/main \ + generateSchema ${SCHEMA_FILE} git add ${SCHEMA_FILE} gen-proto: protoc \ --proto_path . \ - --go_out . \ + --go_out ${PWD} \ protobuf/** gen-clean: - rm -rf ${PWD}/gen-ts-api/ - rm -rf ${PWD}/api/api/ - rm -rf ${PWD}/api/docs/ - rm -rf ${PWD}/api/test/ - rm -rf ${PWD}/api/*.go + rm -rf ${PWD}/${GEN_API_TS}/ + rm -rf ${PWD}/${GEN_API_GO}/api/ + rm -rf ${PWD}/${GEN_API_GO}/docs/ + rm -rf ${PWD}/${GEN_API_GO}/test/ + rm -rf ${PWD}/${GEN_API_GO}/*.go gen-tag: git add Makefile @@ -123,15 +133,15 @@ gen-client-go: --additional-properties=packageName=api \ -i /local/schema.yml \ -g go \ - -o /local/api \ - -c /local/api/config.yaml - cd ${PWD}/api/ + -o /local/${GEN_API_GO} \ + -c /local/${GEN_API_GO}/config.yaml + cd ${PWD}/${GEN_API_GO}/ rm -f .travis.yml go.mod go.sum go get - go fmt . + go fmt ${PWD}/${GEN_API_GO}/ go mod tidy - gofumpt -l -w . || true - git add . + gofumpt -l -w ${PWD}/${GEN_API_GO}/ || true + git add ${PWD}/${GEN_API_GO}/ gen-client-ts: docker run \ @@ -140,15 +150,15 @@ gen-client-ts: openapitools/openapi-generator-cli:v6.6.0 generate \ -i /local/${SCHEMA_FILE} \ -g typescript-fetch \ - -o /local/gen-ts-api \ + -o /local/${GEN_API_TS} \ --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=gravity-api,npmVersion=${VERSION} \ --git-repo-id BeryJu \ --git-user-id gravity - cd ${PWD}/gen-ts-api && npm i - \cp -rf ${PWD}/gen-ts-api/* ${PWD}/web/node_modules/gravity-api + cd ${PWD}/${GEN_API_TS} && npm i + \cp -rf ${PWD}/${GEN_API_TS}/* ${PWD}/web/node_modules/gravity-api gen-client-ts-publish: gen-client-ts - cd ${PWD}/gen-ts-api + cd ${PWD}/${GEN_API_TS} npm publish cd ${PWD}/web npm i gravity-api@${VERSION} diff --git a/api/.openapi-generator/FILES b/api/.openapi-generator/FILES index e54bcb245..807c91f5c 100644 --- a/api/.openapi-generator/FILES +++ b/api/.openapi-generator/FILES @@ -226,15 +226,4 @@ model_types_api_metrics_role.go model_types_dhcp_option.go model_types_oidc_config.go response.go -test/api_cluster_instances_test.go -test/api_cluster_test.go -test/api_roles_api_test.go -test/api_roles_backup_test.go -test/api_roles_dhcp_test.go -test/api_roles_discovery_test.go -test/api_roles_dns_test.go -test/api_roles_etcd_test.go -test/api_roles_monitoring_test.go -test/api_roles_tftp_test.go -test/api_roles_tsdb_test.go utils.go diff --git a/cmd/cli/cli_health.go b/cmd/cli/cli_health.go index 7a719b10d..e0341b247 100644 --- a/cmd/cli/cli_health.go +++ b/cmd/cli/cli_health.go @@ -1,21 +1,33 @@ package cli import ( + "encoding/json" + "fmt" "os" "github.com/spf13/cobra" + "go.uber.org/zap" ) var healthCmd = &cobra.Command{ Use: "health", Short: "Check health and version", Run: func(cmd *cobra.Command, args []string) { - v, hr, err := apiClient.ClusterInstancesApi.ClusterGetInstanceInfo(cmd.Context()).Execute() + c, hr, err := apiClient.ClusterApi.ClusterGetClusterInfo(cmd.Context()).Execute() if err != nil { checkApiError(hr, err) os.Exit(1) } - logger.Info(v.Version) + m := map[string]interface{}{ + "clusterVersion": c.ClusterVersion, + "instances": c.Instances, + } + b, err := json.MarshalIndent(m, "", "\t") + if err != nil { + logger.Warn("failed to render JSON", zap.Error(err)) + return + } + fmt.Println(string(b)) }, } diff --git a/cmd/generateSchema.go b/cmd/server/generateSchema.go similarity index 95% rename from cmd/generateSchema.go rename to cmd/server/generateSchema.go index f6e2b25af..166cbaea9 100644 --- a/cmd/generateSchema.go +++ b/cmd/server/generateSchema.go @@ -1,4 +1,4 @@ -package cmd +package server import ( "context" @@ -69,6 +69,8 @@ var generateSchemaCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(generateSchemaCmd) + if extconfig.Get().Debug { + rootCmd.AddCommand(generateSchemaCmd) + } generateSchemaCmd.PersistentFlags().StringVarP(&schemaFormat, "format", "f", "yaml", "Output format (yaml/json)") } diff --git a/cmd/generateSchema_test.go b/cmd/server/generateSchema_test.go similarity index 76% rename from cmd/generateSchema_test.go rename to cmd/server/generateSchema_test.go index 00a1f1757..64eb18996 100644 --- a/cmd/generateSchema_test.go +++ b/cmd/server/generateSchema_test.go @@ -1,10 +1,10 @@ -package cmd_test +package server_test import ( "encoding/json" "testing" - "beryju.io/gravity/cmd" + "beryju.io/gravity/cmd/server" "beryju.io/gravity/pkg/tests" "github.com/stretchr/testify/assert" ) @@ -12,7 +12,7 @@ import ( func TestGenerateSchema(t *testing.T) { defer tests.Setup(t)() called := false - cmd.GenerateSchema(tests.Context(), "json", func(schema []byte) { + server.GenerateSchema(tests.Context(), "json", func(schema []byte) { assert.NotEqual(t, "", string(schema)) var out interface{} err := json.Unmarshal(schema, &out) diff --git a/cmd/server/main/main.go b/cmd/server/main/main.go new file mode 100644 index 000000000..35e02ef1b --- /dev/null +++ b/cmd/server/main/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "beryju.io/gravity/cmd/server" +) + +func main() { + server.Execute() +} diff --git a/cmd/root.go b/cmd/server/root.go similarity index 96% rename from cmd/root.go rename to cmd/server/root.go index 7761cfc88..cc0c03e16 100644 --- a/cmd/root.go +++ b/cmd/server/root.go @@ -1,4 +1,4 @@ -package cmd +package server import ( "os" diff --git a/cmd/server.go b/cmd/server/server.go similarity index 96% rename from cmd/server.go rename to cmd/server/server.go index 7916a12bb..99f384a36 100644 --- a/cmd/server.go +++ b/cmd/server/server.go @@ -1,4 +1,4 @@ -package cmd +package server import ( "os" diff --git a/hack/e2e/dhcp-client.Dockerfile b/hack/e2e/dhcp-client.Dockerfile index e835007a4..45b4d465d 100644 --- a/hack/e2e/dhcp-client.Dockerfile +++ b/hack/e2e/dhcp-client.Dockerfile @@ -1,7 +1,10 @@ FROM docker.io/library/ubuntu:24.04 +ENV DEBIAN_FRONTEND=noninteractive + RUN apt-get update && \ - apt-get install -y iproute2 isc-dhcp-client tcpdump + apt-get install -y --no-install-recommends iproute2 isc-dhcp-client tcpdump && \ + apt-get clean COPY ./dhcp-client/entrypoint.sh /entrypoint.sh diff --git a/hack/e2e/dhcp-relay.Dockerfile b/hack/e2e/dhcp-relay.Dockerfile index 8cf2a0fa2..b8d5aeeaf 100644 --- a/hack/e2e/dhcp-relay.Dockerfile +++ b/hack/e2e/dhcp-relay.Dockerfile @@ -1,9 +1,9 @@ -FROM library/ubuntu:24.04 +FROM docker.io/library/ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get install -y --no-install-recommends isc-dhcp-relay && \ + apt-get install -y --no-install-recommends isc-dhcp-relay iproute2 tcpdump && \ apt-get clean STOPSIGNAL SIGINT diff --git a/main.go b/main.go deleted file mode 100644 index 4879a338e..000000000 --- a/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "beryju.io/gravity/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/pkg/roles/dhcp/dhcp_handler4.go b/pkg/roles/dhcp/dhcp_handler4.go index d0394e14b..a7a30d3ce 100644 --- a/pkg/roles/dhcp/dhcp_handler4.go +++ b/pkg/roles/dhcp/dhcp_handler4.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "os" "strings" "sync" @@ -16,6 +17,15 @@ import ( "golang.org/x/net/ipv4" ) +func getIP(addr net.Addr) net.IP { + clientIP := "" + switch addr := addr.(type) { + case *net.UDPAddr: + clientIP = addr.IP.String() + } + return net.ParseIP(clientIP) +} + var ErrNilResponse = errors.New("no DHCP response") // Credit to CoreDHCP @@ -51,6 +61,12 @@ func (h *handler4) Serve() error { } } +var debugDHCPGatewayReplyPeer bool + +func init() { + debugDHCPGatewayReplyPeer = os.Getenv("GRAVITY_DEBUG_DHCP_GATEWAY_REPLY_CIADDR") != "" +} + func (h *handler4) Handle(buf []byte, oob *ipv4.ControlMessage, peer net.Addr) error { if extconfig.Get().ListenOnlyMode { return nil @@ -94,12 +110,24 @@ func (h *handler4) Handle(buf []byte, oob *ipv4.ControlMessage, peer net.Addr) e useEthernet := false var p *net.UDPAddr if !r.GatewayIPAddr.IsUnspecified() { + r.log.Debug("sending response to gateway") + // giaddr should be set to the Relay's IP Address, however it is the IP of the subnet + // the client should get an IP for. We might not always be able to directly reply to that IP + // especially in environments where we can't adjust firewall/routing rules. (like e2e tests) + // when this environment variable is set, reply directly to the IP we got the UDP request from, + // which is not the RFC defined behaviour p = &net.UDPAddr{IP: r.GatewayIPAddr, Port: dhcpv4.ServerPort} + if debugDHCPGatewayReplyPeer { + p.IP = getIP(r.peer) + } } else if resp.MessageType() == dhcpv4.MessageTypeNak { + r.log.Debug("sending response to bcast (NAK)") p = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort} } else if !r.ClientIPAddr.IsUnspecified() { + r.log.Debug("sending response to client") p = &net.UDPAddr{IP: r.ClientIPAddr, Port: dhcpv4.ClientPort} } else if r.IsBroadcast() { + r.log.Debug("sending response to bcast") p = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort} } else { // sends a layer2 frame so that we can define the destination MAC address diff --git a/pkg/roles/dhcp/dhcp_handler4_discover.go b/pkg/roles/dhcp/dhcp_handler4_discover.go index 86773d882..ef59a884d 100644 --- a/pkg/roles/dhcp/dhcp_handler4_discover.go +++ b/pkg/roles/dhcp/dhcp_handler4_discover.go @@ -20,13 +20,13 @@ func (r *Role) HandleDHCPDiscover4(req *Request4) *dhcpv4.DHCPv4 { } err := match.Put(req.Context, int64(r.cfg.LeaseNegotiateTimeout)) if err != nil { - req.log.Warn("failed to update lease", zap.Error(err)) + req.log.Warn("failed to update lease during discover creation", zap.Error(err)) } } else { go func() { err := match.Put(req.Context, match.scope.TTL) if err != nil { - req.log.Warn("failed to update lease", zap.Error(err)) + req.log.Warn("failed to update lease during discover", zap.Error(err)) } }() } diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index 116442e97..3c4e8a60e 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -58,7 +58,7 @@ func (r *Role) FindLease(req *Request4) *Lease { go func() { err := lease.Put(req.Context, lease.scope.TTL) if err != nil { - r.log.Warn("failed to update lease", zap.Error(err)) + r.log.Warn("failed to update lease for re-assigned IP", zap.Error(err)) } }() } @@ -228,7 +228,7 @@ func (l *Lease) createReply(req *Request4) *dhcpv4.DHCPv4 { go func() { err := l.Put(req.Context, l.Expiry) if err != nil { - l.log.Warn("failed to update lease", zap.Error(err)) + l.log.Warn("failed to update lease for updated hostname", zap.Error(err)) } }() } diff --git a/pkg/roles/dhcp/role.go b/pkg/roles/dhcp/role.go index a267b40e2..5dca5449e 100644 --- a/pkg/roles/dhcp/role.go +++ b/pkg/roles/dhcp/role.go @@ -147,7 +147,6 @@ func (r *Role) initServer4() error { IP: net.ParseIP("0.0.0.0"), Port: r.cfg.Port, } - var err error ifName := extconfig.Get().Instance.Interface udpConn, err := server4.NewIPv4UDPConn(ifName, laddr) if err != nil { diff --git a/pkg/tests/utils.go b/pkg/tests/utils.go index 23ceabe6c..c8d7b4ec1 100644 --- a/pkg/tests/utils.go +++ b/pkg/tests/utils.go @@ -4,12 +4,10 @@ import ( "context" "encoding/json" "fmt" - "net" "net/netip" "runtime" "strings" "testing" - "time" "beryju.io/gravity/pkg/extconfig" "beryju.io/gravity/pkg/storage" @@ -122,24 +120,3 @@ func Listen(port int32) string { } return extconfig.Get().Listen(port) } - -func WaitForPort(port int32) { - max := 30 - try := 0 - listen := Listen(port) - time.Sleep(500 * time.Millisecond) - for { - ln, err := net.Listen("tcp", listen) - if ln != nil { - _ = ln.Close() - } - if err != nil { - return - } - try += 1 - if try >= max { - panic(fmt.Errorf("failed to wait for port '%s' to be listening", listen)) - } - time.Sleep(1 * time.Millisecond) - } -} diff --git a/tests/cli_e2e_test.go b/tests/cli_e2e_test.go new file mode 100644 index 000000000..89431d5bd --- /dev/null +++ b/tests/cli_e2e_test.go @@ -0,0 +1,15 @@ +package tests + +import ( + "testing" + + "beryju.io/gravity/tests/gravity" + "github.com/stretchr/testify/assert" +) + +func TestCLI_Health(t *testing.T) { + gr := gravity.New(t, gravity.WithEnv("LOG_LEVEL", "warn")) + + _, health := ExecCommand(t, gr.Container(), []string{"gravity", "cli", "health"}) + assert.Contains(t, string(health), "gravity-1") +} diff --git a/tests/cluster_e2e_test.go b/tests/cluster_e2e_test.go index 2c7207d09..934024eaf 100644 --- a/tests/cluster_e2e_test.go +++ b/tests/cluster_e2e_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "beryju.io/gravity/tests/gravity" "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go" @@ -21,7 +22,7 @@ func TestCluster_Join(t *testing.T) { testcontainers.CleanupNetwork(t, net) // Create initial gravity node - gr := RunGravity(t, net) + gr := gravity.New(t, gravity.WithNet(net)) cwd, err := os.Getwd() assert.NoError(t, err) @@ -36,7 +37,7 @@ func TestCluster_Join(t *testing.T) { Networks: []string{net.Name}, Env: map[string]string{ "LOG_LEVEL": "debug", - "ETCD_JOIN_CLUSTER": fmt.Sprintf("%s,http://gravity-1:8008", GravityToken), + "ETCD_JOIN_CLUSTER": fmt.Sprintf("%s,http://gravity-1:8008", gravity.Token()), "GOCOVERDIR": "/coverage", }, HostConfigModifier: func(hostConfig *container.HostConfig) { diff --git a/tests/dhcp_e2e_test.go b/tests/dhcp_e2e_test.go index ac1d3c64f..48bfa6ff2 100644 --- a/tests/dhcp_e2e_test.go +++ b/tests/dhcp_e2e_test.go @@ -2,10 +2,10 @@ package tests import ( "fmt" - "io" "testing" "beryju.io/gravity/api" + "beryju.io/gravity/tests/gravity" "github.com/docker/docker/api/types/container" dockernetwork "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" @@ -14,7 +14,7 @@ import ( "github.com/testcontainers/testcontainers-go/network" ) -func TestDHCP_Single(t *testing.T) { +func TestDHCP_Simple(t *testing.T) { ctx := Context(t) net, err := network.New( @@ -32,7 +32,7 @@ func TestDHCP_Single(t *testing.T) { assert.NoError(t, err) testcontainers.CleanupNetwork(t, net) - g := RunGravity(t, net) + g := gravity.New(t, gravity.WithNet(net)) ac := g.APIClient() // Create test network @@ -67,10 +67,7 @@ func TestDHCP_Single(t *testing.T) { testcontainers.CleanupContainer(t, tester) assert.NoError(t, err) - _, out, err := tester.Exec(ctx, []string{"dhclient", "-v"}) - assert.NoError(t, err) - body, err := io.ReadAll(out) - assert.NoError(t, err) + _, body := ExecCommand(t, tester, []string{"dhclient", "-v"}) assert.Contains(t, string(body), "DHCPOFFER of") assert.Contains(t, string(body), "DHCPREQUEST for") assert.Contains(t, string(body), "DHCPACK of") @@ -101,7 +98,7 @@ func TestDHCP_Parallel(t *testing.T) { assert.NoError(t, err) testcontainers.CleanupNetwork(t, net) - g := RunGravity(t, net) + g := gravity.New(t, gravity.WithNet(net)) ac := g.APIClient() // Create test network @@ -138,10 +135,7 @@ func TestDHCP_Parallel(t *testing.T) { testcontainers.CleanupContainer(t, tester) assert.NoError(t, err) - _, out, err := tester.Exec(ctx, []string{"dhclient", "-v"}) - assert.NoError(t, err) - body, err := io.ReadAll(out) - assert.NoError(t, err) + _, body := ExecCommand(t, tester, []string{"dhclient", "-v"}) assert.Contains(t, string(body), "DHCPOFFER of") assert.Contains(t, string(body), "DHCPREQUEST for") assert.Contains(t, string(body), "DHCPACK of") @@ -156,3 +150,119 @@ func TestDHCP_Parallel(t *testing.T) { // assert.Len(t, sc.Leases, 50) // }() } + +func TestDHCP_Relay(t *testing.T) { + ctx := Context(t) + + netA, err := network.New( + ctx, + network.WithIPAM(&dockernetwork.IPAM{ + Driver: "default", + Config: []dockernetwork.IPAMConfig{ + { + Subnet: "10.100.0.0/24", + }, + }, + }), + network.WithAttachable(), + ) + assert.NoError(t, err) + testcontainers.CleanupNetwork(t, netA) + + netB, err := network.New( + ctx, + network.WithIPAM(&dockernetwork.IPAM{ + Driver: "default", + Config: []dockernetwork.IPAMConfig{ + { + Subnet: "10.101.0.0/24", + }, + }, + }), + network.WithAttachable(), + ) + assert.NoError(t, err) + testcontainers.CleanupNetwork(t, netB) + + g := gravity.New(t, + gravity.WithEnv("GRAVITY_DEBUG_DHCP_GATEWAY_REPLY_CIADDR", "true"), + gravity.WithNet(netA)) + gip, err := g.Container().ContainerIP(ctx) + assert.NoError(t, err) + + ac := g.APIClient() + // Create test network + _, err = ac.RolesDhcpApi.DhcpPutScopes(ctx).DhcpAPIScopesPutInput(api.DhcpAPIScopesPutInput{ + SubnetCidr: "10.100.0.0/24", + Ttl: 86400, + Ipam: map[string]string{ + "range_end": "10.100.0.200", + "range_start": "10.100.0.100", + "type": "internal", + }, + Options: []api.TypesDHCPOption{}, + }).Scope("network-A").Execute() + assert.NoError(t, err) + _, err = ac.RolesDhcpApi.DhcpPutScopes(ctx).DhcpAPIScopesPutInput(api.DhcpAPIScopesPutInput{ + SubnetCidr: "10.101.0.0/24", + Ttl: 86400, + Ipam: map[string]string{ + "range_end": "10.101.0.200", + "range_start": "10.101.0.100", + "type": "internal", + }, + Options: []api.TypesDHCPOption{}, + }).Scope("network-B").Execute() + assert.NoError(t, err) + + // DHCP relay + relay, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: "../hack/e2e/", + Dockerfile: "dhcp-relay.Dockerfile", + Repo: "gravity-dhcp-relay", + KeepImage: true, + }, + Cmd: []string{"-d", gip}, + Networks: []string{netA.Name, netB.Name}, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.CapAdd = strslice.StrSlice{"NET_ADMIN"} + }, + }, + Started: true, + }) + testcontainers.CleanupContainer(t, relay) + assert.NoError(t, err) + + // DHCP tester + tester, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: "../hack/e2e/", + Dockerfile: "dhcp-client.Dockerfile", + Repo: "gravity-dhcp-client", + KeepImage: true, + }, + Networks: []string{netB.Name}, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.CapAdd = strslice.StrSlice{"NET_ADMIN"} + }, + }, + Started: true, + }) + testcontainers.CleanupContainer(t, tester) + assert.NoError(t, err) + + _, body := ExecCommand(t, tester, []string{"dhclient", "-v"}) + assert.Contains(t, string(body), "DHCPOFFER of") + assert.Contains(t, string(body), "DHCPREQUEST for") + assert.Contains(t, string(body), "DHCPACK of") + assert.Contains(t, string(body), "bound to") + + // Check correct lease exists + sc, _, err := ac.RolesDhcpApi.DhcpGetLeases(ctx).Scope("network-B").Execute() + assert.NoError(t, err) + assert.Len(t, sc.Leases, 1) + assert.Equal(t, "10.101.0.100", sc.Leases[0].Address) +} diff --git a/tests/dns_e2e_test.go b/tests/dns_e2e_test.go index fdf44f548..cece93fc2 100644 --- a/tests/dns_e2e_test.go +++ b/tests/dns_e2e_test.go @@ -3,13 +3,14 @@ package tests import ( "testing" + "beryju.io/gravity/tests/gravity" "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go" ) func TestDNS_SimpleDefault(t *testing.T) { ctx := Context(t) - RunGravity(t, nil) + gravity.New(t) // DHCP tester tester, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ diff --git a/tests/gravity/gravity.go b/tests/gravity/gravity.go new file mode 100644 index 000000000..9c309a1b8 --- /dev/null +++ b/tests/gravity/gravity.go @@ -0,0 +1,116 @@ +package gravity + +import ( + "context" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "testing" + + "beryju.io/gravity/api" + "beryju.io/gravity/pkg/extconfig" + "github.com/docker/docker/api/types/container" + "github.com/gorilla/securecookie" + "github.com/stretchr/testify/assert" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +var ( + gravityPassword string + gravityToken string +) + +func init() { + gravityPassword = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) + gravityToken = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) +} + +func Passowrd() string { + return gravityPassword +} + +func Token() string { + return gravityToken +} + +type Gravity struct { + container testcontainers.Container + t *testing.T +} + +type GravityOption func(req *testcontainers.ContainerRequest) + +func WithEnv(key string, value string) GravityOption { + return func(req *testcontainers.ContainerRequest) { + req.Env[key] = value + } +} + +func WithNet(net *testcontainers.DockerNetwork) GravityOption { + return func(req *testcontainers.ContainerRequest) { + req.Networks = append(req.Networks, net.Name) + } +} + +func New(t *testing.T, opts ...GravityOption) *Gravity { + ctx := context.Background() + cwd, err := os.Getwd() + assert.NoError(t, err) + + req := testcontainers.ContainerRequest{ + Image: "gravity:e2e-test", + ExposedPorts: []string{"8008", "8009"}, + WaitingFor: wait.ForHTTP("/healthz/ready").WithPort("8009"), + Hostname: "gravity-1", + Env: map[string]string{ + "LOG_LEVEL": "debug", + "ADMIN_PASSWORD": gravityPassword, + "ADMIN_TOKEN": gravityToken, + "GOCOVERDIR": "/coverage", + }, + Networks: []string{}, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.Binds = []string{ + fmt.Sprintf("%s:/coverage", filepath.Join(cwd, "/coverage")), + } + }, + } + + for _, opt := range opts { + opt(&req) + } + + gravityContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + testcontainers.CleanupContainer(t, gravityContainer) + assert.NoError(t, err) + + return &Gravity{ + container: gravityContainer, + t: t, + } +} + +func (g *Gravity) APIClient() *api.APIClient { + ctx := context.Background() + addr, err := g.container.Host(ctx) + assert.NoError(g.t, err) + port, err := g.container.MappedPort(ctx, "8008") + assert.NoError(g.t, err) + + config := api.NewConfiguration() + config.Debug = true + config.Scheme = "http" + config.Host = fmt.Sprintf("%s:%s", addr, port.Port()) + config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", gravityToken)) + config.UserAgent = fmt.Sprintf("gravity-e2e-testing/%s", extconfig.FullVersion()) + return api.NewAPIClient(config) +} + +func (g *Gravity) Container() testcontainers.Container { + return g.container +} diff --git a/tests/utils.go b/tests/utils.go index 2063533a7..b9126547c 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -2,33 +2,14 @@ package tests import ( "context" - "encoding/base64" - "fmt" "io" - "os" - "path/filepath" "testing" - "beryju.io/gravity/api" - "beryju.io/gravity/pkg/extconfig" - "github.com/docker/docker/api/types/container" - "github.com/gorilla/securecookie" "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/exec" - "github.com/testcontainers/testcontainers-go/wait" ) -var ( - GravityPassword string - GravityToken string -) - -func init() { - GravityPassword = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) - GravityToken = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) -} - func Context(t *testing.T) context.Context { ctx, cn := context.WithCancel(context.Background()) t.Cleanup(func() { @@ -37,67 +18,6 @@ func Context(t *testing.T) context.Context { return ctx } -type Gravity struct { - container testcontainers.Container - t *testing.T -} - -func RunGravity(t *testing.T, net *testcontainers.DockerNetwork) *Gravity { - ctx := Context(t) - cwd, err := os.Getwd() - assert.NoError(t, err) - - req := testcontainers.ContainerRequest{ - Image: "gravity:e2e-test", - ExposedPorts: []string{"8008", "8009"}, - WaitingFor: wait.ForHTTP("/healthz/ready").WithPort("8009"), - Hostname: "gravity-1", - Env: map[string]string{ - "LOG_LEVEL": "debug", - "ADMIN_PASSWORD": GravityPassword, - "ADMIN_TOKEN": GravityToken, - "GOCOVERDIR": "/coverage", - }, - HostConfigModifier: func(hostConfig *container.HostConfig) { - hostConfig.Binds = []string{ - fmt.Sprintf("%s:/coverage", filepath.Join(cwd, "/coverage")), - } - }, - } - - if net != nil { - req.Networks = []string{net.Name} - } - - gravityContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - }) - testcontainers.CleanupContainer(t, gravityContainer) - assert.NoError(t, err) - - return &Gravity{ - container: gravityContainer, - t: t, - } -} - -func (g *Gravity) APIClient() *api.APIClient { - ctx := context.Background() - addr, err := g.container.Host(ctx) - assert.NoError(g.t, err) - port, err := g.container.MappedPort(ctx, "8008") - assert.NoError(g.t, err) - - config := api.NewConfiguration() - config.Debug = true - config.Scheme = "http" - config.Host = fmt.Sprintf("%s:%s", addr, port.Port()) - config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", GravityToken)) - config.UserAgent = fmt.Sprintf("gravity-e2e-testing/%s", extconfig.FullVersion()) - return api.NewAPIClient(config) -} - func ExecCommand(t *testing.T, co testcontainers.Container, cmd []string, options ...exec.ProcessOption) (int, string) { ctx := Context(t) options = append(options, exec.Multiplexed())