Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions docs/content/docs/install/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ The following environment variables can be set.
- `ETCD_PEER_PORT`: Port used for etcd peer traffic. Defaults to `2380`. This may need to be changed when running in Kubernetes.
- `ETCD_ENDPOINT`: etcd Client endpoint. Defaults to `localhost:2379` when using embedded etcd.
- `ETCD_JOIN_CLUSTER`: Set to a join cluster token to join the node to a cluster. See [Clustering](./cluster).
- `IMPORT_CONFIGS`: A list of base64-encoded configs or file URIs to import configs from on first startup. For example:

```bash
IMPORT_CONFIGS=file:///config.json
# Multiple configs
IMPORT_CONFIGS=file:///configA.json|file:///configB.json
# Base64 import
IMPORT_CONFIGS=eyJlbnRyaWVzIjogW3sia2V5IjogIi9ncmF2aXR5L2ZvbyIsInZhbHVlIjogIlptOXYifV19
```

### Changing Environment Variables

Expand Down
20 changes: 20 additions & 0 deletions hack/e2e/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"entries": [
{
"key": "/dhcp/scopes/network-A",
"value": "eyJkbnMiOnsiem9uZSI6IiIsInNlYXJjaCI6bnVsbCwiYWRkWm9uZUluSG9zdG5hbWUiOmZhbHNlfSwiaXBhbSI6eyJyYW5nZV9lbmQiOiIxMC4xMDAuMC4yMDAiLCJyYW5nZV9zdGFydCI6IjEwLjEwMC4wLjEwMCIsInR5cGUiOiJpbnRlcm5hbCJ9LCJzdWJuZXRDaWRyIjoiMTAuMTAwLjAuMC8yNCIsIm9wdGlvbnMiOlt7InRhZyI6bnVsbCwidGFnTmFtZSI6InJvdXRlciIsInZhbHVlIjoiIiwidmFsdWU2NCI6bnVsbCwidmFsdWVIZXgiOm51bGx9XSwidHRsIjo4NjQwMCwiZGVmYXVsdCI6ZmFsc2UsImhvb2siOiIifQ=="
},
{
"key": "/dhcp/scopes/network-B",
"value": "eyJkbnMiOnsiem9uZSI6IiIsInNlYXJjaCI6bnVsbCwiYWRkWm9uZUluSG9zdG5hbWUiOmZhbHNlfSwiaXBhbSI6eyJyYW5nZV9lbmQiOiIxMC4xMDEuMC4yMDAiLCJyYW5nZV9zdGFydCI6IjEwLjEwMS4wLjEwMCIsInR5cGUiOiJpbnRlcm5hbCJ9LCJzdWJuZXRDaWRyIjoiMTAuMTAxLjAuMC8yNCIsIm9wdGlvbnMiOlt7InRhZyI6bnVsbCwidGFnTmFtZSI6InJvdXRlciIsInZhbHVlIjoiIiwidmFsdWU2NCI6bnVsbCwidmFsdWVIZXgiOm51bGx9XSwidHRsIjo4NjQwMCwiZGVmYXVsdCI6ZmFsc2UsImhvb2siOiIifQ=="
},
{
"key": "/dns/zones/.",
"value": "eyJoYW5kbGVyQ29uZmlncyI6W3sidHlwZSI6Im1lbW9yeSJ9LHsidHlwZSI6ImV0Y2QifSx7ImNhY2hlX3R0bCI6IjM2MDAiLCJ0byI6IjEuMS4xLjE6NTMiLCJ0eXBlIjoiZm9yd2FyZF9pcCJ9XSwiZGVmYXVsdFRUTCI6MzYwMCwiYXV0aG9yaXRhdGl2ZSI6ZmFsc2UsImhvb2siOiIifQ=="
},
{
"key": "/role/cluster",
"value": "eyJzZXR1cCI6dHJ1ZX0="
}
]
}
7 changes: 7 additions & 0 deletions hack/e2e/container/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
echo "Removing IP Addresses"
ip addr flush eth0

