mirror of
https://github.com/siderolabs/discovery-service.git
synced 2025-03-14 09:55:08 +00:00
feat: implement new discovery service
This includes new in-memory core, new gRPC API, tests, etc. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:22:39Z by kres 907039b.
|
||||
# Generated on 2021-09-22T21:32:59Z by kres 2a27963-dirty.
|
||||
|
||||
codecov:
|
||||
require_ci_to_pass: false
|
||||
@ -9,7 +9,7 @@ coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 50%
|
||||
target: 15%
|
||||
threshold: 0.5%
|
||||
base: auto
|
||||
if_ci_failed: success
|
||||
|
@ -1,12 +1,16 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:22:39Z by kres 907039b.
|
||||
# Generated on 2021-09-21T18:51:27Z by kres 7412de4-dirty.
|
||||
|
||||
---
|
||||
policies:
|
||||
- type: commit
|
||||
spec:
|
||||
dco: true
|
||||
gpg: false
|
||||
gpg:
|
||||
required: true
|
||||
identity:
|
||||
gitHubOrganization: talos-systems
|
||||
spellcheck:
|
||||
locale: US
|
||||
maximumOfOneCommit: true
|
||||
|
@ -1,11 +1,12 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:32:42Z by kres 907039b.
|
||||
# Generated on 2021-09-22T18:49:05Z by kres 2a27963-dirty.
|
||||
|
||||
**
|
||||
!api
|
||||
!cmd
|
||||
!internal
|
||||
!pkg
|
||||
!cmd
|
||||
!go.mod
|
||||
!go.sum
|
||||
!.golangci.yml
|
||||
|
30
.drone.yml
30
.drone.yml
@ -1,7 +1,7 @@
|
||||
---
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:23:41Z by kres 907039b.
|
||||
# Generated on 2021-09-21T13:33:04Z by kres 7412de4-dirty.
|
||||
|
||||
kind: pipeline
|
||||
type: kubernetes
|
||||
@ -105,11 +105,11 @@ steps:
|
||||
depends_on:
|
||||
- unit-tests
|
||||
|
||||
- name: kubespan-manager
|
||||
- name: discovery-service
|
||||
pull: always
|
||||
image: autonomy/build-container:latest
|
||||
commands:
|
||||
- make kubespan-manager
|
||||
- make discovery-service
|
||||
volumes:
|
||||
- name: outer-docker-socket
|
||||
path: /var/outer-run
|
||||
@ -139,11 +139,11 @@ steps:
|
||||
depends_on:
|
||||
- base
|
||||
|
||||
- name: image-kubespan-manager
|
||||
- name: image-discovery-service
|
||||
pull: always
|
||||
image: autonomy/build-container:latest
|
||||
commands:
|
||||
- make image-kubespan-manager
|
||||
- make image-discovery-service
|
||||
volumes:
|
||||
- name: outer-docker-socket
|
||||
path: /var/outer-run
|
||||
@ -154,16 +154,16 @@ steps:
|
||||
- name: ssh
|
||||
path: /root/.ssh
|
||||
depends_on:
|
||||
- kubespan-manager
|
||||
- discovery-service
|
||||
- lint
|
||||
- unit-tests
|
||||
|
||||
- name: push-kubespan-manager
|
||||
- name: push-discovery-service
|
||||
pull: always
|
||||
image: autonomy/build-container:latest
|
||||
commands:
|
||||
- docker login ghcr.io --username "$${GHCR_USERNAME}" --password "$${GHCR_PASSWORD}"
|
||||
- make image-kubespan-manager
|
||||
- make image-discovery-service
|
||||
environment:
|
||||
GHCR_PASSWORD:
|
||||
from_secret: ghcr_token
|
||||
@ -184,14 +184,14 @@ steps:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- image-kubespan-manager
|
||||
- image-discovery-service
|
||||
|
||||
- name: push-kubespan-manager-latest
|
||||
- name: push-discovery-service-latest
|
||||
pull: always
|
||||
image: autonomy/build-container:latest
|
||||
commands:
|
||||
- docker login ghcr.io --username "$${GHCR_USERNAME}" --password "$${GHCR_PASSWORD}"
|
||||
- make image-kubespan-manager TAG=latest
|
||||
- make image-discovery-service TAG=latest
|
||||
environment:
|
||||
GHCR_PASSWORD:
|
||||
from_secret: ghcr_token
|
||||
@ -214,7 +214,7 @@ steps:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- push-kubespan-manager
|
||||
- push-discovery-service
|
||||
|
||||
- name: release-notes
|
||||
pull: always
|
||||
@ -236,8 +236,8 @@ steps:
|
||||
depends_on:
|
||||
- unit-tests
|
||||
- coverage
|
||||
- kubespan-manager
|
||||
- image-kubespan-manager
|
||||
- discovery-service
|
||||
- image-discovery-service
|
||||
- lint
|
||||
|
||||
- name: release
|
||||
@ -270,7 +270,7 @@ steps:
|
||||
|
||||
services:
|
||||
- name: docker
|
||||
image: docker:19.03-dind
|
||||
image: docker:20.10-dind
|
||||
entrypoint:
|
||||
- dockerd
|
||||
commands:
|
||||
|
@ -1,7 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:32:42Z by kres 907039b.
|
||||
|
||||
# Generated on 2021-09-21T13:11:46Z by kres 7412de4-dirty.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
@ -22,7 +21,6 @@ output:
|
||||
uniq-by-line: true
|
||||
path-prefix: ""
|
||||
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
dogsled:
|
||||
@ -38,7 +36,7 @@ linters-settings:
|
||||
lines: 60
|
||||
statements: 40
|
||||
gci:
|
||||
local-prefixes: github.com/talos-systems/kubespan-manager
|
||||
local-prefixes: github.com/talos-systems/discovery-service
|
||||
gocognit:
|
||||
min-complexity: 30
|
||||
nestif:
|
||||
@ -60,7 +58,7 @@ linters-settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
goimports:
|
||||
local-prefixes: github.com/talos-systems/kubespan-manager
|
||||
local-prefixes: github.com/talos-systems/discovery-service
|
||||
golint:
|
||||
min-confidence: 0.8
|
||||
gomnd:
|
||||
@ -116,23 +114,30 @@ linters-settings:
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- gas
|
||||
- typecheck
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- funlen
|
||||
- godox
|
||||
- gomnd
|
||||
- goerr113
|
||||
- nestif
|
||||
- wrapcheck
|
||||
- paralleltest
|
||||
- exhaustivestruct
|
||||
- forbidigo
|
||||
disable-all: false
|
||||
fast: false
|
||||
|
||||
disable:
|
||||
- exhaustivestruct
|
||||
- forbidigo
|
||||
- funlen
|
||||
- gas
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- godox
|
||||
- goerr113
|
||||
- gomnd
|
||||
- gomoddirectives
|
||||
- nestif
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- thelper
|
||||
- typecheck
|
||||
- wrapcheck
|
||||
# abandoned linters for which golangci shows the warning that the repo is archived by the owner
|
||||
- interfacer
|
||||
- maligned
|
||||
- golint
|
||||
- scopelint
|
||||
|
||||
issues:
|
||||
exclude: []
|
||||
@ -141,10 +146,8 @@ issues:
|
||||
exclude-case-sensitive: false
|
||||
max-issues-per-linter: 10
|
||||
max-same-issues: 3
|
||||
|
||||
new: false
|
||||
|
||||
severity:
|
||||
default-severity: error
|
||||
|
||||
case-sensitive: false
|
||||
|
14
.kres.yaml
Normal file
14
.kres.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
kind: golang.Protobuf
|
||||
spec:
|
||||
vtProtobufEnabled: true
|
||||
specs:
|
||||
- source: https://raw.githubusercontent.com/protocolbuffers/protobuf/master/src/google/protobuf/duration.proto
|
||||
subdirectory: vendor/google
|
||||
skipCompile: true
|
||||
- source: api/v1alpha1/cluster.proto
|
||||
subdirectory: v1alpha1/pb
|
||||
---
|
||||
kind: service.CodeCov
|
||||
spec:
|
||||
targetThreshold: 15
|
67
Dockerfile
67
Dockerfile
@ -2,13 +2,10 @@
|
||||
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:32:42Z by kres 907039b.
|
||||
# Generated on 2021-09-22T18:49:05Z by kres 2a27963-dirty.
|
||||
|
||||
ARG TOOLCHAIN
|
||||
|
||||
# cleaned up specs and compiled versions
|
||||
FROM scratch AS generate
|
||||
|
||||
FROM ghcr.io/talos-systems/ca-certificates:v0.3.0-12-g90722c3 AS image-ca-certificates
|
||||
|
||||
FROM ghcr.io/talos-systems/fhs:v0.3.0-12-g90722c3 AS image-fhs
|
||||
@ -22,6 +19,11 @@ COPY .markdownlint.json .
|
||||
COPY ./README.md ./README.md
|
||||
RUN markdownlint --ignore "CHANGELOG.md" --ignore "**/node_modules/**" --ignore '**/hack/chglog/**' --rules /node_modules/sentences-per-line/index.js .
|
||||
|
||||
# collects proto specs
|
||||
FROM scratch AS proto-specs
|
||||
ADD https://raw.githubusercontent.com/protocolbuffers/protobuf/master/src/google/protobuf/duration.proto /api/vendor/google/
|
||||
ADD api/v1alpha1/cluster.proto /api/v1alpha1/pb/
|
||||
|
||||
# base toolchain image
|
||||
FROM ${TOOLCHAIN} AS toolchain
|
||||
RUN apk --update --no-cache add bash curl build-base protoc protobuf-dev
|
||||
@ -31,12 +33,22 @@ FROM toolchain AS tools
|
||||
ENV GO111MODULE on
|
||||
ENV CGO_ENABLED 0
|
||||
ENV GOPATH /go
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b /bin v1.38.0
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b /bin v1.42.1
|
||||
ARG GOFUMPT_VERSION
|
||||
RUN cd $(mktemp -d) \
|
||||
&& go mod init tmp \
|
||||
&& go get mvdan.cc/gofumpt/gofumports@${GOFUMPT_VERSION} \
|
||||
RUN go install mvdan.cc/gofumpt/gofumports@${GOFUMPT_VERSION} \
|
||||
&& mv /go/bin/gofumports /bin/gofumports
|
||||
ARG PROTOBUF_GO_VERSION
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOBUF_GO_VERSION}
|
||||
RUN mv /go/bin/protoc-gen-go /bin
|
||||
ARG GRPC_GO_VERSION
|
||||
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${GRPC_GO_VERSION}
|
||||
RUN mv /go/bin/protoc-gen-go-grpc /bin
|
||||
ARG GRPC_GATEWAY_VERSION
|
||||
RUN go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION}
|
||||
RUN mv /go/bin/protoc-gen-grpc-gateway /bin
|
||||
ARG VTPROTOBUF_VERSION
|
||||
RUN go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto@${VTPROTOBUF_VERSION}
|
||||
RUN mv /go/bin/protoc-gen-go-vtproto /bin
|
||||
|
||||
# tools and sources
|
||||
FROM tools AS base
|
||||
@ -45,22 +57,23 @@ COPY ./go.mod .
|
||||
COPY ./go.sum .
|
||||
RUN --mount=type=cache,target=/go/pkg go mod download
|
||||
RUN --mount=type=cache,target=/go/pkg go mod verify
|
||||
COPY ./api ./api
|
||||
COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY ./pkg ./pkg
|
||||
COPY ./cmd ./cmd
|
||||
RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null
|
||||
|
||||
# builds kubespan-manager-linux-amd64
|
||||
FROM base AS kubespan-manager-linux-amd64-build
|
||||
COPY --from=generate / /
|
||||
WORKDIR /src/cmd/kubespan-manager
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go build -ldflags "-s -w" -o /kubespan-manager-linux-amd64
|
||||
# runs protobuf compiler
|
||||
FROM tools AS proto-compile
|
||||
COPY --from=proto-specs / /
|
||||
RUN protoc -I/api --go_out=paths=source_relative:/api --go-grpc_out=paths=source_relative:/api --go-vtproto_out=paths=source_relative:/api --go-vtproto_opt=features=marshal+unmarshal+size /api/v1alpha1/pb/cluster.proto
|
||||
RUN rm /api/v1alpha1/pb/cluster.proto
|
||||
|
||||
# runs gofumpt
|
||||
FROM base AS lint-gofumpt
|
||||
RUN find . -name '*.pb.go' | xargs -r rm
|
||||
RUN find . -name '*.pb.gw.go' | xargs -r rm
|
||||
RUN FILES="$(gofumports -l -local github.com/talos-systems/kubespan-manager .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumports -w -local github.com/talos-systems/kubespan-manager .':\n${FILES}"; exit 1)
|
||||
RUN FILES="$(gofumports -l -local github.com/talos-systems/discovery-service .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumports -w -local github.com/talos-systems/discovery-service .':\n${FILES}"; exit 1)
|
||||
|
||||
# runs golangci-lint
|
||||
FROM base AS lint-golangci-lint
|
||||
@ -78,19 +91,29 @@ FROM base AS unit-tests-run
|
||||
ARG TESTPKGS
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp go test -v -covermode=atomic -coverprofile=coverage.txt -coverpkg=${TESTPKGS} -count 1 ${TESTPKGS}
|
||||
|
||||
FROM scratch AS kubespan-manager-linux-amd64
|
||||
COPY --from=kubespan-manager-linux-amd64-build /kubespan-manager-linux-amd64 /kubespan-manager-linux-amd64
|
||||
# cleaned up specs and compiled versions
|
||||
FROM scratch AS generate
|
||||
COPY --from=proto-compile /api/ /api/
|
||||
|
||||
FROM scratch AS unit-tests
|
||||
COPY --from=unit-tests-run /src/coverage.txt /coverage.txt
|
||||
|
||||
FROM kubespan-manager-linux-${TARGETARCH} AS kubespan-manager
|
||||
# builds discovery-service-linux-amd64
|
||||
FROM base AS discovery-service-linux-amd64-build
|
||||
COPY --from=generate / /
|
||||
WORKDIR /src/cmd/discovery-service
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go build -ldflags "-s -w" -o /discovery-service-linux-amd64
|
||||
|
||||
FROM scratch AS image-kubespan-manager
|
||||
FROM scratch AS discovery-service-linux-amd64
|
||||
COPY --from=discovery-service-linux-amd64-build /discovery-service-linux-amd64 /discovery-service-linux-amd64
|
||||
|
||||
FROM discovery-service-linux-${TARGETARCH} AS discovery-service
|
||||
|
||||
FROM scratch AS image-discovery-service
|
||||
ARG TARGETARCH
|
||||
COPY --from=kubespan-manager kubespan-manager-linux-${TARGETARCH} /kubespan-manager
|
||||
COPY --from=discovery-service discovery-service-linux-${TARGETARCH} /discovery-service
|
||||
COPY --from=image-fhs / /
|
||||
COPY --from=image-ca-certificates / /
|
||||
LABEL org.opencontainers.image.source https://github.com/talos-systems/wglan-manager
|
||||
ENTRYPOINT ["/kubespan-manager"]
|
||||
LABEL org.opencontainers.image.source https://github.com/talos-systems/discovery-service
|
||||
ENTRYPOINT ["/discovery-service"]
|
||||
|
||||
|
39
Makefile
39
Makefile
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:32:42Z by kres 907039b.
|
||||
# Generated on 2021-09-22T14:57:10Z by kres 2a27963-dirty.
|
||||
|
||||
# common variables
|
||||
|
||||
@ -12,10 +12,11 @@ REGISTRY ?= ghcr.io
|
||||
USERNAME ?= talos-systems
|
||||
REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME)
|
||||
GOFUMPT_VERSION ?= abc0db2c416aca0f60ea33c23c76665f6e7ba0b6
|
||||
GO_VERSION ?= 1.16
|
||||
PROTOBUF_GO_VERSION ?= 1.25.0
|
||||
GO_VERSION ?= 1.17
|
||||
PROTOBUF_GO_VERSION ?= 1.27.1
|
||||
GRPC_GO_VERSION ?= 1.1.0
|
||||
GRPC_GATEWAY_VERSION ?= 2.4.0
|
||||
VTPROTOBUF_VERSION ?= 81d623a9a700ede8ef765e5ab08b3aa1f5b4d5a8
|
||||
TESTPKGS ?= ./...
|
||||
KRES_IMAGE ?= ghcr.io/talos-systems/kres:latest
|
||||
|
||||
@ -39,8 +40,9 @@ COMMON_ARGS += --build-arg=GOFUMPT_VERSION=$(GOFUMPT_VERSION)
|
||||
COMMON_ARGS += --build-arg=PROTOBUF_GO_VERSION=$(PROTOBUF_GO_VERSION)
|
||||
COMMON_ARGS += --build-arg=GRPC_GO_VERSION=$(GRPC_GO_VERSION)
|
||||
COMMON_ARGS += --build-arg=GRPC_GATEWAY_VERSION=$(GRPC_GATEWAY_VERSION)
|
||||
COMMON_ARGS += --build-arg=VTPROTOBUF_VERSION=$(VTPROTOBUF_VERSION)
|
||||
COMMON_ARGS += --build-arg=TESTPKGS=$(TESTPKGS)
|
||||
TOOLCHAIN ?= docker.io/golang:1.16-alpine
|
||||
TOOLCHAIN ?= docker.io/golang:1.17-alpine
|
||||
|
||||
# help menu
|
||||
|
||||
@ -75,7 +77,7 @@ respectively.
|
||||
|
||||
endef
|
||||
|
||||
all: unit-tests kubespan-manager image-kubespan-manager lint
|
||||
all: unit-tests discovery-service image-discovery-service lint
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Cleans up all artifacts.
|
||||
@ -97,8 +99,11 @@ lint-gofumpt: ## Runs gofumpt linter.
|
||||
fmt: ## Formats the source code
|
||||
@docker run --rm -it -v $(PWD):/src -w /src golang:$(GO_VERSION) \
|
||||
bash -c "export GO111MODULE=on; export GOPROXY=https://proxy.golang.org; \
|
||||
cd /tmp && go mod init tmp && go get mvdan.cc/gofumpt/gofumports@$(GOFUMPT_VERSION) && \
|
||||
cd - && gofumports -w -local github.com/talos-systems/kubespan-manager ."
|
||||
go install mvdan.cc/gofumpt/gofumports@$(GOFUMPT_VERSION) && \
|
||||
gofumports -w -local github.com/talos-systems/discovery-service ."
|
||||
|
||||
generate: ## Generate .proto definitions.
|
||||
@$(MAKE) local-$@ DEST=./
|
||||
|
||||
.PHONY: base
|
||||
base: ## Prepare base toolchain
|
||||
@ -116,15 +121,15 @@ unit-tests-race: ## Performs unit tests with race detection enabled.
|
||||
coverage: ## Upload coverage data to codecov.io.
|
||||
bash -c "bash <(curl -s https://codecov.io/bash) -f $(ARTIFACTS)/coverage.txt -X fix"
|
||||
|
||||
.PHONY: $(ARTIFACTS)/kubespan-manager-linux-amd64
|
||||
$(ARTIFACTS)/kubespan-manager-linux-amd64:
|
||||
@$(MAKE) local-kubespan-manager-linux-amd64 DEST=$(ARTIFACTS)
|
||||
.PHONY: $(ARTIFACTS)/discovery-service-linux-amd64
|
||||
$(ARTIFACTS)/discovery-service-linux-amd64:
|
||||
@$(MAKE) local-discovery-service-linux-amd64 DEST=$(ARTIFACTS)
|
||||
|
||||
.PHONY: kubespan-manager-linux-amd64
|
||||
kubespan-manager-linux-amd64: $(ARTIFACTS)/kubespan-manager-linux-amd64 ## Builds executable for kubespan-manager-linux-amd64.
|
||||
.PHONY: discovery-service-linux-amd64
|
||||
discovery-service-linux-amd64: $(ARTIFACTS)/discovery-service-linux-amd64 ## Builds executable for discovery-service-linux-amd64.
|
||||
|
||||
.PHONY: kubespan-manager
|
||||
kubespan-manager: kubespan-manager-linux-amd64
|
||||
.PHONY: discovery-service
|
||||
discovery-service: discovery-service-linux-amd64 ## Builds executables for discovery-service.
|
||||
|
||||
.PHONY: lint-markdown
|
||||
lint-markdown: ## Runs markdownlint.
|
||||
@ -133,9 +138,9 @@ lint-markdown: ## Runs markdownlint.
|
||||
.PHONY: lint
|
||||
lint: lint-golangci-lint lint-gofumpt lint-markdown ## Run all linters for the project.
|
||||
|
||||
.PHONY: image-kubespan-manager
|
||||
image-kubespan-manager: ## Builds image for kubespan-manager.
|
||||
@$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/kubespan-manager:$(TAG)"
|
||||
.PHONY: image-discovery-service
|
||||
image-discovery-service: ## Builds image for discovery-service.
|
||||
@$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/discovery-service:$(TAG)"
|
||||
|
||||
.PHONY: rekres
|
||||
rekres:
|
||||
|
15
README.md
15
README.md
@ -1,3 +1,14 @@
|
||||
# wglan-manager
|
||||
# Talos Discovery Service (for KubeSpan)
|
||||
|
||||
A trivial implementation of a Wireguard LAN manager for Talos.
|
||||
Discovery Service provides cluster membership and KubeSpan peer information for Talos clusters.
|
||||
|
||||
## Overview
|
||||
|
||||
Discovery Service provides centralized service for Talos nodes to exchange information about nodes of the cluster.
|
||||
|
||||
Talos runs "official" instance of the service, and anyone can run their own instance on-prem or in the cloud.
|
||||
|
||||
Discovery service doesn't store any data, all data is ephemeral and is kept only in memory.
|
||||
Node information is expired (if not updated) after 30 minutes.
|
||||
Discovery service doesn't see actual node information, it only stores and updates encrypted blobs.
|
||||
Discovery data should be submitted encrypted by the client, and service doesn't have the encryption key.
|
||||
|
114
api/v1alpha1/cluster.proto
Normal file
114
api/v1alpha1/cluster.proto
Normal file
@ -0,0 +1,114 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package sidero.discovery;
|
||||
|
||||
option go_package = "github.com/talos-systems/api/v1alpha1/pb";
|
||||
|
||||
import "vendor/google/duration.proto";
|
||||
|
||||
service Cluster {
|
||||
// Hello is the first request sent by the client.
|
||||
//
|
||||
// Server might redirect the client to a different instance.
|
||||
rpc Hello(HelloRequest) returns (HelloResponse);
|
||||
// AffiliateUpdate updates (or creates) affiliate in the cluster.
|
||||
rpc AffiliateUpdate(AffiliateUpdateRequest) returns (AffiliateUpdateResponse);
|
||||
// AffiliateDelete deletes affiliate from the cluster.
|
||||
rpc AffiliateDelete(AffiliateDeleteRequest) returns (AffiliateDeleteResponse);
|
||||
// List affiliates in the cluster.
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
// Watch affiliate updates in the cluster.
|
||||
rpc Watch(WatchRequest) returns (stream WatchResponse);
|
||||
}
|
||||
|
||||
// Common definitions.
|
||||
|
||||
message Affiliate {
|
||||
// Affiliate ID.
|
||||
string id = 1;
|
||||
// Affiliate data (encrypted).
|
||||
bytes data = 2;
|
||||
// Affiliate endpoints (encrypted)/
|
||||
repeated bytes endpoints = 3;
|
||||
}
|
||||
|
||||
// Hello RPC
|
||||
|
||||
message HelloRequest {
|
||||
// ClusterID of the client.
|
||||
string cluster_id = 1;
|
||||
// Client version.
|
||||
string client_version = 2;
|
||||
}
|
||||
|
||||
message RedirectMessage {
|
||||
// URL of the service endpoint to connect to.
|
||||
string endpoint_url = 1;
|
||||
}
|
||||
|
||||
message HelloResponse {
|
||||
// If redirect is present, client should immediately reconnect to a different endpoint.
|
||||
optional RedirectMessage redirect = 1;
|
||||
// Client IP as seen by the server.
|
||||
bytes client_ip = 2;
|
||||
}
|
||||
|
||||
// AffiliateUpdate RPC
|
||||
|
||||
message AffiliateUpdateRequest {
|
||||
// Client ClusterID.
|
||||
string cluster_id = 1;
|
||||
// Affiliate ID to update.
|
||||
string affiliate_id = 2;
|
||||
// Encrypted affiliate data.
|
||||
//
|
||||
// If missing, affiliate data is not updated.
|
||||
optional bytes affiliate_data = 3;
|
||||
// Encrypted list of affiliate endpoints.
|
||||
//
|
||||
// Endpoints are merged with the existing list of endpoints.
|
||||
repeated bytes affiliate_endpoints = 4;
|
||||
// TTL for the new data submitted.
|
||||
google.protobuf.Duration ttl = 5;
|
||||
}
|
||||
|
||||
message AffiliateUpdateResponse {
|
||||
}
|
||||
|
||||
// AffiliateDelete RPC
|
||||
|
||||
message AffiliateDeleteRequest {
|
||||
// Client ClusterID.
|
||||
string cluster_id = 1;
|
||||
// Affiliate ID to delete.
|
||||
string affiliate_id = 2;
|
||||
}
|
||||
|
||||
message AffiliateDeleteResponse {
|
||||
}
|
||||
|
||||
// List RPC
|
||||
|
||||
message ListRequest {
|
||||
// Client ClusterID.
|
||||
string cluster_id = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
// List of cluster affiliates.
|
||||
repeated Affiliate affiliates = 1;
|
||||
}
|
||||
|
||||
// Watch RPC
|
||||
|
||||
message WatchRequest {
|
||||
// Client ClusterID.
|
||||
string cluster_id = 1;
|
||||
}
|
||||
|
||||
message WatchResponse {
|
||||
// List of cluster affiliates.
|
||||
Affiliate affiliate = 1;
|
||||
// Flag that affiliate was deleted, only ID field is valid.
|
||||
bool deleted = 2;
|
||||
}
|
1003
api/v1alpha1/pb/cluster.pb.go
Normal file
1003
api/v1alpha1/pb/cluster.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
292
api/v1alpha1/pb/cluster_grpc.pb.go
Normal file
292
api/v1alpha1/pb/cluster_grpc.pb.go
Normal file
@ -0,0 +1,292 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// ClusterClient is the client API for Cluster service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ClusterClient interface {
|
||||
// Hello is the first request sent by the client.
|
||||
//
|
||||
// Server might redirect the client to a different instance.
|
||||
Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
|
||||
// AffiliateUpdate updates (or creates) affiliate in the cluster.
|
||||
AffiliateUpdate(ctx context.Context, in *AffiliateUpdateRequest, opts ...grpc.CallOption) (*AffiliateUpdateResponse, error)
|
||||
// AffiliateDelete deletes affiliate from the cluster.
|
||||
AffiliateDelete(ctx context.Context, in *AffiliateDeleteRequest, opts ...grpc.CallOption) (*AffiliateDeleteResponse, error)
|
||||
// List affiliates in the cluster.
|
||||
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
|
||||
// Watch affiliate updates in the cluster.
|
||||
Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Cluster_WatchClient, error)
|
||||
}
|
||||
|
||||
type clusterClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewClusterClient(cc grpc.ClientConnInterface) ClusterClient {
|
||||
return &clusterClient{cc}
|
||||
}
|
||||
|
||||
func (c *clusterClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
|
||||
out := new(HelloResponse)
|
||||
err := c.cc.Invoke(ctx, "/sidero.discovery.Cluster/Hello", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) AffiliateUpdate(ctx context.Context, in *AffiliateUpdateRequest, opts ...grpc.CallOption) (*AffiliateUpdateResponse, error) {
|
||||
out := new(AffiliateUpdateResponse)
|
||||
err := c.cc.Invoke(ctx, "/sidero.discovery.Cluster/AffiliateUpdate", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) AffiliateDelete(ctx context.Context, in *AffiliateDeleteRequest, opts ...grpc.CallOption) (*AffiliateDeleteResponse, error) {
|
||||
out := new(AffiliateDeleteResponse)
|
||||
err := c.cc.Invoke(ctx, "/sidero.discovery.Cluster/AffiliateDelete", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) {
|
||||
out := new(ListResponse)
|
||||
err := c.cc.Invoke(ctx, "/sidero.discovery.Cluster/List", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Cluster_WatchClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Cluster_ServiceDesc.Streams[0], "/sidero.discovery.Cluster/Watch", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &clusterWatchClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Cluster_WatchClient interface {
|
||||
Recv() (*WatchResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type clusterWatchClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *clusterWatchClient) Recv() (*WatchResponse, error) {
|
||||
m := new(WatchResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ClusterServer is the server API for Cluster service.
|
||||
// All implementations must embed UnimplementedClusterServer
|
||||
// for forward compatibility
|
||||
type ClusterServer interface {
|
||||
// Hello is the first request sent by the client.
|
||||
//
|
||||
// Server might redirect the client to a different instance.
|
||||
Hello(context.Context, *HelloRequest) (*HelloResponse, error)
|
||||
// AffiliateUpdate updates (or creates) affiliate in the cluster.
|
||||
AffiliateUpdate(context.Context, *AffiliateUpdateRequest) (*AffiliateUpdateResponse, error)
|
||||
// AffiliateDelete deletes affiliate from the cluster.
|
||||
AffiliateDelete(context.Context, *AffiliateDeleteRequest) (*AffiliateDeleteResponse, error)
|
||||
// List affiliates in the cluster.
|
||||
List(context.Context, *ListRequest) (*ListResponse, error)
|
||||
// Watch affiliate updates in the cluster.
|
||||
Watch(*WatchRequest, Cluster_WatchServer) error
|
||||
mustEmbedUnimplementedClusterServer()
|
||||
}
|
||||
|
||||
// UnimplementedClusterServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedClusterServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedClusterServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedClusterServer) AffiliateUpdate(context.Context, *AffiliateUpdateRequest) (*AffiliateUpdateResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AffiliateUpdate not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedClusterServer) AffiliateDelete(context.Context, *AffiliateDeleteRequest) (*AffiliateDeleteResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AffiliateDelete not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedClusterServer) List(context.Context, *ListRequest) (*ListResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedClusterServer) Watch(*WatchRequest, Cluster_WatchServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Watch not implemented")
|
||||
}
|
||||
func (UnimplementedClusterServer) mustEmbedUnimplementedClusterServer() {}
|
||||
|
||||
// UnsafeClusterServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ClusterServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeClusterServer interface {
|
||||
mustEmbedUnimplementedClusterServer()
|
||||
}
|
||||
|
||||
func RegisterClusterServer(s grpc.ServiceRegistrar, srv ClusterServer) {
|
||||
s.RegisterService(&Cluster_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Cluster_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(HelloRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClusterServer).Hello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/sidero.discovery.Cluster/Hello",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClusterServer).Hello(ctx, req.(*HelloRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_AffiliateUpdate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AffiliateUpdateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClusterServer).AffiliateUpdate(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/sidero.discovery.Cluster/AffiliateUpdate",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClusterServer).AffiliateUpdate(ctx, req.(*AffiliateUpdateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_AffiliateDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AffiliateDeleteRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClusterServer).AffiliateDelete(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/sidero.discovery.Cluster/AffiliateDelete",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClusterServer).AffiliateDelete(ctx, req.(*AffiliateDeleteRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClusterServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/sidero.discovery.Cluster/List",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClusterServer).List(ctx, req.(*ListRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_Watch_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(WatchRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(ClusterServer).Watch(m, &clusterWatchServer{stream})
|
||||
}
|
||||
|
||||
type Cluster_WatchServer interface {
|
||||
Send(*WatchResponse) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type clusterWatchServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *clusterWatchServer) Send(m *WatchResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
// Cluster_ServiceDesc is the grpc.ServiceDesc for Cluster service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Cluster_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "sidero.discovery.Cluster",
|
||||
HandlerType: (*ClusterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Hello",
|
||||
Handler: _Cluster_Hello_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AffiliateUpdate",
|
||||
Handler: _Cluster_AffiliateUpdate_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AffiliateDelete",
|
||||
Handler: _Cluster_AffiliateDelete_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _Cluster_List_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Watch",
|
||||
Handler: _Cluster_Watch_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "v1alpha1/pb/cluster.proto",
|
||||
}
|
2215
api/v1alpha1/pb/cluster_vtproto.pb.go
Normal file
2215
api/v1alpha1/pb/cluster_vtproto.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
116
api/vendor/google/duration.proto
vendored
Normal file
116
api/vendor/google/duration.proto
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package google.protobuf;
|
||||
|
||||
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||
option cc_enable_arenas = true;
|
||||
option go_package = "google.golang.org/protobuf/types/known/durationpb";
|
||||
option java_package = "com.google.protobuf";
|
||||
option java_outer_classname = "DurationProto";
|
||||
option java_multiple_files = true;
|
||||
option objc_class_prefix = "GPB";
|
||||
|
||||
// A Duration represents a signed, fixed-length span of time represented
|
||||
// as a count of seconds and fractions of seconds at nanosecond
|
||||
// resolution. It is independent of any calendar and concepts like "day"
|
||||
// or "month". It is related to Timestamp in that the difference between
|
||||
// two Timestamp values is a Duration and it can be added or subtracted
|
||||
// from a Timestamp. Range is approximately +-10,000 years.
|
||||
//
|
||||
// # Examples
|
||||
//
|
||||
// Example 1: Compute Duration from two Timestamps in pseudo code.
|
||||
//
|
||||
// Timestamp start = ...;
|
||||
// Timestamp end = ...;
|
||||
// Duration duration = ...;
|
||||
//
|
||||
// duration.seconds = end.seconds - start.seconds;
|
||||
// duration.nanos = end.nanos - start.nanos;
|
||||
//
|
||||
// if (duration.seconds < 0 && duration.nanos > 0) {
|
||||
// duration.seconds += 1;
|
||||
// duration.nanos -= 1000000000;
|
||||
// } else if (duration.seconds > 0 && duration.nanos < 0) {
|
||||
// duration.seconds -= 1;
|
||||
// duration.nanos += 1000000000;
|
||||
// }
|
||||
//
|
||||
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
|
||||
//
|
||||
// Timestamp start = ...;
|
||||
// Duration duration = ...;
|
||||
// Timestamp end = ...;
|
||||
//
|
||||
// end.seconds = start.seconds + duration.seconds;
|
||||
// end.nanos = start.nanos + duration.nanos;
|
||||
//
|
||||
// if (end.nanos < 0) {
|
||||
// end.seconds -= 1;
|
||||
// end.nanos += 1000000000;
|
||||
// } else if (end.nanos >= 1000000000) {
|
||||
// end.seconds += 1;
|
||||
// end.nanos -= 1000000000;
|
||||
// }
|
||||
//
|
||||
// Example 3: Compute Duration from datetime.timedelta in Python.
|
||||
//
|
||||
// td = datetime.timedelta(days=3, minutes=10)
|
||||
// duration = Duration()
|
||||
// duration.FromTimedelta(td)
|
||||
//
|
||||
// # JSON Mapping
|
||||
//
|
||||
// In JSON format, the Duration type is encoded as a string rather than an
|
||||
// object, where the string ends in the suffix "s" (indicating seconds) and
|
||||
// is preceded by the number of seconds, with nanoseconds expressed as
|
||||
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
|
||||
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
|
||||
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
|
||||
// microsecond should be expressed in JSON format as "3.000001s".
|
||||
//
|
||||
//
|
||||
message Duration {
|
||||
// Signed seconds of the span of time. Must be from -315,576,000,000
|
||||
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
|
||||
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
|
||||
int64 seconds = 1;
|
||||
|
||||
// Signed fractions of a second at nanosecond resolution of the span
|
||||
// of time. Durations less than one second are represented with a 0
|
||||
// `seconds` field and a positive or negative `nanos` field. For durations
|
||||
// of one second or more, a non-zero value for the `nanos` field must be
|
||||
// of the same sign as the `seconds` field. Must be from -999,999,999
|
||||
// to +999,999,999 inclusive.
|
||||
int32 nanos = 2;
|
||||
}
|
185
cmd/discovery-service/main.go
Normal file
185
cmd/discovery-service/main.go
Normal file
@ -0,0 +1,185 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
|
||||
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
||||
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/talos-systems/discovery-service/api/v1alpha1/pb"
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
"github.com/talos-systems/discovery-service/pkg/server"
|
||||
)
|
||||
|
||||
var (
|
||||
listenAddr = ":3000"
|
||||
metricsAddr = ":2122"
|
||||
devMode bool
|
||||
gcInterval time.Duration
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&listenAddr, "addr", ":3000", "addr on which to listen")
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":2122", "prometheus metrics listen addr")
|
||||
flag.BoolVar(&devMode, "debug", false, "enable debug mode")
|
||||
flag.DurationVar(&gcInterval, "gc-interval", time.Minute, "garbage collection interval")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
log.Fatalln("failed to initialize logger:", err)
|
||||
}
|
||||
|
||||
if os.Getenv("MODE") == "dev" {
|
||||
devMode = true
|
||||
}
|
||||
|
||||
if devMode {
|
||||
logger, err = zap.NewDevelopment()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln("failed to initialize development logger:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = signalHandler(context.Background(), logger, run); err != nil {
|
||||
logger.Error("service failed", zap.Error(err))
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func signalHandler(ctx context.Context, logger *zap.Logger, f func(ctx context.Context, logger *zap.Logger) error) error {
|
||||
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
return f(ctx, logger)
|
||||
}
|
||||
|
||||
func recoveryHandler(logger *zap.Logger) grpc_recovery.RecoveryHandlerFunc {
|
||||
return func(p interface{}) error {
|
||||
if logger != nil {
|
||||
logger.Error("grpc panic", zap.Any("panic", p), zap.Stack("stack"))
|
||||
}
|
||||
|
||||
return status.Errorf(codes.Internal, "%v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context, logger *zap.Logger) error {
|
||||
logger.Info("service starting")
|
||||
|
||||
defer logger.Info("service shut down")
|
||||
|
||||
// Recovery is installed as the the first middleware in the chain to handle panics (via defer and recover()) in all subsequent middlewares.
|
||||
|
||||
// Logging is installed as the first middleware (even before recovery middleware) in the chain
|
||||
// so that request in the form it was received and status sent on the wire is logged (error/success).
|
||||
// It also tracks the whole duration of the request, including other middleware overhead.
|
||||
grpc_zap.ReplaceGrpcLoggerV2(logger)
|
||||
|
||||
recoveryOpt := grpc_recovery.WithRecoveryHandler(recoveryHandler(logger))
|
||||
|
||||
serverOptions := []grpc.ServerOption{
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_zap.UnaryServerInterceptor(logger),
|
||||
grpc_prometheus.UnaryServerInterceptor,
|
||||
grpc_recovery.UnaryServerInterceptor(recoveryOpt),
|
||||
),
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_zap.StreamServerInterceptor(logger),
|
||||
grpc_prometheus.StreamServerInterceptor,
|
||||
grpc_recovery.StreamServerInterceptor(recoveryOpt),
|
||||
),
|
||||
}
|
||||
|
||||
state := state.NewState()
|
||||
|
||||
lis, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen: %w", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer(serverOptions...)
|
||||
pb.RegisterClusterServer(s, server.NewClusterServer(state))
|
||||
grpc_prometheus.Register(s)
|
||||
|
||||
var metricsMux http.ServeMux
|
||||
|
||||
metricsMux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
metricsServer := http.Server{
|
||||
Addr: metricsAddr,
|
||||
Handler: &metricsMux,
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
logger.Info("gRPC server starting", zap.Stringer("address", lis.Addr()))
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
return fmt.Errorf("failed to serve: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
logger.Info("metrics starting", zap.String("address", metricsServer.Addr))
|
||||
|
||||
if err := metricsServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer shutdownCancel()
|
||||
|
||||
s.GracefulStop()
|
||||
metricsServer.Shutdown(shutdownCtx) //nolint:errcheck
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
state.RunGC(ctx, logger, gcInterval)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/talos-systems/kubespan-manager/internal/db"
|
||||
"github.com/talos-systems/kubespan-manager/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
listenAddr = ":3000"
|
||||
devMode bool
|
||||
nodeDB db.DB
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&listenAddr, "addr", ":3000", "addr on which to listen")
|
||||
flag.BoolVar(&devMode, "debug", false, "enable debug mode")
|
||||
}
|
||||
|
||||
//nolint:gocognit,gocyclo,cyclop
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
log.Fatalln("failed to initialize logger:", err)
|
||||
}
|
||||
|
||||
if os.Getenv("MODE") == "dev" {
|
||||
devMode = true
|
||||
logger, err = zap.NewDevelopment()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln("failed to initialize development logger:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("REDIS_ADDR") != "" {
|
||||
nodeDB, err = db.NewRedis(os.Getenv("REDIS_ADDR"), logger)
|
||||
if err != nil {
|
||||
log.Fatalln("failed to connect to redis: %w", err)
|
||||
}
|
||||
} else {
|
||||
nodeDB = db.New(logger)
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/:cluster", func(c *fiber.Ctx) error {
|
||||
cluster := c.Params("cluster")
|
||||
if cluster == "" {
|
||||
logger.Error("empty cluster for node list")
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if e := validateClusterID(cluster); e != nil {
|
||||
logger.Error("bad cluster ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
list, e := nodeDB.List(c.Context(), cluster)
|
||||
if e != nil {
|
||||
if errors.Is(e, db.ErrNotFound) {
|
||||
logger.Warn("cluster not found",
|
||||
zap.String("cluster", cluster),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusNotFound)
|
||||
}
|
||||
|
||||
return c.SendStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
logger.Info("listing cluster nodes",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Int("count", len(list)),
|
||||
)
|
||||
|
||||
return c.JSON(list)
|
||||
})
|
||||
|
||||
app.Get("/:cluster/:node", func(c *fiber.Ctx) error {
|
||||
cluster := c.Params("cluster", "")
|
||||
if cluster == "" {
|
||||
logger.Error("empty cluster for node get")
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if e := validateClusterID(cluster); e != nil {
|
||||
logger.Error("bad cluster ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if e := validatePublicKey(c.Params("node")); e != nil {
|
||||
logger.Error("bad node ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", c.Params("node", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
node := c.Params("node", "")
|
||||
if node == "" {
|
||||
logger.Error("empty node for node get",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
n, e := nodeDB.Get(c.Context(), cluster, node)
|
||||
if e != nil {
|
||||
if errors.Is(e, db.ErrNotFound) {
|
||||
logger.Warn("node not found",
|
||||
zap.String("cluster", cluster),
|
||||
zap.String("node", node),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusNotFound)
|
||||
}
|
||||
|
||||
return c.SendStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
logger.Info("returning cluster node",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", n.ID),
|
||||
zap.String("ip", n.IP.String()),
|
||||
zap.Strings("addresses", addressToString(n.Addresses)),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.JSON(n)
|
||||
})
|
||||
|
||||
// PUT addresses to a Node
|
||||
app.Put("/:cluster/:node", func(c *fiber.Ctx) error {
|
||||
var addresses []*types.Address
|
||||
|
||||
if e := validateClusterID(c.Params("cluster")); e != nil {
|
||||
logger.Error("bad cluster ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if e := validatePublicKey(c.Params("node")); e != nil {
|
||||
logger.Error("bad node ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", c.Params("node", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if e := c.BodyParser(&addresses); e != nil {
|
||||
logger.Error("failed to parse node PUT",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", c.Params("node", "")),
|
||||
zap.Error(e),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
node := c.Params("node", "")
|
||||
if node == "" {
|
||||
logger.Error("invalid node key",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", c.Params("node", "")),
|
||||
)
|
||||
}
|
||||
|
||||
if err := nodeDB.AddAddresses(c.Context(), c.Params("cluster", ""), node, addresses...); err != nil {
|
||||
logger.Error("failed to add known endpoints",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", node),
|
||||
zap.Strings("addresses", addressToString(addresses)),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c.SendStatus(http.StatusNoContent)
|
||||
})
|
||||
|
||||
app.Post("/:cluster", func(c *fiber.Ctx) error {
|
||||
n := new(types.Node)
|
||||
|
||||
if err := validateClusterID(c.Params("cluster")); err != nil {
|
||||
logger.Error("bad cluster ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := c.BodyParser(n); err != nil {
|
||||
logger.Error("failed to parse node POST",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := validatePublicKey(n.ID); err != nil {
|
||||
logger.Error("bad node ID",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", n.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := nodeDB.Add(c.Context(), c.Params("cluster", ""), n); err != nil {
|
||||
logger.Error("failed to add/update node",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", n.ID),
|
||||
zap.String("ip", n.IP.String()),
|
||||
zap.Strings("addresses", addressToString(n.Addresses)),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
logger.Info("add/update node",
|
||||
zap.String("cluster", c.Params("cluster", "")),
|
||||
zap.String("node", n.ID),
|
||||
zap.String("ip", n.IP.String()),
|
||||
zap.Strings("addresses", addressToString(n.Addresses)),
|
||||
)
|
||||
|
||||
return c.SendStatus(http.StatusNoContent)
|
||||
})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Hour)
|
||||
|
||||
nodeDB.Clean()
|
||||
}
|
||||
}()
|
||||
logger.Fatal("listen exited",
|
||||
zap.Error(app.Listen(listenAddr)),
|
||||
)
|
||||
}
|
||||
|
||||
func addressToString(addresses []*types.Address) (out []string) {
|
||||
for _, a := range addresses {
|
||||
if !a.IP.IsZero() {
|
||||
out = append(out, a.IP.String())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, a.Name)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func validateClusterID(cluster string) error {
|
||||
if _, err := uuid.Parse(cluster); err != nil {
|
||||
return fmt.Errorf("cluster ID is not a valid UUID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePublicKey(key string) error {
|
||||
if _, err := wgtypes.ParseKey(key); err != nil {
|
||||
return fmt.Errorf("node ID is not a valid wireguard key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
go.mod
38
go.mod
@ -1,12 +1,36 @@
|
||||
module github.com/talos-systems/kubespan-manager
|
||||
module github.com/talos-systems/discovery-service
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/go-redis/redis/v8 v8.11.2
|
||||
github.com/gofiber/fiber/v2 v2.8.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||
inet.af/netaddr v0.0.0-20210525141459-c0eff8545de6
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
google.golang.org/grpc v1.40.0
|
||||
google.golang.org/protobuf v1.26.0-rc.1
|
||||
inet.af/netaddr v0.0.0-20210903134321-85fa6c94624e
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
go.uber.org/atomic v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.5.0 // indirect
|
||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
)
|
||||
|
294
go.sum
294
go.sum
@ -1,101 +1,154 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-redis/redis/v8 v8.11.2 h1:WqlSpAwz8mxDSMCvbyz1Mkiqe0LE5OY4j3lgkvu1Ts0=
|
||||
github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
|
||||
github.com/gofiber/fiber/v2 v2.8.0 h1:BdWvZmg/WY/Vjtjm38aXOp1Lks1BhuyS2b7lSWSPAzk=
|
||||
github.com/gofiber/fiber/v2 v2.8.0/go.mod h1:Ah3IJikrKNRepl/HuVawppS25X7FWohwfCSRn7kJG28=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
|
||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
|
||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.23.0 h1:0ufwSD9BhWa6f8HWdmdq4FHQ23peRo3Ng/Qs8m5NcFs=
|
||||
github.com/valyala/fasthttp v1.23.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
||||
@ -103,83 +156,77 @@ go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
|
||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -187,27 +234,50 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b h1:XDLXhn7ryprJVo+Lpkiib6CIuXE2031GDwtfEm7vLjI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
inet.af/netaddr v0.0.0-20210525141459-c0eff8545de6 h1:Bf++HvcnBFYGQjFx1qhW/3uTMQbv+bfKB6gdEN7JvvM=
|
||||
inet.af/netaddr v0.0.0-20210525141459-c0eff8545de6/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netaddr v0.0.0-20210903134321-85fa6c94624e h1:tvgqez5ZQoBBiBAGNU/fmJy247yB/7++kcLOEoMYup0=
|
||||
inet.af/netaddr v0.0.0-20210903134321-85fa6c94624e/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
|
@ -1,26 +0,0 @@
|
||||
<!-- THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. -->
|
||||
<!-- -->
|
||||
<!-- Generated on 2021-08-13T12:22:39Z by kres 907039b. -->
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
@ -1,32 +0,0 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2021-08-13T12:22:39Z by kres 907039b.
|
||||
|
||||
style: github
|
||||
template: CHANGELOG.tpl.md
|
||||
info:
|
||||
title: CHANGELOG
|
||||
repository_url: https://github.com/talos-systems/wglan-manager
|
||||
options:
|
||||
commits:
|
||||
# filters:
|
||||
# Type:
|
||||
# - feat
|
||||
# - fix
|
||||
# - perf
|
||||
# - refactor
|
||||
commit_groups:
|
||||
# title_maps:
|
||||
# feat: Features
|
||||
# fix: Bug Fixes
|
||||
# perf: Performance Improvements
|
||||
# refactor: Code Refactoring
|
||||
header:
|
||||
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
|
||||
pattern_maps:
|
||||
- Type
|
||||
- Scope
|
||||
- Subject
|
||||
notes:
|
||||
keywords:
|
||||
- BREAKING CHANGE
|
@ -6,8 +6,8 @@
|
||||
# commit to be tagged for the new release
|
||||
commit = "HEAD"
|
||||
|
||||
project_name = "wglan-manager"
|
||||
github_repo = "talos-systems/wglan-manager"
|
||||
project_name = "discovery-service"
|
||||
github_repo = "talos-systems/discovery-service"
|
||||
match_deps = "^github.com/(talos-systems/[a-zA-Z0-9-]+)$"
|
||||
|
||||
# previous = -
|
||||
|
@ -1,170 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package db contains state storage logic.
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/talos-systems/kubespan-manager/pkg/types"
|
||||
)
|
||||
|
||||
// ErrNotFound means that record is not found in DB.
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// AddressExpirationTimeout is the amount of time after which addresses of a node should be expired.
|
||||
const AddressExpirationTimeout = 10 * time.Minute
|
||||
|
||||
// DB manager state persistent storage interface.
|
||||
type DB interface {
|
||||
// Add adds a set of known Endpoints to a node, creating the node, if it does not exist.
|
||||
Add(ctx context.Context, cluster string, n *types.Node) error
|
||||
|
||||
// AddAddresses adds a set of addresses for a node.
|
||||
AddAddresses(ctx context.Context, cluster, id string, ep ...*types.Address) error
|
||||
|
||||
// Clean executes a database cleanup routine.
|
||||
Clean()
|
||||
|
||||
// Get returns the details of the node.
|
||||
Get(ctx context.Context, cluster, id string) (*types.Node, error)
|
||||
|
||||
// List returns the set of Nodes for the given Cluster.
|
||||
List(ctx context.Context, cluster string) ([]*types.Node, error)
|
||||
}
|
||||
|
||||
type ramDB struct {
|
||||
logger *zap.Logger
|
||||
db map[string]map[string]*types.Node
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns a new database.
|
||||
func New(logger *zap.Logger) DB {
|
||||
return &ramDB{
|
||||
logger: logger,
|
||||
db: make(map[string]map[string]*types.Node),
|
||||
}
|
||||
}
|
||||
|
||||
// Add implements DB.
|
||||
func (d *ramDB) Add(ctx context.Context, cluster string, n *types.Node) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
c, ok := d.db[cluster]
|
||||
if !ok {
|
||||
c = make(map[string]*types.Node)
|
||||
d.db[cluster] = c
|
||||
}
|
||||
|
||||
if existing, ok := c[n.ID]; ok {
|
||||
existing.AddAddresses(n.Addresses...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
c[n.ID] = n
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ramDB) AddAddresses(ctx context.Context, cluster, id string, addresses ...*types.Address) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
c, ok := d.db[cluster]
|
||||
if !ok {
|
||||
return fmt.Errorf("cluster does not exist")
|
||||
}
|
||||
|
||||
n, ok := c[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("node does not exist")
|
||||
}
|
||||
|
||||
n.AddAddresses(addresses...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List implements DB.
|
||||
func (d *ramDB) List(ctx context.Context, cluster string) (list []*types.Node, err error) {
|
||||
c, ok := d.db[cluster]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cluster %q not found", cluster)
|
||||
}
|
||||
|
||||
for _, n := range c {
|
||||
n.ExpireAddressesOlderThan(AddressExpirationTimeout)
|
||||
|
||||
if len(n.Addresses) > 0 {
|
||||
list = append(list, n)
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Get implements DB.
|
||||
func (d *ramDB) Get(ctx context.Context, cluster, id string) (*types.Node, error) {
|
||||
d.mu.RLock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
c, ok := d.db[cluster]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cluster %q not found", cluster)
|
||||
}
|
||||
|
||||
n, ok := c[id]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Clean runs the database cleanup routine.
|
||||
func (d *ramDB) Clean() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
var clusterDeleteList []string
|
||||
|
||||
for clusterID, c := range d.db {
|
||||
var nodeDeleteList []string
|
||||
|
||||
for id, n := range c {
|
||||
n.ExpireAddressesOlderThan(AddressExpirationTimeout)
|
||||
|
||||
if len(n.Addresses) < 1 {
|
||||
nodeDeleteList = append(nodeDeleteList, id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range nodeDeleteList {
|
||||
c[id] = nil
|
||||
delete(c, id)
|
||||
}
|
||||
|
||||
if len(c) == 0 {
|
||||
clusterDeleteList = append(clusterDeleteList, clusterID)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range clusterDeleteList {
|
||||
delete(d.db, id)
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/talos-systems/kubespan-manager/pkg/types"
|
||||
)
|
||||
|
||||
const redisTTL = 12 * time.Minute
|
||||
|
||||
type redisDB struct {
|
||||
logger *zap.Logger
|
||||
|
||||
rc *redis.Client
|
||||
}
|
||||
|
||||
// NewRedis creates new redis DB.
|
||||
func NewRedis(addr string, logger *zap.Logger) (DB, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rc := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
})
|
||||
|
||||
if err := rc.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||||
}
|
||||
|
||||
return &redisDB{
|
||||
rc: rc,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *redisDB) clusterNodesKey(cluster string) string {
|
||||
return fmt.Sprintf("cluster:%s:nodelist", cluster)
|
||||
}
|
||||
|
||||
func (d *redisDB) clusterNodeKey(cluster, id string) string {
|
||||
return fmt.Sprintf("cluster:%s:node:%s", cluster, id)
|
||||
}
|
||||
|
||||
func (d *redisDB) clusterAddressKey(cluster string, addr *types.Address) string {
|
||||
if !addr.IP.IsZero() {
|
||||
return fmt.Sprintf("cluster:%s:address:%s", cluster, addr.IP.String())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("cluster:%s:address:%s", cluster, addr.Name)
|
||||
}
|
||||
|
||||
// Add implements db.DB.
|
||||
func (d *redisDB) Add(ctx context.Context, cluster string, n *types.Node) error {
|
||||
tx := d.rc.TxPipeline()
|
||||
|
||||
// Store the node data
|
||||
tx.Set(ctx, d.clusterNodeKey(cluster, n.ID), n, redisTTL)
|
||||
|
||||
// Add the node to the cluster
|
||||
if err := tx.SAdd(ctx, d.clusterNodesKey(cluster), n.ID).Err(); err != nil {
|
||||
return fmt.Errorf("failed to add node %s to cluster %q: %w", n.Name, cluster, err)
|
||||
}
|
||||
|
||||
tx.Expire(ctx, d.clusterNodesKey(cluster), redisTTL)
|
||||
|
||||
// Update the address assignments
|
||||
for _, addr := range n.Addresses {
|
||||
tx.Set(ctx, d.clusterAddressKey(cluster, addr), n.ID, redisTTL)
|
||||
}
|
||||
|
||||
_, err := tx.Exec(ctx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// AddAddresses implements db.DB.
|
||||
func (d *redisDB) AddAddresses(ctx context.Context, cluster, id string, ep ...*types.Address) error {
|
||||
n, err := d.Get(ctx, cluster, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve node %q from cluster %q: %w", id, cluster, err)
|
||||
}
|
||||
|
||||
n.AddAddresses(ep...)
|
||||
|
||||
return d.Add(ctx, cluster, n)
|
||||
}
|
||||
|
||||
// Clean implements db.DB.
|
||||
func (d *redisDB) Clean() {} // no-op
|
||||
|
||||
// Get implements db.DB.
|
||||
func (d *redisDB) Get(ctx context.Context, cluster, id string) (*types.Node, error) {
|
||||
n := new(types.Node)
|
||||
|
||||
if err := d.rc.Get(ctx, d.clusterNodeKey(cluster, id)).Scan(n); err != nil {
|
||||
if errors.Is(redis.Nil, err) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to parse node %q of cluster %q: %w", id, cluster, err)
|
||||
}
|
||||
|
||||
var validAddresses []*types.Address
|
||||
|
||||
for _, a := range n.Addresses {
|
||||
owner, err := d.rc.Get(ctx, d.clusterAddressKey(cluster, a)).Result()
|
||||
if err == nil && owner == id {
|
||||
validAddresses = append(validAddresses, a)
|
||||
}
|
||||
}
|
||||
|
||||
n.Addresses = validAddresses
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// List implements db.DB.
|
||||
func (d *redisDB) List(ctx context.Context, cluster string) ([]*types.Node, error) {
|
||||
nodeList, err := d.rc.SMembers(ctx, d.clusterNodesKey(cluster)).Result()
|
||||
if err != nil {
|
||||
if errors.Is(redis.Nil, err) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to get members of cluster %q: %w", cluster, err)
|
||||
}
|
||||
|
||||
ret := make([]*types.Node, 0, len(nodeList))
|
||||
|
||||
for _, id := range nodeList {
|
||||
n, err := d.Get(ctx, cluster, id)
|
||||
if err != nil {
|
||||
if errors.Is(redis.Nil, err) {
|
||||
d.logger.Debug("removing stale node from cluster",
|
||||
zap.String("node", id),
|
||||
zap.String("cluster", cluster),
|
||||
)
|
||||
|
||||
if err = d.rc.SRem(ctx, d.clusterNodesKey(cluster), id).Err(); err != nil {
|
||||
d.logger.Warn("failed to remove node from cluster set which did not exist",
|
||||
zap.String("node", id),
|
||||
zap.String("cluster", cluster),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
d.logger.Error("failed to get node listen in nodeList",
|
||||
zap.String("node", id),
|
||||
zap.String("cluster", cluster),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, n)
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
106
internal/state/affiliate.go
Normal file
106
internal/state/affiliate.go
Normal file
@ -0,0 +1,106 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Affiliate represents cluster affiliate state.
|
||||
type Affiliate struct {
|
||||
id string
|
||||
expiration time.Time
|
||||
data []byte
|
||||
endpoints []Endpoint
|
||||
|
||||
changed bool
|
||||
}
|
||||
|
||||
// Endpoint is a combination of endpoint itself and its TTL.
|
||||
type Endpoint struct {
|
||||
expiration time.Time
|
||||
data []byte
|
||||
}
|
||||
|
||||
// NewAffiliate constructs new (empty) Affiliate.
|
||||
func NewAffiliate(id string) *Affiliate {
|
||||
return &Affiliate{
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
// ClearChanged clears the changed flag.
|
||||
func (affiliate *Affiliate) ClearChanged() {
|
||||
affiliate.changed = false
|
||||
}
|
||||
|
||||
// IsChanged returns changed flag.
|
||||
func (affiliate *Affiliate) IsChanged() bool {
|
||||
return affiliate.changed
|
||||
}
|
||||
|
||||
// Update affiliate data and expiration.
|
||||
func (affiliate *Affiliate) Update(data []byte, expiration time.Time) {
|
||||
affiliate.data = data
|
||||
affiliate.expiration = expiration
|
||||
affiliate.changed = true
|
||||
}
|
||||
|
||||
// MergeEndpoints and potentially update expiration for endpoints.
|
||||
func (affiliate *Affiliate) MergeEndpoints(endpoints [][]byte, expiration time.Time) {
|
||||
for _, endpoint := range endpoints {
|
||||
found := false
|
||||
|
||||
for i := range affiliate.endpoints {
|
||||
if bytes.Equal(affiliate.endpoints[i].data, endpoint) {
|
||||
found = true
|
||||
|
||||
if affiliate.endpoints[i].expiration.Before(expiration) {
|
||||
affiliate.endpoints[i].expiration = expiration
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
affiliate.endpoints = append(affiliate.endpoints, Endpoint{
|
||||
expiration: expiration,
|
||||
data: endpoint,
|
||||
})
|
||||
|
||||
affiliate.changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if affiliate.expiration.Before(expiration) {
|
||||
affiliate.expiration = expiration
|
||||
}
|
||||
}
|
||||
|
||||
// GarbageCollect affiliate data.
|
||||
//
|
||||
// Endpoints are removed independent of the affiliate data.
|
||||
func (affiliate *Affiliate) GarbageCollect(now time.Time) (remove, changed bool) {
|
||||
if affiliate.expiration.Before(now) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
n := 0
|
||||
|
||||
for _, endpoint := range affiliate.endpoints {
|
||||
if endpoint.expiration.After(now) {
|
||||
affiliate.endpoints[n] = endpoint
|
||||
n++
|
||||
} else {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
affiliate.endpoints = affiliate.endpoints[:n]
|
||||
|
||||
return false, changed
|
||||
}
|
98
internal/state/affiliate_test.go
Normal file
98
internal/state/affiliate_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
)
|
||||
|
||||
func TestAffiliateMutations(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
affiliate := state.NewAffiliate("id1")
|
||||
|
||||
affiliate.ClearChanged()
|
||||
assert.False(t, affiliate.IsChanged())
|
||||
|
||||
affiliate.Update([]byte("data"), now.Add(time.Minute))
|
||||
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "id1",
|
||||
Data: []byte("data"),
|
||||
Endpoints: [][]byte{},
|
||||
}, affiliate.Export())
|
||||
|
||||
assert.True(t, affiliate.IsChanged())
|
||||
|
||||
affiliate.ClearChanged()
|
||||
|
||||
affiliate.Update([]byte("data1"), now.Add(time.Minute))
|
||||
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "id1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
}, affiliate.Export())
|
||||
|
||||
assert.True(t, affiliate.IsChanged())
|
||||
|
||||
affiliate.ClearChanged()
|
||||
|
||||
affiliate.MergeEndpoints([][]byte{
|
||||
[]byte("e1"),
|
||||
[]byte("e2"),
|
||||
}, now.Add(time.Minute))
|
||||
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "id1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{[]byte("e1"), []byte("e2")},
|
||||
}, affiliate.Export())
|
||||
|
||||
assert.True(t, affiliate.IsChanged())
|
||||
affiliate.ClearChanged()
|
||||
|
||||
affiliate.MergeEndpoints([][]byte{
|
||||
[]byte("e1"),
|
||||
}, now.Add(time.Minute))
|
||||
|
||||
assert.False(t, affiliate.IsChanged())
|
||||
|
||||
affiliate.MergeEndpoints([][]byte{
|
||||
[]byte("e1"),
|
||||
[]byte("e3"),
|
||||
}, now.Add(3*time.Minute))
|
||||
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "id1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{[]byte("e1"), []byte("e2"), []byte("e3")},
|
||||
}, affiliate.Export())
|
||||
|
||||
assert.True(t, affiliate.IsChanged())
|
||||
|
||||
remove, changed := affiliate.GarbageCollect(now)
|
||||
assert.False(t, remove)
|
||||
assert.False(t, changed)
|
||||
|
||||
remove, changed = affiliate.GarbageCollect(now.Add(2 * time.Minute))
|
||||
assert.False(t, remove)
|
||||
assert.True(t, changed)
|
||||
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "id1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{[]byte("e1"), []byte("e3")},
|
||||
}, affiliate.Export())
|
||||
|
||||
remove, changed = affiliate.GarbageCollect(now.Add(4 * time.Minute))
|
||||
assert.True(t, remove)
|
||||
assert.True(t, changed)
|
||||
}
|
167
internal/state/cluster.go
Normal file
167
internal/state/cluster.go
Normal file
@ -0,0 +1,167 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cluster is a collection of affiliates.
|
||||
//
|
||||
// Cluster is gc'ed as all its affiliates are gone.
|
||||
type Cluster struct {
|
||||
affiliates map[string]*Affiliate
|
||||
id string
|
||||
subscriptions []*Subscription
|
||||
|
||||
affiliatesMu sync.Mutex
|
||||
subscriptionsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewCluster creates new cluster with specified ID.
|
||||
func NewCluster(id string) *Cluster {
|
||||
return &Cluster{
|
||||
id: id,
|
||||
affiliates: map[string]*Affiliate{},
|
||||
}
|
||||
}
|
||||
|
||||
// WithAffiliate runs a function against the affiliate.
|
||||
//
|
||||
// Cluster state is locked while the function is running.
|
||||
func (cluster *Cluster) WithAffiliate(id string, f func(affiliate *Affiliate)) {
|
||||
cluster.affiliatesMu.Lock()
|
||||
defer cluster.affiliatesMu.Unlock()
|
||||
|
||||
if affiliate, ok := cluster.affiliates[id]; ok {
|
||||
affiliate.ClearChanged()
|
||||
|
||||
f(affiliate)
|
||||
|
||||
if affiliate.IsChanged() {
|
||||
cluster.notify(&Notification{
|
||||
AffiliateID: id,
|
||||
Affiliate: affiliate.Export(),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
affiliate := NewAffiliate(id)
|
||||
f(affiliate)
|
||||
|
||||
cluster.affiliates[id] = affiliate
|
||||
cluster.notify(&Notification{
|
||||
AffiliateID: id,
|
||||
Affiliate: affiliate.Export(),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAffiliate deletes affiliate from the cluster.
|
||||
func (cluster *Cluster) DeleteAffiliate(id string) {
|
||||
cluster.affiliatesMu.Lock()
|
||||
defer cluster.affiliatesMu.Unlock()
|
||||
|
||||
if _, ok := cluster.affiliates[id]; ok {
|
||||
delete(cluster.affiliates, id)
|
||||
|
||||
cluster.notify(&Notification{
|
||||
AffiliateID: id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// List the affiliates.
|
||||
//
|
||||
// List provides a snapshot of the affiliates.
|
||||
func (cluster *Cluster) List() []*AffiliateExport {
|
||||
cluster.affiliatesMu.Lock()
|
||||
defer cluster.affiliatesMu.Unlock()
|
||||
|
||||
result := make([]*AffiliateExport, 0, len(cluster.affiliates))
|
||||
|
||||
for _, affiliate := range cluster.affiliates {
|
||||
result = append(result, affiliate.Export())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Subscribe to the affiliate updates.
|
||||
//
|
||||
// Subscribe returns a snapshot of current list of affiliates and creates new Subscription.
|
||||
func (cluster *Cluster) Subscribe(ch chan<- *Notification) ([]*AffiliateExport, *Subscription) {
|
||||
cluster.affiliatesMu.Lock()
|
||||
defer cluster.affiliatesMu.Unlock()
|
||||
cluster.subscriptionsMu.Lock()
|
||||
defer cluster.subscriptionsMu.Unlock()
|
||||
|
||||
snapshot := make([]*AffiliateExport, 0, len(cluster.affiliates))
|
||||
|
||||
for _, affiliate := range cluster.affiliates {
|
||||
snapshot = append(snapshot, affiliate.Export())
|
||||
}
|
||||
|
||||
subscription := &Subscription{
|
||||
cluster: cluster,
|
||||
errCh: make(chan error, 1),
|
||||
ch: ch,
|
||||
}
|
||||
|
||||
cluster.subscriptions = append(cluster.subscriptions, subscription)
|
||||
|
||||
return snapshot, subscription
|
||||
}
|
||||
|
||||
func (cluster *Cluster) unsubscribe(subscription *Subscription) {
|
||||
cluster.subscriptionsMu.Lock()
|
||||
defer cluster.subscriptionsMu.Unlock()
|
||||
|
||||
for i := range cluster.subscriptions {
|
||||
if cluster.subscriptions[i] == subscription {
|
||||
cluster.subscriptions[i] = cluster.subscriptions[len(cluster.subscriptions)-1]
|
||||
cluster.subscriptions[len(cluster.subscriptions)-1] = nil
|
||||
cluster.subscriptions = cluster.subscriptions[:len(cluster.subscriptions)-1]
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GarbageCollect the cluster.
|
||||
func (cluster *Cluster) GarbageCollect(now time.Time) (empty bool) {
|
||||
cluster.affiliatesMu.Lock()
|
||||
defer cluster.affiliatesMu.Unlock()
|
||||
|
||||
for id, affiliate := range cluster.affiliates {
|
||||
remove, changed := affiliate.GarbageCollect(now)
|
||||
|
||||
if remove {
|
||||
delete(cluster.affiliates, id)
|
||||
}
|
||||
|
||||
if changed {
|
||||
cluster.notify(&Notification{
|
||||
AffiliateID: id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return len(cluster.affiliates) == 0
|
||||
}
|
||||
|
||||
func (cluster *Cluster) notify(notifications ...*Notification) {
|
||||
cluster.subscriptionsMu.Lock()
|
||||
subscriptions := append([]*Subscription(nil), cluster.subscriptions...)
|
||||
cluster.subscriptionsMu.Unlock()
|
||||
|
||||
for _, notification := range notifications {
|
||||
for _, subscription := range subscriptions {
|
||||
subscription.notify(notification)
|
||||
}
|
||||
}
|
||||
}
|
243
internal/state/cluster_test.go
Normal file
243
internal/state/cluster_test.go
Normal file
@ -0,0 +1,243 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
)
|
||||
|
||||
func TestClusterMutations(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
cluster := state.NewCluster("cluster1")
|
||||
|
||||
remove := cluster.GarbageCollect(now)
|
||||
assert.True(t, remove)
|
||||
|
||||
assert.Len(t, cluster.List(), 0)
|
||||
|
||||
cluster.WithAffiliate("af1", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
assert.Len(t, cluster.List(), 1)
|
||||
|
||||
updates := make(chan *state.Notification, 1)
|
||||
|
||||
snapshot, subscription := cluster.Subscribe(updates)
|
||||
defer subscription.Close()
|
||||
|
||||
assert.Len(t, snapshot, 1)
|
||||
|
||||
cluster.WithAffiliate("af1", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data1"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
assert.Len(t, cluster.List(), 1)
|
||||
assert.Equal(t, []*state.AffiliateExport{
|
||||
{
|
||||
ID: "af1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
}, cluster.List())
|
||||
|
||||
select {
|
||||
case notification := <-updates:
|
||||
assert.Equal(t, "af1", notification.AffiliateID)
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "af1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
}, notification.Affiliate)
|
||||
case <-time.After(time.Second):
|
||||
assert.Fail(t, "no notification")
|
||||
}
|
||||
|
||||
cluster.WithAffiliate("af2", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data2"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
assert.Len(t, cluster.List(), 2)
|
||||
|
||||
list := cluster.List()
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID })
|
||||
assert.Equal(t, []*state.AffiliateExport{
|
||||
{
|
||||
ID: "af1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
{
|
||||
ID: "af2",
|
||||
Data: []byte("data2"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
}, list)
|
||||
|
||||
select {
|
||||
case notification := <-updates:
|
||||
assert.Equal(t, "af2", notification.AffiliateID)
|
||||
assert.Equal(t, &state.AffiliateExport{
|
||||
ID: "af2",
|
||||
Data: []byte("data2"),
|
||||
Endpoints: [][]byte{},
|
||||
}, notification.Affiliate)
|
||||
case <-time.After(time.Second):
|
||||
assert.Fail(t, "no notification")
|
||||
}
|
||||
|
||||
cluster.DeleteAffiliate("af1")
|
||||
|
||||
assert.Len(t, cluster.List(), 1)
|
||||
assert.Equal(t, []*state.AffiliateExport{
|
||||
{
|
||||
ID: "af2",
|
||||
Data: []byte("data2"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
}, cluster.List())
|
||||
|
||||
select {
|
||||
case notification := <-updates:
|
||||
assert.Equal(t, "af1", notification.AffiliateID)
|
||||
assert.Nil(t, notification.Affiliate)
|
||||
case <-time.After(time.Second):
|
||||
assert.Fail(t, "no notification")
|
||||
}
|
||||
|
||||
empty := cluster.GarbageCollect(now)
|
||||
assert.False(t, empty)
|
||||
|
||||
empty = cluster.GarbageCollect(now.Add(2 * time.Minute))
|
||||
assert.True(t, empty)
|
||||
|
||||
select {
|
||||
case notification := <-updates:
|
||||
assert.Equal(t, "af2", notification.AffiliateID)
|
||||
assert.Nil(t, notification.Affiliate)
|
||||
case <-time.After(time.Second):
|
||||
assert.Fail(t, "no notification")
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-subscription.ErrCh():
|
||||
assert.NoError(t, err)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
cluster := state.NewCluster("cluster2")
|
||||
|
||||
// create live and dead subscribers
|
||||
liveSubscribers := make([]*state.Subscription, 5)
|
||||
deadSubscribers := make([]*state.Subscription, 2)
|
||||
|
||||
channels := make([]chan *state.Notification, len(liveSubscribers))
|
||||
|
||||
for i := range liveSubscribers {
|
||||
var snapshot []*state.AffiliateExport
|
||||
|
||||
channels[i] = make(chan *state.Notification, 16)
|
||||
|
||||
snapshot, liveSubscribers[i] = cluster.Subscribe(channels[i])
|
||||
assert.Empty(t, snapshot)
|
||||
|
||||
defer liveSubscribers[i].Close()
|
||||
}
|
||||
|
||||
for i := range deadSubscribers {
|
||||
var snapshot []*state.AffiliateExport
|
||||
|
||||
snapshot, deadSubscribers[i] = cluster.Subscribe(make(chan<- *state.Notification, 1))
|
||||
assert.Empty(t, snapshot)
|
||||
}
|
||||
|
||||
cluster.WithAffiliate("af1", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data1"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
cluster.WithAffiliate("af2", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data2"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
cluster.WithAffiliate("af2", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data2_1"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
cluster.DeleteAffiliate("af2")
|
||||
|
||||
// dead subscribers should have errored out
|
||||
for i := range deadSubscribers {
|
||||
select {
|
||||
case err := <-deadSubscribers[i].ErrCh():
|
||||
assert.Error(t, err)
|
||||
default:
|
||||
assert.Fail(t, "error is expected")
|
||||
}
|
||||
}
|
||||
|
||||
// live subscribers should have no error
|
||||
for i := range liveSubscribers {
|
||||
select {
|
||||
case <-liveSubscribers[i].ErrCh():
|
||||
assert.Fail(t, "error is not expected")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
assertNotification := func(ch <-chan *state.Notification, expected *state.Notification) {
|
||||
select {
|
||||
case notification := <-ch:
|
||||
assert.Equal(t, expected, notification)
|
||||
default:
|
||||
assert.Fail(t, "no message")
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range channels {
|
||||
assertNotification(ch, &state.Notification{
|
||||
AffiliateID: "af1",
|
||||
Affiliate: &state.AffiliateExport{
|
||||
ID: "af1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
})
|
||||
|
||||
assertNotification(ch, &state.Notification{
|
||||
AffiliateID: "af2",
|
||||
Affiliate: &state.AffiliateExport{
|
||||
ID: "af2",
|
||||
Data: []byte("data2"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
})
|
||||
|
||||
assertNotification(ch, &state.Notification{
|
||||
AffiliateID: "af2",
|
||||
Affiliate: &state.AffiliateExport{
|
||||
ID: "af2",
|
||||
Data: []byte("data2_1"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
})
|
||||
|
||||
assertNotification(ch, &state.Notification{
|
||||
AffiliateID: "af2",
|
||||
})
|
||||
}
|
||||
}
|
28
internal/state/export.go
Normal file
28
internal/state/export.go
Normal file
@ -0,0 +1,28 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state
|
||||
|
||||
// AffiliateExport represents a read-only copy of affiliate.
|
||||
type AffiliateExport struct {
|
||||
ID string
|
||||
Data []byte
|
||||
Endpoints [][]byte
|
||||
}
|
||||
|
||||
// Export the affiliate into AffiliateExport.
|
||||
func (affiliate *Affiliate) Export() *AffiliateExport {
|
||||
result := &AffiliateExport{
|
||||
ID: affiliate.id,
|
||||
Data: affiliate.data,
|
||||
}
|
||||
|
||||
result.Endpoints = make([][]byte, 0, len(affiliate.endpoints))
|
||||
|
||||
for _, endpoint := range affiliate.endpoints {
|
||||
result.Endpoints = append(result.Endpoints, endpoint.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
71
internal/state/state.go
Normal file
71
internal/state/state.go
Normal file
@ -0,0 +1,71 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package state implements server state with clusters, affiliates, subscriptions, etc.
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// State keeps the discovery service state.
|
||||
type State struct {
|
||||
clusters sync.Map
|
||||
}
|
||||
|
||||
// NewState create new instance of State.
|
||||
func NewState() *State {
|
||||
return &State{}
|
||||
}
|
||||
|
||||
// GetCluster returns (or creates) new cluster by ID.
|
||||
func (state *State) GetCluster(id string) *Cluster {
|
||||
if v, ok := state.clusters.Load(id); ok {
|
||||
return v.(*Cluster)
|
||||
}
|
||||
|
||||
v, _ := state.clusters.LoadOrStore(id, NewCluster(id))
|
||||
|
||||
return v.(*Cluster)
|
||||
}
|
||||
|
||||
// GarbageCollect recursively each cluster, and remove empty clusters.
|
||||
func (state *State) GarbageCollect(now time.Time) (removedClusters int) {
|
||||
state.clusters.Range(func(key, value interface{}) bool {
|
||||
cluster := value.(*Cluster) //nolint:errcheck,forcetypeassert
|
||||
|
||||
if cluster.GarbageCollect(now) {
|
||||
state.clusters.Delete(key)
|
||||
|
||||
removedClusters++
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return removedClusters
|
||||
}
|
||||
|
||||
// RunGC runs the garbage collection on interval.
|
||||
func (state *State) RunGC(ctx context.Context, logger *zap.Logger, interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for ctx.Err() == nil {
|
||||
removedClusters := state.GarbageCollect(time.Now())
|
||||
|
||||
if removedClusters > 0 {
|
||||
logger.Info("garbage collection run", zap.Int("removed_clusters", removedClusters))
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
37
internal/state/state_test.go
Normal file
37
internal/state/state_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
)
|
||||
|
||||
func TestState(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
st := state.NewState()
|
||||
|
||||
deletedClusters := st.GarbageCollect(now)
|
||||
assert.Equal(t, 0, deletedClusters)
|
||||
|
||||
st.GetCluster("id1")
|
||||
st.GetCluster("id2").WithAffiliate("af1", func(affiliate *state.Affiliate) {
|
||||
affiliate.Update([]byte("data1"), now.Add(time.Minute))
|
||||
})
|
||||
|
||||
deletedClusters = st.GarbageCollect(now)
|
||||
assert.Equal(t, 1, deletedClusters)
|
||||
|
||||
deletedClusters = st.GarbageCollect(now.Add(2 * time.Minute))
|
||||
assert.Equal(t, 1, deletedClusters)
|
||||
|
||||
deletedClusters = st.GarbageCollect(now)
|
||||
assert.Equal(t, 0, deletedClusters)
|
||||
}
|
45
internal/state/subscribe.go
Normal file
45
internal/state/subscribe.go
Normal file
@ -0,0 +1,45 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package state
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Notification about affiliate update.
|
||||
//
|
||||
// Affiliate is nil, then affiliate was deleted.
|
||||
type Notification struct {
|
||||
Affiliate *AffiliateExport
|
||||
AffiliateID string
|
||||
}
|
||||
|
||||
// Subscription is a handle returned to the subscriber.
|
||||
type Subscription struct {
|
||||
cluster *Cluster
|
||||
|
||||
errCh chan error
|
||||
ch chan<- *Notification
|
||||
}
|
||||
|
||||
// ErrCh returns error channel, whenever there's error on the channel, subscription is invalid.
|
||||
func (subscription *Subscription) ErrCh() <-chan error {
|
||||
return subscription.errCh
|
||||
}
|
||||
|
||||
// Close subscription (unsubscribe).
|
||||
func (subscription *Subscription) Close() {
|
||||
subscription.cluster.unsubscribe(subscription)
|
||||
}
|
||||
|
||||
func (subscription *Subscription) notify(notification *Notification) {
|
||||
select {
|
||||
case subscription.ch <- notification:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
subscription.errCh <- fmt.Errorf("lost update")
|
||||
|
||||
subscription.Close()
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package client implements http client for the manager API.
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/talos-systems/kubespan-manager/pkg/types"
|
||||
)
|
||||
|
||||
// Add adds a Node to the Cluster database, updating it if it already exists.
|
||||
func Add(rootURL, clusterID string, n *types.Node) error {
|
||||
body := new(bytes.Buffer)
|
||||
|
||||
if err := json.NewEncoder(body).Encode(n); err != nil {
|
||||
return fmt.Errorf("failed to encode Node information: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(context.TODO(), http.MethodPost, fmt.Sprintf("%s/%s", rootURL, clusterID), body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to post Node information: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to post Node information: %w", err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return fmt.Errorf("server rejected Node information: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddresses adds a list of addresses to a node.
|
||||
func AddAddresses(rootURL, clusterID, id string, epList ...*types.Address) error {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if err := json.NewEncoder(buf).Encode(epList); err != nil {
|
||||
return fmt.Errorf("failed to encode known endpoints: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(context.TODO(), http.MethodPut, fmt.Sprintf("%s/%s/%s", rootURL, clusterID, id), buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make PUT request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add endpoints to node %q/%q: %w", clusterID, id, err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return fmt.Errorf("server rejected new endpoints for node %q/%q: %s", clusterID, id, resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the Node defined by the given public key, if and only if it exists within the given Cluster ID.
|
||||
func Get(rootURL, clusterID, publicKey string) (*types.Node, error) {
|
||||
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, fmt.Sprintf("%s/%s/%s", rootURL, clusterID, publicKey), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request node %q/%q from server %q: %w", clusterID, publicKey, rootURL, err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request node %q/%q from server %q: %w", clusterID, publicKey, rootURL, err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
node := new(types.Node)
|
||||
if err = json.NewDecoder(resp.Body).Decode(node); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response from server: %w", err)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// List returns the set of Nodes associated with the given Cluster ID.
|
||||
func List(rootURL, clusterID string) ([]*types.Node, error) {
|
||||
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, fmt.Sprintf("%s/%s", rootURL, clusterID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request list from server %q: %w", rootURL, err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request list from server %q: %w", rootURL, err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
var list []*types.Node
|
||||
if err = json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response from server: %w", err)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
18
pkg/server/metrics.go
Normal file
18
pkg/server/metrics.go
Normal file
@ -0,0 +1,18 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
prom "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var metricVersionGauge = prom.NewGaugeVec(prom.GaugeOpts{
|
||||
Name: "discovery_hello_requests",
|
||||
Help: "Number of hello requests split by client version.",
|
||||
}, []string{"client_version"})
|
||||
|
||||
func init() {
|
||||
prom.MustRegister(metricVersionGauge)
|
||||
}
|
183
pkg/server/server.go
Normal file
183
pkg/server/server.go
Normal file
@ -0,0 +1,183 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package server implements server-side part of gRPC API.
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/discovery-service/api/v1alpha1/pb"
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
)
|
||||
|
||||
const updateBuffer = 32
|
||||
|
||||
// ClusterServer implements discovery cluster gRPC API.
|
||||
type ClusterServer struct {
|
||||
pb.UnimplementedClusterServer
|
||||
|
||||
state *state.State
|
||||
}
|
||||
|
||||
// NewClusterServer builds new ClusterServer.
|
||||
func NewClusterServer(state *state.State) *ClusterServer {
|
||||
return &ClusterServer{
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
// Hello implements cluster API.
|
||||
func (srv *ClusterServer) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
|
||||
metricVersionGauge.WithLabelValues(req.ClientVersion).Inc()
|
||||
|
||||
if err := validateClusterID(req.ClusterId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &pb.HelloResponse{}
|
||||
|
||||
if peer, ok := peer.FromContext(ctx); ok {
|
||||
if addr, ok := peer.Addr.(*net.TCPAddr); ok {
|
||||
if ip, ok := netaddr.FromStdIP(addr.IP); ok {
|
||||
var err error
|
||||
|
||||
resp.ClientIp, err = ip.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AffiliateUpdate implements cluster API.
|
||||
func (srv *ClusterServer) AffiliateUpdate(ctx context.Context, req *pb.AffiliateUpdateRequest) (*pb.AffiliateUpdateResponse, error) {
|
||||
if err := validateClusterID(req.ClusterId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateAffiliateID(req.AffiliateId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.state.GetCluster(req.ClusterId).WithAffiliate(req.AffiliateId, func(affiliate *state.Affiliate) {
|
||||
expiration := time.Now().Add(req.Ttl.AsDuration())
|
||||
|
||||
if len(req.AffiliateData) > 0 {
|
||||
affiliate.Update(req.AffiliateData, expiration)
|
||||
}
|
||||
|
||||
affiliate.MergeEndpoints(req.AffiliateEndpoints, expiration)
|
||||
})
|
||||
|
||||
return &pb.AffiliateUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
// AffiliateDelete implements cluster API.
|
||||
func (srv *ClusterServer) AffiliateDelete(ctx context.Context, req *pb.AffiliateDeleteRequest) (*pb.AffiliateDeleteResponse, error) {
|
||||
if err := validateClusterID(req.ClusterId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateAffiliateID(req.AffiliateId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.state.GetCluster(req.ClusterId).DeleteAffiliate(req.AffiliateId)
|
||||
|
||||
return &pb.AffiliateDeleteResponse{}, nil
|
||||
}
|
||||
|
||||
// List implements cluster API.
|
||||
func (srv *ClusterServer) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
|
||||
if err := validateClusterID(req.ClusterId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
affiliates := srv.state.GetCluster(req.ClusterId).List()
|
||||
resp := &pb.ListResponse{
|
||||
Affiliates: make([]*pb.Affiliate, 0, len(affiliates)),
|
||||
}
|
||||
|
||||
for _, affiliate := range affiliates {
|
||||
resp.Affiliates = append(resp.Affiliates, &pb.Affiliate{
|
||||
Id: affiliate.ID,
|
||||
Data: affiliate.Data,
|
||||
Endpoints: affiliate.Endpoints,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Watch implements cluster API.
|
||||
func (srv *ClusterServer) Watch(req *pb.WatchRequest, server pb.Cluster_WatchServer) error {
|
||||
if err := validateClusterID(req.ClusterId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make enough room to handle connection issues
|
||||
updates := make(chan *state.Notification, updateBuffer)
|
||||
|
||||
snapshot, subscription := srv.state.GetCluster(req.ClusterId).Subscribe(updates)
|
||||
defer subscription.Close()
|
||||
|
||||
for _, affiliate := range snapshot {
|
||||
if err := server.Send(&pb.WatchResponse{
|
||||
Affiliate: &pb.Affiliate{
|
||||
Id: affiliate.ID,
|
||||
Data: affiliate.Data,
|
||||
Endpoints: affiliate.Endpoints,
|
||||
},
|
||||
}); err != nil {
|
||||
if status.Code(err) == codes.Canceled {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-server.Context().Done():
|
||||
return nil
|
||||
case err := <-subscription.ErrCh():
|
||||
return status.Errorf(codes.Aborted, "subscription canceled: %s", err)
|
||||
case notification := <-updates:
|
||||
resp := &pb.WatchResponse{}
|
||||
|
||||
if notification.Affiliate == nil {
|
||||
resp.Deleted = true
|
||||
resp.Affiliate = &pb.Affiliate{
|
||||
Id: notification.AffiliateID,
|
||||
}
|
||||
} else {
|
||||
resp.Affiliate = &pb.Affiliate{
|
||||
Id: notification.Affiliate.ID,
|
||||
Data: notification.Affiliate.Data,
|
||||
Endpoints: notification.Affiliate.Endpoints,
|
||||
}
|
||||
}
|
||||
|
||||
if err := server.Send(resp); err != nil {
|
||||
if status.Code(err) == codes.Canceled {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
324
pkg/server/server_test.go
Normal file
324
pkg/server/server_test.go
Normal file
@ -0,0 +1,324 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/talos-systems/discovery-service/api/v1alpha1/pb"
|
||||
"github.com/talos-systems/discovery-service/internal/state"
|
||||
"github.com/talos-systems/discovery-service/pkg/server"
|
||||
)
|
||||
|
||||
func setupServer(t *testing.T) (address string) {
|
||||
t.Helper()
|
||||
|
||||
lis, err := net.Listen("tcp", "localhost:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterClusterServer(s, server.NewClusterServer(state.NewState()))
|
||||
|
||||
go func() {
|
||||
require.NoError(t, s.Serve(lis))
|
||||
}()
|
||||
|
||||
t.Cleanup(s.Stop)
|
||||
|
||||
return lis.Addr().String()
|
||||
}
|
||||
|
||||
func TestServerAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
addr := setupServer(t)
|
||||
|
||||
conn, err := grpc.Dial(addr, grpc.WithInsecure())
|
||||
require.NoError(t, err)
|
||||
|
||||
client := pb.NewClusterClient(conn)
|
||||
|
||||
t.Run("Hello", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.Hello(ctx, &pb.HelloRequest{
|
||||
ClusterId: "fake",
|
||||
ClientVersion: "v0.12.0",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []byte{0x7f, 0x0, 0x0, 0x1}, resp.ClientIp) // 127.0.0.1
|
||||
})
|
||||
|
||||
t.Run("AffiliateUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
_, err := client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake1",
|
||||
AffiliateId: "af1",
|
||||
AffiliateData: []byte("data1"),
|
||||
AffiliateEndpoints: [][]byte{
|
||||
[]byte("e1"),
|
||||
[]byte("e2"),
|
||||
},
|
||||
Ttl: durationpb.New(time.Minute),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := client.List(ctx, &pb.ListRequest{
|
||||
ClusterId: "fake1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, resp.Affiliates, 1)
|
||||
assert.Equal(t, "af1", resp.Affiliates[0].Id)
|
||||
assert.Equal(t, []byte("data1"), resp.Affiliates[0].Data)
|
||||
assert.Equal(t, [][]byte{[]byte("e1"), []byte("e2")}, resp.Affiliates[0].Endpoints)
|
||||
|
||||
// add more endpoints
|
||||
_, err = client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake1",
|
||||
AffiliateId: "af1",
|
||||
AffiliateEndpoints: [][]byte{
|
||||
[]byte("e3"),
|
||||
[]byte("e2"),
|
||||
},
|
||||
Ttl: durationpb.New(time.Minute),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err = client.List(ctx, &pb.ListRequest{
|
||||
ClusterId: "fake1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, resp.Affiliates, 1)
|
||||
assert.Equal(t, "af1", resp.Affiliates[0].Id)
|
||||
assert.Equal(t, []byte("data1"), resp.Affiliates[0].Data)
|
||||
assert.Equal(t, [][]byte{[]byte("e1"), []byte("e2"), []byte("e3")}, resp.Affiliates[0].Endpoints)
|
||||
})
|
||||
|
||||
t.Run("AffiliateDelete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
_, err := client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{
|
||||
ClusterId: "fake2",
|
||||
AffiliateId: "af1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake2",
|
||||
AffiliateId: "af1",
|
||||
AffiliateData: []byte("data1"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{
|
||||
ClusterId: "fake2",
|
||||
AffiliateId: "af1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := client.List(ctx, &pb.ListRequest{
|
||||
ClusterId: "fake2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp.Affiliates, 0)
|
||||
})
|
||||
|
||||
t.Run("Watch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake3",
|
||||
AffiliateId: "af1",
|
||||
AffiliateData: []byte("data1"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cli, err := client.Watch(ctx, &pb.WatchRequest{
|
||||
ClusterId: "fake3",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
msg, err := cli.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, proto.Equal(&pb.WatchResponse{
|
||||
Deleted: false,
|
||||
Affiliate: &pb.Affiliate{
|
||||
Id: "af1",
|
||||
Data: []byte("data1"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
}, msg))
|
||||
|
||||
_, err = client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake3",
|
||||
AffiliateId: "af2",
|
||||
AffiliateData: []byte("data2"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
msg, err = cli.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, proto.Equal(&pb.WatchResponse{
|
||||
Deleted: false,
|
||||
Affiliate: &pb.Affiliate{
|
||||
Id: "af2",
|
||||
Data: []byte("data2"),
|
||||
Endpoints: [][]byte{},
|
||||
},
|
||||
}, msg))
|
||||
|
||||
_, err = client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{
|
||||
ClusterId: "fake3",
|
||||
AffiliateId: "af1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
msg, err = cli.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, proto.Equal(&pb.WatchResponse{
|
||||
Deleted: true,
|
||||
Affiliate: &pb.Affiliate{
|
||||
Id: "af1",
|
||||
},
|
||||
}, msg))
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
addr := setupServer(t)
|
||||
|
||||
conn, err := grpc.Dial(addr, grpc.WithInsecure())
|
||||
require.NoError(t, err)
|
||||
|
||||
client := pb.NewClusterClient(conn)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Hello", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := client.Hello(ctx, &pb.HelloRequest{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.Hello(ctx, &pb.HelloRequest{
|
||||
ClusterId: strings.Repeat("A", 1024),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
})
|
||||
|
||||
t.Run("AffiliateUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: strings.Repeat("A", 1024),
|
||||
AffiliateId: "fake",
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.AffiliateUpdate(ctx, &pb.AffiliateUpdateRequest{
|
||||
ClusterId: "fake",
|
||||
AffiliateId: strings.Repeat("A", 1024),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
})
|
||||
|
||||
t.Run("AffiliateDelete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{
|
||||
ClusterId: strings.Repeat("A", 1024),
|
||||
AffiliateId: "fake",
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.AffiliateDelete(ctx, &pb.AffiliateDeleteRequest{
|
||||
ClusterId: "fake",
|
||||
AffiliateId: strings.Repeat("A", 1024),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := client.List(ctx, &pb.ListRequest{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
_, err = client.List(ctx, &pb.ListRequest{
|
||||
ClusterId: strings.Repeat("A", 1024),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
})
|
||||
|
||||
t.Run("Watch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.Watch(ctx, &pb.WatchRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cli.Recv()
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
|
||||
cli, err = client.Watch(ctx, &pb.WatchRequest{
|
||||
ClusterId: strings.Repeat("A", 1024),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cli.Recv()
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
})
|
||||
}
|
39
pkg/server/validate.go
Normal file
39
pkg/server/validate.go
Normal file
@ -0,0 +1,39 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
clusterIDMax = 256
|
||||
affiliateIDMax = 256
|
||||
)
|
||||
|
||||
func validateClusterID(id string) error {
|
||||
if len(id) < 1 {
|
||||
return status.Errorf(codes.InvalidArgument, "cluster ID can't be empty")
|
||||
}
|
||||
|
||||
if len(id) > clusterIDMax {
|
||||
return status.Errorf(codes.InvalidArgument, "cluster ID is too long")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAffiliateID(id string) error {
|
||||
if len(id) < 1 {
|
||||
return status.Errorf(codes.InvalidArgument, "affiliate ID can't be empty")
|
||||
}
|
||||
|
||||
if len(id) > affiliateIDMax {
|
||||
return status.Errorf(codes.InvalidArgument, "affiliate ID is too long")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package types contains all manager data types.
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// Address describes an IP or DNS address with optional Port.
|
||||
type Address struct {
|
||||
// LastReported indicates the time at which this address was last reported.
|
||||
LastReported time.Time `json:"lastReported"`
|
||||
// IP is the IP address of this NodeAddress, if known.
|
||||
IP netaddr.IP `json:"ip,omitempty"`
|
||||
// Name is the DNS name of this NodeAddress, if known.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Port is the port number for this NodeAddress, if known.
|
||||
Port uint16 `json:"port,omitempty"`
|
||||
}
|
||||
|
||||
// EqualHost indicates whether two addresses have the same host portion, ignoring the ports.
|
||||
func (a *Address) EqualHost(other *Address) bool {
|
||||
if !a.IP.IsZero() || !other.IP.IsZero() {
|
||||
return a.IP == other.IP
|
||||
}
|
||||
|
||||
return a.Name == other.Name
|
||||
}
|
||||
|
||||
// Equal indicates whether two addresses are equal.
|
||||
func (a *Address) Equal(other *Address) bool {
|
||||
if !a.EqualHost(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.Port == other.Port
|
||||
}
|
||||
|
||||
// Endpoint returns a UDP endpoint address for the Address, using the defaultPort if none is known.
|
||||
func (a *Address) Endpoint(defaultPort uint16) (*net.UDPAddr, error) {
|
||||
proto := "udp"
|
||||
addr := a.Name
|
||||
port := a.Port
|
||||
|
||||
if !a.IP.IsZero() {
|
||||
addr = a.IP.String()
|
||||
|
||||
if a.IP.Is6() {
|
||||
proto = "udp6"
|
||||
addr = "[" + addr + "]"
|
||||
} else {
|
||||
proto = "udp4"
|
||||
}
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
port = defaultPort
|
||||
}
|
||||
|
||||
return net.ResolveUDPAddr(proto, fmt.Sprintf("%s:%d", addr, port))
|
||||
}
|
||||
|
||||
// Node describes a Wireguard Peer.
|
||||
type Node struct {
|
||||
// Name is the human-readable identifier of this Node.
|
||||
// Usually, this is the kubernetes Node name.
|
||||
// It *should* generally be unique, but it is not required to be so.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// ID is the unique identifier for this Node.
|
||||
// Usually, this is the Wireguard Public Key of the Node.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// IP is the IP address of the Wireguard interface on this Node.
|
||||
IP netaddr.IP `json:"ip,omitempty"`
|
||||
|
||||
// Addresses is a list of addresses for the Node.
|
||||
Addresses []*Address `json:"selfIPs,omitempty"`
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// AddAddresses adds a set of addresses to a Node.
|
||||
func (n *Node) AddAddresses(addresses ...*Address) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
for _, a := range addresses {
|
||||
var found bool
|
||||
|
||||
if a.LastReported.IsZero() {
|
||||
a.LastReported = time.Now()
|
||||
}
|
||||
|
||||
for _, existing := range n.Addresses {
|
||||
if a.EqualHost(existing) {
|
||||
found = true
|
||||
|
||||
if a.Port > 0 {
|
||||
existing.Port = a.Port
|
||||
}
|
||||
|
||||
existing.LastReported = a.LastReported
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
n.Addresses = append(n.Addresses, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExpireAddressesOlderThan removes addresses from the Node which have not been reported within the given timeframe.
|
||||
func (n *Node) ExpireAddressesOlderThan(maxAge time.Duration) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
i := 0
|
||||
|
||||
for _, a := range n.Addresses {
|
||||
if time.Since(a.LastReported) < maxAge {
|
||||
n.Addresses[i] = a
|
||||
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
n.Addresses = n.Addresses[:i]
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (n *Node) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(n)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (n *Node) UnmarshalBinary(data []byte) error {
|
||||
*n = Node{}
|
||||
|
||||
return json.Unmarshal(data, n)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/kubespan-manager/pkg/types"
|
||||
)
|
||||
|
||||
func TestEncoderDecoder(t *testing.T) {
|
||||
n := &types.Node{
|
||||
Name: "tester",
|
||||
ID: "IHOPEfmiUG1kE832FAxm77J5WP0O1ZHp9OwqbGowL1E=",
|
||||
IP: netaddr.MustParseIP("2001:db8:1001::1"),
|
||||
Addresses: []*types.Address{
|
||||
{
|
||||
Name: "mynode.mydomain.com",
|
||||
Port: 51512,
|
||||
LastReported: time.Now(),
|
||||
},
|
||||
{
|
||||
IP: netaddr.MustParseIP("2001:db8:2002::2"),
|
||||
Port: 52522,
|
||||
LastReported: time.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := n.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal node: %w", err)
|
||||
}
|
||||
|
||||
n2 := new(types.Node)
|
||||
if err = n2.UnmarshalBinary(data); err != nil {
|
||||
t.Errorf("failed to unmarshal node: %w", err)
|
||||
}
|
||||
|
||||
if n.ID != n2.ID {
|
||||
t.Errorf("IDs do not match: %s != %s", n.ID, n2.ID)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user