From 3877f3caf84fedeca238578c30d1a7e20fb48ab4 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 2 Nov 2024 20:26:34 +0100 Subject: [PATCH 01/17] dhcp: fix scope moving when it shouldn't --- pkg/roles/dhcp/leases.go | 13 ++++++++++++- pkg/roles/dhcp/scopes.go | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index 8ef08f567..d9a17fca2 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -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 diff --git a/pkg/roles/dhcp/scopes.go b/pkg/roles/dhcp/scopes.go index 635324c90..843894662 100644 --- a/pkg/roles/dhcp/scopes.go +++ b/pkg/roles/dhcp/scopes.go @@ -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() @@ -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)) From 232ec9edba97b191e4fb9763ccff2c4968bd6ca4 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 2 Nov 2024 21:00:12 +0100 Subject: [PATCH 02/17] initial testing env --- hack/e2e/config.json | 20 ++++++++ hack/e2e/container/entrypoint.sh | 7 +++ hack/e2e/docker-compose.yaml | 81 ++++++++++++++++++++++++++++++++ hack/e2e/relay.Dockerfile | 11 +++++ hack/e2e/test.Dockerfile | 8 ++++ 5 files changed, 127 insertions(+) create mode 100644 hack/e2e/config.json create mode 100755 hack/e2e/container/entrypoint.sh create mode 100644 hack/e2e/docker-compose.yaml create mode 100644 hack/e2e/relay.Dockerfile create mode 100644 hack/e2e/test.Dockerfile diff --git a/hack/e2e/config.json b/hack/e2e/config.json new file mode 100644 index 000000000..1eb02144f --- /dev/null +++ b/hack/e2e/config.json @@ -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=" + } + ] +} diff --git a/hack/e2e/container/entrypoint.sh b/hack/e2e/container/entrypoint.sh new file mode 100755 index 000000000..a10406fbc --- /dev/null +++ b/hack/e2e/container/entrypoint.sh @@ -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 diff --git a/hack/e2e/docker-compose.yaml b/hack/e2e/docker-compose.yaml new file mode 100644 index 000000000..ab5b9c368 --- /dev/null +++ b/hack/e2e/docker-compose.yaml @@ -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 diff --git a/hack/e2e/relay.Dockerfile b/hack/e2e/relay.Dockerfile new file mode 100644 index 000000000..8cf2a0fa2 --- /dev/null +++ b/hack/e2e/relay.Dockerfile @@ -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" ] diff --git a/hack/e2e/test.Dockerfile b/hack/e2e/test.Dockerfile new file mode 100644 index 000000000..bd99029e3 --- /dev/null +++ b/hack/e2e/test.Dockerfile @@ -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"] From aabee13699003bb66ccac1ce37b8ac9f2499bc58 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 5 Nov 2024 21:16:36 +0100 Subject: [PATCH 03/17] dhcp: experimentally make leases scope-scoped --- pkg/roles/dhcp/api_leases.go | 9 +++--- pkg/roles/dhcp/api_leases_test.go | 12 ++++--- pkg/roles/dhcp/api_scopes.go | 43 +++++++++---------------- pkg/roles/dhcp/api_scopes_test.go | 3 +- pkg/roles/dhcp/ipam_internal_test.go | 3 +- pkg/roles/dhcp/leases.go | 8 +++-- pkg/roles/dhcp/leases_watch.go | 24 +++++++++++--- pkg/roles/dhcp/types/role.go | 1 - pkg/roles/discovery/api_devices_test.go | 6 ++-- 9 files changed, 60 insertions(+), 49 deletions(-) diff --git a/pkg/roles/dhcp/api_leases.go b/pkg/roles/dhcp/api_leases.go index 377c1ab11..679dd2ec9 100644 --- a/pkg/roles/dhcp/api_leases.go +++ b/pkg/roles/dhcp/api_leases.go @@ -50,7 +50,8 @@ func (r *Role) APILeasesGet() usecase.Interactor { leaseKey := r.i.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + input.ScopeName, ) if input.Identifier == "" { leaseKey = leaseKey.Prefix(true) @@ -67,9 +68,6 @@ func (r *Role) APILeasesGet() usecase.Interactor { r.log.Warn("failed to parse lease", zap.Error(err)) continue } - if l.ScopeKey != input.ScopeName { - continue - } al := &APILease{ Identifier: l.Identifier, Address: l.Address, @@ -186,7 +184,8 @@ func (r *Role) APILeasesDelete() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input APILeasesDeleteInput, output *struct{}) error { key := r.i.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + input.Scope, input.Identifier, ) _, err := r.i.KV().Delete( diff --git a/pkg/roles/dhcp/api_leases_test.go b/pkg/roles/dhcp/api_leases_test.go index e3e691ff2..06d36742a 100644 --- a/pkg/roles/dhcp/api_leases_test.go +++ b/pkg/roles/dhcp/api_leases_test.go @@ -40,7 +40,8 @@ func TestAPILeasesGet(t *testing.T) { ctx, inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + scope.Name, lease.Identifier, ).String(), tests.MustJSON(lease), @@ -83,7 +84,8 @@ func TestAPILeasesPut(t *testing.T) { inst.KV(), inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + scope.Name, name, ), dhcp.Lease{ @@ -116,7 +118,8 @@ func TestAPILeasesDelete(t *testing.T) { ctx, inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + scope.Name, lease.Identifier, ).String(), tests.MustJSON(lease), @@ -132,7 +135,8 @@ func TestAPILeasesDelete(t *testing.T) { inst.KV(), inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + scope.Name, lease.Identifier, ), ) diff --git a/pkg/roles/dhcp/api_scopes.go b/pkg/roles/dhcp/api_scopes.go index 58dbb8ae2..a8883e0dd 100644 --- a/pkg/roles/dhcp/api_scopes.go +++ b/pkg/roles/dhcp/api_scopes.go @@ -56,28 +56,6 @@ func (r *Role) APIScopesGet() usecase.Interactor { r.log.Warn("failed to get scopes", zap.Error(err)) return status.Wrap(errors.New("failed to get scopes"), status.Internal) } - // Fetch all leases for statistics - rawLeases, err := r.i.KV().Get( - ctx, - r.i.KV().Key( - types.KeyRole, - types.KeyLeases, - ).String(), - clientv3.WithPrefix(), - ) - if err != nil { - r.log.Warn("failed to get leases", zap.Error(err)) - return status.Wrap(errors.New("failed to get leases"), status.Internal) - } - leases := []*Lease{} - for _, rl := range rawLeases.Kvs { - l, err := r.leaseFromKV(rl) - if err != nil { - r.log.Warn("failed to parse lease", zap.Error(err)) - continue - } - leases = append(leases, l) - } // Generate summarized statistics sum := APIScopeStatistics{} @@ -92,12 +70,23 @@ func (r *Role) APIScopesGet() usecase.Interactor { Usable: sc.ipam.UsableSize().Uint64(), Used: 0, } - for _, l := range leases { - if l.ScopeKey != sc.Name { - continue - } - stat.Used += 1 + // Fetch all leases for statistics + rawLeases, err := r.i.KV().Get( + ctx, + r.i.KV().Key( + types.KeyRole, + types.KeyScopes, + sc.Name, + ).Prefix(true).String(), + clientv3.WithPrefix(), + clientv3.WithCountOnly(), + ) + if err != nil { + r.log.Warn("failed to get leases", zap.Error(err)) + return status.Wrap(errors.New("failed to get leases"), status.Internal) } + stat.Used = uint64(len(rawLeases.Kvs)) + sum.Usable += stat.Usable sum.Used += stat.Used output.Scopes = append(output.Scopes, &APIScope{ diff --git a/pkg/roles/dhcp/api_scopes_test.go b/pkg/roles/dhcp/api_scopes_test.go index a7dea72a4..40e723de6 100644 --- a/pkg/roles/dhcp/api_scopes_test.go +++ b/pkg/roles/dhcp/api_scopes_test.go @@ -51,7 +51,8 @@ func TestAPIScopesGet(t *testing.T) { ctx, inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + "test", lease.Identifier, ).String(), tests.MustJSON(lease), diff --git a/pkg/roles/dhcp/ipam_internal_test.go b/pkg/roles/dhcp/ipam_internal_test.go index 524eea128..f916fc369 100644 --- a/pkg/roles/dhcp/ipam_internal_test.go +++ b/pkg/roles/dhcp/ipam_internal_test.go @@ -70,7 +70,8 @@ func TestIPAMInternal_NextFreeAddress_UniqueParallel(t *testing.T) { ctx, inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + scope.Name, lease.Identifier, ).String(), tests.MustJSON(lease), diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index d9a17fca2..c6732332e 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -110,9 +110,10 @@ func (l *Lease) setLeaseIP(req *Request4) { func (r *Role) leaseFromKV(raw *mvccpb.KeyValue) (*Lease, error) { prefix := r.i.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, ).Prefix(true).String() - identifier := strings.TrimPrefix(string(raw.Key), prefix) + keyParts := strings.SplitN(prefix, "/", 2) + identifier := strings.TrimPrefix(string(raw.Key), prefix+"/"+keyParts[0]) l := r.NewLease(identifier) err := json.Unmarshal(raw.Value, &l) if err != nil { @@ -152,7 +153,8 @@ func (l *Lease) Put(ctx context.Context, expiry int64, opts ...clientv3.OpOption leaseKey := l.inst.KV().Key( types.KeyRole, - types.KeyLeases, + types.KeyScopes, + l.ScopeKey, l.Identifier, ) _, err = l.inst.KV().Put( diff --git a/pkg/roles/dhcp/leases_watch.go b/pkg/roles/dhcp/leases_watch.go index 7dd1899f7..5b591308c 100644 --- a/pkg/roles/dhcp/leases_watch.go +++ b/pkg/roles/dhcp/leases_watch.go @@ -3,6 +3,7 @@ package dhcp import ( "context" "errors" + "strings" "time" "beryju.io/gravity/pkg/roles/dhcp/types" @@ -32,12 +33,13 @@ func (r *Role) handleLeaseOp(ev *clientv3.Event) { } func (r *Role) loadInitialLeases(ctx context.Context) { + prefix := r.i.KV().Key( + types.KeyRole, + types.KeyScopes, + ).Prefix(true).String() leases, err := r.i.KV().Get( ctx, - r.i.KV().Key( - types.KeyRole, - types.KeyLeases, - ).Prefix(true).String(), + prefix, clientv3.WithPrefix(), ) if err != nil { @@ -49,6 +51,10 @@ func (r *Role) loadInitialLeases(ctx context.Context) { return } for _, lease := range leases.Kvs { + relKey := strings.ReplaceAll(string(lease.Key), prefix, "") + if !strings.Contains("/", relKey) { + continue + } r.handleLeaseOp(&clientv3.Event{ Type: mvccpb.PUT, Kv: lease, @@ -57,13 +63,21 @@ func (r *Role) loadInitialLeases(ctx context.Context) { } func (r *Role) startWatchLeases() { + prefix := r.i.KV().Key( + types.KeyRole, + types.KeyScopes, + ).Prefix(true).String() watchChan := r.i.KV().Watch( r.ctx, - r.i.KV().Key(types.KeyRole, types.KeyLeases).Prefix(true).String(), + prefix, clientv3.WithPrefix(), ) for watchResp := range watchChan { for _, event := range watchResp.Events { + relKey := strings.ReplaceAll(string(event.Kv.Key), prefix, "") + if !strings.Contains("/", relKey) { + continue + } r.handleLeaseOp(event) } } diff --git a/pkg/roles/dhcp/types/role.go b/pkg/roles/dhcp/types/role.go index a5ef2699f..f8b6822f1 100644 --- a/pkg/roles/dhcp/types/role.go +++ b/pkg/roles/dhcp/types/role.go @@ -2,6 +2,5 @@ package types const ( KeyRole = "dhcp" - KeyLeases = "leases" KeyScopes = "scopes" ) diff --git a/pkg/roles/discovery/api_devices_test.go b/pkg/roles/discovery/api_devices_test.go index d6c0511f8..40de25157 100644 --- a/pkg/roles/discovery/api_devices_test.go +++ b/pkg/roles/discovery/api_devices_test.go @@ -97,7 +97,8 @@ func TestDeviceApplyDHCP(t *testing.T) { inst.KV(), inst.KV().Key( dhcpTypes.KeyRole, - dhcpTypes.KeyLeases, + dhcpTypes.KeyScopes, + name, "aa:bb:cc", ), dhcp.Lease{ @@ -193,7 +194,8 @@ func TestDeviceApplyDHCPWithDNS(t *testing.T) { inst.KV(), inst.KV().Key( dhcpTypes.KeyRole, - dhcpTypes.KeyLeases, + dhcpTypes.KeyScopes, + name, "aa:bb:cc", ), dhcp.Lease{ From 45e91ef9adb9d84ac208054c6f1e23bb1511ba8b Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 6 Nov 2024 22:03:17 +0100 Subject: [PATCH 04/17] actually migrate code for new structure --- pkg/roles/dhcp/api_leases.go | 50 ++++++++-- pkg/roles/dhcp/api_leases_test.go | 6 +- pkg/roles/dhcp/ipam_internal.go | 10 +- pkg/roles/dhcp/ipam_internal_test.go | 2 +- pkg/roles/dhcp/leases.go | 58 +++--------- pkg/roles/dhcp/leases_watch.go | 84 ----------------- pkg/roles/dhcp/metrics.go | 16 +--- pkg/roles/dhcp/role.go | 12 --- pkg/roles/dhcp/scope_selector.go | 53 +++++++++++ pkg/roles/dhcp/scopes.go | 135 +++++++++++++++------------ pkg/roles/dhcp/scopes_watch.go | 12 ++- pkg/tests/utils.go | 23 ----- 12 files changed, 202 insertions(+), 259 deletions(-) delete mode 100644 pkg/roles/dhcp/leases_watch.go create mode 100644 pkg/roles/dhcp/scope_selector.go diff --git a/pkg/roles/dhcp/api_leases.go b/pkg/roles/dhcp/api_leases.go index 679dd2ec9..4cb676a86 100644 --- a/pkg/roles/dhcp/api_leases.go +++ b/pkg/roles/dhcp/api_leases.go @@ -47,11 +47,16 @@ func (r *Role) APILeasesGet() usecase.Interactor { r.log.Warn("failed to get scope", zap.Error(err)) return status.Wrap(errors.New("failed to get scope"), status.Internal) } + s, err := r.scopeFromKV(rawScope.Kvs[0]) + if err != nil { + r.log.Warn("failed to parse scope", zap.Error(err)) + return status.Wrap(err, status.Internal) + } leaseKey := r.i.KV().Key( types.KeyRole, types.KeyScopes, - input.ScopeName, + s.Name, ) if input.Identifier == "" { leaseKey = leaseKey.Prefix(true) @@ -63,7 +68,7 @@ func (r *Role) APILeasesGet() usecase.Interactor { return status.Wrap(err, status.Internal) } for _, lease := range rawLeases.Kvs { - l, err := r.leaseFromKV(lease) + l, err := s.leaseFromKV(lease) if err != nil { r.log.Warn("failed to parse lease", zap.Error(err)) continue @@ -127,7 +132,7 @@ func (r *Role) APILeasesPut() usecase.Interactor { return status.Wrap(errors.New("failed to construct scope"), status.Internal) } - l := r.NewLease(input.Identifier) + l := scope.NewLease(input.Identifier) l.Address = input.Address l.Hostname = input.Hostname l.AddressLeaseTime = input.AddressLeaseTime @@ -156,13 +161,40 @@ type APILeasesWOLInput struct { func (r *Role) APILeasesWOL() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input APILeasesWOLInput, output *struct{}) error { - r.leasesM.RLock() - l, ok := r.leases[input.Identifier] - r.leasesM.RUnlock() - if !ok { - return status.InvalidArgument + rawScope, err := r.i.KV().Get( + ctx, + r.i.KV().Key( + types.KeyRole, + types.KeyScopes, + input.Scope, + ).String(), + ) + if err != nil || len(rawScope.Kvs) < 1 { + r.log.Warn("failed to get scope", zap.Error(err)) + return status.Wrap(errors.New("failed to get scope"), status.Internal) } - err := l.sendWOL() + scope, err := r.scopeFromKV(rawScope.Kvs[0]) + if err != nil { + r.log.Warn("failed to construct scope", zap.Error(err)) + return status.Wrap(errors.New("failed to construct scope"), status.Internal) + } + + leaseKey := r.i.KV().Key( + types.KeyRole, + types.KeyScopes, + scope.Name, + input.Identifier, + ) + rawLeases, err := r.i.KV().Get(ctx, leaseKey.String(), clientv3.WithPrefix()) + if err != nil || len(rawLeases.Kvs) < 1 { + return status.Wrap(err, status.InvalidArgument) + } + l, err := scope.leaseFromKV(rawLeases.Kvs[0]) + if err != nil { + return status.Wrap(err, status.Internal) + } + + err = l.sendWOL() if err != nil { return status.Wrap(err, status.Internal) } diff --git a/pkg/roles/dhcp/api_leases_test.go b/pkg/roles/dhcp/api_leases_test.go index 06d36742a..0eba9e75c 100644 --- a/pkg/roles/dhcp/api_leases_test.go +++ b/pkg/roles/dhcp/api_leases_test.go @@ -33,7 +33,7 @@ func TestAPILeasesGet(t *testing.T) { types.KeyScopes, scope.Name, ).String(), - tests.MustJSON(scope), + tests.MustJSON(&scope), )) lease := testLease() tests.PanicIfError(inst.KV().Put( @@ -70,7 +70,7 @@ func TestAPILeasesPut(t *testing.T) { types.KeyScopes, scope.Name, ).String(), - tests.MustJSON(scope), + tests.MustJSON(&scope), )) assert.NoError(t, role.APILeasesPut().Interact(ctx, dhcp.APILeasesPutInput{ Identifier: name, @@ -111,7 +111,7 @@ func TestAPILeasesDelete(t *testing.T) { types.KeyScopes, scope.Name, ).String(), - tests.MustJSON(scope), + tests.MustJSON(&scope), )) lease := testLease() tests.PanicIfError(inst.KV().Put( diff --git a/pkg/roles/dhcp/ipam_internal.go b/pkg/roles/dhcp/ipam_internal.go index bb612a976..8e313169f 100644 --- a/pkg/roles/dhcp/ipam_internal.go +++ b/pkg/roles/dhcp/ipam_internal.go @@ -147,13 +147,9 @@ func (i *InternalIPAM) IsIPFree(ip netip.Addr, identifier *string) bool { return false } // check for existing leases - i.role.leasesM.RLock() - defer i.role.leasesM.RUnlock() - for _, l := range i.role.leases { - // Ignore leases from other scopes - if l.ScopeKey != i.scope.Name { - continue - } + i.scope.leasesSync.RLock() + defer i.scope.leasesSync.RUnlock() + for _, l := range i.scope.leases { if l.Address != ip.String() { continue } diff --git a/pkg/roles/dhcp/ipam_internal_test.go b/pkg/roles/dhcp/ipam_internal_test.go index f916fc369..e1cb18576 100644 --- a/pkg/roles/dhcp/ipam_internal_test.go +++ b/pkg/roles/dhcp/ipam_internal_test.go @@ -58,7 +58,7 @@ func TestIPAMInternal_NextFreeAddress_UniqueParallel(t *testing.T) { types.KeyScopes, scope.Name, ).String(), - tests.MustJSON(scope), + tests.MustJSON(&scope), )) // Create fake leases to test against for i := 0; i < iter-10; i++ { diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index c6732332e..5e257e209 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" - "fmt" "math" "net" "net/netip" @@ -42,47 +41,24 @@ type Lease struct { } func (r *Role) FindLease(req *Request4) *Lease { - r.leasesM.RLock() - defer r.leasesM.RUnlock() - lease, ok := r.leases[r.DeviceIdentifier(req.DHCPv4)] + expectedScope := r.findScopeForRequest(req) + expectedScope.leasesSync.RLock() + defer expectedScope.leasesSync.RUnlock() + lease, ok := expectedScope.leases[r.DeviceIdentifier(req.DHCPv4)] if !ok { return nil } - // Check if the leases's scope matches the expected scope to handle this request - 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 - lease.ScopeKey = expectedScope.Name - lease.setLeaseIP(req) - lease.log.Info("Re-assigning address for lease due to changed request scope", zap.String("newIP", lease.Address)) - go func() { - err := lease.Put(req.Context, lease.scope.TTL) - if err != nil { - r.log.Warn("failed to update lease", zap.Error(err)) - } - }() - } return lease } -func (r *Role) NewLease(identifier string) *Lease { +func (s *Scope) NewLease(identifier string) *Lease { return &Lease{ - inst: r.i, + inst: s.inst, Identifier: identifier, - log: r.log.With(zap.String("identifier", identifier)), + log: s.log.With(zap.String("identifier", identifier)), Expiry: 0, + scope: s, + ScopeKey: s.Name, } } @@ -107,27 +83,21 @@ func (l *Lease) setLeaseIP(req *Request4) { l.scope.ipam.UseIP(*ip, l.Identifier) } -func (r *Role) leaseFromKV(raw *mvccpb.KeyValue) (*Lease, error) { - prefix := r.i.KV().Key( +func (s *Scope) leaseFromKV(raw *mvccpb.KeyValue) (*Lease, error) { + prefix := s.inst.KV().Key( types.KeyRole, types.KeyScopes, ).Prefix(true).String() keyParts := strings.SplitN(prefix, "/", 2) identifier := strings.TrimPrefix(string(raw.Key), prefix+"/"+keyParts[0]) - l := r.NewLease(identifier) + l := s.NewLease(identifier) err := json.Unmarshal(raw.Value, &l) if err != nil { return l, err } l.etcdKey = string(raw.Key) - - r.scopesM.RLock() - scope, ok := r.scopes[l.ScopeKey] - r.scopesM.RUnlock() - if !ok { - return l, fmt.Errorf("DHCP lease with invalid scope key: %s", l.ScopeKey) - } - l.scope = scope + l.scope = s + l.ScopeKey = s.Name return l, nil } diff --git a/pkg/roles/dhcp/leases_watch.go b/pkg/roles/dhcp/leases_watch.go deleted file mode 100644 index 5b591308c..000000000 --- a/pkg/roles/dhcp/leases_watch.go +++ /dev/null @@ -1,84 +0,0 @@ -package dhcp - -import ( - "context" - "errors" - "strings" - "time" - - "beryju.io/gravity/pkg/roles/dhcp/types" - "go.etcd.io/etcd/api/v3/mvccpb" - clientv3 "go.etcd.io/etcd/client/v3" - "go.uber.org/zap" -) - -func (r *Role) handleLeaseOp(ev *clientv3.Event) { - rec, err := r.leaseFromKV(ev.Kv) - if ev.Type == clientv3.EventTypeDelete { - r.leasesM.Lock() - defer r.leasesM.Unlock() - delete(r.leases, rec.Identifier) - } else { - // Check if the lease parsed above actually was parsed correctly, - // we don't care for that when removing, but prevent adding - // empty leases - if err != nil { - r.log.Warn("failed to parse lease", zap.Error(err)) - return - } - r.leasesM.Lock() - defer r.leasesM.Unlock() - r.leases[rec.Identifier] = rec - } -} - -func (r *Role) loadInitialLeases(ctx context.Context) { - prefix := r.i.KV().Key( - types.KeyRole, - types.KeyScopes, - ).Prefix(true).String() - leases, err := r.i.KV().Get( - ctx, - prefix, - clientv3.WithPrefix(), - ) - if err != nil { - r.log.Warn("failed to list initial leases", zap.Error(err)) - if !errors.Is(err, context.Canceled) { - time.Sleep(5 * time.Second) - r.loadInitialLeases(ctx) - } - return - } - for _, lease := range leases.Kvs { - relKey := strings.ReplaceAll(string(lease.Key), prefix, "") - if !strings.Contains("/", relKey) { - continue - } - r.handleLeaseOp(&clientv3.Event{ - Type: mvccpb.PUT, - Kv: lease, - }) - } -} - -func (r *Role) startWatchLeases() { - prefix := r.i.KV().Key( - types.KeyRole, - types.KeyScopes, - ).Prefix(true).String() - watchChan := r.i.KV().Watch( - r.ctx, - prefix, - clientv3.WithPrefix(), - ) - for watchResp := range watchChan { - for _, event := range watchResp.Events { - relKey := strings.ReplaceAll(string(event.Kv.Key), prefix, "") - if !strings.Contains("/", relKey) { - continue - } - r.handleLeaseOp(event) - } - } -} diff --git a/pkg/roles/dhcp/metrics.go b/pkg/roles/dhcp/metrics.go index b5cc41d7c..25dbb22b5 100644 --- a/pkg/roles/dhcp/metrics.go +++ b/pkg/roles/dhcp/metrics.go @@ -1,8 +1,6 @@ package dhcp import ( - "math/big" - "beryju.io/gravity/pkg/extconfig" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -36,14 +34,8 @@ var ( func (s *Scope) calculateUsage() { usable := s.ipam.UsableSize() dhcpScopeSize.WithLabelValues(s.Name).Set(float64(usable.Uint64())) - used := big.NewInt(0) - s.role.leasesM.RLock() - defer s.role.leasesM.RUnlock() - for _, lease := range s.role.leases { - if lease.ScopeKey != s.Name { - continue - } - used = used.Add(used, big.NewInt(1)) - } - dhcpScopeUsage.WithLabelValues(s.Name).Set(float64(used.Uint64())) + s.leasesSync.RLock() + defer s.leasesSync.RUnlock() + used := len(s.leases) + dhcpScopeUsage.WithLabelValues(s.Name).Set(float64(used)) } diff --git a/pkg/roles/dhcp/role.go b/pkg/roles/dhcp/role.go index 5c6ba40f6..1aae51945 100644 --- a/pkg/roles/dhcp/role.go +++ b/pkg/roles/dhcp/role.go @@ -26,7 +26,6 @@ type Role struct { ctx context.Context scopes map[string]*Scope - leases map[string]*Lease cfg *RoleConfig @@ -35,7 +34,6 @@ type Role struct { oui *oui.OuiDb scopesM sync.RWMutex - leasesM sync.RWMutex } func New(instance roles.Instance) *Role { @@ -44,8 +42,6 @@ func New(instance roles.Instance) *Role { i: instance, scopes: make(map[string]*Scope), scopesM: sync.RWMutex{}, - leases: make(map[string]*Lease), - leasesM: sync.RWMutex{}, ctx: instance.Context(), } r.s4 = &handler4{ @@ -78,16 +74,8 @@ func (r *Role) Start(ctx context.Context, config []byte) error { start := sentry.TransactionFromContext(ctx).StartChild("gravity.dhcp.start") defer start.Finish() r.loadInitialScopes(start.Context()) - r.loadInitialLeases(start.Context()) - - // Since scope usage relies on r.leases, but r.leases is loaded after the scopes, - // manually update the usage - for _, s := range r.scopes { - s.calculateUsage() - } go r.startWatchScopes() - go r.startWatchLeases() err := r.initServer4() if err != nil { diff --git a/pkg/roles/dhcp/scope_selector.go b/pkg/roles/dhcp/scope_selector.go new file mode 100644 index 000000000..367ceb7f7 --- /dev/null +++ b/pkg/roles/dhcp/scope_selector.go @@ -0,0 +1,53 @@ +package dhcp + +import ( + "net" + + "go.uber.org/zap" +) + +type scopeSelector func(scope *Scope) int + +func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSelector) *Scope { + var match *Scope + longestBits := 0 + r.scopesM.RLock() + defer r.scopesM.RUnlock() + // To prioritise requests from a DHCP relay being matched correctly, give their subnet + // match a 1 bit more priority + const dhcpRelayBias = 1 + for _, scope := range r.scopes { + // 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)) + match = scope + longestBits = gatewayMatchBits + dhcpRelayBias + } + // Handle local broadcast, check with the instance's listening IP + // Only consider local scopes if we don't have a match already + localMatchBits := scope.match(net.ParseIP(req.LocalIP())) + if localMatchBits > -1 && localMatchBits > longestBits { + req.log.Debug("selected scope based on cidr match (instance/interface IP)", zap.String("scope", scope.Name)) + match = scope + longestBits = localMatchBits + } + // Fallback to default scope if we don't already have a match + if match == nil && scope.Default { + req.log.Debug("selected scope based on default flag", zap.String("scope", scope.Name)) + match = scope + } + } + if match != nil { + req.log.Debug("final scope selection", zap.String("scope", match.Name)) + } + return match +} diff --git a/pkg/roles/dhcp/scopes.go b/pkg/roles/dhcp/scopes.go index 843894662..59d5b7bca 100644 --- a/pkg/roles/dhcp/scopes.go +++ b/pkg/roles/dhcp/scopes.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" "strings" + "sync" "time" "beryju.io/gravity/pkg/roles" @@ -26,17 +27,19 @@ type ScopeDNS struct { type Scope struct { ipam IPAM inst roles.Instance - DNS *ScopeDNS `json:"dns"` - - IPAM map[string]string `json:"ipam"` role *Role log *zap.Logger cidr netip.Prefix Name string `json:"-"` - etcdKey string + etcdKey string + leases map[string]*Lease + leasesWatchCtx context.CancelFunc + leasesSync sync.RWMutex + DNS *ScopeDNS `json:"dns"` + IPAM map[string]string `json:"ipam"` SubnetCIDR string `json:"subnetCidr"` Options []*types.DHCPOption `json:"options"` TTL int64 `json:"ttl"` @@ -46,13 +49,15 @@ type Scope struct { func (r *Role) NewScope(name string) *Scope { return &Scope{ - Name: name, - inst: r.i, - role: r, - TTL: int64((7 * 24 * time.Hour).Seconds()), - log: r.log.With(zap.String("scope", name)), - DNS: &ScopeDNS{}, - IPAM: make(map[string]string), + Name: name, + inst: r.i, + role: r, + TTL: int64((7 * 24 * time.Hour).Seconds()), + log: r.log.With(zap.String("scope", name)), + DNS: &ScopeDNS{}, + IPAM: make(map[string]string), + leases: make(map[string]*Lease), + leasesSync: sync.RWMutex{}, } } @@ -96,52 +101,6 @@ func (s *Scope) ipamType(previous *Scope) (IPAM, error) { } } -type scopeSelector func(scope *Scope) int - -func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSelector) *Scope { - var match *Scope - longestBits := 0 - r.scopesM.RLock() - defer r.scopesM.RUnlock() - // To prioritise requests from a DHCP relay being matched correctly, give their subnet - // match a 1 bit more priority - const dhcpRelayBias = 1 - for _, scope := range r.scopes { - // 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)) - match = scope - longestBits = gatewayMatchBits + dhcpRelayBias - } - // Handle local broadcast, check with the instance's listening IP - // Only consider local scopes if we don't have a match already - localMatchBits := scope.match(net.ParseIP(req.LocalIP())) - if localMatchBits > -1 && localMatchBits > longestBits { - req.log.Debug("selected scope based on cidr match (instance/interface IP)", zap.String("scope", scope.Name)) - match = scope - longestBits = localMatchBits - } - // Fallback to default scope if we don't already have a match - if match == nil && scope.Default { - req.log.Debug("selected scope based on default flag", zap.String("scope", scope.Name)) - match = scope - } - } - if match != nil { - req.log.Debug("final scope selection", zap.String("scope", match.Name)) - } - return match -} - func (s *Scope) match(peer net.IP) int { ip, err := netip.ParseAddr(peer.String()) if err != nil { @@ -156,11 +115,8 @@ func (s *Scope) match(peer net.IP) int { func (s *Scope) createLeaseFor(req *Request4) *Lease { ident := s.role.DeviceIdentifier(req.DHCPv4) - lease := s.role.NewLease(ident) + lease := s.NewLease(ident) lease.Hostname = req.HostName() - - lease.scope = s - lease.ScopeKey = s.Name lease.setLeaseIP(req) req.log.Info("creating new DHCP lease", zap.String("ip", lease.Address), zap.String("identifier", ident)) return lease @@ -210,3 +166,60 @@ func (s *Scope) executeHook(method string, args ...interface{}) { }, }, args...) } + +func (s *Scope) watchScopeLeases(ctx context.Context) { + evtHandler := func(ev *clientv3.Event) { + lease, err := s.leaseFromKV(ev.Kv) + defer s.calculateUsage() + if ev.Type == clientv3.EventTypeDelete { + delete(s.leases, lease.Identifier) + } else { + // Check if the record parsed above actually was parsed correctly, + // we don't care for that when removing, but prevent adding + // empty leases + if err != nil { + return + } + s.leasesSync.Lock() + defer s.leasesSync.Unlock() + s.leases[lease.Identifier] = lease + } + } + ctx, canc := context.WithCancel(ctx) + s.leasesWatchCtx = canc + + prefix := s.inst.KV().Key(s.etcdKey).Prefix(true).String() + + leases, err := s.inst.KV().Get(ctx, prefix, clientv3.WithPrefix()) + if err != nil { + s.log.Warn("failed to list initial leases", zap.Error(err)) + time.Sleep(5 * time.Second) + s.watchScopeLeases(ctx) + return + } + for _, lease := range leases.Kvs { + evtHandler(&clientv3.Event{ + Type: mvccpb.PUT, + Kv: lease, + }) + } + + watchChan := s.inst.KV().Watch( + ctx, + prefix, + clientv3.WithPrefix(), + ) + go func() { + for watchResp := range watchChan { + for _, event := range watchResp.Events { + go evtHandler(event) + } + } + }() +} + +func (s *Scope) StopWatchingLeases() { + if s != nil && s.leasesWatchCtx != nil { + s.leasesWatchCtx() + } +} diff --git a/pkg/roles/dhcp/scopes_watch.go b/pkg/roles/dhcp/scopes_watch.go index 46c0ac184..417fd1cfc 100644 --- a/pkg/roles/dhcp/scopes_watch.go +++ b/pkg/roles/dhcp/scopes_watch.go @@ -12,7 +12,7 @@ import ( "go.uber.org/zap" ) -func (r *Role) handleScopeOp(t mvccpb.Event_EventType, kv *mvccpb.KeyValue) bool { +func (r *Role) handleScopeOp(t mvccpb.Event_EventType, kv *mvccpb.KeyValue, ctx context.Context) bool { prefix := r.i.KV().Key(types.KeyRole, types.KeyScopes).Prefix(true) relKey := strings.TrimPrefix(string(kv.Key), prefix.String()) // we only care about scope-level updates, everything underneath doesn't matter @@ -23,14 +23,20 @@ func (r *Role) handleScopeOp(t mvccpb.Event_EventType, kv *mvccpb.KeyValue) bool r.log.Debug("removed scope", zap.String("key", relKey)) r.scopesM.Lock() defer r.scopesM.Unlock() + sc := r.scopes[relKey] + sc.StopWatchingLeases() delete(r.scopes, relKey) } else if t == mvccpb.PUT { s, err := r.scopeFromKV(kv) if err != nil { r.log.Warn("failed to convert scope from event", zap.Error(err)) } else { + s.watchScopeLeases(ctx) s.calculateUsage() r.scopesM.Lock() + if oldScope, ok := r.scopes[s.Name]; ok { + oldScope.StopWatchingLeases() + } r.scopes[s.Name] = s r.scopesM.Unlock() r.log.Debug("added scope", zap.String("name", s.Name)) @@ -57,7 +63,7 @@ func (r *Role) loadInitialScopes(ctx context.Context) { return } for _, scope := range scopes.Kvs { - r.handleScopeOp(mvccpb.PUT, scope) + r.handleScopeOp(mvccpb.PUT, scope, ctx) } } @@ -69,7 +75,7 @@ func (r *Role) startWatchScopes() { ) for watchResp := range watchChan { for _, event := range watchResp.Events { - if r.handleScopeOp(event.Type, event.Kv) { + if r.handleScopeOp(event.Type, event.Kv, r.ctx) { r.log.Debug("scope watch update", zap.String("key", string(event.Kv.Key))) } } diff --git a/pkg/tests/utils.go b/pkg/tests/utils.go index 647281175..35546d543 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" @@ -108,24 +106,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) - } -} From 39e67bab61127250c4d6d186116c2cef24ce2b26 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 01:26:11 +0100 Subject: [PATCH 05/17] dont use log colours in CI --- pkg/extconfig/log.go | 4 +++- pkg/extconfig/version.go | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/extconfig/log.go b/pkg/extconfig/log.go index 2927d8eb4..3feb83562 100644 --- a/pkg/extconfig/log.go +++ b/pkg/extconfig/log.go @@ -39,7 +39,9 @@ func (e *ExtConfig) BuildLoggerWithLevel(l zapcore.Level) *zap.Logger { config.Development = false config.Encoding = "console" config.EncoderConfig = zap.NewDevelopmentEncoderConfig() - config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + if !CI() { + config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + } } config.EncoderConfig.EncodeDuration = zapcore.MillisDurationEncoder log, err := config.Build() diff --git a/pkg/extconfig/version.go b/pkg/extconfig/version.go index d3696b158..b9b627fae 100644 --- a/pkg/extconfig/version.go +++ b/pkg/extconfig/version.go @@ -11,8 +11,12 @@ var ( BuildHash = "" ) +func CI() bool { + return strings.EqualFold(os.Getenv("CI"), "true") +} + func FullVersion() string { - if os.Getenv("CI") == "true" { + if CI() { Version = "99.99.99" BuildHash = "test" } From f0e3e98decee8fcf694151c24e0d925d8ef1b09e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 01:57:37 +0100 Subject: [PATCH 06/17] add initial migration --- pkg/roles/dhcp/role_migrations.go | 101 ++++++++++++++++++++++++++++++ pkg/roles/dhcp/types/role.go | 5 +- 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 pkg/roles/dhcp/role_migrations.go diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go new file mode 100644 index 000000000..3d9644e7c --- /dev/null +++ b/pkg/roles/dhcp/role_migrations.go @@ -0,0 +1,101 @@ +package dhcp + +import ( + "context" + "encoding/json" + "strings" + + "beryju.io/gravity/pkg/instance/migrate" + "beryju.io/gravity/pkg/roles/dhcp/types" + "beryju.io/gravity/pkg/storage" + clientv3 "go.etcd.io/etcd/client/v3" + "go.uber.org/zap" +) + +func (r *Role) migrateMoveInitial() { + r.log.Info("Running initial move migration") + res, err := r.i.KV().Get( + r.ctx, + r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), + clientv3.WithPrefix(), + ) + if err != nil { + r.log.Warn("failed to get legacy leases", zap.Error(err)) + return + } + // Copy from legacy prefix (global scope) to new prefix (scope-scoped) + for _, kv := range res.Kvs { + l := &Lease{} + err = json.Unmarshal(kv.Value, &l) + if err != nil { + r.log.Warn("failed to parse lease", zap.Error(err)) + continue + } + _, err = r.i.KV().Put( + r.ctx, + r.i.KV().Key(types.KeyRole, types.KeyScopes, l.ScopeKey).String(), + string(kv.Value), + ) + if err != nil { + r.log.Warn("failed to migrate lease", zap.Error(err)) + continue + } + } +} + +// func (r *Role) migrateMoveBackground() { +// watchChan := r.i.KV().Watch( +// r.ctx, +// r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), +// clientv3.WithPrefix(), +// ) +// for watchResp := range watchChan { +// for _, event := range watchResp.Events { +// switch event.Type { +// case clientv3.EventTypeDelete: +// r.i.KV().Delete(r.ctx) +// } +// } +// } +// } + +func (r *Role) RegisterMigrations() { + r.i.Migrator().AddMigration(&migrate.InlineMigration{ + MigrationName: "dhcp-move", + ActivateOnVersion: migrate.MustParseConstraint("< 0.17.0"), + HookFunc: func(ctx context.Context) (*storage.Client, error) { + pureKV := r.i.KV() + leasePrefix := r.i.KV().Key(types.KeyRole, types.KeyScopes).Prefix(true).String() + + r.migrateMoveInitial() + + return r.i.KV().WithHooks(storage.StorageHook{ + PutPost: func(ctx context.Context, key, val string, res *clientv3.PutResponse, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) { + relKey := strings.TrimPrefix(key, leasePrefix) + parts := strings.Split(relKey, "/") + shouldIntercept := len(parts) == 2 + if shouldIntercept { + r.log.Debug("hooking DHCP lease write for migration", zap.String("key", key)) + leaseKey := pureKV.Key( + types.KeyRole, + types.KeyLegacyLeases, + parts[1], + ).String() + r.log.Debug("Writing lease to legacy key", zap.String("key", leaseKey)) + _, err := pureKV.Put( + ctx, + leaseKey, + val, + opts..., + ) + if err != nil { + return nil, err + } + } + return res, nil + }, + }), nil + }, + }) + +} diff --git a/pkg/roles/dhcp/types/role.go b/pkg/roles/dhcp/types/role.go index f8b6822f1..97dd6f7bf 100644 --- a/pkg/roles/dhcp/types/role.go +++ b/pkg/roles/dhcp/types/role.go @@ -1,6 +1,7 @@ package types const ( - KeyRole = "dhcp" - KeyScopes = "scopes" + KeyRole = "dhcp" + KeyLegacyLeases = "leases" + KeyScopes = "scopes" ) From a96c2b89148f2d498141c0337eb2d9902c0d7c6e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 02:05:29 +0100 Subject: [PATCH 07/17] fix initial migration --- pkg/roles/dhcp/role_migrations.go | 2 +- pkg/roles/dhcp/scopes_watch.go | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go index 3d9644e7c..b4796a4d0 100644 --- a/pkg/roles/dhcp/role_migrations.go +++ b/pkg/roles/dhcp/role_migrations.go @@ -33,7 +33,7 @@ func (r *Role) migrateMoveInitial() { } _, err = r.i.KV().Put( r.ctx, - r.i.KV().Key(types.KeyRole, types.KeyScopes, l.ScopeKey).String(), + r.i.KV().Key(types.KeyRole, types.KeyScopes, l.ScopeKey, l.Identifier).String(), string(kv.Value), ) if err != nil { diff --git a/pkg/roles/dhcp/scopes_watch.go b/pkg/roles/dhcp/scopes_watch.go index 417fd1cfc..797742b86 100644 --- a/pkg/roles/dhcp/scopes_watch.go +++ b/pkg/roles/dhcp/scopes_watch.go @@ -30,17 +30,17 @@ func (r *Role) handleScopeOp(t mvccpb.Event_EventType, kv *mvccpb.KeyValue, ctx s, err := r.scopeFromKV(kv) if err != nil { r.log.Warn("failed to convert scope from event", zap.Error(err)) - } else { - s.watchScopeLeases(ctx) - s.calculateUsage() - r.scopesM.Lock() - if oldScope, ok := r.scopes[s.Name]; ok { - oldScope.StopWatchingLeases() - } - r.scopes[s.Name] = s - r.scopesM.Unlock() - r.log.Debug("added scope", zap.String("name", s.Name)) + return false + } + s.watchScopeLeases(ctx) + s.calculateUsage() + r.scopesM.Lock() + if oldScope, ok := r.scopes[s.Name]; ok { + oldScope.StopWatchingLeases() } + r.scopes[s.Name] = s + r.scopesM.Unlock() + r.log.Debug("added scope", zap.String("name", s.Name)) } return true } From 50db6e6241ceb253fae46835e596ce7e1aebbfcf Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 02:07:36 +0100 Subject: [PATCH 08/17] fix expiry missing --- web/src/pages/dhcp/DHCPLeaseForm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/dhcp/DHCPLeaseForm.ts b/web/src/pages/dhcp/DHCPLeaseForm.ts index 5927beb0b..36b24d13f 100644 --- a/web/src/pages/dhcp/DHCPLeaseForm.ts +++ b/web/src/pages/dhcp/DHCPLeaseForm.ts @@ -44,6 +44,7 @@ export class DHCPLeaseForm extends ModelForm { if (!data.addressLeaseTime) { data.addressLeaseTime = "0"; } + data.expiry = -1; if (this.instance && this.needsRecreate(data)) { await new RolesDhcpApi(DEFAULT_CONFIG).dhcpDeleteLeases({ scope: this.scope || "", From 99334a6e3f35a9448bf8fb8d98d62b3e98574802 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 02:15:37 +0100 Subject: [PATCH 09/17] fix some minor things --- pkg/instance/migrate/migrate.go | 9 ++++++--- pkg/roles/dhcp/role_migrations.go | 16 +++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pkg/instance/migrate/migrate.go b/pkg/instance/migrate/migrate.go index 6e8810af7..c22b5ea8b 100644 --- a/pkg/instance/migrate/migrate.go +++ b/pkg/instance/migrate/migrate.go @@ -11,6 +11,7 @@ import ( "beryju.io/gravity/pkg/roles" "beryju.io/gravity/pkg/storage" "github.com/Masterminds/semver/v3" + "github.com/getsentry/sentry-go" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" ) @@ -77,15 +78,17 @@ func (mi *Migrator) Run(ctx context.Context) (*storage.Client, error) { } mi.log.Debug("Checking migrations to activate for cluster version", zap.String("clusterVersion", cv.String())) cli := mi.ri.KV() + span := sentry.TransactionFromContext(ctx).StartChild("gravity.instance.migrate") + defer span.Finish() for _, m := range mi.migrations { mi.log.Debug("Checking if migration needs to be run", zap.String("migration", m.Name())) - enabled, err := m.Check(cv, ctx) + enabled, err := m.Check(cv, span.Context()) if err != nil { mi.log.Warn("failed to check if migration should be enabled", zap.String("migration", m.Name()), zap.Error(err)) return nil, err } if enabled { - _cli, err := m.Hook(ctx) + _cli, err := m.Hook(span.Context()) if err != nil { mi.log.Warn("failed to hook for migration", zap.String("migration", m.Name()), zap.Error(err)) return nil, err @@ -94,7 +97,7 @@ func (mi *Migrator) Run(ctx context.Context) (*storage.Client, error) { cli = _cli } else { mi.log.Info("Running cleanup for migration", zap.String("migration", m.Name())) - err := m.Cleanup(ctx) + err := m.Cleanup(span.Context()) if err != nil { mi.log.Warn("failed to cleanup migration", zap.String("migration", m.Name()), zap.Error(err)) continue diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go index b4796a4d0..c5e99f127 100644 --- a/pkg/roles/dhcp/role_migrations.go +++ b/pkg/roles/dhcp/role_migrations.go @@ -12,13 +12,10 @@ import ( "go.uber.org/zap" ) -func (r *Role) migrateMoveInitial() { +func (r *Role) migrateMoveInitial(ctx context.Context) { r.log.Info("Running initial move migration") - res, err := r.i.KV().Get( - r.ctx, - r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), - clientv3.WithPrefix(), - ) + pfx := r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String() + res, err := r.i.KV().Get(ctx, pfx, clientv3.WithPrefix()) if err != nil { r.log.Warn("failed to get legacy leases", zap.Error(err)) return @@ -26,14 +23,15 @@ func (r *Role) migrateMoveInitial() { // Copy from legacy prefix (global scope) to new prefix (scope-scoped) for _, kv := range res.Kvs { l := &Lease{} + ident := strings.TrimPrefix(string(kv.Key), pfx) err = json.Unmarshal(kv.Value, &l) if err != nil { r.log.Warn("failed to parse lease", zap.Error(err)) continue } _, err = r.i.KV().Put( - r.ctx, - r.i.KV().Key(types.KeyRole, types.KeyScopes, l.ScopeKey, l.Identifier).String(), + ctx, + r.i.KV().Key(types.KeyRole, types.KeyScopes, l.ScopeKey, ident).String(), string(kv.Value), ) if err != nil { @@ -67,7 +65,7 @@ func (r *Role) RegisterMigrations() { pureKV := r.i.KV() leasePrefix := r.i.KV().Key(types.KeyRole, types.KeyScopes).Prefix(true).String() - r.migrateMoveInitial() + r.migrateMoveInitial(ctx) return r.i.KV().WithHooks(storage.StorageHook{ PutPost: func(ctx context.Context, key, val string, res *clientv3.PutResponse, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) { From b64a453bc01535c403382a3d774c22af4f201682 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 03:03:45 +0100 Subject: [PATCH 10/17] fix lease extraction --- pkg/instance/migrate/migrate.go | 2 +- pkg/roles/dhcp/leases.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/instance/migrate/migrate.go b/pkg/instance/migrate/migrate.go index c22b5ea8b..aa087473b 100644 --- a/pkg/instance/migrate/migrate.go +++ b/pkg/instance/migrate/migrate.go @@ -88,12 +88,12 @@ func (mi *Migrator) Run(ctx context.Context) (*storage.Client, error) { return nil, err } if enabled { + mi.log.Info("Enabling migration", zap.String("migration", m.Name())) _cli, err := m.Hook(span.Context()) if err != nil { mi.log.Warn("failed to hook for migration", zap.String("migration", m.Name()), zap.Error(err)) return nil, err } - mi.log.Info("Enabling migration", zap.String("migration", m.Name())) cli = _cli } else { mi.log.Info("Running cleanup for migration", zap.String("migration", m.Name())) diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index 5e257e209..a75d64d59 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -87,9 +87,9 @@ func (s *Scope) leaseFromKV(raw *mvccpb.KeyValue) (*Lease, error) { prefix := s.inst.KV().Key( types.KeyRole, types.KeyScopes, + s.Name, ).Prefix(true).String() - keyParts := strings.SplitN(prefix, "/", 2) - identifier := strings.TrimPrefix(string(raw.Key), prefix+"/"+keyParts[0]) + identifier := strings.TrimPrefix(string(raw.Key), prefix) l := s.NewLease(identifier) err := json.Unmarshal(raw.Value, &l) if err != nil { From 2c86d034eb7ebd3ea28c6de2560555291c84650d Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 03:05:59 +0100 Subject: [PATCH 11/17] make cluster info return cluster version not node version --- pkg/instance/api_instance.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/instance/api_instance.go b/pkg/instance/api_instance.go index 75a57cf9a..42ab764a0 100644 --- a/pkg/instance/api_instance.go +++ b/pkg/instance/api_instance.go @@ -6,6 +6,7 @@ import ( "strings" "beryju.io/gravity/pkg/extconfig" + "beryju.io/gravity/pkg/instance/migrate" "beryju.io/gravity/pkg/instance/types" "github.com/swaggest/usecase" "github.com/swaggest/usecase/status" @@ -63,7 +64,13 @@ type APIInstanceInfo struct { func (i *Instance) APIInstanceInfo() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *APIInstanceInfo) error { - output.Version = extconfig.Version + ri := i.ForRole("api", ctx) + m := migrate.New(ri) + cv, err := m.GetClusterVersion(ctx) + if err != nil { + return status.Internal + } + output.Version = cv.String() output.BuildHash = extconfig.BuildHash output.Dirs = extconfig.Get().Dirs() output.CurrentInstanceIP = extconfig.Get().Instance.IP From be84d61a79e72c1fdc4da7acfac1ce6001a58b02 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 03:35:06 +0100 Subject: [PATCH 12/17] show cluster version status --- pkg/instance/api_instance.go | 11 ++++++- .../{api_cluster.go => api_cluster_member.go} | 0 pkg/roles/dhcp/role_migrations.go | 4 +++ schema.yml | 3 ++ web/src/pages/cluster/RoleAPIConfigForm.ts | 6 +++- web/src/pages/overview/cards/VersionCard.ts | 33 ++++++++++++++----- 6 files changed, 47 insertions(+), 10 deletions(-) rename pkg/roles/api/{api_cluster.go => api_cluster_member.go} (100%) diff --git a/pkg/instance/api_instance.go b/pkg/instance/api_instance.go index 42ab764a0..6260838d0 100644 --- a/pkg/instance/api_instance.go +++ b/pkg/instance/api_instance.go @@ -15,11 +15,20 @@ import ( ) type APIInstancesOutput struct { - Instances []InstanceInfo `json:"instances" required:"true"` + ClusterVersion string `json:"clusterVersion" required:"true"` + Instances []InstanceInfo `json:"instances" required:"true"` } func (i *Instance) APIInstances() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *APIInstancesOutput) error { + ri := i.ForRole("api", ctx) + m := migrate.New(ri) + cv, err := m.GetClusterVersion(ctx) + if err != nil { + return status.Internal + } + output.ClusterVersion = cv.String() + prefix := i.kv.Key(types.KeyInstance).Prefix(true).String() instances, err := i.kv.Get( ctx, diff --git a/pkg/roles/api/api_cluster.go b/pkg/roles/api/api_cluster_member.go similarity index 100% rename from pkg/roles/api/api_cluster.go rename to pkg/roles/api/api_cluster_member.go diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go index c5e99f127..d21553c6c 100644 --- a/pkg/roles/dhcp/role_migrations.go +++ b/pkg/roles/dhcp/role_migrations.go @@ -61,6 +61,10 @@ func (r *Role) RegisterMigrations() { r.i.Migrator().AddMigration(&migrate.InlineMigration{ MigrationName: "dhcp-move", ActivateOnVersion: migrate.MustParseConstraint("< 0.17.0"), + CleanupFunc: func(ctx context.Context) error { + r.log.Warn("Cleanup called") + return nil + }, HookFunc: func(ctx context.Context) (*storage.Client, error) { pureKV := r.i.KV() leasePrefix := r.i.KV().Key(types.KeyRole, types.KeyScopes).Prefix(true).String() diff --git a/schema.yml b/schema.yml index 1fa5b1f9a..3c8186a86 100644 --- a/schema.yml +++ b/schema.yml @@ -2376,12 +2376,15 @@ components: type: object InstanceAPIInstancesOutput: properties: + clusterVersion: + type: string instances: items: $ref: '#/components/schemas/InstanceInstanceInfo' nullable: true type: array required: + - clusterVersion - instances type: object InstanceAPIRoleRestartInput: diff --git a/web/src/pages/cluster/RoleAPIConfigForm.ts b/web/src/pages/cluster/RoleAPIConfigForm.ts index 960106e64..61b1fb105 100644 --- a/web/src/pages/cluster/RoleAPIConfigForm.ts +++ b/web/src/pages/cluster/RoleAPIConfigForm.ts @@ -57,7 +57,11 @@ export class RoleAPIConfigForm extends ModelForm { />

Secret used to sign cookies.

- + { +export class VersionCard extends AdminStatusCard { header = "Version"; headerLink = "#/cluster/nodes"; - getPrimaryValue(): Promise { - return new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInfo(); + clusterInfo?: InstanceAPIInstanceInfo; + + async getPrimaryValue(): Promise { + this.clusterInfo = await new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInfo(); + return await new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInstances(); } - getStatus(value: InstanceAPIInstanceInfo): Promise { + getStatus(value: InstanceAPIInstancesOutput): Promise { + const matching = + value.instances?.filter((inst) => { + return inst.version === value.clusterVersion; + }).length === value.instances?.length; + if (!matching) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle pf-m-warning", + message: html`Mismatched version in cluster!`, + }); + } return Promise.resolve({ icon: "fa fa-check-circle pf-m-success", - message: html`${value?.buildHash.substring(0, 7)}`, + message: html`Matching versions across nodes.`, }); } renderValue(): TemplateResult { return html` - ${this.value?.version} + ${this.value?.clusterVersion} `; } } From bc622166ee07d0d86b08cf2c4f33d8795a8e3b3e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 03:39:40 +0100 Subject: [PATCH 13/17] add cleanup --- pkg/roles/dhcp/role_migrations.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go index d21553c6c..5b18a0ef8 100644 --- a/pkg/roles/dhcp/role_migrations.go +++ b/pkg/roles/dhcp/role_migrations.go @@ -62,7 +62,13 @@ func (r *Role) RegisterMigrations() { MigrationName: "dhcp-move", ActivateOnVersion: migrate.MustParseConstraint("< 0.17.0"), CleanupFunc: func(ctx context.Context) error { - r.log.Warn("Cleanup called") + res, err := r.i.KV().Delete(ctx, + r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), + clientv3.WithPrefix()) + if err != nil { + return err + } + r.log.Info("Successfully cleaned up old DHCP leases", zap.Int64("count", res.Deleted)) return nil }, HookFunc: func(ctx context.Context) (*storage.Client, error) { From 17f6cdbf13af028c6fa92106fc5cd0080403fd89 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 03:44:22 +0100 Subject: [PATCH 14/17] add background migration --- pkg/instance/api_instance.go | 8 +----- pkg/roles/dhcp/role_migrations.go | 48 +++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/pkg/instance/api_instance.go b/pkg/instance/api_instance.go index 6260838d0..837b3974e 100644 --- a/pkg/instance/api_instance.go +++ b/pkg/instance/api_instance.go @@ -73,13 +73,7 @@ type APIInstanceInfo struct { func (i *Instance) APIInstanceInfo() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *APIInstanceInfo) error { - ri := i.ForRole("api", ctx) - m := migrate.New(ri) - cv, err := m.GetClusterVersion(ctx) - if err != nil { - return status.Internal - } - output.Version = cv.String() + output.Version = extconfig.Version output.BuildHash = extconfig.BuildHash output.Dirs = extconfig.Get().Dirs() output.CurrentInstanceIP = extconfig.Get().Instance.IP diff --git a/pkg/roles/dhcp/role_migrations.go b/pkg/roles/dhcp/role_migrations.go index 5b18a0ef8..d2942eda9 100644 --- a/pkg/roles/dhcp/role_migrations.go +++ b/pkg/roles/dhcp/role_migrations.go @@ -41,21 +41,38 @@ func (r *Role) migrateMoveInitial(ctx context.Context) { } } -// func (r *Role) migrateMoveBackground() { -// watchChan := r.i.KV().Watch( -// r.ctx, -// r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), -// clientv3.WithPrefix(), -// ) -// for watchResp := range watchChan { -// for _, event := range watchResp.Events { -// switch event.Type { -// case clientv3.EventTypeDelete: -// r.i.KV().Delete(r.ctx) -// } -// } -// } -// } +func (r *Role) migrateMoveBackground(ctx context.Context) { + watchChan := r.i.KV().Watch( + ctx, + r.i.KV().Key(types.KeyRole, types.KeyLegacyLeases).Prefix(true).String(), + clientv3.WithPrefix(), + ) + type partialLease struct { + ScopeKey string `json:"scopeKey"` + } + for watchResp := range watchChan { + for _, event := range watchResp.Events { + pl := partialLease{} + err := json.Unmarshal(event.Kv.Value, &pl) + if err != nil { + r.log.Warn("failed to parse partial lease", zap.Error(err)) + continue + } + ident := strings.Split(string(event.Kv.Key), "/")[2] + newKey := r.i.KV().Key(types.KeyRole, types.KeyScopes, pl.ScopeKey, ident).String() + switch event.Type { + case clientv3.EventTypePut: + _, err = r.i.KV().Put(ctx, newKey, string(event.Kv.Value)) + case clientv3.EventTypeDelete: + _, err = r.i.KV().Delete(ctx, newKey) + } + if err != nil { + r.log.Warn("failed to mirror legacy lease operation", zap.Error(err)) + continue + } + } + } +} func (r *Role) RegisterMigrations() { r.i.Migrator().AddMigration(&migrate.InlineMigration{ @@ -76,6 +93,7 @@ func (r *Role) RegisterMigrations() { leasePrefix := r.i.KV().Key(types.KeyRole, types.KeyScopes).Prefix(true).String() r.migrateMoveInitial(ctx) + go r.migrateMoveBackground(ctx) return r.i.KV().WithHooks(storage.StorageHook{ PutPost: func(ctx context.Context, key, val string, res *clientv3.PutResponse, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) { From 0857a30f59bcb86680e3f3b05ea90c93b051f9d7 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 04:02:24 +0100 Subject: [PATCH 15/17] fix some badly named API endpoints --- pkg/instance/api.go | 4 +- pkg/instance/api_instance.go | 29 ++++--- pkg/instance/api_instance_test.go | 4 +- schema.yml | 83 ++++++++++--------- web/src/pages/overview/OverviewPage.ts | 4 +- .../overview/cards/CurrentInstanceCard.ts | 6 +- web/src/pages/overview/cards/VersionCard.ts | 18 ++-- 7 files changed, 75 insertions(+), 73 deletions(-) diff --git a/pkg/instance/api.go b/pkg/instance/api.go index 00a85d19d..4deec089a 100644 --- a/pkg/instance/api.go +++ b/pkg/instance/api.go @@ -9,8 +9,8 @@ import ( func (i *Instance) setupInstanceAPI() { i.ForRole("instance", i.rootContext).AddEventListener(apitypes.EventTopicAPIMuxSetup, func(ev *roles.Event) { svc := ev.Payload.Data["svc"].(*web.Service) - svc.Get("/api/v1/cluster/instances", i.APIInstances()) - svc.Get("/api/v1/cluster/info", i.APIInstanceInfo()) + svc.Get("/api/v1/cluster", i.APIClusterInfo()) + svc.Get("/api/v1/cluster/instance", i.APIInstanceInfo()) svc.Post("/api/v1/cluster/roles/restart", i.APIClusterRoleRestart()) }) } diff --git a/pkg/instance/api_instance.go b/pkg/instance/api_instance.go index 837b3974e..b1159c0ad 100644 --- a/pkg/instance/api_instance.go +++ b/pkg/instance/api_instance.go @@ -14,13 +14,14 @@ import ( "go.uber.org/zap" ) -type APIInstancesOutput struct { - ClusterVersion string `json:"clusterVersion" required:"true"` - Instances []InstanceInfo `json:"instances" required:"true"` +type APIClusterInfoOutput struct { + ClusterVersion string `json:"clusterVersion" required:"true"` + ClusterVersionShort string `json:"clusterVersionShort" required:"true"` + Instances []InstanceInfo `json:"instances" required:"true"` } -func (i *Instance) APIInstances() usecase.Interactor { - u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *APIInstancesOutput) error { +func (i *Instance) APIClusterInfo() usecase.Interactor { + u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *APIClusterInfoOutput) error { ri := i.ForRole("api", ctx) m := migrate.New(ri) cv, err := m.GetClusterVersion(ctx) @@ -28,6 +29,8 @@ func (i *Instance) APIInstances() usecase.Interactor { return status.Internal } output.ClusterVersion = cv.String() + sv, _ := cv.SetMetadata("") + output.ClusterVersionShort = sv.String() prefix := i.kv.Key(types.KeyInstance).Prefix(true).String() instances, err := i.kv.Get( @@ -54,9 +57,9 @@ func (i *Instance) APIInstances() usecase.Interactor { } return nil }) - u.SetName("cluster.get_instances") - u.SetTitle("Instances") - u.SetTags("cluster/instances") + u.SetName("cluster.get_cluster_info") + u.SetTitle("Cluster") + u.SetTags("cluster") u.SetExpectedErrors(status.Internal) return u } @@ -67,8 +70,8 @@ type APIInstanceInfo struct { Dirs *extconfig.ExtConfigDirs `json:"dirs" required:"true"` - CurrentInstanceIdentifier string `json:"currentInstanceIdentifier" required:"true"` - CurrentInstanceIP string `json:"currentInstanceIP" required:"true"` + InstanceIdentifier string `json:"instanceIdentifier" required:"true"` + InstanceIP string `json:"instanceIP" required:"true"` } func (i *Instance) APIInstanceInfo() usecase.Interactor { @@ -76,11 +79,11 @@ func (i *Instance) APIInstanceInfo() usecase.Interactor { output.Version = extconfig.Version output.BuildHash = extconfig.BuildHash output.Dirs = extconfig.Get().Dirs() - output.CurrentInstanceIP = extconfig.Get().Instance.IP - output.CurrentInstanceIdentifier = extconfig.Get().Instance.Identifier + output.InstanceIP = extconfig.Get().Instance.IP + output.InstanceIdentifier = extconfig.Get().Instance.Identifier return nil }) - u.SetName("cluster.get_info") + u.SetName("cluster.get_instance_info") u.SetTitle("Instance") u.SetTags("cluster/instances") u.SetExpectedErrors(status.Internal) diff --git a/pkg/instance/api_instance_test.go b/pkg/instance/api_instance_test.go index fff4ced78..4068b32f6 100644 --- a/pkg/instance/api_instance_test.go +++ b/pkg/instance/api_instance_test.go @@ -23,7 +23,7 @@ func TestAPIInstances(t *testing.T) { defer tests.Setup(t)() rootInst := instance.New() - var output instance.APIInstancesOutput - assert.NoError(t, rootInst.APIInstances().Interact(tests.Context(), struct{}{}, &output)) + var output instance.APIClusterInfoOutput + assert.NoError(t, rootInst.APIClusterInfo().Interact(tests.Context(), struct{}{}, &output)) assert.NotNil(t, output) } diff --git a/schema.yml b/schema.yml index 3c8186a86..3ea4d2bc4 100644 --- a/schema.yml +++ b/schema.yml @@ -243,6 +243,25 @@ paths: summary: Backup status tags: - roles/backup + /api/v1/cluster: + get: + operationId: cluster.get_cluster_info + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/InstanceAPIClusterInfoOutput' + description: OK + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrResponse' + description: Internal Server Error + summary: Cluster + tags: + - cluster /api/v1/cluster/export: post: operationId: api.export @@ -287,9 +306,9 @@ paths: summary: Import Cluster tags: - roles/api - /api/v1/cluster/info: + /api/v1/cluster/instance: get: - operationId: cluster.get_info + operationId: cluster.get_instance_info responses: "200": content: @@ -306,25 +325,6 @@ paths: summary: Instance tags: - cluster/instances - /api/v1/cluster/instances: - get: - operationId: cluster.get_instances - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/InstanceAPIInstancesOutput' - description: OK - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/RestErrResponse' - description: Internal Server Error - summary: Instances - tags: - - cluster/instances /api/v1/cluster/node/logs: get: operationId: api.get_log_messages @@ -2355,37 +2355,40 @@ components: tftpLocalDir: type: string type: object - InstanceAPIInstanceInfo: + InstanceAPIClusterInfoOutput: properties: - buildHash: + clusterVersion: type: string - currentInstanceIP: + clusterVersionShort: type: string - currentInstanceIdentifier: + instances: + items: + $ref: '#/components/schemas/InstanceInstanceInfo' + nullable: true + type: array + required: + - clusterVersion + - clusterVersionShort + - instances + type: object + InstanceAPIInstanceInfo: + properties: + buildHash: type: string dirs: $ref: '#/components/schemas/ExtconfigExtConfigDirs' + instanceIP: + type: string + instanceIdentifier: + type: string version: type: string required: - version - buildHash - dirs - - currentInstanceIdentifier - - currentInstanceIP - type: object - InstanceAPIInstancesOutput: - properties: - clusterVersion: - type: string - instances: - items: - $ref: '#/components/schemas/InstanceInstanceInfo' - nullable: true - type: array - required: - - clusterVersion - - instances + - instanceIdentifier + - instanceIP type: object InstanceAPIRoleRestartInput: properties: diff --git a/web/src/pages/overview/OverviewPage.ts b/web/src/pages/overview/OverviewPage.ts index 06d06e055..cf7c951df 100644 --- a/web/src/pages/overview/OverviewPage.ts +++ b/web/src/pages/overview/OverviewPage.ts @@ -58,10 +58,10 @@ export class OverviewPage extends AKElement {
-
+
-
+
diff --git a/web/src/pages/overview/cards/CurrentInstanceCard.ts b/web/src/pages/overview/cards/CurrentInstanceCard.ts index c91b8c4a1..647e04711 100644 --- a/web/src/pages/overview/cards/CurrentInstanceCard.ts +++ b/web/src/pages/overview/cards/CurrentInstanceCard.ts @@ -11,16 +11,16 @@ export class CurrentInstanceCard extends AdminStatusCard { - return new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInfo(); + return new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInstanceInfo(); } getStatus(data: InstanceAPIInstanceInfo): Promise { return Promise.resolve({ icon: "fa fa-check-circle pf-m-success", - message: html`${data.currentInstanceIP}`, + message: html`${data.instanceIP}`, }); } renderValue() { - return html`${this.value?.currentInstanceIdentifier}`; + return html`${this.value?.instanceIdentifier}`; } } diff --git a/web/src/pages/overview/cards/VersionCard.ts b/web/src/pages/overview/cards/VersionCard.ts index c0394e984..1de2ed4af 100644 --- a/web/src/pages/overview/cards/VersionCard.ts +++ b/web/src/pages/overview/cards/VersionCard.ts @@ -1,7 +1,6 @@ import { - ClusterInstancesApi, - InstanceAPIInstanceInfo, - InstanceAPIInstancesOutput, + ClusterApi, + InstanceAPIClusterInfoOutput, } from "gravity-api"; import { TemplateResult, html } from "lit"; @@ -11,18 +10,15 @@ import { DEFAULT_CONFIG } from "../../../api/Config"; import { AdminStatus, AdminStatusCard } from "./AdminStatusCard"; @customElement("gravity-overview-card-version") -export class VersionCard extends AdminStatusCard { +export class VersionCard extends AdminStatusCard { header = "Version"; headerLink = "#/cluster/nodes"; - clusterInfo?: InstanceAPIInstanceInfo; - - async getPrimaryValue(): Promise { - this.clusterInfo = await new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInfo(); - return await new ClusterInstancesApi(DEFAULT_CONFIG).clusterGetInstances(); + async getPrimaryValue(): Promise { + return await new ClusterApi(DEFAULT_CONFIG).clusterGetClusterInfo(); } - getStatus(value: InstanceAPIInstancesOutput): Promise { + getStatus(value: InstanceAPIClusterInfoOutput): Promise { const matching = value.instances?.filter((inst) => { return inst.version === value.clusterVersion; @@ -41,7 +37,7 @@ export class VersionCard extends AdminStatusCard { renderValue(): TemplateResult { return html` ${this.value?.clusterVersion} From 4661091737e701aeed0ab9725089c4f6d9727d66 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 04:10:03 +0100 Subject: [PATCH 16/17] update go api --- api/.openapi-generator/FILES | 17 +- api/README.md | 6 +- api/api/openapi.yaml | 110 +++++------ api/api_cluster.go | 130 +++++++++++++ api/api_cluster_instances.go | 128 +------------ api/client.go | 3 + api/docs/ClusterApi.md | 68 +++++++ api/docs/ClusterInstancesApi.md | 76 +------- api/docs/InstanceAPIClusterInfoOutput.md | 103 +++++++++++ api/docs/InstanceAPIInstanceInfo.md | 60 +++--- api/model_instance_api_cluster_info_output.go | 173 ++++++++++++++++++ api/model_instance_api_instance_info.go | 78 ++++---- api/test/api_cluster_test.go | 35 ++++ cmd/cli/cli_health.go | 2 +- 14 files changed, 667 insertions(+), 322 deletions(-) create mode 100644 api/api_cluster.go create mode 100644 api/docs/ClusterApi.md create mode 100644 api/docs/InstanceAPIClusterInfoOutput.md create mode 100644 api/model_instance_api_cluster_info_output.go create mode 100644 api/test/api_cluster_test.go diff --git a/api/.openapi-generator/FILES b/api/.openapi-generator/FILES index 23b95ba71..e8eb89a65 100644 --- a/api/.openapi-generator/FILES +++ b/api/.openapi-generator/FILES @@ -2,6 +2,7 @@ .travis.yml README.md api/openapi.yaml +api_cluster.go api_cluster_instances.go api_roles_api.go api_roles_backup.go @@ -52,6 +53,7 @@ docs/BackupAPIRoleConfigInput.md docs/BackupAPIRoleConfigOutput.md docs/BackupBackupStatus.md docs/BackupRoleConfig.md +docs/ClusterApi.md docs/ClusterInstancesApi.md docs/DhcpAPILease.md docs/DhcpAPILeaseInfo.md @@ -84,8 +86,8 @@ docs/DnsAPIZonesGetOutput.md docs/DnsAPIZonesPutInput.md docs/DnsRoleConfig.md docs/ExtconfigExtConfigDirs.md +docs/InstanceAPIClusterInfoOutput.md docs/InstanceAPIInstanceInfo.md -docs/InstanceAPIInstancesOutput.md docs/InstanceAPIRoleRestartInput.md docs/InstanceInstanceInfo.md docs/MonitoringAPIRoleConfigInput.md @@ -188,8 +190,8 @@ model_dns_api_zones_get_output.go model_dns_api_zones_put_input.go model_dns_role_config.go model_extconfig_ext_config_dirs.go +model_instance_api_cluster_info_output.go model_instance_api_instance_info.go -model_instance_api_instances_output.go model_instance_api_role_restart_input.go model_instance_instance_info.go model_monitoring_api_role_config_input.go @@ -212,14 +214,5 @@ 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_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 +test/api_cluster_test.go utils.go diff --git a/api/README.md b/api/README.md index 1187a1af4..68b53b584 100644 --- a/api/README.md +++ b/api/README.md @@ -77,8 +77,8 @@ All URIs are relative to *http://localhost* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- -*ClusterInstancesApi* | [**ClusterGetInfo**](docs/ClusterInstancesApi.md#clustergetinfo) | **Get** /api/v1/cluster/info | Instance -*ClusterInstancesApi* | [**ClusterGetInstances**](docs/ClusterInstancesApi.md#clustergetinstances) | **Get** /api/v1/cluster/instances | Instances +*ClusterApi* | [**ClusterGetClusterInfo**](docs/ClusterApi.md#clustergetclusterinfo) | **Get** /api/v1/cluster | Cluster +*ClusterInstancesApi* | [**ClusterGetInstanceInfo**](docs/ClusterInstancesApi.md#clustergetinstanceinfo) | **Get** /api/v1/cluster/instance | Instance *ClusterInstancesApi* | [**ClusterInstanceRoleRestart**](docs/ClusterInstancesApi.md#clusterinstancerolerestart) | **Post** /api/v1/cluster/roles/restart | Instance roles *RolesApiApi* | [**ApiAuthConfig**](docs/RolesApiApi.md#apiauthconfig) | **Get** /api/v1/auth/config | API Users *RolesApiApi* | [**ApiDeleteTokens**](docs/RolesApiApi.md#apideletetokens) | **Delete** /api/v1/auth/tokens | Tokens @@ -213,8 +213,8 @@ Class | Method | HTTP request | Description - [DnsAPIZonesPutInput](docs/DnsAPIZonesPutInput.md) - [DnsRoleConfig](docs/DnsRoleConfig.md) - [ExtconfigExtConfigDirs](docs/ExtconfigExtConfigDirs.md) + - [InstanceAPIClusterInfoOutput](docs/InstanceAPIClusterInfoOutput.md) - [InstanceAPIInstanceInfo](docs/InstanceAPIInstanceInfo.md) - - [InstanceAPIInstancesOutput](docs/InstanceAPIInstancesOutput.md) - [InstanceAPIRoleRestartInput](docs/InstanceAPIRoleRestartInput.md) - [InstanceInstanceInfo](docs/InstanceInstanceInfo.md) - [MonitoringAPIRoleConfigInput](docs/MonitoringAPIRoleConfigInput.md) diff --git a/api/api/openapi.yaml b/api/api/openapi.yaml index 08243398c..fc64f8001 100644 --- a/api/api/openapi.yaml +++ b/api/api/openapi.yaml @@ -258,6 +258,25 @@ paths: summary: Backup status tags: - roles/backup + /api/v1/cluster: + get: + operationId: cluster.get_cluster_info + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/InstanceAPIClusterInfoOutput' + description: OK + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrResponse' + description: Internal Server Error + summary: Cluster + tags: + - cluster /api/v1/cluster/export: post: operationId: api.export @@ -302,9 +321,9 @@ paths: summary: Import Cluster tags: - roles/api - /api/v1/cluster/info: + /api/v1/cluster/instance: get: - operationId: cluster.get_info + operationId: cluster.get_instance_info responses: "200": content: @@ -321,25 +340,6 @@ paths: summary: Instance tags: - cluster/instances - /api/v1/cluster/instances: - get: - operationId: cluster.get_instances - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/InstanceAPIInstancesOutput' - description: OK - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/RestErrResponse' - description: Internal Server Error - summary: Instances - tags: - - cluster/instances /api/v1/cluster/node/logs: get: operationId: api.get_log_messages @@ -3053,37 +3053,9 @@ components: tftpLocalDir: type: string type: object - InstanceAPIInstanceInfo: - example: - currentInstanceIdentifier: currentInstanceIdentifier - currentInstanceIP: currentInstanceIP - buildHash: buildHash - dirs: - backupDir: backupDir - etcdDir: etcdDir - certDir: certDir - tftpLocalDir: tftpLocalDir - version: version - properties: - buildHash: - type: string - currentInstanceIP: - type: string - currentInstanceIdentifier: - type: string - dirs: - $ref: '#/components/schemas/ExtconfigExtConfigDirs' - version: - type: string - required: - - buildHash - - currentInstanceIP - - currentInstanceIdentifier - - dirs - - version - type: object - InstanceAPIInstancesOutput: + InstanceAPIClusterInfoOutput: example: + clusterVersionShort: clusterVersionShort instances: - identifier: identifier ip: ip @@ -3097,15 +3069,51 @@ components: - roles - roles version: version + clusterVersion: clusterVersion properties: + clusterVersion: + type: string + clusterVersionShort: + type: string instances: items: $ref: '#/components/schemas/InstanceInstanceInfo' nullable: true type: array required: + - clusterVersion + - clusterVersionShort - instances type: object + InstanceAPIInstanceInfo: + example: + buildHash: buildHash + dirs: + backupDir: backupDir + etcdDir: etcdDir + certDir: certDir + tftpLocalDir: tftpLocalDir + instanceIdentifier: instanceIdentifier + version: version + instanceIP: instanceIP + properties: + buildHash: + type: string + dirs: + $ref: '#/components/schemas/ExtconfigExtConfigDirs' + instanceIP: + type: string + instanceIdentifier: + type: string + version: + type: string + required: + - buildHash + - dirs + - instanceIP + - instanceIdentifier + - version + type: object InstanceAPIRoleRestartInput: example: roleId: roleId diff --git a/api/api_cluster.go b/api/api_cluster.go new file mode 100644 index 000000000..e94ae5337 --- /dev/null +++ b/api/api_cluster.go @@ -0,0 +1,130 @@ +/* +gravity + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +API version: 0.16.0 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package api + +import ( + "bytes" + "context" + "io" + "net/http" + "net/url" +) + +// ClusterApiService ClusterApi service +type ClusterApiService service + +type ApiClusterGetClusterInfoRequest struct { + ctx context.Context + ApiService *ClusterApiService +} + +func (r ApiClusterGetClusterInfoRequest) Execute() (*InstanceAPIClusterInfoOutput, *http.Response, error) { + return r.ApiService.ClusterGetClusterInfoExecute(r) +} + +/* +ClusterGetClusterInfo Cluster + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return ApiClusterGetClusterInfoRequest +*/ +func (a *ClusterApiService) ClusterGetClusterInfo(ctx context.Context) ApiClusterGetClusterInfoRequest { + return ApiClusterGetClusterInfoRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// +// @return InstanceAPIClusterInfoOutput +func (a *ClusterApiService) ClusterGetClusterInfoExecute(r ApiClusterGetClusterInfoRequest) (*InstanceAPIClusterInfoOutput, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *InstanceAPIClusterInfoOutput + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ClusterApiService.ClusterGetClusterInfo") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/api/v1/cluster" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 500 { + var v RestErrResponse + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/api/api_cluster_instances.go b/api/api_cluster_instances.go index 3e9c94e20..c013ca8bb 100644 --- a/api/api_cluster_instances.go +++ b/api/api_cluster_instances.go @@ -21,23 +21,23 @@ import ( // ClusterInstancesApiService ClusterInstancesApi service type ClusterInstancesApiService service -type ApiClusterGetInfoRequest struct { +type ApiClusterGetInstanceInfoRequest struct { ctx context.Context ApiService *ClusterInstancesApiService } -func (r ApiClusterGetInfoRequest) Execute() (*InstanceAPIInstanceInfo, *http.Response, error) { - return r.ApiService.ClusterGetInfoExecute(r) +func (r ApiClusterGetInstanceInfoRequest) Execute() (*InstanceAPIInstanceInfo, *http.Response, error) { + return r.ApiService.ClusterGetInstanceInfoExecute(r) } /* -ClusterGetInfo Instance +ClusterGetInstanceInfo Instance @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiClusterGetInfoRequest + @return ApiClusterGetInstanceInfoRequest */ -func (a *ClusterInstancesApiService) ClusterGetInfo(ctx context.Context) ApiClusterGetInfoRequest { - return ApiClusterGetInfoRequest{ +func (a *ClusterInstancesApiService) ClusterGetInstanceInfo(ctx context.Context) ApiClusterGetInstanceInfoRequest { + return ApiClusterGetInstanceInfoRequest{ ApiService: a, ctx: ctx, } @@ -46,7 +46,7 @@ func (a *ClusterInstancesApiService) ClusterGetInfo(ctx context.Context) ApiClus // Execute executes the request // // @return InstanceAPIInstanceInfo -func (a *ClusterInstancesApiService) ClusterGetInfoExecute(r ApiClusterGetInfoRequest) (*InstanceAPIInstanceInfo, *http.Response, error) { +func (a *ClusterInstancesApiService) ClusterGetInstanceInfoExecute(r ApiClusterGetInstanceInfoRequest) (*InstanceAPIInstanceInfo, *http.Response, error) { var ( localVarHTTPMethod = http.MethodGet localVarPostBody interface{} @@ -54,120 +54,12 @@ func (a *ClusterInstancesApiService) ClusterGetInfoExecute(r ApiClusterGetInfoRe localVarReturnValue *InstanceAPIInstanceInfo ) - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ClusterInstancesApiService.ClusterGetInfo") + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ClusterInstancesApiService.ClusterGetInstanceInfo") if err != nil { return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} } - localVarPath := localBasePath + "/api/v1/cluster/info" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 500 { - var v RestErrResponse - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiClusterGetInstancesRequest struct { - ctx context.Context - ApiService *ClusterInstancesApiService -} - -func (r ApiClusterGetInstancesRequest) Execute() (*InstanceAPIInstancesOutput, *http.Response, error) { - return r.ApiService.ClusterGetInstancesExecute(r) -} - -/* -ClusterGetInstances Instances - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiClusterGetInstancesRequest -*/ -func (a *ClusterInstancesApiService) ClusterGetInstances(ctx context.Context) ApiClusterGetInstancesRequest { - return ApiClusterGetInstancesRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// -// @return InstanceAPIInstancesOutput -func (a *ClusterInstancesApiService) ClusterGetInstancesExecute(r ApiClusterGetInstancesRequest) (*InstanceAPIInstancesOutput, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *InstanceAPIInstancesOutput - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ClusterInstancesApiService.ClusterGetInstances") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/v1/cluster/instances" + localVarPath := localBasePath + "/api/v1/cluster/instance" localVarHeaderParams := make(map[string]string) localVarQueryParams := url.Values{} diff --git a/api/client.go b/api/client.go index 926835a5a..d4ac7475d 100644 --- a/api/client.go +++ b/api/client.go @@ -48,6 +48,8 @@ type APIClient struct { // API Services + ClusterApi *ClusterApiService + ClusterInstancesApi *ClusterInstancesApiService RolesApiApi *RolesApiApiService @@ -85,6 +87,7 @@ func NewAPIClient(cfg *Configuration) *APIClient { c.common.client = c // API Services + c.ClusterApi = (*ClusterApiService)(&c.common) c.ClusterInstancesApi = (*ClusterInstancesApiService)(&c.common) c.RolesApiApi = (*RolesApiApiService)(&c.common) c.RolesBackupApi = (*RolesBackupApiService)(&c.common) diff --git a/api/docs/ClusterApi.md b/api/docs/ClusterApi.md new file mode 100644 index 000000000..f4d6821f7 --- /dev/null +++ b/api/docs/ClusterApi.md @@ -0,0 +1,68 @@ +# \ClusterApi + +All URIs are relative to *http://localhost* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**ClusterGetClusterInfo**](ClusterApi.md#ClusterGetClusterInfo) | **Get** /api/v1/cluster | Cluster + + + +## ClusterGetClusterInfo + +> InstanceAPIClusterInfoOutput ClusterGetClusterInfo(ctx).Execute() + +Cluster + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "beryju.io/gravity/api" +) + +func main() { + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + resp, r, err := apiClient.ClusterApi.ClusterGetClusterInfo(context.Background()).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `ClusterApi.ClusterGetClusterInfo``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } + // response from `ClusterGetClusterInfo`: InstanceAPIClusterInfoOutput + fmt.Fprintf(os.Stdout, "Response from `ClusterApi.ClusterGetClusterInfo`: %v\n", resp) +} +``` + +### Path Parameters + +This endpoint does not need any parameter. + +### Other Parameters + +Other parameters are passed through a pointer to a apiClusterGetClusterInfoRequest struct via the builder pattern + + +### Return type + +[**InstanceAPIClusterInfoOutput**](InstanceAPIClusterInfoOutput.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + diff --git a/api/docs/ClusterInstancesApi.md b/api/docs/ClusterInstancesApi.md index 87970296d..5a9fa8cdf 100644 --- a/api/docs/ClusterInstancesApi.md +++ b/api/docs/ClusterInstancesApi.md @@ -4,15 +4,14 @@ All URIs are relative to *http://localhost* Method | HTTP request | Description ------------- | ------------- | ------------- -[**ClusterGetInfo**](ClusterInstancesApi.md#ClusterGetInfo) | **Get** /api/v1/cluster/info | Instance -[**ClusterGetInstances**](ClusterInstancesApi.md#ClusterGetInstances) | **Get** /api/v1/cluster/instances | Instances +[**ClusterGetInstanceInfo**](ClusterInstancesApi.md#ClusterGetInstanceInfo) | **Get** /api/v1/cluster/instance | Instance [**ClusterInstanceRoleRestart**](ClusterInstancesApi.md#ClusterInstanceRoleRestart) | **Post** /api/v1/cluster/roles/restart | Instance roles -## ClusterGetInfo +## ClusterGetInstanceInfo -> InstanceAPIInstanceInfo ClusterGetInfo(ctx).Execute() +> InstanceAPIInstanceInfo ClusterGetInstanceInfo(ctx).Execute() Instance @@ -32,13 +31,13 @@ func main() { configuration := openapiclient.NewConfiguration() apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.ClusterInstancesApi.ClusterGetInfo(context.Background()).Execute() + resp, r, err := apiClient.ClusterInstancesApi.ClusterGetInstanceInfo(context.Background()).Execute() if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `ClusterInstancesApi.ClusterGetInfo``: %v\n", err) + fmt.Fprintf(os.Stderr, "Error when calling `ClusterInstancesApi.ClusterGetInstanceInfo``: %v\n", err) fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) } - // response from `ClusterGetInfo`: InstanceAPIInstanceInfo - fmt.Fprintf(os.Stdout, "Response from `ClusterInstancesApi.ClusterGetInfo`: %v\n", resp) + // response from `ClusterGetInstanceInfo`: InstanceAPIInstanceInfo + fmt.Fprintf(os.Stdout, "Response from `ClusterInstancesApi.ClusterGetInstanceInfo`: %v\n", resp) } ``` @@ -48,7 +47,7 @@ This endpoint does not need any parameter. ### Other Parameters -Other parameters are passed through a pointer to a apiClusterGetInfoRequest struct via the builder pattern +Other parameters are passed through a pointer to a apiClusterGetInstanceInfoRequest struct via the builder pattern ### Return type @@ -69,65 +68,6 @@ No authorization required [[Back to README]](../README.md) -## ClusterGetInstances - -> InstanceAPIInstancesOutput ClusterGetInstances(ctx).Execute() - -Instances - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "beryju.io/gravity/api" -) - -func main() { - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.ClusterInstancesApi.ClusterGetInstances(context.Background()).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `ClusterInstancesApi.ClusterGetInstances``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ClusterGetInstances`: InstanceAPIInstancesOutput - fmt.Fprintf(os.Stdout, "Response from `ClusterInstancesApi.ClusterGetInstances`: %v\n", resp) -} -``` - -### Path Parameters - -This endpoint does not need any parameter. - -### Other Parameters - -Other parameters are passed through a pointer to a apiClusterGetInstancesRequest struct via the builder pattern - - -### Return type - -[**InstanceAPIInstancesOutput**](InstanceAPIInstancesOutput.md) - -### Authorization - -No authorization required - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - ## ClusterInstanceRoleRestart > ClusterInstanceRoleRestart(ctx).InstanceAPIRoleRestartInput(instanceAPIRoleRestartInput).Execute() diff --git a/api/docs/InstanceAPIClusterInfoOutput.md b/api/docs/InstanceAPIClusterInfoOutput.md new file mode 100644 index 000000000..43d8a53f2 --- /dev/null +++ b/api/docs/InstanceAPIClusterInfoOutput.md @@ -0,0 +1,103 @@ +# InstanceAPIClusterInfoOutput + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**ClusterVersion** | **string** | | +**ClusterVersionShort** | **string** | | +**Instances** | [**[]InstanceInstanceInfo**](InstanceInstanceInfo.md) | | + +## Methods + +### NewInstanceAPIClusterInfoOutput + +`func NewInstanceAPIClusterInfoOutput(clusterVersion string, clusterVersionShort string, instances []InstanceInstanceInfo, ) *InstanceAPIClusterInfoOutput` + +NewInstanceAPIClusterInfoOutput instantiates a new InstanceAPIClusterInfoOutput object +This constructor will assign default values to properties that have it defined, +and makes sure properties required by API are set, but the set of arguments +will change when the set of required properties is changed + +### NewInstanceAPIClusterInfoOutputWithDefaults + +`func NewInstanceAPIClusterInfoOutputWithDefaults() *InstanceAPIClusterInfoOutput` + +NewInstanceAPIClusterInfoOutputWithDefaults instantiates a new InstanceAPIClusterInfoOutput object +This constructor will only assign default values to properties that have it defined, +but it doesn't guarantee that properties required by API are set + +### GetClusterVersion + +`func (o *InstanceAPIClusterInfoOutput) GetClusterVersion() string` + +GetClusterVersion returns the ClusterVersion field if non-nil, zero value otherwise. + +### GetClusterVersionOk + +`func (o *InstanceAPIClusterInfoOutput) GetClusterVersionOk() (*string, bool)` + +GetClusterVersionOk returns a tuple with the ClusterVersion field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetClusterVersion + +`func (o *InstanceAPIClusterInfoOutput) SetClusterVersion(v string)` + +SetClusterVersion sets ClusterVersion field to given value. + + +### GetClusterVersionShort + +`func (o *InstanceAPIClusterInfoOutput) GetClusterVersionShort() string` + +GetClusterVersionShort returns the ClusterVersionShort field if non-nil, zero value otherwise. + +### GetClusterVersionShortOk + +`func (o *InstanceAPIClusterInfoOutput) GetClusterVersionShortOk() (*string, bool)` + +GetClusterVersionShortOk returns a tuple with the ClusterVersionShort field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetClusterVersionShort + +`func (o *InstanceAPIClusterInfoOutput) SetClusterVersionShort(v string)` + +SetClusterVersionShort sets ClusterVersionShort field to given value. + + +### GetInstances + +`func (o *InstanceAPIClusterInfoOutput) GetInstances() []InstanceInstanceInfo` + +GetInstances returns the Instances field if non-nil, zero value otherwise. + +### GetInstancesOk + +`func (o *InstanceAPIClusterInfoOutput) GetInstancesOk() (*[]InstanceInstanceInfo, bool)` + +GetInstancesOk returns a tuple with the Instances field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetInstances + +`func (o *InstanceAPIClusterInfoOutput) SetInstances(v []InstanceInstanceInfo)` + +SetInstances sets Instances field to given value. + + +### SetInstancesNil + +`func (o *InstanceAPIClusterInfoOutput) SetInstancesNil(b bool)` + + SetInstancesNil sets the value for Instances to be an explicit nil + +### UnsetInstances +`func (o *InstanceAPIClusterInfoOutput) UnsetInstances()` + +UnsetInstances ensures that no value is present for Instances, not even an explicit nil + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/api/docs/InstanceAPIInstanceInfo.md b/api/docs/InstanceAPIInstanceInfo.md index 67b8995de..fab460379 100644 --- a/api/docs/InstanceAPIInstanceInfo.md +++ b/api/docs/InstanceAPIInstanceInfo.md @@ -5,16 +5,16 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **BuildHash** | **string** | | -**CurrentInstanceIP** | **string** | | -**CurrentInstanceIdentifier** | **string** | | **Dirs** | [**ExtconfigExtConfigDirs**](ExtconfigExtConfigDirs.md) | | +**InstanceIP** | **string** | | +**InstanceIdentifier** | **string** | | **Version** | **string** | | ## Methods ### NewInstanceAPIInstanceInfo -`func NewInstanceAPIInstanceInfo(buildHash string, currentInstanceIP string, currentInstanceIdentifier string, dirs ExtconfigExtConfigDirs, version string, ) *InstanceAPIInstanceInfo` +`func NewInstanceAPIInstanceInfo(buildHash string, dirs ExtconfigExtConfigDirs, instanceIP string, instanceIdentifier string, version string, ) *InstanceAPIInstanceInfo` NewInstanceAPIInstanceInfo instantiates a new InstanceAPIInstanceInfo object This constructor will assign default values to properties that have it defined, @@ -49,64 +49,64 @@ and a boolean to check if the value has been set. SetBuildHash sets BuildHash field to given value. -### GetCurrentInstanceIP +### GetDirs -`func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIP() string` +`func (o *InstanceAPIInstanceInfo) GetDirs() ExtconfigExtConfigDirs` -GetCurrentInstanceIP returns the CurrentInstanceIP field if non-nil, zero value otherwise. +GetDirs returns the Dirs field if non-nil, zero value otherwise. -### GetCurrentInstanceIPOk +### GetDirsOk -`func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIPOk() (*string, bool)` +`func (o *InstanceAPIInstanceInfo) GetDirsOk() (*ExtconfigExtConfigDirs, bool)` -GetCurrentInstanceIPOk returns a tuple with the CurrentInstanceIP field if it's non-nil, zero value otherwise +GetDirsOk returns a tuple with the Dirs field if it's non-nil, zero value otherwise and a boolean to check if the value has been set. -### SetCurrentInstanceIP +### SetDirs -`func (o *InstanceAPIInstanceInfo) SetCurrentInstanceIP(v string)` +`func (o *InstanceAPIInstanceInfo) SetDirs(v ExtconfigExtConfigDirs)` -SetCurrentInstanceIP sets CurrentInstanceIP field to given value. +SetDirs sets Dirs field to given value. -### GetCurrentInstanceIdentifier +### GetInstanceIP -`func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIdentifier() string` +`func (o *InstanceAPIInstanceInfo) GetInstanceIP() string` -GetCurrentInstanceIdentifier returns the CurrentInstanceIdentifier field if non-nil, zero value otherwise. +GetInstanceIP returns the InstanceIP field if non-nil, zero value otherwise. -### GetCurrentInstanceIdentifierOk +### GetInstanceIPOk -`func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIdentifierOk() (*string, bool)` +`func (o *InstanceAPIInstanceInfo) GetInstanceIPOk() (*string, bool)` -GetCurrentInstanceIdentifierOk returns a tuple with the CurrentInstanceIdentifier field if it's non-nil, zero value otherwise +GetInstanceIPOk returns a tuple with the InstanceIP field if it's non-nil, zero value otherwise and a boolean to check if the value has been set. -### SetCurrentInstanceIdentifier +### SetInstanceIP -`func (o *InstanceAPIInstanceInfo) SetCurrentInstanceIdentifier(v string)` +`func (o *InstanceAPIInstanceInfo) SetInstanceIP(v string)` -SetCurrentInstanceIdentifier sets CurrentInstanceIdentifier field to given value. +SetInstanceIP sets InstanceIP field to given value. -### GetDirs +### GetInstanceIdentifier -`func (o *InstanceAPIInstanceInfo) GetDirs() ExtconfigExtConfigDirs` +`func (o *InstanceAPIInstanceInfo) GetInstanceIdentifier() string` -GetDirs returns the Dirs field if non-nil, zero value otherwise. +GetInstanceIdentifier returns the InstanceIdentifier field if non-nil, zero value otherwise. -### GetDirsOk +### GetInstanceIdentifierOk -`func (o *InstanceAPIInstanceInfo) GetDirsOk() (*ExtconfigExtConfigDirs, bool)` +`func (o *InstanceAPIInstanceInfo) GetInstanceIdentifierOk() (*string, bool)` -GetDirsOk returns a tuple with the Dirs field if it's non-nil, zero value otherwise +GetInstanceIdentifierOk returns a tuple with the InstanceIdentifier field if it's non-nil, zero value otherwise and a boolean to check if the value has been set. -### SetDirs +### SetInstanceIdentifier -`func (o *InstanceAPIInstanceInfo) SetDirs(v ExtconfigExtConfigDirs)` +`func (o *InstanceAPIInstanceInfo) SetInstanceIdentifier(v string)` -SetDirs sets Dirs field to given value. +SetInstanceIdentifier sets InstanceIdentifier field to given value. ### GetVersion diff --git a/api/model_instance_api_cluster_info_output.go b/api/model_instance_api_cluster_info_output.go new file mode 100644 index 000000000..1d8b5f5d4 --- /dev/null +++ b/api/model_instance_api_cluster_info_output.go @@ -0,0 +1,173 @@ +/* +gravity + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +API version: 0.16.0 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package api + +import ( + "encoding/json" +) + +// checks if the InstanceAPIClusterInfoOutput type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &InstanceAPIClusterInfoOutput{} + +// InstanceAPIClusterInfoOutput struct for InstanceAPIClusterInfoOutput +type InstanceAPIClusterInfoOutput struct { + ClusterVersion string `json:"clusterVersion"` + ClusterVersionShort string `json:"clusterVersionShort"` + Instances []InstanceInstanceInfo `json:"instances"` +} + +// NewInstanceAPIClusterInfoOutput instantiates a new InstanceAPIClusterInfoOutput object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewInstanceAPIClusterInfoOutput(clusterVersion string, clusterVersionShort string, instances []InstanceInstanceInfo) *InstanceAPIClusterInfoOutput { + this := InstanceAPIClusterInfoOutput{} + this.ClusterVersion = clusterVersion + this.ClusterVersionShort = clusterVersionShort + this.Instances = instances + return &this +} + +// NewInstanceAPIClusterInfoOutputWithDefaults instantiates a new InstanceAPIClusterInfoOutput object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewInstanceAPIClusterInfoOutputWithDefaults() *InstanceAPIClusterInfoOutput { + this := InstanceAPIClusterInfoOutput{} + return &this +} + +// GetClusterVersion returns the ClusterVersion field value +func (o *InstanceAPIClusterInfoOutput) GetClusterVersion() string { + if o == nil { + var ret string + return ret + } + + return o.ClusterVersion +} + +// GetClusterVersionOk returns a tuple with the ClusterVersion field value +// and a boolean to check if the value has been set. +func (o *InstanceAPIClusterInfoOutput) GetClusterVersionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.ClusterVersion, true +} + +// SetClusterVersion sets field value +func (o *InstanceAPIClusterInfoOutput) SetClusterVersion(v string) { + o.ClusterVersion = v +} + +// GetClusterVersionShort returns the ClusterVersionShort field value +func (o *InstanceAPIClusterInfoOutput) GetClusterVersionShort() string { + if o == nil { + var ret string + return ret + } + + return o.ClusterVersionShort +} + +// GetClusterVersionShortOk returns a tuple with the ClusterVersionShort field value +// and a boolean to check if the value has been set. +func (o *InstanceAPIClusterInfoOutput) GetClusterVersionShortOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.ClusterVersionShort, true +} + +// SetClusterVersionShort sets field value +func (o *InstanceAPIClusterInfoOutput) SetClusterVersionShort(v string) { + o.ClusterVersionShort = v +} + +// GetInstances returns the Instances field value +// If the value is explicit nil, the zero value for []InstanceInstanceInfo will be returned +func (o *InstanceAPIClusterInfoOutput) GetInstances() []InstanceInstanceInfo { + if o == nil { + var ret []InstanceInstanceInfo + return ret + } + + return o.Instances +} + +// GetInstancesOk returns a tuple with the Instances field value +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *InstanceAPIClusterInfoOutput) GetInstancesOk() ([]InstanceInstanceInfo, bool) { + if o == nil || IsNil(o.Instances) { + return nil, false + } + return o.Instances, true +} + +// SetInstances sets field value +func (o *InstanceAPIClusterInfoOutput) SetInstances(v []InstanceInstanceInfo) { + o.Instances = v +} + +func (o InstanceAPIClusterInfoOutput) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o InstanceAPIClusterInfoOutput) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["clusterVersion"] = o.ClusterVersion + toSerialize["clusterVersionShort"] = o.ClusterVersionShort + if o.Instances != nil { + toSerialize["instances"] = o.Instances + } + return toSerialize, nil +} + +type NullableInstanceAPIClusterInfoOutput struct { + value *InstanceAPIClusterInfoOutput + isSet bool +} + +func (v NullableInstanceAPIClusterInfoOutput) Get() *InstanceAPIClusterInfoOutput { + return v.value +} + +func (v *NullableInstanceAPIClusterInfoOutput) Set(val *InstanceAPIClusterInfoOutput) { + v.value = val + v.isSet = true +} + +func (v NullableInstanceAPIClusterInfoOutput) IsSet() bool { + return v.isSet +} + +func (v *NullableInstanceAPIClusterInfoOutput) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableInstanceAPIClusterInfoOutput(val *InstanceAPIClusterInfoOutput) *NullableInstanceAPIClusterInfoOutput { + return &NullableInstanceAPIClusterInfoOutput{value: val, isSet: true} +} + +func (v NullableInstanceAPIClusterInfoOutput) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableInstanceAPIClusterInfoOutput) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/api/model_instance_api_instance_info.go b/api/model_instance_api_instance_info.go index 11964323e..212bc8b6c 100644 --- a/api/model_instance_api_instance_info.go +++ b/api/model_instance_api_instance_info.go @@ -19,23 +19,23 @@ var _ MappedNullable = &InstanceAPIInstanceInfo{} // InstanceAPIInstanceInfo struct for InstanceAPIInstanceInfo type InstanceAPIInstanceInfo struct { - BuildHash string `json:"buildHash"` - CurrentInstanceIP string `json:"currentInstanceIP"` - CurrentInstanceIdentifier string `json:"currentInstanceIdentifier"` - Dirs ExtconfigExtConfigDirs `json:"dirs"` - Version string `json:"version"` + BuildHash string `json:"buildHash"` + Dirs ExtconfigExtConfigDirs `json:"dirs"` + InstanceIP string `json:"instanceIP"` + InstanceIdentifier string `json:"instanceIdentifier"` + Version string `json:"version"` } // NewInstanceAPIInstanceInfo instantiates a new InstanceAPIInstanceInfo object // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewInstanceAPIInstanceInfo(buildHash string, currentInstanceIP string, currentInstanceIdentifier string, dirs ExtconfigExtConfigDirs, version string) *InstanceAPIInstanceInfo { +func NewInstanceAPIInstanceInfo(buildHash string, dirs ExtconfigExtConfigDirs, instanceIP string, instanceIdentifier string, version string) *InstanceAPIInstanceInfo { this := InstanceAPIInstanceInfo{} this.BuildHash = buildHash - this.CurrentInstanceIP = currentInstanceIP - this.CurrentInstanceIdentifier = currentInstanceIdentifier this.Dirs = dirs + this.InstanceIP = instanceIP + this.InstanceIdentifier = instanceIdentifier this.Version = version return &this } @@ -72,76 +72,76 @@ func (o *InstanceAPIInstanceInfo) SetBuildHash(v string) { o.BuildHash = v } -// GetCurrentInstanceIP returns the CurrentInstanceIP field value -func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIP() string { +// GetDirs returns the Dirs field value +func (o *InstanceAPIInstanceInfo) GetDirs() ExtconfigExtConfigDirs { if o == nil { - var ret string + var ret ExtconfigExtConfigDirs return ret } - return o.CurrentInstanceIP + return o.Dirs } -// GetCurrentInstanceIPOk returns a tuple with the CurrentInstanceIP field value +// GetDirsOk returns a tuple with the Dirs field value // and a boolean to check if the value has been set. -func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIPOk() (*string, bool) { +func (o *InstanceAPIInstanceInfo) GetDirsOk() (*ExtconfigExtConfigDirs, bool) { if o == nil { return nil, false } - return &o.CurrentInstanceIP, true + return &o.Dirs, true } -// SetCurrentInstanceIP sets field value -func (o *InstanceAPIInstanceInfo) SetCurrentInstanceIP(v string) { - o.CurrentInstanceIP = v +// SetDirs sets field value +func (o *InstanceAPIInstanceInfo) SetDirs(v ExtconfigExtConfigDirs) { + o.Dirs = v } -// GetCurrentInstanceIdentifier returns the CurrentInstanceIdentifier field value -func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIdentifier() string { +// GetInstanceIP returns the InstanceIP field value +func (o *InstanceAPIInstanceInfo) GetInstanceIP() string { if o == nil { var ret string return ret } - return o.CurrentInstanceIdentifier + return o.InstanceIP } -// GetCurrentInstanceIdentifierOk returns a tuple with the CurrentInstanceIdentifier field value +// GetInstanceIPOk returns a tuple with the InstanceIP field value // and a boolean to check if the value has been set. -func (o *InstanceAPIInstanceInfo) GetCurrentInstanceIdentifierOk() (*string, bool) { +func (o *InstanceAPIInstanceInfo) GetInstanceIPOk() (*string, bool) { if o == nil { return nil, false } - return &o.CurrentInstanceIdentifier, true + return &o.InstanceIP, true } -// SetCurrentInstanceIdentifier sets field value -func (o *InstanceAPIInstanceInfo) SetCurrentInstanceIdentifier(v string) { - o.CurrentInstanceIdentifier = v +// SetInstanceIP sets field value +func (o *InstanceAPIInstanceInfo) SetInstanceIP(v string) { + o.InstanceIP = v } -// GetDirs returns the Dirs field value -func (o *InstanceAPIInstanceInfo) GetDirs() ExtconfigExtConfigDirs { +// GetInstanceIdentifier returns the InstanceIdentifier field value +func (o *InstanceAPIInstanceInfo) GetInstanceIdentifier() string { if o == nil { - var ret ExtconfigExtConfigDirs + var ret string return ret } - return o.Dirs + return o.InstanceIdentifier } -// GetDirsOk returns a tuple with the Dirs field value +// GetInstanceIdentifierOk returns a tuple with the InstanceIdentifier field value // and a boolean to check if the value has been set. -func (o *InstanceAPIInstanceInfo) GetDirsOk() (*ExtconfigExtConfigDirs, bool) { +func (o *InstanceAPIInstanceInfo) GetInstanceIdentifierOk() (*string, bool) { if o == nil { return nil, false } - return &o.Dirs, true + return &o.InstanceIdentifier, true } -// SetDirs sets field value -func (o *InstanceAPIInstanceInfo) SetDirs(v ExtconfigExtConfigDirs) { - o.Dirs = v +// SetInstanceIdentifier sets field value +func (o *InstanceAPIInstanceInfo) SetInstanceIdentifier(v string) { + o.InstanceIdentifier = v } // GetVersion returns the Version field value @@ -179,9 +179,9 @@ func (o InstanceAPIInstanceInfo) MarshalJSON() ([]byte, error) { func (o InstanceAPIInstanceInfo) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} toSerialize["buildHash"] = o.BuildHash - toSerialize["currentInstanceIP"] = o.CurrentInstanceIP - toSerialize["currentInstanceIdentifier"] = o.CurrentInstanceIdentifier toSerialize["dirs"] = o.Dirs + toSerialize["instanceIP"] = o.InstanceIP + toSerialize["instanceIdentifier"] = o.InstanceIdentifier toSerialize["version"] = o.Version return toSerialize, nil } diff --git a/api/test/api_cluster_test.go b/api/test/api_cluster_test.go new file mode 100644 index 000000000..7b1d21ed9 --- /dev/null +++ b/api/test/api_cluster_test.go @@ -0,0 +1,35 @@ +/* +gravity + +Testing ClusterApiService + +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); + +package api + +import ( + "context" + "testing" + + openapiclient "beryju.io/gravity/api" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_api_ClusterApiService(t *testing.T) { + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + + t.Run("Test ClusterApiService ClusterGetClusterInfo", func(t *testing.T) { + t.Skip("skip test") // remove to run test + + resp, httpRes, err := apiClient.ClusterApi.ClusterGetClusterInfo(context.Background()).Execute() + + require.Nil(t, err) + require.NotNil(t, resp) + assert.Equal(t, 200, httpRes.StatusCode) + }) +} diff --git a/cmd/cli/cli_health.go b/cmd/cli/cli_health.go index 12595e6b5..7a719b10d 100644 --- a/cmd/cli/cli_health.go +++ b/cmd/cli/cli_health.go @@ -10,7 +10,7 @@ var healthCmd = &cobra.Command{ Use: "health", Short: "Check health and version", Run: func(cmd *cobra.Command, args []string) { - v, hr, err := apiClient.ClusterInstancesApi.ClusterGetInfo(cmd.Context()).Execute() + v, hr, err := apiClient.ClusterInstancesApi.ClusterGetInstanceInfo(cmd.Context()).Execute() if err != nil { checkApiError(hr, err) os.Exit(1) From 412313a60ad2ec4c6d7889e6bdedef781f94e881 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 25 Nov 2024 04:15:44 +0100 Subject: [PATCH 17/17] give highest priority to clientIP address --- pkg/roles/dhcp/scope_selector.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pkg/roles/dhcp/scope_selector.go b/pkg/roles/dhcp/scope_selector.go index 367ceb7f7..9e49d93b7 100644 --- a/pkg/roles/dhcp/scope_selector.go +++ b/pkg/roles/dhcp/scope_selector.go @@ -6,9 +6,7 @@ import ( "go.uber.org/zap" ) -type scopeSelector func(scope *Scope) int - -func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSelector) *Scope { +func (r *Role) findScopeForRequest(req *Request4) *Scope { var match *Scope longestBits := 0 r.scopesM.RLock() @@ -16,14 +14,14 @@ func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSe // To prioritise requests from a DHCP relay being matched correctly, give their subnet // match a 1 bit more priority const dhcpRelayBias = 1 + const clientIPBias = 2 for _, scope := range r.scopes { - // Check additional selectors (highest priority) - for _, sel := range additionalSelectors { - m := sel(scope) - if m > -1 && m > longestBits { - match = scope - longestBits = m - } + // Check based on Client IP Address (highest priority) + clientIPMatchBits := scope.match(req.ClientIPAddr) + if clientIPMatchBits > -1 && clientIPMatchBits+clientIPBias > longestBits { + req.log.Debug("selected scope based on client IP", zap.String("scope", scope.Name)) + match = scope + longestBits = clientIPMatchBits + clientIPBias } // Check based on gateway IP (next highest priority) gatewayMatchBits := scope.match(req.GatewayIPAddr)