trap "echo Shutting down; exit 0" SIGTERM SIGINT SIGKILL
/bin/sleep infinity &
wait
81 changes: 81 additions & 0 deletions hack/e2e/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
networks:
network-A:
labels:
io.beryju.gravity/testing: "true"
# Required so we can set the IP for the gravity container directly here
# not used to assign IP addresses to test containers
ipam:
driver: default
config:
- subnet: 10.100.0.0/24
network-B:
labels:
io.beryju.gravity/testing: "true"
# Required so we can set the IP for the gravity container directly here
# not used to assign IP addresses to test containers
ipam:
driver: default
config:
- subnet: 10.101.0.0/24

services:
gravity:
image: ghcr.io/beryju/gravity:latest
networks:
network-A:
ipv4_address: 10.100.0.10
ports:
- 8008:8008
hostname: gravity1
volumes:
- ./config.json:/config.json
environment:
BOOTSTRAP_ROLES: dns;dhcp;api;etcd;discovery;backup;monitoring;tftp
IMPORT_CONFIGS: file:///config.json
LOG_LEVEL: debug
ADMIN_PASSWORD: test # testing password
relay:
build:
context: .
dockerfile: relay.Dockerfile
image: ghcr.io/beryju/gravity-dhcp-relay:latest
cap_add:
- NET_ADMIN
networks:
network-A:
ipv4_address: 10.100.0.5
network-B:
ipv4_address: 10.101.0.5
depends_on:
- gravity
command: -d -iu eth1 -id eth0 10.100.0.10

ubuntu-net-a:
build:
context: .
dockerfile: test.Dockerfile
image: ghcr.io/beryju/gravity-testing:latest
cap_add:
- NET_ADMIN
networks:
- network-A
depends_on:
- gravity
deploy:
mode: replicated
replicas: 6
ubuntu-net-b:
build:
context: .
dockerfile: test.Dockerfile
image: ghcr.io/beryju/gravity-testing:latest
cap_add:
- NET_ADMIN
networks:
- network-B
depends_on:
- gravity
deploy:
mode: replicated
replicas: 6
11 changes: 11 additions & 0 deletions hack/e2e/relay.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM library/ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
apt-get install -y --no-install-recommends isc-dhcp-relay && \
apt-get clean

STOPSIGNAL SIGINT

ENTRYPOINT [ "/usr/sbin/dhcrelay" ]
8 changes: 8 additions & 0 deletions hack/e2e/test.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM docker.io/library/ubuntu:24.04

RUN apt-get update && \
apt-get install -y iproute2 isc-dhcp-client tcpdump

COPY ./container/entrypoint.sh /entrypoint.sh

CMD ["/entrypoint.sh"]
9 changes: 5 additions & 4 deletions pkg/extconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ type ExtConfig struct {
Interface string `env:"INSTANCE_INTERFACE"`
Listen string `env:"INSTANCE_LISTEN"`
}
LogLevel string `env:"LOG_LEVEL,default=info"`
DataPath string `env:"DATA_PATH,default=./data"`
BootstrapRoles string `env:"BOOTSTRAP_ROLES,default=dns;dhcp;api;etcd;discovery;backup;monitoring;tsdb;tftp"`
FallbackDNS string `env:"FALLBACK_DNS,default=1.1.1.1:53"`
LogLevel string `env:"LOG_LEVEL,default=info"`
DataPath string `env:"DATA_PATH,default=./data"`
BootstrapRoles string `env:"BOOTSTRAP_ROLES,default=dns;dhcp;api;etcd;discovery;backup;monitoring;tsdb;tftp"`
FallbackDNS string `env:"FALLBACK_DNS,default=1.1.1.1:53"`
ImportConfigs []string `env:"IMPORT_CONFIGS"`

Sentry struct {
DSN string `env:"SENTRY_DSN,default=https://731a93aa4a1a42a2960ac9eecee628c5@sentry.beryju.org/2"`
Expand Down
1 change: 1 addition & 0 deletions pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ func (i *Instance) checkFirstStart(ctx context.Context) {
return
}
i.log.Info("Initial startup")
i.autoImportConfig()
inst.DispatchEvent(
types.EventTopicInstanceFirstStart,
roles.NewEvent(ctx, map[string]interface{}{}),
Expand Down
71 changes: 71 additions & 0 deletions pkg/instance/instance_auto_import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package instance

import (
"context"
"encoding/base64"
"encoding/json"
"net/url"
"os"
"strings"

"beryju.io/gravity/pkg/extconfig"
"beryju.io/gravity/pkg/roles/api"
"github.com/pkg/errors"
"go.uber.org/zap"
)

func (i *Instance) autoImportConfig() {
for _, p := range extconfig.Get().ImportConfigs {
var c []byte
id := p
if strings.HasPrefix(p, "file://") {
u, err := url.Parse(p)
if err != nil {
i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err))
continue
}
_c, err := os.ReadFile(u.Path)
if err != nil {
i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err))
continue
}
c = _c
} else {
id := p[20:]
dec, err := base64.StdEncoding.DecodeString(p)
if err != nil {
i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err))
continue
}
c = dec
}
err := i.importSingleConfig(c)
if err != nil {
i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err))
} else {
i.log.Info("Successfully imported config", zap.String("path", id))
}
}
}

func (i *Instance) importSingleConfig(c []byte) error {
var input api.APIImportInput
err := json.Unmarshal(c, &input)
if err != nil {
return errors.Wrap(err, "failed to unmarshal config")
}
inst := i.ForRole("root", context.Background())
for _, entry := range input.Entries {
val, err := base64.StdEncoding.DecodeString(entry.Value)
if err != nil {
i.log.Warn("failed to decode value", zap.Error(err), zap.String("key", entry.Key))
continue
}
_, err = inst.KV().Put(context.Background(), entry.Key, string(val))
if err != nil {
i.log.Warn("failed to put value", zap.Error(err), zap.String("key", entry.Key))
continue
}
}
return nil
}
13 changes: 12 additions & 1 deletion pkg/roles/dhcp/leases.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ func (r *Role) FindLease(req *Request4) *Lease {
return nil
}
// Check if the leases's scope matches the expected scope to handle this request
expectedScope := r.findScopeForRequest(req)
expectedScope := r.findScopeForRequest(req, func(scope *Scope) int {
// Consider the existing lease for finding the scope
// Check how many bits of the leases address match the scope
sm := scope.match(net.ParseIP(lease.Address))
// If the matching bits match how many bits are in the CIDR, and
// the scope of the lease matches the scope we're filtering, we've
// got a match
if sm == lease.scope.cidr.Bits() && lease.ScopeKey == scope.Name {
return 99
}
return -1
})
if expectedScope != nil && lease.scope != expectedScope {
// We have a specific scope to handle this request but it doesn't match the lease
lease.scope = expectedScope
Expand Down
14 changes: 12 additions & 2 deletions pkg/roles/dhcp/scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ func (s *Scope) ipamType(previous *Scope) (IPAM, error) {
}
}

func (r *Role) findScopeForRequest(req *Request4) *Scope {
type scopeSelector func(scope *Scope) int

func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSelector) *Scope {
var match *Scope
longestBits := 0
r.scopesM.RLock()
Expand All @@ -105,7 +107,15 @@ func (r *Role) findScopeForRequest(req *Request4) *Scope {
// match a 1 bit more priority
const dhcpRelayBias = 1
for _, scope := range r.scopes {
// Check based on gateway IP (highest priority)
// Check additional selectors (highest priority)
for _, sel := range additionalSelectors {
m := sel(scope)
if m > -1 && m > longestBits {
match = scope
longestBits = m
}
}
// Check based on gateway IP (next highest priority)
gatewayMatchBits := scope.match(req.GatewayIPAddr)
if gatewayMatchBits > -1 && gatewayMatchBits+dhcpRelayBias > longestBits {
req.log.Debug("selected scope based on cidr match (gateway IP)", zap.String("scope", scope.Name))
Expand Down