├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── Makefile ├── README.md ├── build ├── cnf.Dockerfile ├── crossdns.Dockerfile ├── ep-controller.Dockerfile └── proxy.Dockerfile ├── cmd ├── cnf │ ├── app │ │ └── cnf.go │ └── main.go ├── crossdns │ └── main.go ├── ep-controller │ └── main.go └── proxy │ ├── app │ ├── contrack.go │ ├── proxy.go │ └── proxy_linux.go │ └── main.go ├── doc ├── build-images.md └── pic │ ├── arch.png │ ├── ovnmaster.png │ ├── servicediscovery.png │ └── tunnel.png ├── examples ├── client-import.yaml └── server-export.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── demo │ ├── README.md │ ├── Vagrantfile │ ├── config │ │ └── config.yaml │ ├── deployment.yaml │ ├── insecure_keys │ │ ├── key │ │ └── key.pub │ └── vagrant-setup.sh ├── tools.go ├── update-codegen.sh └── verify-codegen.sh ├── pkg ├── apis │ └── fleetboard.io │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ └── zz_generated.deepcopy.go ├── cnf │ └── CNFManager.go ├── config │ └── config.go ├── controller │ ├── endpointslice │ │ ├── endpointslice.go │ │ ├── metrics │ │ │ ├── cache.go │ │ │ └── metrics.go │ │ ├── reconciler.go │ │ ├── topologycache │ │ │ ├── event.go │ │ │ ├── sliceinfo.go │ │ │ ├── topologycache.go │ │ │ └── utils.go │ │ ├── util │ │ │ ├── controller_utils.go │ │ │ ├── endpointset.go │ │ │ ├── endpointslice_tracker.go │ │ │ └── trigger_time_tracker.go │ │ └── utils.go │ ├── mcs │ │ ├── serviceexport_controller.go │ │ └── serviceimport_controller.go │ ├── syncer │ │ ├── agent.go │ │ └── agent_test.go │ └── tunnels │ │ ├── innertunnel_controller.go │ │ └── intertunnel_controller.go ├── dedinic │ ├── cni_handler.go │ ├── ipam.go │ ├── ipam_test.go │ ├── kubelet.go │ ├── netlink.go │ ├── netlink_test.go │ ├── nri.go │ └── type.go ├── generated │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── fleetboard.io │ │ │ └── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_fleetboard.io_client.go │ │ │ └── fake_peer.go │ │ │ ├── fleetboard.io_client.go │ │ │ ├── generated_expansion.go │ │ │ └── peer.go │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── fleetboard.io │ │ │ ├── interface.go │ │ │ └── v1alpha1 │ │ │ │ ├── interface.go │ │ │ │ └── peer.go │ │ │ ├── generic.go │ │ │ └── internalinterfaces │ │ │ └── factory_interfaces.go │ └── listers │ │ └── fleetboard.io │ │ └── v1alpha1 │ │ ├── expansion_generated.go │ │ └── peer.go ├── known │ ├── annotations.go │ ├── constants.go │ ├── labels.go │ ├── tunnel.go │ └── types.go ├── plugin │ ├── crossdns.go │ ├── parse.go │ └── setup.go ├── proxy │ ├── config │ │ └── config.go │ ├── endpoints.go │ ├── endpointslicecache.go │ ├── ipvs │ │ ├── graceful_termination.go │ │ ├── ipset.go │ │ ├── netlink.go │ │ ├── netlink_linux.go │ │ └── proxier.go │ ├── service_import.go │ ├── topology.go │ └── types.go └── tunnel │ ├── options.go │ ├── tunnel.go │ └── wireguard_tools.go └── utils ├── deploy.go ├── endpointslice.go ├── kubeconfig.go ├── netutil.go ├── netutil_test.go ├── peerManager.go ├── pod.go ├── pod_test.go ├── slice.go └── utils.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | merge_group: 4 | pull_request: 5 | jobs: 6 | golangci: 7 | name: pull-fleetboard-golang-ci 8 | env: 9 | GOPATH: ${{ github.workspace }} 10 | GO111MODULE: on 11 | defaults: 12 | run: 13 | working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 14 | strategy: 15 | max-parallel: 3 16 | ## this will contain a matrix of all of the combinations 17 | ## we wish to test again: 18 | matrix: 19 | go-version: [ 1.21.x, 1.22.x ] 20 | os: [ ubuntu-latest ] 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - name: Install Go 24 | uses: actions/setup-go@v5 25 | with: 26 | go-version: ${{ matrix.go-version }} 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 1 31 | path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 32 | - name: Cache go modules and build cache 33 | uses: actions/cache@v4 34 | with: 35 | # In order: 36 | # * Module download cache 37 | # * Build cache (Linux) 38 | # * Build cache (Mac) 39 | # * Build cache (Windows) 40 | path: | 41 | ${{ env.GOPATH }}/pkg/mod 42 | ${{ env.GOPATH }}/pkg/sumdb 43 | ~/.cache/go-build 44 | ~/Library/Caches/go-build 45 | # %LocalAppData%\go-build 46 | key: ${{ matrix.os }}-go-${{ hashFiles('**/go.sum') }} 47 | restore-keys: | 48 | ${{ matrix.os }}-go- 49 | - name: Golang Lint 50 | run: make lint 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | vendor/ 4 | .DS_Store 5 | **/.DS_Store -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: 3 | - internal/cache # extracted from Go code 4 | - internal/renameio # extracted from Go code 5 | - internal/robustio # extracted from Go code 6 | 7 | linters-settings: 8 | govet: 9 | check-shadowing: true 10 | maligned: 11 | suggest-new: true 12 | misspell: 13 | locale: US 14 | revive: 15 | rules: 16 | - name: package-comments 17 | disabled: true 18 | 19 | linters: 20 | enable-all: false 21 | enable: 22 | - bodyclose 23 | - dogsled 24 | - errcheck 25 | - exportloopref 26 | - gocheckcompilerdirectives 27 | - goconst 28 | - gocritic 29 | - gofmt 30 | - goimports 31 | # - gomnd 32 | - goprintffuncname 33 | - gosec 34 | - gosimple 35 | - govet 36 | - ineffassign 37 | - lll 38 | - nakedret 39 | - noctx 40 | - nolintlint 41 | - revive 42 | - staticcheck 43 | - stylecheck 44 | - typecheck 45 | - unconvert 46 | - unparam 47 | - unused 48 | - whitespace 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 2 | CRD_OPTIONS ?= "crd:crdVersions=v1,generateEmbeddedObjectMeta=true" 3 | 4 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 5 | ifeq (,$(shell go env GOBIN)) 6 | GOBIN=$(shell go env GOPATH)/bin 7 | else 8 | GOBIN=$(shell go env GOBIN) 9 | endif 10 | 11 | GIT_COMMIT = $(shell git rev-parse HEAD) 12 | ifeq ($(shell git tag --points-at ${GIT_COMMIT}),) 13 | GIT_VERSION=$(shell echo ${GIT_COMMIT} | cut -c 1-7) 14 | else 15 | GIT_VERSION=$(shell git describe --abbrev=0 --tags --always) 16 | endif 17 | 18 | IMAGE_TAG = ${GIT_VERSION} 19 | REGISTRY ?= ghcr.io 20 | REGISTRY_NAMESPACE ?= fleetboard-io 21 | 22 | 23 | DOCKERARGS?= 24 | ifdef HTTP_PROXY 25 | DOCKERARGS += --build-arg http_proxy=$(HTTP_PROXY) 26 | endif 27 | ifdef HTTPS_PROXY 28 | DOCKERARGS += --build-arg https_proxy=$(HTTPS_PROXY) 29 | endif 30 | 31 | lint: golangci-lint 32 | golangci-lint run -c .golangci.yaml --timeout=10m 33 | 34 | 35 | # Generate manifests e.g. CRD, RBAC etc. 36 | manifests: controller-gen 37 | $(CONTROLLER_GEN) $(CRD_OPTIONS) paths="./..." output:crd:artifacts:config=deploy/hub/crds/ 38 | 39 | # find or download controller-gen 40 | # download controller-gen if necessary 41 | controller-gen: 42 | ifeq (, $(shell which controller-gen)) 43 | @{ \ 44 | set -e ;\ 45 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 46 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 47 | go mod init tmp ;\ 48 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 ;\ 49 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 50 | } 51 | CONTROLLER_GEN=$(GOBIN)/controller-gen 52 | else 53 | CONTROLLER_GEN=$(shell which controller-gen) 54 | endif 55 | 56 | 57 | all: crossdns cnf proxy ep-controller 58 | 59 | crossdns: 60 | CGO_ENABLED=0 go build -ldflags="-s -w" -a -installsuffix cgo -o bin/crossdns cmd/crossdns/main.go 61 | 62 | cnf: 63 | CGO_ENABLED=0 go build -ldflags "-w -s" -a -installsuffix cgo -o bin/cnf cmd/cnf/main.go 64 | 65 | proxy: 66 | CGO_ENABLED=0 go build -ldflags "-w -s" -a -installsuffix cgo -o bin/proxy cmd/proxy/main.go 67 | 68 | ep-controller: 69 | CGO_ENABLED=0 go build -ldflags "-w -s" -a -installsuffix cgo -o bin/ep-controller cmd/ep-controller/main.go 70 | 71 | images: 72 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/crossdns.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:${IMAGE_TAG} 73 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/cnf.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:${IMAGE_TAG} 74 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/ep-controller.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:${IMAGE_TAG} 75 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/proxy.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:${IMAGE_TAG} 76 | 77 | image-crossdns: 78 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/crossdns.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:${IMAGE_TAG} 79 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:${IMAGE_TAG} 80 | docker tag ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:${IMAGE_TAG} ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:latest 81 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/crossdns:latest 82 | 83 | image-cnf: 84 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/cnf.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:${IMAGE_TAG} 85 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:${IMAGE_TAG} 86 | docker tag ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:${IMAGE_TAG} ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:latest 87 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/cnf:latest 88 | 89 | image-proxy: 90 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/proxy.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:${IMAGE_TAG} 91 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:${IMAGE_TAG} 92 | docker tag ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:${IMAGE_TAG} ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:latest 93 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/proxy:latest 94 | 95 | image-ep-controller: 96 | docker buildx build --platform linux/amd64,linux/arm64 $(DOCKERARGS) -f ./build/ep-controller.Dockerfile ./ -t ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:${IMAGE_TAG} 97 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:${IMAGE_TAG} 98 | docker tag ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:${IMAGE_TAG} ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:latest 99 | docker push ${REGISTRY}/${REGISTRY_NAMESPACE}/controller:latest 100 | 101 | 102 | images-push: image-crossdns image-cnf image-proxy image-ep-controller 103 | 104 | # find or download golangci-lint 105 | # download golangci-lint if necessary 106 | golangci-lint: 107 | ifeq (, $(shell which golangci-lint)) 108 | @{ \ 109 | set -e ;\ 110 | export GO111MODULE=on; \ 111 | GOLANG_LINT_TMP_DIR=$$(mktemp -d) ;\ 112 | cd $$GOLANG_LINT_TMP_DIR ;\ 113 | go mod init tmp ;\ 114 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.2 ;\ 115 | rm -rf $$GOLANG_LINT_TMP_DIR ;\ 116 | } 117 | GOLANG_LINT=$(shell go env GOPATH)/bin/golangci-lint 118 | else 119 | GOLANG_LINT=$(shell which golangci-lint) 120 | endif 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FleetBoard 2 | 3 | `Fleetboard` connects kubernetes clusters by a new build parallel network securely, it requires no public IP for 4 | child cluster and has no limits of cluster IP CIDR or CNI types of kubernetes clusters and also 5 | provide service discovery ability. 6 | 7 | With ``Fleetboard``, you don't need to impose any specific requirements on the cluster or be aware of the cluster nodes. 8 | Additionally, there are no intrusive modifications to the cluster. All tunnels and network policies are configured 9 | within the containers. 10 | 11 | It consists of several parts for networking between clusters: 12 | 13 | - `cnf` adds second network interface for pods and establishes VPN tunnels across inner-cluster and inter-cluster. 14 | - `crossdns` provides DNS discovery of Services across clusters. 15 | 16 | ## Architecture 17 | 18 |  19 | 20 | ## Hub Cluster 21 | 22 | We use hub cluster to exchange MCS related resources for connecting clusters, and establish secure tunnels with 23 | all other participating clusters. Hub defines a set of ServiceAccount, Secrets and RBAC to enable `Syncer` and 24 | `cnf`to securely access the Hub cluster's API. 25 | 26 | ## Child cluster 27 | 28 | For every service in the cluster that has a `ServiceExport` created, a new `EndpointSlice` will be generated to represent 29 | the running pods and include references to the endpoint's secondary IP. These `EndpointSlice` resources will be exported 30 | to the `Hub Cluster` and synchronized with other clusters. 31 | 32 | ``Fleetboard`` deploys ``cnf`` as a `DaemonSet` in the child clusters. A leader pod in cnf will be elected to establish 33 | a VPN tunnel to the `Hub Cluster` and create tunnels to other cnf replicas on different nodes within the child cluster. 34 | 35 | Additionally, all workload pods in the clusters will have a second network interface allocated by the ``cnf`` pod on the 36 | same node, with this second interface assigned to the ``cnf`` network namespace. 37 | 38 | ## Helm Chart Installation and Clear 39 | 40 | `Fleetboard` is pretty easy to install with `Helm`. Make sure you already have at least 2 Kubernetes clusters, 41 | please refer to this installation guide [Helm Chart Page](https://fleetboard-io.github.io/fleetboard-charts/). 42 | 43 | After the installation, add cross cluster DNS config segment, in `coredns` configmap, and restart coredns pods. 44 | The `cluster-ip` of `crossdns` is a static cluster IP, usually `10.96.0.11` , check before setting. 45 | ```yaml 46 | fleetboard.local:53 { 47 | forward . 10.96.0.11 48 | } 49 | ``` 50 | ```shell 51 | # restart kube-dns 52 | $ kubectl delete pod -n kube-system --selector=k8s-app=kube-dns 53 | ``` 54 | 55 | ### Test examples: 56 | Create the server example in a cluster. 57 | ```shell 58 | $ kubectl create -f https://raw.githubusercontent.com/fleetboard-io/fleetboard/main/examples/server-export.yaml 59 | ``` 60 | 61 | Create the client example in another cluster. 62 | ```shell 63 | $ kubectl create -f https://raw.githubusercontent.com/fleetboard-io/fleetboard/main/examples/client-import.yaml 64 | ``` 65 | 66 | 67 | Test it in client cluster. 68 | ```shell 69 | $ kubectl exec -it nginx-app-xxx -c alpine -- curl nginx-svc.default.svc.fleetboard.local 70 | 71 | 72 |
73 |If you see this page, the nginx web server is successfully installed and 85 | working. Further configuration is required.
86 | 87 |For online documentation and support please refer to
88 | nginx.org.
89 | Commercial support is available at
90 | nginx.com.
Thank you for using nginx.
93 | 94 | 95 | ``` 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /build/cnf.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | RUN apk update && apk add --no-cache make git 5 | COPY ../go.mod ../go.sum ./ 6 | RUN go mod download 7 | COPY .. . 8 | RUN make cnf 9 | 10 | 11 | FROM alpine:latest 12 | 13 | # Install required packages 14 | RUN apk update && apk add --no-cache \ 15 | iproute2 \ 16 | bridge-utils \ 17 | tcpdump \ 18 | iputils \ 19 | wireguard-tools \ 20 | wget \ 21 | openresolv \ 22 | iptables \ 23 | vim 24 | 25 | WORKDIR /cnf 26 | COPY --from=builder /workspace/bin/cnf ./ 27 | -------------------------------------------------------------------------------- /build/crossdns.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | RUN apk add make 5 | COPY ../go.mod ../go.sum ./ 6 | RUN go mod download 7 | COPY .. . 8 | RUN make crossdns 9 | 10 | 11 | FROM scratch 12 | 13 | COPY --from=builder /workspace/bin/crossdns / 14 | EXPOSE 53 53/udp 15 | ENTRYPOINT ["/crossdns"] -------------------------------------------------------------------------------- /build/ep-controller.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | RUN apk add make 5 | COPY ../go.mod ../go.sum ./ 6 | RUN go mod download 7 | COPY .. . 8 | RUN make ep-controller 9 | 10 | 11 | FROM alpine:3.17.2 12 | 13 | COPY --from=builder /workspace/bin/ep-controller / 14 | ENTRYPOINT ["/ep-controller"] -------------------------------------------------------------------------------- /build/proxy.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | RUN apk update && apk add --no-cache make git 5 | COPY ../go.mod ../go.sum ./ 6 | RUN go mod download 7 | COPY .. . 8 | RUN make proxy 9 | 10 | 11 | FROM alpine:latest 12 | 13 | # Install required packages 14 | RUN apk update && apk add --no-cache \ 15 | iproute2 \ 16 | bridge-utils \ 17 | tcpdump \ 18 | iputils \ 19 | wireguard-tools \ 20 | wget \ 21 | openresolv \ 22 | iptables \ 23 | vim \ 24 | ipvsadm \ 25 | ipset 26 | 27 | WORKDIR /proxy 28 | COPY --from=builder /workspace/bin/proxy ./ 29 | -------------------------------------------------------------------------------- /cmd/cnf/app/cnf.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | utilerrors "k8s.io/apimachinery/pkg/util/errors" 8 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 9 | utilfeature "k8s.io/apiserver/pkg/util/feature" 10 | "k8s.io/client-go/rest" 11 | cliflag "k8s.io/component-base/cli/flag" 12 | logsapi "k8s.io/component-base/logs/api/v1" 13 | "k8s.io/component-base/term" 14 | "k8s.io/component-base/version/verflag" 15 | "k8s.io/klog/v2" 16 | 17 | "github.com/fleetboard-io/fleetboard/pkg/cnf" 18 | "github.com/fleetboard-io/fleetboard/pkg/tunnel" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | func init() { 23 | utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) 24 | } 25 | 26 | // NewCNFCommand creates a *cobra.Command object with default parameters 27 | func NewCNFCommand(ctx context.Context) *cobra.Command { 28 | o := tunnel.NewOptions() 29 | cmd := &cobra.Command{ 30 | Use: "cnf", 31 | Long: `cnf use dedinic as local ip manager and tunnel manager`, 32 | // stop printing usage when the command errors 33 | SilenceUsage: true, 34 | PersistentPreRunE: func(*cobra.Command, []string) error { 35 | // silence client-go warnings. 36 | rest.SetDefaultWarningHandler(rest.NoWarnings{}) 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | verflag.PrintAndExitIfRequested() 41 | fs := cmd.Flags() 42 | 43 | // Activate logging as soon as possible, after that 44 | // show flags with the final logging configuration. 45 | if err := logsapi.ValidateAndApply(o.Logs, utilfeature.DefaultFeatureGate); err != nil { 46 | return err 47 | } 48 | cliflag.PrintFlags(fs) 49 | 50 | // set default options 51 | if err := o.Complete(); err != nil { 52 | return err 53 | } 54 | // validate options 55 | if errs := o.Validate(); len(errs) != 0 { 56 | return utilerrors.NewAggregate(errs) 57 | } 58 | 59 | cm, err := cnf.NewCNFManager(o) 60 | if err != nil { 61 | return err 62 | } 63 | if err = cm.Run(ctx); err != nil { 64 | klog.Error(err) 65 | return err 66 | } 67 | return nil 68 | }, 69 | Args: func(cmd *cobra.Command, args []string) error { 70 | for _, arg := range args { 71 | if len(arg) > 0 { 72 | return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args) 73 | } 74 | } 75 | return nil 76 | }, 77 | } 78 | 79 | fs := cmd.Flags() 80 | namedFlagSets := o.Flags() 81 | for _, f := range namedFlagSets.FlagSets { 82 | fs.AddFlagSet(f) 83 | } 84 | 85 | cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) 86 | cliflag.SetUsageAndHelpFunc(cmd, namedFlagSets, cols) 87 | 88 | return cmd 89 | } 90 | -------------------------------------------------------------------------------- /cmd/cnf/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "k8s.io/component-base/cli" 10 | "k8s.io/klog/v2" 11 | 12 | "github.com/fleetboard-io/fleetboard/cmd/cnf/app" 13 | ) 14 | 15 | var gracefulStopCh = make(chan os.Signal, 2) 16 | 17 | func main() { 18 | command := app.NewCNFCommand(GracefulStopWithContext()) 19 | code := cli.Run(command) 20 | os.Exit(code) 21 | } 22 | 23 | func GracefulStopWithContext() context.Context { 24 | signal.Notify(gracefulStopCh, syscall.SIGTERM, syscall.SIGINT) 25 | 26 | ctx, cancel := context.WithCancel(context.Background()) 27 | 28 | go func() { 29 | // waiting for os signal to stop the program 30 | oscall := <-gracefulStopCh 31 | klog.Warningf("shutting down, caused by %s", oscall) 32 | cancel() 33 | <-gracefulStopCh 34 | os.Exit(1) 35 | }() 36 | 37 | return ctx 38 | } 39 | -------------------------------------------------------------------------------- /cmd/crossdns/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "k8s.io/apimachinery/pkg/util/rand" 7 | 8 | "github.com/coredns/coredns/core/dnsserver" 9 | "github.com/coredns/coredns/coremain" 10 | _ "github.com/coredns/coredns/plugin/errors" 11 | _ "github.com/coredns/coredns/plugin/health" 12 | _ "github.com/coredns/coredns/plugin/ready" 13 | _ "github.com/coredns/coredns/plugin/trace" 14 | _ "github.com/coredns/coredns/plugin/whoami" 15 | _ "github.com/fleetboard-io/fleetboard/pkg/plugin" 16 | ) 17 | 18 | var directives = []string{ 19 | "trace", 20 | "errors", 21 | "health", 22 | "ready", 23 | "crossdns", 24 | "whoami", 25 | } 26 | 27 | func init() { 28 | dnsserver.Directives = directives 29 | } 30 | 31 | func main() { 32 | rand.Seed(time.Now().UnixNano()) 33 | coremain.Run() 34 | } 35 | -------------------------------------------------------------------------------- /cmd/ep-controller/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | "github.com/sirupsen/logrus" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/util/wait" 14 | "k8s.io/client-go/informers" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/rest" 17 | "k8s.io/client-go/tools/clientcmd" 18 | "k8s.io/client-go/tools/leaderelection" 19 | "k8s.io/client-go/tools/leaderelection/resourcelock" 20 | "k8s.io/klog/v2" 21 | 22 | "github.com/fleetboard-io/fleetboard/pkg/controller/endpointslice" 23 | "github.com/fleetboard-io/fleetboard/pkg/known" 24 | ) 25 | 26 | var ( 27 | kubeconfig string 28 | processIdentify string 29 | namespace = known.FleetboardSystemNamespace 30 | ) 31 | 32 | func main() { 33 | klog.InitFlags(nil) 34 | flag.StringVar(&kubeconfig, "kubeconfig", 35 | filepath.Join(os.Getenv("HOME"), ".kube", "config"), "absolute path to the kubeconfig file") 36 | flag.Parse() 37 | 38 | ns := os.Getenv(known.EnvPodNamespace) 39 | if ns != "" { 40 | namespace = ns 41 | } 42 | config, err := rest.InClusterConfig() 43 | if err != nil { 44 | // fallback to kube config 45 | if val := os.Getenv("KUBECONFIG"); len(val) != 0 { 46 | kubeconfig = val 47 | } 48 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 49 | if err != nil { 50 | logrus.Fatalf("The kubeconfig cannot be loaded: %v\n", err) 51 | } 52 | } 53 | 54 | clientset, err := kubernetes.NewForConfig(config) 55 | if err != nil { 56 | klog.Fatal(err) 57 | } 58 | 59 | // todo select master 60 | processIdentify = uuid.New().String() 61 | 62 | ctx, cancel := context.WithCancel(context.Background()) 63 | stop := make(chan struct{}) 64 | 65 | defer func() { 66 | close(stop) 67 | cancel() 68 | }() 69 | startLeaderElection(ctx, clientset, stop) 70 | select {} 71 | } 72 | 73 | func controllerRun(clientset *kubernetes.Clientset, stop chan struct{}, ctx context.Context) { 74 | factory := informers.NewSharedInformerFactory(clientset, time.Second*5) 75 | factory.Start(stop) 76 | eps := endpointslice.NewController(ctx, 77 | factory.Core().V1().Pods(), 78 | factory.Core().V1().Services(), 79 | factory.Core().V1().Nodes(), 80 | factory.Discovery().V1().EndpointSlices(), 81 | 100, 82 | clientset, 83 | 1*time.Second) 84 | 85 | factory.Start(wait.NeverStop) 86 | 87 | go eps.Run(ctx, 1) 88 | } 89 | 90 | // startLeaderElection 91 | func startLeaderElection(ctx context.Context, clientset *kubernetes.Clientset, stop chan struct{}) { 92 | klog.Infof("[%s]creat master lock for election", processIdentify) 93 | lock := &resourcelock.LeaseLock{ 94 | LeaseMeta: metav1.ObjectMeta{ 95 | Name: "fleetboard-dedinic-controller", 96 | Namespace: namespace, 97 | }, 98 | Client: clientset.CoordinationV1(), 99 | LockConfig: resourcelock.ResourceLockConfig{ 100 | Identity: processIdentify, 101 | }, 102 | } 103 | klog.Infof("[%s]start election...", processIdentify) 104 | leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ 105 | Lock: lock, 106 | ReleaseOnCancel: true, 107 | LeaseDuration: 10 * time.Second, 108 | RenewDeadline: 5 * time.Second, 109 | RetryPeriod: 2 * time.Second, 110 | Callbacks: leaderelection.LeaderCallbacks{ 111 | OnStartedLeading: func(ctx context.Context) { 112 | klog.Infof("[%s] this process is leader,only leader can executor the logic", processIdentify) 113 | controllerRun(clientset, stop, ctx) 114 | }, 115 | OnStoppedLeading: func() { 116 | klog.Infof("[%s] lose leader", processIdentify) 117 | os.Exit(0) 118 | }, 119 | OnNewLeader: func(identity string) { 120 | if identity == processIdentify { 121 | klog.Infof("[%s]get leader result,the current process is leader", processIdentify) 122 | return 123 | } 124 | klog.Infof("[%s]get leader result,leader is : [%s]", processIdentify, identity) 125 | }, 126 | }, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /cmd/proxy/app/contrack.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package app 18 | 19 | import ( 20 | "errors" 21 | "os" 22 | "strconv" 23 | "strings" 24 | 25 | "k8s.io/component-helpers/node/util/sysctl" 26 | "k8s.io/klog/v2" 27 | "k8s.io/utils/mount" 28 | ) 29 | 30 | // Conntracker is an interface to the global sysctl. Descriptions of the various 31 | // sysctl fields can be found here: 32 | // 33 | // https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt 34 | type Conntracker interface { 35 | // SetMax adjusts nf_conntrack_max. 36 | SetMax(max int) error 37 | // SetTCPEstablishedTimeout adjusts nf_conntrack_tcp_timeout_established. 38 | SetTCPEstablishedTimeout(seconds int) error 39 | // SetTCPCloseWaitTimeout nf_conntrack_tcp_timeout_close_wait. 40 | SetTCPCloseWaitTimeout(seconds int) error 41 | } 42 | 43 | type realConntracker struct{} 44 | 45 | var errReadOnlySysFS = errors.New("readOnlySysFS") 46 | 47 | func (rct realConntracker) SetMax(max int) error { 48 | if err := rct.setIntSysCtl("nf_conntrack_max", max); err != nil { 49 | return err 50 | } 51 | klog.InfoS("Setting nf_conntrack_max", "nfConntrackMax", max) 52 | 53 | // Linux does not support writing to /sys/module/nf_conntrack/parameters/hashsize 54 | // when the writer process is not in the initial network namespace 55 | // (https://github.com/torvalds/linux/blob/v4.10/net/netfilter/nf_conntrack_core.c#L1795-L1796). 56 | // Usually that's fine. But in some configurations such as with github.com/kinvolk/kubeadm-nspawn, 57 | // kube-proxy is in another netns. 58 | // Therefore, check if writing in hashsize is necessary and skip the writing if not. 59 | hashsize, err := readIntStringFile("/sys/module/nf_conntrack/parameters/hashsize") 60 | if err != nil { 61 | return err 62 | } 63 | if hashsize >= (max / 4) { 64 | return nil 65 | } 66 | 67 | // sysfs is expected to be mounted as 'rw'. However, it may be 68 | // unexpectedly mounted as 'ro' by docker because of a known docker 69 | // issue (https://github.com/docker/docker/issues/24000). Setting 70 | // conntrack will fail when sysfs is readonly. When that happens, we 71 | // don't set conntrack hashsize and return a special error 72 | // errReadOnlySysFS here. The caller should deal with 73 | // errReadOnlySysFS differently. 74 | writable, err := isSysFSWritable() 75 | if err != nil { 76 | return err 77 | } 78 | if !writable { 79 | return errReadOnlySysFS 80 | } 81 | // TODO: generify this and sysctl to a new sysfs.WriteInt() 82 | klog.InfoS("Setting conntrack hashsize", "conntrackHashsize", max/4) 83 | return writeIntStringFile("/sys/module/nf_conntrack/parameters/hashsize", max/4) 84 | } 85 | 86 | func (rct realConntracker) SetTCPEstablishedTimeout(seconds int) error { 87 | return rct.setIntSysCtl("nf_conntrack_tcp_timeout_established", seconds) 88 | } 89 | 90 | func (rct realConntracker) SetTCPCloseWaitTimeout(seconds int) error { 91 | return rct.setIntSysCtl("nf_conntrack_tcp_timeout_close_wait", seconds) 92 | } 93 | 94 | func (realConntracker) setIntSysCtl(name string, value int) error { 95 | entry := "net/netfilter/" + name 96 | 97 | sys := sysctl.New() 98 | if val, _ := sys.GetSysctl(entry); val != value && val < value { 99 | klog.InfoS("Set sysctl", "entry", entry, "value", value) 100 | if err := sys.SetSysctl(entry, value); err != nil { 101 | return err 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | // isSysFSWritable checks /proc/mounts to see whether sysfs is 'rw' or not. 108 | func isSysFSWritable() (bool, error) { 109 | const permWritable = "rw" 110 | const sysfsDevice = "sysfs" 111 | m := mount.New("" /* default mount path */) 112 | mountPoints, err := m.List() 113 | if err != nil { 114 | klog.ErrorS(err, "Failed to list mount points") 115 | return false, err 116 | } 117 | 118 | for _, mountPoint := range mountPoints { 119 | if mountPoint.Type != sysfsDevice { 120 | continue 121 | } 122 | // Check whether sysfs is 'rw' 123 | if len(mountPoint.Opts) > 0 && mountPoint.Opts[0] == permWritable { 124 | return true, nil 125 | } 126 | klog.ErrorS(nil, "Sysfs is not writable", "mountPoint", mountPoint, "mountOptions", mountPoint.Opts) 127 | return false, errReadOnlySysFS 128 | } 129 | 130 | return false, errors.New("no sysfs mounted") 131 | } 132 | 133 | func readIntStringFile(filename string) (int, error) { 134 | b, err := os.ReadFile(filename) 135 | if err != nil { 136 | return -1, err 137 | } 138 | return strconv.Atoi(strings.TrimSpace(string(b))) 139 | } 140 | 141 | func writeIntStringFile(filename string, value int) error { 142 | return os.WriteFile(filename, []byte(strconv.Itoa(value)), 0640) //nolint:all 143 | } 144 | -------------------------------------------------------------------------------- /cmd/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "k8s.io/component-base/cli" 7 | _ "k8s.io/component-base/logs/json/register" 8 | 9 | "github.com/fleetboard-io/fleetboard/cmd/proxy/app" 10 | ) 11 | 12 | func main() { 13 | command := app.NewProxyCommand() 14 | code := cli.Run(command) 15 | os.Exit(code) 16 | } 17 | -------------------------------------------------------------------------------- /doc/build-images.md: -------------------------------------------------------------------------------- 1 | # How to build FleetBoard 2 | 3 | ## Build the fleetboard image 4 | 5 | If you don't prefer to build `Fleetboard` images by yourself, you can directly pull images from the [ghcr.io/fleetboard-io](https://github.com/orgs/fleetboard-io/packages) registry. 6 | 7 | ### Prerequisites 8 | 9 | - Docker / Podman 10 | 11 | For contributor need to login to the `ghcr.io` registry. 12 | Get a Github Token with `read:packages` and `write:packages` permissions. 13 | On the dev env login into the `ghcr.io` registry with the following command: 14 | 15 | ```bash 16 | # Login to the ghcr.io registry 17 | # GHCR_USER is the Github username 18 | # GHCR_PAT is the Github Personal Access Token 19 | echo $GHCR_PAT | docker login ghcr.io -u $GHCR_USER --password-stdin 20 | ``` 21 | 22 | Clone the repo to local directory 23 | 24 | ```bash 25 | git clone https://github.com/fleetboard-io/fleetboard.git 26 | cd fleetboard 27 | ``` 28 | 29 | ### Build the all Images and push to registry (ghcr.io) 30 | ```bash 31 | make images 32 | ``` 33 | 34 | ### DediNic Image 35 | 36 | ```bash 37 | make dedinic-image 38 | ``` 39 | 40 | ### Ep-Controller Image 41 | 42 | ```bash 43 | make ep-controller-image 44 | ``` 45 | 46 | 47 | ## Build the Fleetboard Binary 48 | 49 | - todo -------------------------------------------------------------------------------- /doc/pic/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fleetboard-io/fleetboard/a9011f9bc1082375a5618a7c65e758367870f64a/doc/pic/arch.png -------------------------------------------------------------------------------- /doc/pic/ovnmaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fleetboard-io/fleetboard/a9011f9bc1082375a5618a7c65e758367870f64a/doc/pic/ovnmaster.png -------------------------------------------------------------------------------- /doc/pic/servicediscovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fleetboard-io/fleetboard/a9011f9bc1082375a5618a7c65e758367870f64a/doc/pic/servicediscovery.png -------------------------------------------------------------------------------- /doc/pic/tunnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fleetboard-io/fleetboard/a9011f9bc1082375a5618a7c65e758367870f64a/doc/pic/tunnel.png -------------------------------------------------------------------------------- /examples/client-import.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-app 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: nginx-app 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx-app 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.14.2 18 | imagePullPolicy: IfNotPresent 19 | ports: 20 | - containerPort: 80 21 | - name: busybox 22 | command: [ "/bin/ash", "-c", "trap : TERM INT; sleep infinity " ] 23 | image: busybox 24 | imagePullPolicy: IfNotPresent 25 | securityContext: 26 | privileged: false 27 | capabilities: 28 | add: [ "NET_ADMIN", "NET_RAW" ] 29 | - name: alpine 30 | command: [ "sleep","infinity" ] 31 | image: alpine/curl 32 | imagePullPolicy: IfNotPresent 33 | 34 | --- 35 | apiVersion: multicluster.x-k8s.io/v1alpha1 36 | kind: ServiceImport 37 | metadata: 38 | name: nginx-svc 39 | spec: 40 | type: "ClusterSetIP" 41 | ports: 42 | - name: http 43 | protocol: TCP 44 | port: 80 45 | sessionAffinity: None 46 | -------------------------------------------------------------------------------- /examples/server-export.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-app 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: nginx-app 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx-app 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.14.2 18 | imagePullPolicy: IfNotPresent 19 | ports: 20 | - containerPort: 80 21 | - name: busybox 22 | command: [ "/bin/ash", "-c", "trap : TERM INT; sleep infinity " ] 23 | image: busybox 24 | imagePullPolicy: IfNotPresent 25 | securityContext: 26 | privileged: false 27 | capabilities: 28 | add: [ "NET_ADMIN", "NET_RAW" ] 29 | - name: alpine 30 | command: [ "sleep","infinity" ] 31 | image: alpine/curl 32 | imagePullPolicy: IfNotPresent 33 | 34 | --- 35 | apiVersion: v1 36 | kind: Service 37 | metadata: 38 | name: nginx-svc 39 | namespace: default 40 | spec: 41 | ports: 42 | - port: 80 43 | protocol: TCP 44 | targetPort: 80 45 | selector: 46 | app: nginx-app 47 | type: ClusterIP 48 | 49 | --- 50 | apiVersion: multicluster.x-k8s.io/v1alpha1 51 | kind: ServiceExport 52 | metadata: 53 | name: nginx-svc 54 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/demo/README.md: -------------------------------------------------------------------------------- 1 | # Start a Quick Demo 2 | 3 | 4 | ## Bring up Virtual Machines 5 | This project uses Vagrant tool for provisioning Virtual Machines automatically. The setup bash script contains the 6 | Linux instructions to install dependencies and plugins required for its usage. This script supports two 7 | Virtualization technologies (Libvirt and VirtualBox). 8 | 9 | $ sudo ./setup.sh -p libvirt 10 | There is a default.yml in the ./config directory which creates multiple vm. 11 | 12 | Once Vagrant is installed, it's possible to provision a vm using the following instructions: 13 | 14 | $ vagrant up 15 | In-depth documentation and use cases of various Vagrant commands Vagrant commands is available on the Vagrant site. 16 | 17 | ## Deploy k8s cluster with FleetBoard 18 | 19 | ```bash 20 | vagrant provision --provision-with deployment 21 | ``` -------------------------------------------------------------------------------- /hack/demo/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # SPDX-license-identifier: Apache-2.0 4 | ############################################################################## 5 | # Copyright (c) 2018 6 | # All rights reserved. This program and the accompanying materials 7 | # are made available under the terms of the Apache License, Version 2.0 8 | # which accompanies this distribution, and is available at 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | ############################################################################## 11 | 12 | box = { 13 | :virtualbox => { :name => 'generic/ubuntu2204', :version => 'v4.3.12'}, 14 | :libvirt => { :name => 'generic/ubuntu2204', :version => 'v4.3.12'} 15 | } 16 | 17 | require 'yaml' 18 | pdf = File.dirname(__FILE__) + '/config/default.yml' 19 | nodes = YAML.load_file(pdf) 20 | 21 | provider = (ENV['VAGRANT_DEFAULT_PROVIDER'] || :libvirt).to_sym 22 | puts "[INFO] Provider: #{provider} " 23 | 24 | if ENV['no_proxy'] != nil or ENV['NO_PROXY'] 25 | $no_proxy = ENV['NO_PROXY'] || ENV['no_proxy'] || "127.0.0.1,localhost" 26 | nodes.each do |node| 27 | $no_proxy += "," + node['ip'] 28 | end 29 | $subnet = "192.168.121" 30 | if provider == :virtualbox 31 | $subnet = "10.0.2" 32 | end 33 | # NOTE: This range is based on vagrant-libvirt network definition CIDR 192.168.121.0/27 34 | (1..31).each do |i| 35 | $no_proxy += ",#{$subnet}.#{i}" 36 | end 37 | end 38 | 39 | Vagrant.configure("2") do |config| 40 | config.vm.box = box[provider][:name] 41 | config.vm.box_version = box[provider][:version] 42 | config.ssh.insert_key = false 43 | 44 | if ENV['http_proxy'] != nil and ENV['https_proxy'] != nil 45 | if Vagrant.has_plugin?('vagrant-proxyconf') 46 | config.proxy.http = ENV['http_proxy'] || ENV['HTTP_PROXY'] || "" 47 | config.proxy.https = ENV['https_proxy'] || ENV['HTTPS_PROXY'] || "" 48 | config.proxy.no_proxy = $no_proxy 49 | config.proxy.enabled = { docker: false } 50 | end 51 | end 52 | config.vm.provider 'libvirt' do |v| 53 | v.nested = true 54 | v.cpu_mode = 'host-passthrough' 55 | v.management_network_address = "192.168.121.0/27" 56 | v.random_hostname = true 57 | end 58 | 59 | sync_type = "virtualbox" 60 | if provider == :libvirt 61 | sync_type = "nfs" 62 | end 63 | 64 | nodes.each do |node| 65 | config.vm.define node['name'] do |nodeconfig| 66 | nodeconfig.vm.hostname = node['name'] 67 | nodeconfig.vm.network :private_network, :ip => node['ip'], :type => :static 68 | nodeconfig.vm.provider 'virtualbox' do |v| 69 | v.customize ["modifyvm", :id, "--memory", node['memory']] 70 | v.customize ["modifyvm", :id, "--cpus", node['cpus']] 71 | if node.has_key? "volumes" 72 | node['volumes'].each do |volume| 73 | $volume_file = "#{node['name']}-#{volume['name']}.vdi" 74 | unless File.exist?($volume_file) 75 | v.customize ['createmedium', 'disk', '--filename', $volume_file, '--size', volume['size']] 76 | end 77 | v.customize ['storageattach', :id, '--storagectl', 'IDE Controller', '--port', 1, '--device', 0, '--type', 'hdd', '--medium', $volume_file] 78 | end 79 | end 80 | end 81 | nodeconfig.vm.provider 'libvirt' do |v| 82 | v.memory = node['memory'] 83 | v.cpus = node['cpus'] 84 | nodeconfig.vm.provision 'shell' do |sh| 85 | sh.path = "node.sh" 86 | if node.has_key? "volumes" 87 | $volume_mounts_dict = '' 88 | node['volumes'].each do |volume| 89 | $volume_mounts_dict += "#{volume['name']}=#{volume['mount']}," 90 | $volume_file = "./#{node['name']}-#{volume['name']}.qcow2" 91 | v.storage :file, :bus => 'sata', :device => volume['name'], :size => volume['size'] 92 | end 93 | sh.args = ['-v', $volume_mounts_dict[0...-1]] 94 | end 95 | end 96 | end 97 | # Only run ansible provisioner once by specifying that it runs 98 | # only for the last node in the list. 99 | if node['name'] == nodes[-1]['name'] 100 | nodeconfig.vm.provision 'deployment', type: 'ansible', run: 'never' do |ansible| 101 | # ansible.verbose = 'vvv' 102 | ansible.limit ='all' # Do not limit ansible hosts to the last node 103 | ansible.playbook = 'deployment.yml' 104 | ansible.groups = { 105 | 'all:vars' => { 'ansible_python_interpreter' => '/usr/bin/python3' } 106 | } 107 | end 108 | 109 | end 110 | end 111 | end 112 | end -------------------------------------------------------------------------------- /hack/demo/config/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # SPDX-license-identifier: Apache-2.0 3 | ############################################################################## 4 | # Copyright (c) 2018 5 | # All rights reserved. This program and the accompanying materials 6 | # are made available under the terms of the Apache License, Version 2.0 7 | # which accompanies this distribution, and is available at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | ############################################################################## 10 | 11 | - name: "hub-node-0" 12 | ip: "10.10.10.13" 13 | memory: 8192 14 | cpus: 4 15 | - name: "child-node-0" 16 | ip: "10.10.10.14" 17 | memory: 8192 18 | cpus: 4 19 | - name: child-node-1" 20 | ip: "10.10.10.15" 21 | memory: 8192 22 | cpus: 4 23 | - name: "child-node-2" 24 | ip: "10.10.10.16" 25 | memory: 8192 26 | cpus: 4 -------------------------------------------------------------------------------- /hack/demo/insecure_keys/key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /hack/demo/insecure_keys/key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | /* 5 | Copyright 2019 The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 21 | package tools 22 | 23 | import _ "k8s.io/code-generator" 24 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 22 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo $GOPATH/src/k8s.io/code-generator)} 23 | echo $SCRIPT_ROOT 24 | # generate the code with: 25 | # --output-base because this script should also be able to run inside the vendor dir of 26 | # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir 27 | # instead of the $GOPATH directly. For normal projects this can be dropped. 28 | bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ 29 | github.com/fleetboard-io/fleetboard/pkg/generated github.com/fleetboard-io/fleetboard/pkg/apis \ 30 | fleetboard.io:v1alpha1 \ 31 | --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \ 32 | --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt 33 | 34 | # To use your own boilerplate text append: 35 | # --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt 36 | -------------------------------------------------------------------------------- /hack/verify-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 22 | 23 | DIFFROOT="${SCRIPT_ROOT}/pkg" 24 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" 25 | _tmp="${SCRIPT_ROOT}/_tmp" 26 | 27 | cleanup() { 28 | rm -rf "${_tmp}" 29 | } 30 | trap "cleanup" EXIT SIGINT 31 | 32 | cleanup 33 | 34 | mkdir -p "${TMP_DIFFROOT}" 35 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 36 | 37 | "${SCRIPT_ROOT}/hack/update-codegen.sh" 38 | echo "diffing ${DIFFROOT} against freshly generated codegen" 39 | ret=0 40 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 41 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 42 | if [[ $ret -eq 0 ]] 43 | then 44 | echo "${DIFFROOT} up to date." 45 | else 46 | echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /pkg/apis/fleetboard.io/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=fleetboard.io 19 | 20 | // Package v1alpha1 is the v1alpha1 version of the API. 21 | package v1alpha1 22 | -------------------------------------------------------------------------------- /pkg/apis/fleetboard.io/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | // SchemeGroupVersion is group version used to register these objects. 10 | var SchemeGroupVersion = schema.GroupVersion{Group: "fleetboard.io", Version: "v1alpha1"} 11 | 12 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind. 13 | func Kind(kind string) schema.GroupKind { 14 | return SchemeGroupVersion.WithKind(kind).GroupKind() 15 | } 16 | 17 | // Resource takes an unqualified resource and returns a Group qualified GroupResource. 18 | func Resource(resource string) schema.GroupResource { 19 | return SchemeGroupVersion.WithResource(resource).GroupResource() 20 | } 21 | 22 | var ( 23 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 24 | AddToScheme = SchemeBuilder.AddToScheme 25 | ) 26 | 27 | // Adds the list of known types to Scheme. 28 | func addKnownTypes(scheme *runtime.Scheme) error { 29 | scheme.AddKnownTypes(SchemeGroupVersion, 30 | &Peer{}, 31 | &PeerList{}, 32 | ) 33 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/apis/fleetboard.io/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +genclient 8 | // +genclient:noStatus 9 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 10 | // +kubebuilder:resource:scope="Namespaced",shortName=peer;peers,categories=fleetboard 11 | type Peer struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty"` 14 | Spec PeerSpec `json:"spec"` 15 | } 16 | 17 | type PeerSpec struct { 18 | // +kubebuilder:validation:MaxLength=63 19 | // +kubebuilder:validation:MinLength=1 20 | ClusterID string `json:"cluster_id"` // name must be unique. 21 | PodCIDR []string `json:"cluster_cidr"` 22 | Endpoint string `json:"endpoint"` // public node address: ip + node port 23 | Port int `json:"port"` 24 | PublicKey string `json:"public_key"` // wire-guard public key 25 | IsHub bool `json:"ishub"` 26 | // the peer will be public and will be connected directly by other cluster. 27 | // isPublic is true only works when `endpoint` is not empty. 28 | // +optional 29 | IsPublic bool `json:"isPublic"` 30 | } 31 | 32 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 33 | type PeerList struct { 34 | metav1.TypeMeta `json:",inline"` 35 | metav1.ListMeta `json:"metadata"` 36 | Items []Peer `json:"items"` 37 | } 38 | -------------------------------------------------------------------------------- /pkg/apis/fleetboard.io/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Fleetboard Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | // Code generated by deepcopy-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *Peer) DeepCopyInto(out *Peer) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | return 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Peer. 37 | func (in *Peer) DeepCopy() *Peer { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(Peer) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *Peer) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *PeerList) DeepCopyInto(out *PeerList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]Peer, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | return 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerList. 70 | func (in *PeerList) DeepCopy() *PeerList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(PeerList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *PeerList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *PeerSpec) DeepCopyInto(out *PeerSpec) { 89 | *out = *in 90 | if in.PodCIDR != nil { 91 | in, out := &in.PodCIDR, &out.PodCIDR 92 | *out = make([]string, len(*in)) 93 | copy(*out, *in) 94 | } 95 | return 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerSpec. 99 | func (in *PeerSpec) DeepCopy() *PeerSpec { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(PeerSpec) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/metrics/cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package metrics 18 | 19 | import ( 20 | "math" 21 | "sync" 22 | 23 | endpointsliceutil "github.com/fleetboard-io/fleetboard/pkg/controller/endpointslice/util" 24 | "k8s.io/apimachinery/pkg/types" 25 | ) 26 | 27 | // NewCache returns a new Cache with the specified endpointsPerSlice. 28 | func NewCache(endpointsPerSlice int32) *Cache { 29 | return &Cache{ 30 | maxEndpointsPerSlice: endpointsPerSlice, 31 | cache: map[types.NamespacedName]*ServicePortCache{}, 32 | } 33 | } 34 | 35 | // Cache tracks values for total numbers of desired endpoints as well as the 36 | // efficiency of EndpointSlice endpoints distribution. 37 | type Cache struct { 38 | // maxEndpointsPerSlice references the maximum number of endpoints that 39 | // should be added to an EndpointSlice. 40 | maxEndpointsPerSlice int32 41 | 42 | // lock protects changes to numEndpoints, numSlicesActual, numSlicesDesired, 43 | // and cache. 44 | lock sync.Mutex 45 | // numEndpoints represents the total number of endpoints stored in 46 | // EndpointSlices. 47 | numEndpoints int 48 | // numSlicesActual represents the total number of EndpointSlices. 49 | numSlicesActual int 50 | // numSlicesDesired represents the desired number of EndpointSlices. 51 | numSlicesDesired int 52 | // cache stores a ServicePortCache grouped by NamespacedNames representing 53 | // Services. 54 | cache map[types.NamespacedName]*ServicePortCache 55 | } 56 | 57 | // ServicePortCache tracks values for total numbers of desired endpoints as well 58 | // as the efficiency of EndpointSlice endpoints distribution for each unique 59 | // Service Port combination. 60 | type ServicePortCache struct { 61 | items map[endpointsliceutil.PortMapKey]EfficiencyInfo 62 | } 63 | 64 | // EfficiencyInfo stores the number of Endpoints and Slices for calculating 65 | // total numbers of desired endpoints and the efficiency of EndpointSlice 66 | // endpoints distribution. 67 | type EfficiencyInfo struct { 68 | Endpoints int 69 | Slices int 70 | } 71 | 72 | // NewServicePortCache initializes and returns a new ServicePortCache. 73 | func NewServicePortCache() *ServicePortCache { 74 | return &ServicePortCache{ 75 | items: map[endpointsliceutil.PortMapKey]EfficiencyInfo{}, 76 | } 77 | } 78 | 79 | // Set updates the ServicePortCache to contain the provided EfficiencyInfo 80 | // for the provided PortMapKey. 81 | func (spc *ServicePortCache) Set(pmKey endpointsliceutil.PortMapKey, eInfo EfficiencyInfo) { 82 | spc.items[pmKey] = eInfo 83 | } 84 | 85 | // totals returns the total number of endpoints and slices represented by a 86 | // ServicePortCache. 87 | func (spc *ServicePortCache) totals(maxEndpointsPerSlice int) (int, int, int) { 88 | var actualSlices, desiredSlices, endpoints int 89 | for _, eInfo := range spc.items { 90 | endpoints += eInfo.Endpoints 91 | actualSlices += eInfo.Slices 92 | desiredSlices += numDesiredSlices(eInfo.Endpoints, maxEndpointsPerSlice) 93 | } 94 | // there is always a placeholder slice 95 | if desiredSlices == 0 { 96 | desiredSlices = 1 97 | } 98 | return actualSlices, desiredSlices, endpoints 99 | } 100 | 101 | // UpdateServicePortCache updates a ServicePortCache in the global cache for a 102 | // given Service and updates the corresponding metrics. 103 | // Parameters: 104 | // * serviceNN refers to a NamespacedName representing the Service. 105 | // * spCache refers to a ServicePortCache for the specified Service. 106 | func (c *Cache) UpdateServicePortCache(serviceNN types.NamespacedName, spCache *ServicePortCache) { 107 | c.lock.Lock() 108 | defer c.lock.Unlock() 109 | 110 | var prevActualSlices, prevDesiredSlices, prevEndpoints int 111 | if existingSPCache, ok := c.cache[serviceNN]; ok { 112 | prevActualSlices, prevDesiredSlices, prevEndpoints = existingSPCache.totals(int(c.maxEndpointsPerSlice)) 113 | } 114 | 115 | currActualSlices, currDesiredSlices, currEndpoints := spCache.totals(int(c.maxEndpointsPerSlice)) 116 | // To keep numEndpoints up to date, add the difference between the number of 117 | // endpoints in the provided spCache and any spCache it might be replacing. 118 | c.numEndpoints = c.numEndpoints + currEndpoints - prevEndpoints 119 | 120 | c.numSlicesDesired += currDesiredSlices - prevDesiredSlices 121 | c.numSlicesActual += currActualSlices - prevActualSlices 122 | 123 | c.cache[serviceNN] = spCache 124 | c.updateMetrics() 125 | } 126 | 127 | // DeleteService removes references of a Service from the global cache and 128 | // updates the corresponding metrics. 129 | func (c *Cache) DeleteService(serviceNN types.NamespacedName) { 130 | c.lock.Lock() 131 | defer c.lock.Unlock() 132 | 133 | if spCache, ok := c.cache[serviceNN]; ok { 134 | actualSlices, desiredSlices, endpoints := spCache.totals(int(c.maxEndpointsPerSlice)) 135 | c.numEndpoints -= endpoints 136 | c.numSlicesDesired -= desiredSlices 137 | c.numSlicesActual -= actualSlices 138 | c.updateMetrics() 139 | delete(c.cache, serviceNN) 140 | } 141 | } 142 | 143 | // updateMetrics updates metrics with the values from this Cache. 144 | // Must be called holding lock. 145 | func (c *Cache) updateMetrics() { 146 | NumEndpointSlices.WithLabelValues().Set(float64(c.numSlicesActual)) 147 | DesiredEndpointSlices.WithLabelValues().Set(float64(c.numSlicesDesired)) 148 | EndpointsDesired.WithLabelValues().Set(float64(c.numEndpoints)) 149 | } 150 | 151 | // numDesiredSlices calculates the number of EndpointSlices that would exist 152 | // with ideal endpoint distribution. 153 | func numDesiredSlices(numEndpoints, maxEndpointsPerSlice int) int { 154 | if numEndpoints == 0 { 155 | return 0 156 | } 157 | if numEndpoints <= maxEndpointsPerSlice { 158 | return 1 159 | } 160 | return int(math.Ceil(float64(numEndpoints) / float64(maxEndpointsPerSlice))) 161 | } 162 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package metrics 18 | 19 | import ( 20 | "sync" 21 | 22 | "k8s.io/component-base/metrics" 23 | "k8s.io/component-base/metrics/legacyregistry" 24 | ) 25 | 26 | // EndpointSliceSubsystem - subsystem name used for Endpoint Slices. 27 | const EndpointSliceSubsystem = "endpoint_slice_controller" 28 | 29 | var ( 30 | // EndpointsAddedPerSync tracks the number of endpoints added on each 31 | // Service sync. 32 | EndpointsAddedPerSync = metrics.NewHistogramVec( 33 | &metrics.HistogramOpts{ 34 | Subsystem: EndpointSliceSubsystem, 35 | Name: "endpoints_added_per_sync", 36 | Help: "Number of endpoints added on each Service sync", 37 | StabilityLevel: metrics.ALPHA, 38 | Buckets: metrics.ExponentialBuckets(2, 2, 15), 39 | }, 40 | []string{}, 41 | ) 42 | // EndpointsRemovedPerSync tracks the number of endpoints removed on each 43 | // Service sync. 44 | EndpointsRemovedPerSync = metrics.NewHistogramVec( 45 | &metrics.HistogramOpts{ 46 | Subsystem: EndpointSliceSubsystem, 47 | Name: "endpoints_removed_per_sync", 48 | Help: "Number of endpoints removed on each Service sync", 49 | StabilityLevel: metrics.ALPHA, 50 | Buckets: metrics.ExponentialBuckets(2, 2, 15), 51 | }, 52 | []string{}, 53 | ) 54 | // EndpointsDesired tracks the total number of desired endpoints. 55 | EndpointsDesired = metrics.NewGaugeVec( 56 | &metrics.GaugeOpts{ 57 | Subsystem: EndpointSliceSubsystem, 58 | Name: "endpoints_desired", 59 | Help: "Number of endpoints desired", 60 | StabilityLevel: metrics.ALPHA, 61 | }, 62 | []string{}, 63 | ) 64 | // NumEndpointSlices tracks the number of EndpointSlices in a cluster. 65 | NumEndpointSlices = metrics.NewGaugeVec( 66 | &metrics.GaugeOpts{ 67 | Subsystem: EndpointSliceSubsystem, 68 | Name: "num_endpoint_slices", 69 | Help: "Number of EndpointSlices", 70 | StabilityLevel: metrics.ALPHA, 71 | }, 72 | []string{}, 73 | ) 74 | // DesiredEndpointSlices tracks the number of EndpointSlices that would 75 | // exist with perfect endpoint allocation. 76 | DesiredEndpointSlices = metrics.NewGaugeVec( 77 | &metrics.GaugeOpts{ 78 | Subsystem: EndpointSliceSubsystem, 79 | Name: "desired_endpoint_slices", 80 | Help: "Number of EndpointSlices that would exist with perfect endpoint allocation", 81 | StabilityLevel: metrics.ALPHA, 82 | }, 83 | []string{}, 84 | ) 85 | 86 | // EndpointSliceChanges tracks the number of changes to Endpoint Slices. 87 | EndpointSliceChanges = metrics.NewCounterVec( 88 | &metrics.CounterOpts{ 89 | Subsystem: EndpointSliceSubsystem, 90 | Name: "changes", 91 | Help: "Number of EndpointSlice changes", 92 | StabilityLevel: metrics.ALPHA, 93 | }, 94 | []string{"operation"}, 95 | ) 96 | 97 | // EndpointSlicesChangedPerSync observes the number of EndpointSlices 98 | // changed per sync. 99 | EndpointSlicesChangedPerSync = metrics.NewHistogramVec( 100 | &metrics.HistogramOpts{ 101 | Subsystem: EndpointSliceSubsystem, 102 | Name: "endpointslices_changed_per_sync", 103 | Help: "Number of EndpointSlices changed on each Service sync", 104 | }, 105 | []string{"topology"}, // either "Auto" or "Disabled" 106 | ) 107 | 108 | // EndpointSliceSyncs tracks the number of sync operations the controller 109 | // runs along with their result. 110 | EndpointSliceSyncs = metrics.NewCounterVec( 111 | &metrics.CounterOpts{ 112 | Subsystem: EndpointSliceSubsystem, 113 | Name: "syncs", 114 | Help: "Number of EndpointSlice syncs", 115 | StabilityLevel: metrics.ALPHA, 116 | }, 117 | []string{"result"}, // either "success", "stale", or "error" 118 | ) 119 | ) 120 | 121 | var registerMetrics sync.Once 122 | 123 | // RegisterMetrics registers EndpointSlice metrics. 124 | func RegisterMetrics() { 125 | registerMetrics.Do(func() { 126 | legacyregistry.MustRegister(EndpointsAddedPerSync) 127 | legacyregistry.MustRegister(EndpointsRemovedPerSync) 128 | legacyregistry.MustRegister(EndpointsDesired) 129 | legacyregistry.MustRegister(NumEndpointSlices) 130 | legacyregistry.MustRegister(DesiredEndpointSlices) 131 | legacyregistry.MustRegister(EndpointSliceChanges) 132 | legacyregistry.MustRegister(EndpointSlicesChangedPerSync) 133 | legacyregistry.MustRegister(EndpointSliceSyncs) 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/topologycache/event.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package topologycache 18 | 19 | // TopologyAwareHints events messages list. 20 | const ( 21 | NoZoneSpecified = "One or more endpoints do not have a zone specified" 22 | NoAllocatedHintsForZones = "No hints allocated for zones" 23 | TopologyAwareHintsEnabled = "Topology Aware Hints has been enabled" 24 | TopologyAwareHintsDisabled = "Topology Aware Hints has been disabled" 25 | InsufficientNodeInfo = "Insufficient Node information: allocatable CPU or zone not " + 26 | "specified on one or more nodes" 27 | NodesReadyInOneZoneOnly = "Nodes only ready in one zone" 28 | InsufficientNumberOfEndpoints = "Insufficient number of endpoints" 29 | MinAllocationExceedsOverloadThreshold = "Unable to allocate minimum required endpoints to each zone without " + 30 | "exceeding overload threshold" 31 | ) 32 | 33 | // EventBuilder let's us construct events in the code. 34 | // We use it to build events and return them from a function instead of publishing them from within it. 35 | // EventType, Reason, and Message fields are equivalent to the v1.Event fields - 36 | // https://pkg.go.dev/k8s.io/api/core/v1#Event. 37 | type EventBuilder struct { 38 | EventType string 39 | Reason string 40 | Message string 41 | } 42 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/topologycache/sliceinfo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package topologycache 18 | 19 | import ( 20 | discovery "k8s.io/api/discovery/v1" 21 | ) 22 | 23 | // SliceInfo stores information about EndpointSlices for the reconciliation 24 | // process. 25 | type SliceInfo struct { 26 | ServiceKey string 27 | AddressType discovery.AddressType 28 | ToCreate []*discovery.EndpointSlice 29 | ToUpdate []*discovery.EndpointSlice 30 | Unchanged []*discovery.EndpointSlice 31 | } 32 | 33 | func (si *SliceInfo) getTotalReadyEndpoints() int { 34 | totalEndpoints := 0 35 | for _, slice := range si.ToCreate { 36 | totalEndpoints += numReadyEndpoints(slice.Endpoints) 37 | } 38 | for _, slice := range si.ToUpdate { 39 | totalEndpoints += numReadyEndpoints(slice.Endpoints) 40 | } 41 | for _, slice := range si.Unchanged { 42 | totalEndpoints += numReadyEndpoints(slice.Endpoints) 43 | } 44 | return totalEndpoints 45 | } 46 | 47 | // getAllocatedHintsByZone sums up the allocated hints we currently have in 48 | // unchanged slices and marks slices for update as necessary. A slice needs to 49 | // be updated if any of the following are true: 50 | // - It has an endpoint without zone hints 51 | // - It has an endpoint hint for a zone that no longer needs any 52 | // - It has endpoint hints that would make the minimum allocations necessary 53 | // impossible with changes to slices that are already being updated or 54 | // created. 55 | func (si *SliceInfo) getAllocatedHintsByZone(allocations map[string]allocation) EndpointZoneInfo { 56 | allocatedHintsByZone := EndpointZoneInfo{} 57 | 58 | // Using filtering in place to remove any endpoints that are no longer 59 | // unchanged (https://github.com/golang/go/wiki/SliceTricks#filter-in-place) 60 | j := 0 61 | for _, slice := range si.Unchanged { 62 | hintsByZone := getHintsByZone(slice, allocatedHintsByZone, allocations) 63 | if hintsByZone == nil { 64 | si.ToUpdate = append(si.ToUpdate, slice.DeepCopy()) 65 | } else { 66 | si.Unchanged[j] = slice 67 | j++ 68 | for zone, numHints := range hintsByZone { 69 | allocatedHintsByZone[zone] += numHints 70 | } 71 | } 72 | } 73 | 74 | si.Unchanged = si.Unchanged[:j] 75 | return allocatedHintsByZone 76 | } 77 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/util/endpointset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "sort" 21 | 22 | discovery "k8s.io/api/discovery/v1" 23 | ) 24 | 25 | // endpointHash is used to uniquely identify endpoints. Only including addresses 26 | // and hostnames as unique identifiers allows us to do more in place updates 27 | // should attributes such as topology or conditions change. 28 | type endpointHash string 29 | type endpointHashObj struct { 30 | Addresses []string 31 | Hostname string 32 | Namespace string 33 | Name string 34 | } 35 | 36 | func hashEndpoint(endpoint *discovery.Endpoint) endpointHash { 37 | sort.Strings(endpoint.Addresses) 38 | hashObj := endpointHashObj{Addresses: endpoint.Addresses} 39 | if endpoint.Hostname != nil { 40 | hashObj.Hostname = *endpoint.Hostname 41 | } 42 | if endpoint.TargetRef != nil { 43 | hashObj.Namespace = endpoint.TargetRef.Namespace 44 | hashObj.Name = endpoint.TargetRef.Name 45 | } 46 | 47 | return endpointHash(deepHashObjectToString(hashObj)) 48 | } 49 | 50 | // EndpointSet provides simple methods for comparing sets of Endpoints. 51 | type EndpointSet map[endpointHash]*discovery.Endpoint 52 | 53 | // Insert adds items to the set. 54 | func (s EndpointSet) Insert(items ...*discovery.Endpoint) EndpointSet { 55 | for _, item := range items { 56 | s[hashEndpoint(item)] = item 57 | } 58 | return s 59 | } 60 | 61 | // Delete removes all items from the set. 62 | func (s EndpointSet) Delete(items ...*discovery.Endpoint) EndpointSet { 63 | for _, item := range items { 64 | delete(s, hashEndpoint(item)) 65 | } 66 | return s 67 | } 68 | 69 | // Has returns true if and only if item is contained in the set. 70 | func (s EndpointSet) Has(item *discovery.Endpoint) bool { 71 | _, contained := s[hashEndpoint(item)] 72 | return contained 73 | } 74 | 75 | // Returns an endpoint matching the hash if contained in the set. 76 | func (s EndpointSet) Get(item *discovery.Endpoint) *discovery.Endpoint { 77 | return s[hashEndpoint(item)] 78 | } 79 | 80 | // UnsortedList returns the slice with contents in random order. 81 | func (s EndpointSet) UnsortedList() []*discovery.Endpoint { 82 | endpoints := make([]*discovery.Endpoint, 0, len(s)) 83 | for _, endpoint := range s { 84 | endpoints = append(endpoints, endpoint) 85 | } 86 | return endpoints 87 | } 88 | 89 | // Returns a single element from the set. 90 | func (s EndpointSet) PopAny() (*discovery.Endpoint, bool) { 91 | for _, endpoint := range s { 92 | s.Delete(endpoint) 93 | return endpoint, true 94 | } 95 | return nil, false 96 | } 97 | 98 | // Len returns the size of the set. 99 | func (s EndpointSet) Len() int { 100 | return len(s) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/controller/endpointslice/util/trigger_time_tracker.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "sync" 21 | "time" 22 | 23 | v1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | // TriggerTimeTracker is used to compute an EndpointsLastChangeTriggerTime 27 | // annotation. See the documentation for that annotation for more details. 28 | // 29 | // Please note that this util may compute a wrong EndpointsLastChangeTriggerTime 30 | // if the same object changes multiple times between two consecutive syncs. 31 | // We're aware of this limitation but we decided to accept it, as fixing it 32 | // would require a major rewrite of the endpoint(Slice) controller and 33 | // Informer framework. Such situations, i.e. frequent updates of the same object 34 | // in a single sync period, should be relatively rare and therefore this util 35 | // should provide a good approximation of the EndpointsLastChangeTriggerTime. 36 | type TriggerTimeTracker struct { 37 | // ServiceStates is a map, indexed by Service object key, storing the last 38 | // known Service object state observed during the most recent call of the 39 | // ComputeEndpointLastChangeTriggerTime function. 40 | ServiceStates map[ServiceKey]ServiceState 41 | 42 | // mutex guarding the serviceStates map. 43 | mutex sync.Mutex 44 | } 45 | 46 | // NewTriggerTimeTracker creates a new instance of the TriggerTimeTracker. 47 | func NewTriggerTimeTracker() *TriggerTimeTracker { 48 | return &TriggerTimeTracker{ 49 | ServiceStates: make(map[ServiceKey]ServiceState), 50 | } 51 | } 52 | 53 | // ServiceKey is a key uniquely identifying a Service. 54 | type ServiceKey struct { 55 | // namespace, name composing a namespaced name - an unique identifier of every Service. 56 | Namespace, Name string 57 | } 58 | 59 | // ServiceState represents a state of an Service object that is known to this util. 60 | type ServiceState struct { 61 | // lastServiceTriggerTime is a service trigger time observed most recently. 62 | lastServiceTriggerTime time.Time 63 | // lastPodTriggerTimes is a map (Pod name -> time) storing the pod trigger 64 | // times that were observed during the most recent call of the 65 | // ComputeEndpointLastChangeTriggerTime function. 66 | lastPodTriggerTimes map[string]time.Time 67 | } 68 | 69 | // ComputeEndpointLastChangeTriggerTime updates the state of the Service/Endpoint 70 | // object being synced and returns the time that should be exported as the 71 | // EndpointsLastChangeTriggerTime annotation. 72 | // 73 | // If the method returns a 'zero' time the EndpointsLastChangeTriggerTime 74 | // annotation shouldn't be exported. 75 | // 76 | // Please note that this function may compute a wrong value if the same object 77 | // (pod/service) changes multiple times between two consecutive syncs. 78 | // 79 | // Important: This method is go-routing safe but only when called for different 80 | // keys. The method shouldn't be called concurrently for the same key! This 81 | // contract is fulfilled in the current implementation of the endpoint(slice) 82 | // controller. 83 | func (t *TriggerTimeTracker) ComputeEndpointLastChangeTriggerTime( 84 | namespace string, service *v1.Service, pods []*v1.Pod) time.Time { 85 | key := ServiceKey{Namespace: namespace, Name: service.Name} 86 | // As there won't be any concurrent calls for the same key, we need to guard 87 | // access only to the serviceStates map. 88 | t.mutex.Lock() 89 | state, wasKnown := t.ServiceStates[key] 90 | t.mutex.Unlock() 91 | 92 | // Update the state before returning. 93 | defer func() { 94 | t.mutex.Lock() 95 | t.ServiceStates[key] = state 96 | t.mutex.Unlock() 97 | }() 98 | 99 | // minChangedTriggerTime is the min trigger time of all trigger times that 100 | // have changed since the last sync. 101 | var minChangedTriggerTime time.Time 102 | podTriggerTimes := make(map[string]time.Time) 103 | for _, pod := range pods { 104 | if podTriggerTime := getPodTriggerTime(pod); !podTriggerTime.IsZero() { 105 | podTriggerTimes[pod.Name] = podTriggerTime 106 | if podTriggerTime.After(state.lastPodTriggerTimes[pod.Name]) { 107 | // Pod trigger time has changed since the last sync, update minChangedTriggerTime. 108 | minChangedTriggerTime = min(minChangedTriggerTime, podTriggerTime) 109 | } 110 | } 111 | } 112 | serviceTriggerTime := getServiceTriggerTime(service) 113 | if serviceTriggerTime.After(state.lastServiceTriggerTime) { 114 | // Service trigger time has changed since the last sync, update minChangedTriggerTime. 115 | minChangedTriggerTime = min(minChangedTriggerTime, serviceTriggerTime) 116 | } 117 | 118 | state.lastPodTriggerTimes = podTriggerTimes 119 | state.lastServiceTriggerTime = serviceTriggerTime 120 | 121 | if !wasKnown { 122 | // New Service, use Service creationTimestamp. 123 | return service.CreationTimestamp.Time 124 | } 125 | 126 | // Regular update of endpoint objects, return min of changed trigger times. 127 | return minChangedTriggerTime 128 | } 129 | 130 | // DeleteService deletes service state stored in this util. 131 | func (t *TriggerTimeTracker) DeleteService(namespace, name string) { 132 | key := ServiceKey{Namespace: namespace, Name: name} 133 | t.mutex.Lock() 134 | defer t.mutex.Unlock() 135 | delete(t.ServiceStates, key) 136 | } 137 | 138 | // getPodTriggerTime returns the time of the pod change (trigger) that resulted 139 | // or will result in the endpoint object change. 140 | func getPodTriggerTime(pod *v1.Pod) (triggerTime time.Time) { 141 | if readyCondition := getPodReadyCondition(&pod.Status); readyCondition != nil { 142 | triggerTime = readyCondition.LastTransitionTime.Time 143 | } 144 | return triggerTime 145 | } 146 | 147 | // getServiceTriggerTime returns the time of the service change (trigger) that 148 | // resulted or will result in the endpoint change. 149 | func getServiceTriggerTime(service *v1.Service) (triggerTime time.Time) { 150 | return service.CreationTimestamp.Time 151 | } 152 | 153 | // min returns minimum of the currentMin and newValue or newValue if the currentMin is not set. 154 | func min(currentMin, newValue time.Time) time.Time { 155 | if currentMin.IsZero() || newValue.Before(currentMin) { 156 | return newValue 157 | } 158 | return currentMin 159 | } 160 | -------------------------------------------------------------------------------- /pkg/controller/syncer/agent.go: -------------------------------------------------------------------------------- 1 | package syncer 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "fmt" 8 | "time" 9 | 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | validations "k8s.io/apimachinery/pkg/util/validation" 13 | "k8s.io/apimachinery/pkg/util/wait" 14 | kubeinformers "k8s.io/client-go/informers" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/rest" 17 | "k8s.io/klog/v2" 18 | mcsclientset "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned" 19 | mcsInformers "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions" 20 | 21 | "github.com/fleetboard-io/fleetboard/pkg/controller/mcs" 22 | "github.com/fleetboard-io/fleetboard/pkg/known" 23 | "github.com/fleetboard-io/fleetboard/pkg/tunnel" 24 | "github.com/fleetboard-io/fleetboard/utils" 25 | "github.com/pkg/errors" 26 | ) 27 | 28 | type AgentConfig struct { 29 | ServiceImportCounterName string 30 | ServiceExportCounterName string 31 | } 32 | 33 | type Syncer struct { 34 | ClusterID string 35 | 36 | HubKubeConfig *rest.Config 37 | SyncerConf known.SyncerConfig 38 | ServiceExportController *mcs.ServiceExportController 39 | ServiceImportController *mcs.ServiceImportController 40 | // local mcs related informer factory 41 | McsInformerFactory mcsInformers.SharedInformerFactory 42 | // local k8s informer factory 43 | KubeInformerFactory kubeinformers.SharedInformerFactory 44 | // local k8s clientset 45 | KubeClientSet kubernetes.Interface 46 | // hub k8s informer factory 47 | HubInformerFactory kubeinformers.SharedInformerFactory 48 | LocalMcsClientSet *mcsclientset.Clientset 49 | } 50 | 51 | // New create a syncer client, it only works in cluster level 52 | func New(spec *tunnel.Specification, syncerConf known.SyncerConfig, hubKubeConfig *rest.Config) (*Syncer, error) { 53 | if errs := validations.IsDNS1123Label(spec.ClusterID); len(errs) > 0 { 54 | return nil, errors.Errorf("%s is not a valid ClusterID %v", spec.ClusterID, errs) 55 | } 56 | 57 | localKubeClientSet := kubernetes.NewForConfigOrDie(syncerConf.LocalRestConfig) 58 | mcsClientSet := mcsclientset.NewForConfigOrDie(syncerConf.LocalRestConfig) 59 | 60 | hubK8sClient := kubernetes.NewForConfigOrDie(hubKubeConfig) 61 | hubInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(hubK8sClient, known.DefaultResync, 62 | kubeinformers.WithNamespace(spec.ShareNamespace)) 63 | // creates the informer factory 64 | kubeInformerFactory := kubeinformers.NewSharedInformerFactory(localKubeClientSet, known.DefaultResync) 65 | mcsInformerFactory := mcsInformers.NewSharedInformerFactory(mcsClientSet, known.DefaultResync) 66 | 67 | serviceExportController, err := mcs.NewServiceExportController(spec.ClusterID, hubK8sClient, 68 | kubeInformerFactory.Discovery().V1().EndpointSlices(), kubeInformerFactory.Core().V1().Services(), 69 | mcsClientSet, mcsInformerFactory) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | serviceImportController, err := mcs.NewServiceImportController(localKubeClientSet, 75 | hubInformerFactory.Discovery().V1().EndpointSlices(), mcsClientSet, mcsInformerFactory) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | syncerConf.LocalClusterID = spec.ClusterID 81 | syncerConf.RemoteNamespace = spec.ShareNamespace 82 | 83 | syncer := &Syncer{ 84 | SyncerConf: syncerConf, 85 | LocalMcsClientSet: mcsClientSet, 86 | HubKubeConfig: hubKubeConfig, 87 | ServiceExportController: serviceExportController, 88 | ServiceImportController: serviceImportController, 89 | KubeInformerFactory: kubeInformerFactory, 90 | KubeClientSet: localKubeClientSet, 91 | McsInformerFactory: mcsInformerFactory, 92 | HubInformerFactory: hubInformerFactory, 93 | } 94 | 95 | return syncer, nil 96 | } 97 | 98 | func (s *Syncer) Start(ctx context.Context) (err error) { 99 | defer utilruntime.HandleCrash() 100 | 101 | // Start the informer factories to begin populating the informer caches 102 | s.KubeInformerFactory.Start(ctx.Done()) 103 | s.McsInformerFactory.Start(ctx.Done()) 104 | s.HubInformerFactory.Start(ctx.Done()) 105 | 106 | klog.Info("Starting Syncer and init virtual service CIDR...") 107 | var cidr string 108 | if cidr, err = s.ServiceImportController.IPAM.InitNewCIDR(s.LocalMcsClientSet, s.KubeClientSet); err != nil { 109 | klog.Errorf("we allocate for virtual service failed for %v", err) 110 | return err 111 | } else { 112 | klog.Infof("we allocate %s for virtual service in this cluster", cidr) 113 | } 114 | 115 | err = s.updateServiceCIDRToCNFPod(ctx, cidr) 116 | if err != nil { 117 | klog.Errorf("Failed to update cnf pod : %v with inner cluster ip cidr", err) 118 | return err 119 | } 120 | 121 | go wait.UntilWithContext(ctx, func(ctx context.Context) { 122 | s.ServiceExportController.Run(ctx, s.SyncerConf.RemoteNamespace) 123 | }, time.Duration(0)) 124 | 125 | go wait.UntilWithContext(ctx, func(ctx context.Context) { 126 | s.ServiceImportController.Run(ctx, s.SyncerConf.RemoteNamespace) 127 | }, time.Duration(0)) 128 | 129 | <-ctx.Done() 130 | return nil 131 | } 132 | 133 | func (s *Syncer) updateServiceCIDRToCNFPod(ctx context.Context, cidr string) error { 134 | // update cidr to all the cnf pod 135 | results, err := s.KubeClientSet.CoreV1().Pods(known.FleetboardSystemNamespace).List(ctx, metav1.ListOptions{ 136 | LabelSelector: known.LabelCNFPod, 137 | }) 138 | if err != nil { 139 | klog.Errorf("Failed to get latest version cnf pood: %v", err) 140 | return err 141 | } 142 | 143 | var errs error 144 | for i := range results.Items { 145 | pod := &results.Items[i] 146 | retryErr := utils.SetSpecificAnnotations(s.KubeClientSet, pod, 147 | []string{known.FleetboardServiceCIDR}, []string{cidr}, true) 148 | if retryErr != nil { 149 | klog.Errorf("Failed to update cnf pod %s: %v", pod.Name, retryErr) 150 | errs = fmt.Errorf("%v; %v", errs, retryErr) 151 | } 152 | } 153 | return errs 154 | } 155 | 156 | func generateSliceName(clusterName, namespace, name string) string { 157 | clusterName = fmt.Sprintf("%s%s%s", clusterName, namespace, name) 158 | hasher := sha256.New() 159 | hasher.Write([]byte(clusterName)) 160 | var namespacePart, namePart string 161 | if len(namespace) > known.MaxNamespaceLength { 162 | namespacePart = namespace[0:known.MaxNamespaceLength] 163 | } else { 164 | namespacePart = namespace 165 | } 166 | 167 | if len(name) > known.MaxNameLength { 168 | namePart = name[0:known.MaxNameLength] 169 | } else { 170 | namePart = name 171 | } 172 | 173 | hashPart := hex.EncodeToString(hasher.Sum(nil)) 174 | 175 | return fmt.Sprintf("%s-%s-%s", namespacePart, namePart, hashPart[8:24]) 176 | } 177 | -------------------------------------------------------------------------------- /pkg/controller/syncer/agent_test.go: -------------------------------------------------------------------------------- 1 | package syncer 2 | 3 | import "testing" 4 | 5 | func Test_generateSliceName(t *testing.T) { 6 | type args struct { 7 | clusterName string 8 | namespace string 9 | name string 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "test1", 18 | args: args{ 19 | name: "demo", 20 | namespace: "test", 21 | clusterName: "cluster1", 22 | }, 23 | want: "dasuidbasi", 24 | }, 25 | 26 | { 27 | name: "test2", 28 | args: args{ 29 | name: "it-a-really-long-name", 30 | namespace: "it-a-really-long-namespace", 31 | clusterName: "cluster1", 32 | }, 33 | want: "dasuidbasi", 34 | }, 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | if got := generateSliceName(tt.args.clusterName, tt.args.namespace, tt.args.name); len(got) > 39 { 39 | t.Errorf("generateSliceName() = %v, want %v", got, tt.want) 40 | } else { 41 | t.Logf("Got name:%s", got) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/dedinic/ipam.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | current "github.com/containernetworking/cni/pkg/types/100" 10 | "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" 11 | "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk" 12 | ) 13 | 14 | func GetIP(rq *CniRequest, ipamConfStr string) (res *current.Result, err error) { 15 | ipamConf, _, err := allocator.LoadIPAMConfig([]byte(ipamConfStr), "") 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | result := ¤t.Result{CNIVersion: current.ImplementedSpecVersion} 21 | 22 | store, err := disk.New(ipamConf.Name, ipamConf.DataDir) 23 | if err != nil { 24 | return nil, err 25 | } 26 | defer store.Close() 27 | 28 | // Keep the allocators we used, so we can release all IPs if an error 29 | // occurs after we start allocating 30 | allocs := []*allocator.IPAllocator{} 31 | 32 | // Store all requested IPs in a map, so we can easily remove ones we use 33 | // and error if some remain 34 | requestedIPs := map[string]net.IP{} // net.IP cannot be a key 35 | 36 | for _, ip := range ipamConf.IPArgs { 37 | requestedIPs[ip.String()] = ip 38 | } 39 | 40 | for idx, rangeSet := range ipamConf.Ranges { 41 | v := rangeSet 42 | allocator := allocator.NewIPAllocator(&v, store, idx) 43 | 44 | // Check to see if there are any custom IPs requested in this range. 45 | var requestedIP net.IP 46 | for k, ip := range requestedIPs { 47 | if v.Contains(ip) { 48 | requestedIP = ip 49 | delete(requestedIPs, k) 50 | break 51 | } 52 | } 53 | 54 | ipConf, err := allocator.Get(rq.ContainerID, rq.IfName, requestedIP) 55 | if err != nil { 56 | // Deallocate all already allocated IPs 57 | for _, alloc := range allocs { 58 | _ = alloc.Release(rq.ContainerID, rq.IfName) 59 | } 60 | return nil, fmt.Errorf("failed to allocate for range %d: %v", idx, err) 61 | } 62 | 63 | allocs = append(allocs, allocator) 64 | 65 | result.IPs = append(result.IPs, ipConf) 66 | } 67 | 68 | // If an IP was requested that wasn't fulfilled, fail 69 | if len(requestedIPs) != 0 { 70 | for _, alloc := range allocs { 71 | _ = alloc.Release(rq.ContainerID, rq.IfName) 72 | } 73 | errstr := "failed to allocate all requested IPs:" 74 | for _, ip := range requestedIPs { 75 | errstr = errstr + " " + ip.String() 76 | } 77 | return nil, errors.New(errstr) 78 | } 79 | 80 | result.Routes = ipamConf.Routes 81 | 82 | return result, nil 83 | } 84 | 85 | func DelIP(rq *CniRequest, ipamConfStr string) error { 86 | ipamConf, _, err := allocator.LoadIPAMConfig([]byte(ipamConfStr), "") 87 | if err != nil { 88 | return err 89 | } 90 | 91 | store, err := disk.New(ipamConf.Name, ipamConf.DataDir) 92 | if err != nil { 93 | return err 94 | } 95 | defer store.Close() 96 | 97 | // Loop through all ranges, releasing all IPs, even if an error occurs 98 | var errorMesgs []string 99 | for idx, rangeset := range ipamConf.Ranges { 100 | set := rangeset 101 | ipAllocator := allocator.NewIPAllocator(&set, store, idx) 102 | 103 | err := ipAllocator.Release(rq.ContainerID, rq.IfName) 104 | if err != nil { 105 | errorMesgs = append(errorMesgs, err.Error()) 106 | } 107 | } 108 | 109 | if errorMesgs != nil { 110 | return errors.New(strings.Join(errorMesgs, ";")) 111 | } 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /pkg/dedinic/ipam_test.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetIP(t *testing.T) { 8 | cniConf := `{ 9 | "cniVersion": "0.3.1", 10 | "name": "dedicate-cni", 11 | "ipam": { 12 | "type": "host-local", 13 | "ranges": [ 14 | [ 15 | { 16 | "subnet": "20.112.0.0/24", 17 | "rangeStart": "20.112.0.10", 18 | "rangeEnd": "20.112.0.200", 19 | "gateway": "20.112.0.1" 20 | } 21 | ] 22 | ] 23 | } 24 | } 25 | ` 26 | rq := &CniRequest{ 27 | PodName: "", 28 | PodNamespace: "", 29 | ContainerID: "ContainerId", 30 | NetNs: "ns", 31 | IfName: "eth-99", 32 | Provider: "", 33 | Routes: nil, 34 | } 35 | 36 | ip, err := GetIP(rq, cniConf) 37 | if err != nil { 38 | t.Fatal(err) 39 | } else { 40 | t.Logf("ip: %v", ip) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/dedinic/kubelet.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strconv" 14 | "time" 15 | 16 | corev1 "k8s.io/api/core/v1" 17 | "k8s.io/klog/v2" 18 | ) 19 | 20 | var defaultAPIAuthTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" // #nosec G101 21 | 22 | type KubeletStub interface { 23 | GetAllPods() (corev1.PodList, error) 24 | } 25 | 26 | type kubeletStub struct { 27 | addr string 28 | port int 29 | scheme string 30 | httpClient *http.Client 31 | token string 32 | } 33 | 34 | func (k kubeletStub) GetAllPods() (corev1.PodList, error) { 35 | urlStr := url.URL{ 36 | Scheme: k.scheme, 37 | Host: net.JoinHostPort(k.addr, strconv.Itoa(k.port)), 38 | Path: "/pods/", 39 | } 40 | podList := corev1.PodList{} 41 | 42 | var bearer = "Bearer " + k.token 43 | req, err := http.NewRequestWithContext(context.TODO(), "GET", urlStr.String(), nil) 44 | if err != nil { 45 | klog.Errorf("Construct http request failed, %v", err) 46 | } 47 | req.Header.Add("Authorization", bearer) 48 | req.Header.Add("Accept", "application/json") 49 | rsp, err := k.httpClient.Do(req) 50 | if err != nil { 51 | klog.Errorf("http get pods err is %v", err) 52 | return podList, err 53 | } 54 | defer rsp.Body.Close() 55 | if rsp.StatusCode != http.StatusOK { 56 | klog.Errorf("response status is not http.StatusOK, err is %v, rsp is %v", err, rsp) 57 | return podList, fmt.Errorf("request %s failed, code %d", urlStr.String(), rsp.StatusCode) 58 | } 59 | 60 | body, err := io.ReadAll(rsp.Body) 61 | if err != nil { 62 | klog.Errorf("http parse response body error, err is %v", err) 63 | return podList, err 64 | } 65 | 66 | // parse json data 67 | err = json.Unmarshal(body, &podList) 68 | if err != nil { 69 | return podList, fmt.Errorf("parse kubelet pod list failed, err: %v", err) 70 | } 71 | return podList, nil 72 | } 73 | 74 | func NewKubeletStub(addr string, port int, scheme string, timeout time.Duration) (KubeletStub, error) { 75 | token, err := os.ReadFile(defaultAPIAuthTokenFile) 76 | if err != nil { 77 | klog.Errorf("no token file, %v", err) 78 | return nil, err 79 | } 80 | 81 | client := &http.Client{ 82 | Timeout: timeout, 83 | Transport: &http.Transport{ 84 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // #nosec G402 85 | }, 86 | } 87 | 88 | return &kubeletStub{ 89 | httpClient: client, 90 | addr: addr, 91 | port: port, 92 | scheme: scheme, 93 | token: string(token), 94 | }, nil 95 | } 96 | -------------------------------------------------------------------------------- /pkg/dedinic/netlink_test.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_GetSubNetMask(t *testing.T) { 8 | mask, err := GetSubNetMask("20.0.0.2/16") 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | t.Logf("mask: %v", mask) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/dedinic/nri.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/containerd/nri/pkg/api" 9 | "github.com/containerd/nri/pkg/stub" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/klog/v2" 12 | 13 | "github.com/fleetboard-io/fleetboard/pkg/known" 14 | ) 15 | 16 | const ( 17 | CNFBridgeName = known.CNFBridgeName 18 | ) 19 | 20 | var ( 21 | NodeCIDR string 22 | TunnelCIDR string 23 | ServiceCIDR string 24 | CNFPodName string 25 | CNFPodNamespace string 26 | CNFPodIP string 27 | CNFBridgeIP string 28 | ) 29 | 30 | type CNIPlugin struct { 31 | Stub stub.Stub 32 | Mask stub.EventMask 33 | kubeClient *kubernetes.Clientset 34 | } 35 | 36 | var ( 37 | csh *cniHandler 38 | _ = stub.ConfigureInterface(&CNIPlugin{}) 39 | ) 40 | 41 | func InitNRIPlugin(kubeClient *kubernetes.Clientset) { 42 | var ( 43 | err error 44 | opts []stub.Option 45 | ) 46 | 47 | pluginName := "hydra" 48 | opts = append(opts, stub.WithPluginName(pluginName)) 49 | pluginIdx := "00" 50 | opts = append(opts, stub.WithPluginIdx(pluginIdx)) 51 | 52 | p := &CNIPlugin{ 53 | kubeClient: kubeClient, 54 | } 55 | events := "runpodsandbox,stoppodsandbox,removepodsandbox" 56 | klog.Info("nri start ....") 57 | 58 | if p.Mask, err = api.ParseEventMask(events); err != nil { 59 | klog.Errorf("nri failed to parse events: %v", err) 60 | } 61 | 62 | if p.Stub, err = stub.New(p, append(opts, stub.WithOnClose(p.OnClose))...); err != nil { 63 | klog.Errorf("nri failed to create nri stub: %v", err) 64 | } 65 | 66 | csh = createCniHandler(kubeClient) 67 | klog.Info(">>>>>>>>>>>>>>>>>>>>> nri CNI Plugin Started - Version Tag 0.0.1 <<<<<<<<<<<<<<<<<<<<<<<<<<") 68 | 69 | err = p.Stub.Run(context.Background()) 70 | if err != nil { 71 | klog.Errorf("nri CNIPlugin exited with error %v", err) 72 | } 73 | } 74 | 75 | func (p *CNIPlugin) Configure(config, runtime, version string) (stub.EventMask, error) { 76 | klog.Infof("got configuration data: %q from runtime %s %s", config, runtime, version) 77 | 78 | return p.Mask, nil 79 | } 80 | 81 | func (p *CNIPlugin) Synchronize(pods []*api.PodSandbox, containers []*api.Container) ([]*api.ContainerUpdate, error) { 82 | for _, pod := range pods { 83 | klog.Infof("[Synchronize]: %v/%v", pod.Namespace, pod.Name) 84 | if isCNFSelf(pod.Namespace, pod.Name) { 85 | klog.Infof("skip the cnf releated pod: %v/%v", pod.Namespace, pod.Name) 86 | continue 87 | } 88 | nsPath, err := GetNSPathFromPod(pod) 89 | if err != nil { 90 | klog.Infof("the namespace path is host-network or cant fount the ns: %v ", err) 91 | continue 92 | } 93 | 94 | klog.V(6).Infof("deal the pod: %v/%v,the namespace path is: %s ", pod.Namespace, pod.Name, nsPath) 95 | 96 | podRequest := &CniRequest{ 97 | PodName: pod.Name, 98 | PodNamespace: pod.Namespace, 99 | ContainerID: pod.GetId(), 100 | NetNs: nsPath, 101 | IfName: known.DediNIC, 102 | } 103 | 104 | if err := csh.handleDel(podRequest); err != nil { 105 | klog.Errorf("delete exist network failed: %v", err) 106 | } 107 | if err := csh.handleAdd(podRequest); err != nil { 108 | klog.Errorf("add network failed: %v", err) 109 | } 110 | } 111 | return nil, nil 112 | } 113 | 114 | func (p *CNIPlugin) Shutdown() { 115 | // dump("Shutdown") 116 | } 117 | 118 | func (p *CNIPlugin) RunPodSandbox(pod *api.PodSandbox) (err error) { 119 | klog.Infof("[RunPodSandbox]: the pod is %s/%s", pod.Namespace, pod.Name) 120 | 121 | nsPath, err := GetNSPathFromPod(pod) 122 | if err != nil { 123 | klog.V(5).Info("the namespace path is hostnetwork ") 124 | return err 125 | } 126 | klog.V(5).Infof("the namespace path is: %s ", nsPath) 127 | klog.V(5).Infof("the pod annotation is: %s ", pod.Annotations) 128 | 129 | podRequest := &CniRequest{ 130 | PodName: pod.Name, 131 | PodNamespace: pod.Namespace, 132 | ContainerID: pod.GetId(), 133 | NetNs: nsPath, 134 | IfName: known.DediNIC, 135 | Provider: known.CNIProviderName, 136 | } 137 | 138 | err = csh.handleAdd(podRequest) 139 | if err != nil { 140 | klog.Errorf("add interface failed for pod: %v", podRequest) 141 | } 142 | return err 143 | } 144 | 145 | func (p *CNIPlugin) StopPodSandbox(pod *api.PodSandbox) error { 146 | klog.Infof("[StopPodSandbox]: the pod is %s--%s", pod.Namespace, pod.Name) 147 | nsPath, err := GetNSPathFromPod(pod) 148 | if err != nil { 149 | klog.Info("the namespace path is host-network or cant fount the ns") 150 | return err 151 | } 152 | klog.Infof("the namespace path is: %s ", nsPath) 153 | podRequest := &CniRequest{ 154 | PodName: pod.Name, 155 | PodNamespace: pod.Namespace, 156 | ContainerID: pod.GetId(), 157 | NetNs: nsPath, 158 | IfName: known.DediNIC, 159 | } 160 | 161 | return csh.handleDel(podRequest) 162 | } 163 | 164 | func (p *CNIPlugin) RemovePodSandbox(pod *api.PodSandbox) error { 165 | klog.Infof("[RemovePodSandbox]: the pod is %s--%s", pod.Namespace, pod.Name) 166 | return nil 167 | } 168 | 169 | func (p *CNIPlugin) OnClose() { 170 | klog.Errorf("cni plugin closed") 171 | os.Exit(0) 172 | } 173 | 174 | func GetNSPathFromPod(pod *api.PodSandbox) (nsPath string, err error) { 175 | for _, ns := range pod.Linux.Namespaces { 176 | if ns.Type == "network" { 177 | nsPath = ns.Path 178 | break 179 | } 180 | } 181 | if nsPath == "" { 182 | klog.V(6).Infof("pod: %v/%v, linux: %v", pod.Namespace, pod.Name, pod.Linux) 183 | return "", fmt.Errorf("nsPath is empty for pod: %s/%s", pod.Namespace, pod.Name) 184 | } 185 | return nsPath, nil 186 | } 187 | -------------------------------------------------------------------------------- /pkg/dedinic/type.go: -------------------------------------------------------------------------------- 1 | package dedinic 2 | 3 | type CniRequest struct { 4 | PodName string `json:"pod_name"` 5 | PodNamespace string `json:"pod_namespace"` 6 | ContainerID string `json:"container_id"` 7 | NetNs string `json:"net_ns"` 8 | Routes []Route `json:"routes"` 9 | IfName string `json:"if_name"` 10 | Provider string `json:"provider"` 11 | } 12 | 13 | // Route represents a requested route 14 | type Route struct { 15 | Destination string `json:"dst"` 16 | Gateway string `json:"gw"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package versioned 19 | 20 | import ( 21 | "fmt" 22 | 23 | fleetboardv1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1" 24 | discovery "k8s.io/client-go/discovery" 25 | rest "k8s.io/client-go/rest" 26 | flowcontrol "k8s.io/client-go/util/flowcontrol" 27 | ) 28 | 29 | type Interface interface { 30 | Discovery() discovery.DiscoveryInterface 31 | FleetboardV1alpha1() fleetboardv1alpha1.FleetboardV1alpha1Interface 32 | } 33 | 34 | // Clientset contains the clients for groups. Each group has exactly one 35 | // version included in a Clientset. 36 | type Clientset struct { 37 | *discovery.DiscoveryClient 38 | fleetboardV1alpha1 *fleetboardv1alpha1.FleetboardV1alpha1Client 39 | } 40 | 41 | // FleetboardV1alpha1 retrieves the FleetboardV1alpha1Client 42 | func (c *Clientset) FleetboardV1alpha1() fleetboardv1alpha1.FleetboardV1alpha1Interface { 43 | return c.fleetboardV1alpha1 44 | } 45 | 46 | // Discovery retrieves the DiscoveryClient 47 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 48 | if c == nil { 49 | return nil 50 | } 51 | return c.DiscoveryClient 52 | } 53 | 54 | // NewForConfig creates a new Clientset for the given config. 55 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 56 | // NewForConfig will generate a rate-limiter in configShallowCopy. 57 | func NewForConfig(c *rest.Config) (*Clientset, error) { 58 | configShallowCopy := *c 59 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 60 | if configShallowCopy.Burst <= 0 { 61 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 62 | } 63 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 64 | } 65 | var cs Clientset 66 | var err error 67 | cs.fleetboardV1alpha1, err = fleetboardv1alpha1.NewForConfig(&configShallowCopy) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return &cs, nil 77 | } 78 | 79 | // NewForConfigOrDie creates a new Clientset for the given config and 80 | // panics if there is an error in the config. 81 | func NewForConfigOrDie(c *rest.Config) *Clientset { 82 | var cs Clientset 83 | cs.fleetboardV1alpha1 = fleetboardv1alpha1.NewForConfigOrDie(c) 84 | 85 | cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) 86 | return &cs 87 | } 88 | 89 | // New creates a new Clientset for the given RESTClient. 90 | func New(c rest.Interface) *Clientset { 91 | var cs Clientset 92 | cs.fleetboardV1alpha1 = fleetboardv1alpha1.New(c) 93 | 94 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 95 | return &cs 96 | } 97 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated clientset. 19 | package versioned 20 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | clientset "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned" 22 | fleetboardv1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1" 23 | fakefleetboardv1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/fake" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "k8s.io/apimachinery/pkg/watch" 26 | "k8s.io/client-go/discovery" 27 | fakediscovery "k8s.io/client-go/discovery/fake" 28 | "k8s.io/client-go/testing" 29 | ) 30 | 31 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 32 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 33 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 34 | // for a real clientset and is mostly useful in simple unit tests. 35 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 36 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 37 | for _, obj := range objects { 38 | if err := o.Add(obj); err != nil { 39 | panic(err) 40 | } 41 | } 42 | 43 | cs := &Clientset{tracker: o} 44 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 45 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 46 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 47 | gvr := action.GetResource() 48 | ns := action.GetNamespace() 49 | watch, err := o.Watch(gvr, ns) 50 | if err != nil { 51 | return false, nil, err 52 | } 53 | return true, watch, nil 54 | }) 55 | 56 | return cs 57 | } 58 | 59 | // Clientset implements clientset.Interface. Meant to be embedded into a 60 | // struct to get a default implementation. This makes faking out just the method 61 | // you want to test easier. 62 | type Clientset struct { 63 | testing.Fake 64 | discovery *fakediscovery.FakeDiscovery 65 | tracker testing.ObjectTracker 66 | } 67 | 68 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 69 | return c.discovery 70 | } 71 | 72 | func (c *Clientset) Tracker() testing.ObjectTracker { 73 | return c.tracker 74 | } 75 | 76 | var ( 77 | _ clientset.Interface = &Clientset{} 78 | _ testing.FakeClient = &Clientset{} 79 | ) 80 | 81 | // FleetboardV1alpha1 retrieves the FleetboardV1alpha1Client 82 | func (c *Clientset) FleetboardV1alpha1() fleetboardv1alpha1.FleetboardV1alpha1Interface { 83 | return &fakefleetboardv1alpha1.FakeFleetboardV1alpha1{Fake: &c.Fake} 84 | } 85 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated fake clientset. 19 | package fake 20 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | fleetboardv1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | ) 28 | 29 | var scheme = runtime.NewScheme() 30 | var codecs = serializer.NewCodecFactory(scheme) 31 | 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | fleetboardv1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package contains the scheme of the automatically generated clientset. 19 | package scheme 20 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package scheme 19 | 20 | import ( 21 | fleetboardv1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | ) 28 | 29 | var Scheme = runtime.NewScheme() 30 | var Codecs = serializer.NewCodecFactory(Scheme) 31 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | fleetboardv1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(Scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated typed clients. 19 | package v1alpha1 20 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // Package fake has the automatically generated clients. 19 | package fake 20 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/fake/fake_fleetboard.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1" 22 | rest "k8s.io/client-go/rest" 23 | testing "k8s.io/client-go/testing" 24 | ) 25 | 26 | type FakeFleetboardV1alpha1 struct { 27 | *testing.Fake 28 | } 29 | 30 | func (c *FakeFleetboardV1alpha1) Peers(namespace string) v1alpha1.PeerInterface { 31 | return &FakePeers{c, namespace} 32 | } 33 | 34 | // RESTClient returns a RESTClient that is used to communicate 35 | // with API server by this client implementation. 36 | func (c *FakeFleetboardV1alpha1) RESTClient() rest.Interface { 37 | var ret *rest.RESTClient 38 | return ret 39 | } 40 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/fake/fake_peer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | "context" 22 | 23 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | labels "k8s.io/apimachinery/pkg/labels" 26 | schema "k8s.io/apimachinery/pkg/runtime/schema" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | testing "k8s.io/client-go/testing" 30 | ) 31 | 32 | // FakePeers implements PeerInterface 33 | type FakePeers struct { 34 | Fake *FakeFleetboardV1alpha1 35 | ns string 36 | } 37 | 38 | var peersResource = schema.GroupVersionResource{Group: "fleetboard.io", Version: "v1alpha1", Resource: "peers"} 39 | 40 | var peersKind = schema.GroupVersionKind{Group: "fleetboard.io", Version: "v1alpha1", Kind: "Peer"} 41 | 42 | // Get takes name of the peer, and returns the corresponding peer object, and an error if there is any. 43 | func (c *FakePeers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) { 44 | obj, err := c.Fake. 45 | Invokes(testing.NewGetAction(peersResource, c.ns, name), &v1alpha1.Peer{}) 46 | 47 | if obj == nil { 48 | return nil, err 49 | } 50 | return obj.(*v1alpha1.Peer), err 51 | } 52 | 53 | // List takes label and field selectors, and returns the list of Peers that match those selectors. 54 | func (c *FakePeers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PeerList, err error) { 55 | obj, err := c.Fake. 56 | Invokes(testing.NewListAction(peersResource, peersKind, c.ns, opts), &v1alpha1.PeerList{}) 57 | 58 | if obj == nil { 59 | return nil, err 60 | } 61 | 62 | label, _, _ := testing.ExtractFromListOptions(opts) 63 | if label == nil { 64 | label = labels.Everything() 65 | } 66 | list := &v1alpha1.PeerList{ListMeta: obj.(*v1alpha1.PeerList).ListMeta} 67 | for _, item := range obj.(*v1alpha1.PeerList).Items { 68 | if label.Matches(labels.Set(item.Labels)) { 69 | list.Items = append(list.Items, item) 70 | } 71 | } 72 | return list, err 73 | } 74 | 75 | // Watch returns a watch.Interface that watches the requested peers. 76 | func (c *FakePeers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 77 | return c.Fake. 78 | InvokesWatch(testing.NewWatchAction(peersResource, c.ns, opts)) 79 | 80 | } 81 | 82 | // Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any. 83 | func (c *FakePeers) Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (result *v1alpha1.Peer, err error) { 84 | obj, err := c.Fake. 85 | Invokes(testing.NewCreateAction(peersResource, c.ns, peer), &v1alpha1.Peer{}) 86 | 87 | if obj == nil { 88 | return nil, err 89 | } 90 | return obj.(*v1alpha1.Peer), err 91 | } 92 | 93 | // Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any. 94 | func (c *FakePeers) Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (result *v1alpha1.Peer, err error) { 95 | obj, err := c.Fake. 96 | Invokes(testing.NewUpdateAction(peersResource, c.ns, peer), &v1alpha1.Peer{}) 97 | 98 | if obj == nil { 99 | return nil, err 100 | } 101 | return obj.(*v1alpha1.Peer), err 102 | } 103 | 104 | // Delete takes name of the peer and deletes it. Returns an error if one occurs. 105 | func (c *FakePeers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 106 | _, err := c.Fake. 107 | Invokes(testing.NewDeleteAction(peersResource, c.ns, name), &v1alpha1.Peer{}) 108 | 109 | return err 110 | } 111 | 112 | // DeleteCollection deletes a collection of objects. 113 | func (c *FakePeers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 114 | action := testing.NewDeleteCollectionAction(peersResource, c.ns, listOpts) 115 | 116 | _, err := c.Fake.Invokes(action, &v1alpha1.PeerList{}) 117 | return err 118 | } 119 | 120 | // Patch applies the patch and returns the patched peer. 121 | func (c *FakePeers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error) { 122 | obj, err := c.Fake. 123 | Invokes(testing.NewPatchSubresourceAction(peersResource, c.ns, name, pt, data, subresources...), &v1alpha1.Peer{}) 124 | 125 | if obj == nil { 126 | return nil, err 127 | } 128 | return obj.(*v1alpha1.Peer), err 129 | } 130 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/fleetboard.io_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 22 | "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/scheme" 23 | rest "k8s.io/client-go/rest" 24 | ) 25 | 26 | type FleetboardV1alpha1Interface interface { 27 | RESTClient() rest.Interface 28 | PeersGetter 29 | } 30 | 31 | // FleetboardV1alpha1Client is used to interact with features provided by the fleetboard.io group. 32 | type FleetboardV1alpha1Client struct { 33 | restClient rest.Interface 34 | } 35 | 36 | func (c *FleetboardV1alpha1Client) Peers(namespace string) PeerInterface { 37 | return newPeers(c, namespace) 38 | } 39 | 40 | // NewForConfig creates a new FleetboardV1alpha1Client for the given config. 41 | func NewForConfig(c *rest.Config) (*FleetboardV1alpha1Client, error) { 42 | config := *c 43 | if err := setConfigDefaults(&config); err != nil { 44 | return nil, err 45 | } 46 | client, err := rest.RESTClientFor(&config) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &FleetboardV1alpha1Client{client}, nil 51 | } 52 | 53 | // NewForConfigOrDie creates a new FleetboardV1alpha1Client for the given config and 54 | // panics if there is an error in the config. 55 | func NewForConfigOrDie(c *rest.Config) *FleetboardV1alpha1Client { 56 | client, err := NewForConfig(c) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return client 61 | } 62 | 63 | // New creates a new FleetboardV1alpha1Client for the given RESTClient. 64 | func New(c rest.Interface) *FleetboardV1alpha1Client { 65 | return &FleetboardV1alpha1Client{c} 66 | } 67 | 68 | func setConfigDefaults(config *rest.Config) error { 69 | gv := v1alpha1.SchemeGroupVersion 70 | config.GroupVersion = &gv 71 | config.APIPath = "/apis" 72 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 73 | 74 | if config.UserAgent == "" { 75 | config.UserAgent = rest.DefaultKubernetesUserAgent() 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // RESTClient returns a RESTClient that is used to communicate 82 | // with API server by this client implementation. 83 | func (c *FleetboardV1alpha1Client) RESTClient() rest.Interface { 84 | if c == nil { 85 | return nil 86 | } 87 | return c.restClient 88 | } 89 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | type PeerExpansion interface{} 21 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/fleetboard.io/v1alpha1/peer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | "time" 23 | 24 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 25 | scheme "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned/scheme" 26 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | rest "k8s.io/client-go/rest" 30 | ) 31 | 32 | // PeersGetter has a method to return a PeerInterface. 33 | // A group's client should implement this interface. 34 | type PeersGetter interface { 35 | Peers(namespace string) PeerInterface 36 | } 37 | 38 | // PeerInterface has methods to work with Peer resources. 39 | type PeerInterface interface { 40 | Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (*v1alpha1.Peer, error) 41 | Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (*v1alpha1.Peer, error) 42 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 43 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 44 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Peer, error) 45 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.PeerList, error) 46 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 47 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error) 48 | PeerExpansion 49 | } 50 | 51 | // peers implements PeerInterface 52 | type peers struct { 53 | client rest.Interface 54 | ns string 55 | } 56 | 57 | // newPeers returns a Peers 58 | func newPeers(c *FleetboardV1alpha1Client, namespace string) *peers { 59 | return &peers{ 60 | client: c.RESTClient(), 61 | ns: namespace, 62 | } 63 | } 64 | 65 | // Get takes name of the peer, and returns the corresponding peer object, and an error if there is any. 66 | func (c *peers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) { 67 | result = &v1alpha1.Peer{} 68 | err = c.client.Get(). 69 | Namespace(c.ns). 70 | Resource("peers"). 71 | Name(name). 72 | VersionedParams(&options, scheme.ParameterCodec). 73 | Do(ctx). 74 | Into(result) 75 | return 76 | } 77 | 78 | // List takes label and field selectors, and returns the list of Peers that match those selectors. 79 | func (c *peers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PeerList, err error) { 80 | var timeout time.Duration 81 | if opts.TimeoutSeconds != nil { 82 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 83 | } 84 | result = &v1alpha1.PeerList{} 85 | err = c.client.Get(). 86 | Namespace(c.ns). 87 | Resource("peers"). 88 | VersionedParams(&opts, scheme.ParameterCodec). 89 | Timeout(timeout). 90 | Do(ctx). 91 | Into(result) 92 | return 93 | } 94 | 95 | // Watch returns a watch.Interface that watches the requested peers. 96 | func (c *peers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 97 | var timeout time.Duration 98 | if opts.TimeoutSeconds != nil { 99 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 100 | } 101 | opts.Watch = true 102 | return c.client.Get(). 103 | Namespace(c.ns). 104 | Resource("peers"). 105 | VersionedParams(&opts, scheme.ParameterCodec). 106 | Timeout(timeout). 107 | Watch(ctx) 108 | } 109 | 110 | // Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any. 111 | func (c *peers) Create(ctx context.Context, peer *v1alpha1.Peer, opts v1.CreateOptions) (result *v1alpha1.Peer, err error) { 112 | result = &v1alpha1.Peer{} 113 | err = c.client.Post(). 114 | Namespace(c.ns). 115 | Resource("peers"). 116 | VersionedParams(&opts, scheme.ParameterCodec). 117 | Body(peer). 118 | Do(ctx). 119 | Into(result) 120 | return 121 | } 122 | 123 | // Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any. 124 | func (c *peers) Update(ctx context.Context, peer *v1alpha1.Peer, opts v1.UpdateOptions) (result *v1alpha1.Peer, err error) { 125 | result = &v1alpha1.Peer{} 126 | err = c.client.Put(). 127 | Namespace(c.ns). 128 | Resource("peers"). 129 | Name(peer.Name). 130 | VersionedParams(&opts, scheme.ParameterCodec). 131 | Body(peer). 132 | Do(ctx). 133 | Into(result) 134 | return 135 | } 136 | 137 | // Delete takes name of the peer and deletes it. Returns an error if one occurs. 138 | func (c *peers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 139 | return c.client.Delete(). 140 | Namespace(c.ns). 141 | Resource("peers"). 142 | Name(name). 143 | Body(&opts). 144 | Do(ctx). 145 | Error() 146 | } 147 | 148 | // DeleteCollection deletes a collection of objects. 149 | func (c *peers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 150 | var timeout time.Duration 151 | if listOpts.TimeoutSeconds != nil { 152 | timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second 153 | } 154 | return c.client.Delete(). 155 | Namespace(c.ns). 156 | Resource("peers"). 157 | VersionedParams(&listOpts, scheme.ParameterCodec). 158 | Timeout(timeout). 159 | Body(&opts). 160 | Do(ctx). 161 | Error() 162 | } 163 | 164 | // Patch applies the patch and returns the patched peer. 165 | func (c *peers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Peer, err error) { 166 | result = &v1alpha1.Peer{} 167 | err = c.client.Patch(pt). 168 | Namespace(c.ns). 169 | Resource("peers"). 170 | Name(name). 171 | SubResource(subresources...). 172 | VersionedParams(&opts, scheme.ParameterCodec). 173 | Body(data). 174 | Do(ctx). 175 | Into(result) 176 | return 177 | } 178 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/fleetboard.io/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package fleetboard 19 | 20 | import ( 21 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/informers/externalversions/fleetboard.io/v1alpha1" 22 | internalinterfaces "github.com/fleetboard-io/fleetboard/pkg/generated/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to each of this group's versions. 26 | type Interface interface { 27 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 28 | V1alpha1() v1alpha1.Interface 29 | } 30 | 31 | type group struct { 32 | factory internalinterfaces.SharedInformerFactory 33 | namespace string 34 | tweakListOptions internalinterfaces.TweakListOptionsFunc 35 | } 36 | 37 | // New returns a new Interface. 38 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 39 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 40 | } 41 | 42 | // V1alpha1 returns a new v1alpha1.Interface. 43 | func (g *group) V1alpha1() v1alpha1.Interface { 44 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/fleetboard.io/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | internalinterfaces "github.com/fleetboard-io/fleetboard/pkg/generated/informers/externalversions/internalinterfaces" 22 | ) 23 | 24 | // Interface provides access to all the informers in this group version. 25 | type Interface interface { 26 | // Peers returns a PeerInformer. 27 | Peers() PeerInformer 28 | } 29 | 30 | type version struct { 31 | factory internalinterfaces.SharedInformerFactory 32 | namespace string 33 | tweakListOptions internalinterfaces.TweakListOptionsFunc 34 | } 35 | 36 | // New returns a new Interface. 37 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 38 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 39 | } 40 | 41 | // Peers returns a PeerInformer. 42 | func (v *version) Peers() PeerInformer { 43 | return &peerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 44 | } 45 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/fleetboard.io/v1alpha1/peer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | time "time" 23 | 24 | fleetboardiov1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 25 | versioned "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned" 26 | internalinterfaces "github.com/fleetboard-io/fleetboard/pkg/generated/informers/externalversions/internalinterfaces" 27 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/generated/listers/fleetboard.io/v1alpha1" 28 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | runtime "k8s.io/apimachinery/pkg/runtime" 30 | watch "k8s.io/apimachinery/pkg/watch" 31 | cache "k8s.io/client-go/tools/cache" 32 | ) 33 | 34 | // PeerInformer provides access to a shared informer and lister for 35 | // Peers. 36 | type PeerInformer interface { 37 | Informer() cache.SharedIndexInformer 38 | Lister() v1alpha1.PeerLister 39 | } 40 | 41 | type peerInformer struct { 42 | factory internalinterfaces.SharedInformerFactory 43 | tweakListOptions internalinterfaces.TweakListOptionsFunc 44 | namespace string 45 | } 46 | 47 | // NewPeerInformer constructs a new informer for Peer type. 48 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 49 | // one. This reduces memory footprint and number of connections to the server. 50 | func NewPeerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 51 | return NewFilteredPeerInformer(client, namespace, resyncPeriod, indexers, nil) 52 | } 53 | 54 | // NewFilteredPeerInformer constructs a new informer for Peer type. 55 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 56 | // one. This reduces memory footprint and number of connections to the server. 57 | func NewFilteredPeerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 58 | return cache.NewSharedIndexInformer( 59 | &cache.ListWatch{ 60 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 61 | if tweakListOptions != nil { 62 | tweakListOptions(&options) 63 | } 64 | return client.FleetboardV1alpha1().Peers(namespace).List(context.TODO(), options) 65 | }, 66 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 67 | if tweakListOptions != nil { 68 | tweakListOptions(&options) 69 | } 70 | return client.FleetboardV1alpha1().Peers(namespace).Watch(context.TODO(), options) 71 | }, 72 | }, 73 | &fleetboardiov1alpha1.Peer{}, 74 | resyncPeriod, 75 | indexers, 76 | ) 77 | } 78 | 79 | func (f *peerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 80 | return NewFilteredPeerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 81 | } 82 | 83 | func (f *peerInformer) Informer() cache.SharedIndexInformer { 84 | return f.factory.InformerFor(&fleetboardiov1alpha1.Peer{}, f.defaultInformer) 85 | } 86 | 87 | func (f *peerInformer) Lister() v1alpha1.PeerLister { 88 | return v1alpha1.NewPeerLister(f.Informer().GetIndexer()) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package externalversions 19 | 20 | import ( 21 | "fmt" 22 | 23 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | cache "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 29 | // sharedInformers based on type 30 | type GenericInformer interface { 31 | Informer() cache.SharedIndexInformer 32 | Lister() cache.GenericLister 33 | } 34 | 35 | type genericInformer struct { 36 | informer cache.SharedIndexInformer 37 | resource schema.GroupResource 38 | } 39 | 40 | // Informer returns the SharedIndexInformer. 41 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 42 | return f.informer 43 | } 44 | 45 | // Lister returns the GenericLister. 46 | func (f *genericInformer) Lister() cache.GenericLister { 47 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 48 | } 49 | 50 | // ForResource gives generic access to a shared informer of the matching type 51 | // TODO extend this to unknown resources with a client pool 52 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 53 | switch resource { 54 | // Group=fleetboard.io, Version=v1alpha1 55 | case v1alpha1.SchemeGroupVersion.WithResource("peers"): 56 | return &genericInformer{resource: resource.GroupResource(), informer: f.Fleetboard().V1alpha1().Peers().Informer()}, nil 57 | 58 | } 59 | 60 | return nil, fmt.Errorf("no informer found for %v", resource) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package internalinterfaces 19 | 20 | import ( 21 | time "time" 22 | 23 | versioned "github.com/fleetboard-io/fleetboard/pkg/generated/clientset/versioned" 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | cache "k8s.io/client-go/tools/cache" 27 | ) 28 | 29 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 30 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 31 | 32 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 33 | type SharedInformerFactory interface { 34 | Start(stopCh <-chan struct{}) 35 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 36 | } 37 | 38 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 39 | type TweakListOptionsFunc func(*v1.ListOptions) 40 | -------------------------------------------------------------------------------- /pkg/generated/listers/fleetboard.io/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | // PeerListerExpansion allows custom methods to be added to 21 | // PeerLister. 22 | type PeerListerExpansion interface{} 23 | 24 | // PeerNamespaceListerExpansion allows custom methods to be added to 25 | // PeerNamespaceLister. 26 | type PeerNamespaceListerExpansion interface{} 27 | -------------------------------------------------------------------------------- /pkg/generated/listers/fleetboard.io/v1alpha1/peer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Fleetboard Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 22 | "k8s.io/apimachinery/pkg/api/errors" 23 | "k8s.io/apimachinery/pkg/labels" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // PeerLister helps list Peers. 28 | // All objects returned here must be treated as read-only. 29 | type PeerLister interface { 30 | // List lists all Peers in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.Peer, err error) 33 | // Peers returns an object that can list and get Peers. 34 | Peers(namespace string) PeerNamespaceLister 35 | PeerListerExpansion 36 | } 37 | 38 | // peerLister implements the PeerLister interface. 39 | type peerLister struct { 40 | indexer cache.Indexer 41 | } 42 | 43 | // NewPeerLister returns a new PeerLister. 44 | func NewPeerLister(indexer cache.Indexer) PeerLister { 45 | return &peerLister{indexer: indexer} 46 | } 47 | 48 | // List lists all Peers in the indexer. 49 | func (s *peerLister) List(selector labels.Selector) (ret []*v1alpha1.Peer, err error) { 50 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 51 | ret = append(ret, m.(*v1alpha1.Peer)) 52 | }) 53 | return ret, err 54 | } 55 | 56 | // Peers returns an object that can list and get Peers. 57 | func (s *peerLister) Peers(namespace string) PeerNamespaceLister { 58 | return peerNamespaceLister{indexer: s.indexer, namespace: namespace} 59 | } 60 | 61 | // PeerNamespaceLister helps list and get Peers. 62 | // All objects returned here must be treated as read-only. 63 | type PeerNamespaceLister interface { 64 | // List lists all Peers in the indexer for a given namespace. 65 | // Objects returned here must be treated as read-only. 66 | List(selector labels.Selector) (ret []*v1alpha1.Peer, err error) 67 | // Get retrieves the Peer from the indexer for a given namespace and name. 68 | // Objects returned here must be treated as read-only. 69 | Get(name string) (*v1alpha1.Peer, error) 70 | PeerNamespaceListerExpansion 71 | } 72 | 73 | // peerNamespaceLister implements the PeerNamespaceLister 74 | // interface. 75 | type peerNamespaceLister struct { 76 | indexer cache.Indexer 77 | namespace string 78 | } 79 | 80 | // List lists all Peers in the indexer for a given namespace. 81 | func (s peerNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Peer, err error) { 82 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 83 | ret = append(ret, m.(*v1alpha1.Peer)) 84 | }) 85 | return ret, err 86 | } 87 | 88 | // Get retrieves the Peer from the indexer for a given namespace and name. 89 | func (s peerNamespaceLister) Get(name string) (*v1alpha1.Peer, error) { 90 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 91 | if err != nil { 92 | return nil, err 93 | } 94 | if !exists { 95 | return nil, errors.NewNotFound(v1alpha1.Resource("peer"), name) 96 | } 97 | return obj.(*v1alpha1.Peer), nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/known/annotations.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | // IPAM annotation const. 4 | const ( 5 | FleetboardConfigPrefix = Fleetboard 6 | FleetboardTunnelCIDR = "fleetboard.io/tunnel_cidr" 7 | FleetboardClusterCIDR = "fleetboard.io/cluster_cidr" 8 | FleetboardNodeCIDR = "fleetboard.io/node_cidr" 9 | FleetboardServiceCIDR = "fleetboard.io/service_cidr" 10 | 11 | PublicKey = "fleetboard.io/public_key" 12 | FleetboardParallelIP = "router.fleetboard.io/parallel_ip" 13 | ) 14 | -------------------------------------------------------------------------------- /pkg/known/constants.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | Fleetboard = "fleetboard" 9 | FleetboardSystemNamespace = "fleetboard-system" 10 | HubClusterName = "hub" 11 | HubSecretName = Fleetboard 12 | ) 13 | 14 | // pod environment variables 15 | const ( 16 | EnvPodName = "FLEETBOARD_PODNAME" 17 | EnvPodNamespace = "FLEETBOARD_PODNAMESPACE" 18 | ) 19 | 20 | const ( 21 | // AppFinalizer are internal finalizer values must be qualified name. 22 | AppFinalizer string = "apps.fleetboard.io/finalizer" 23 | // DefaultResync means the default resync time 24 | DefaultResync = time.Hour * 12 25 | ) 26 | const ( 27 | MaxNamespaceLength = 10 28 | MaxNameLength = 10 29 | ) 30 | 31 | // fields should be ignored when compared 32 | const ( 33 | MetaGeneration = "/metadata/generation" 34 | CreationTimestamp = "/metadata/creationTimestamp" 35 | ManagedFields = "/metadata/managedFields" 36 | MetaUID = "/metadata/uid" 37 | MetaSelflink = "/metadata/selfLink" 38 | MetaResourceVersion = "/metadata/resourceVersion" 39 | 40 | SectionStatus = "/status" 41 | ) 42 | -------------------------------------------------------------------------------- /pkg/known/labels.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | LabelCNFPod = "app=cnf-fleetboard" 5 | 6 | LabelServiceName = "services.fleetboard.io/multi-cluster-service-name" 7 | LabelServiceNameSpace = "services.fleetboard.io/multi-cluster-service-LocalNamespace" 8 | LabelClusterID = "services.fleetboard.io/multi-cluster-cluster-ID" 9 | IsHeadlessKey = "services.fleetboard.io/is-headless" 10 | VirtualClusterIPKey = "services.fleetboard.io/clusterip" 11 | ObjectCreatedByLabel = "fleetboard.io/created-by" 12 | RouterCNFCreatedByLabel = "router.fleetboard.io/cnf=true" 13 | LeaderCNFLabelKey = "router.fleetboard.io/leader" 14 | ) 15 | 16 | const ( 17 | LabelValueManagedBy = "mcs.fleetboard.io" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/known/tunnel.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | type RouteOperation int 4 | 5 | const ( 6 | Add RouteOperation = iota 7 | Delete 8 | ) 9 | 10 | // wireguard tunnel 11 | const ( 12 | // DefaultDeviceName specifies name of WireGuard network device. 13 | DefaultDeviceName = "wg0" 14 | DediNIC = "eth-fleet" 15 | 16 | CNFBridgeName = Fleetboard 17 | CNIProviderName = Fleetboard 18 | 19 | UDPPort = 31820 20 | ) 21 | -------------------------------------------------------------------------------- /pkg/known/types.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | import ( 4 | "k8s.io/client-go/dynamic" 5 | "k8s.io/client-go/rest" 6 | ) 7 | 8 | type SyncerConfig struct { 9 | // LocalRestConfig the REST config used to access the local resources to sync. 10 | LocalRestConfig *rest.Config 11 | 12 | // LocalClient the client used to access local resources to sync. This is optional and is provided for unit testing 13 | // in lieu of the LocalRestConfig. If not specified, one is created from the LocalRestConfig. 14 | LocalClient dynamic.Interface 15 | LocalClusterID string 16 | RemoteNamespace string 17 | } 18 | 19 | type EnvConfig struct { 20 | PodName string 21 | NodeName string 22 | Endpoint string 23 | ClusterID string 24 | BootStrapToken string 25 | } 26 | -------------------------------------------------------------------------------- /pkg/plugin/crossdns.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | v1 "k8s.io/api/discovery/v1" 9 | "k8s.io/apimachinery/pkg/labels" 10 | discoverylisterv1 "k8s.io/client-go/listers/discovery/v1" 11 | "k8s.io/client-go/tools/cache" 12 | "k8s.io/klog/v2" 13 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" 14 | alpha1 "sigs.k8s.io/mcs-api/pkg/client/listers/apis/v1alpha1" 15 | 16 | "github.com/coredns/coredns/plugin" 17 | "github.com/coredns/coredns/plugin/pkg/fall" 18 | "github.com/coredns/coredns/request" 19 | "github.com/fleetboard-io/fleetboard/pkg/known" 20 | "github.com/miekg/dns" 21 | "github.com/pkg/errors" 22 | ) 23 | 24 | type CrossDNS struct { 25 | Next plugin.Handler 26 | Fall fall.F 27 | Zones []string 28 | endpointSlicesLister discoverylisterv1.EndpointSliceLister 29 | epsSynced cache.InformerSynced 30 | SILister alpha1.ServiceImportLister 31 | SISynced cache.InformerSynced 32 | } 33 | 34 | type DNSRecord struct { 35 | IP string 36 | HostName string 37 | ClusterName string 38 | } 39 | 40 | func (c CrossDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { 41 | state := &request.Request{W: w, Req: r} 42 | qname := state.QName() 43 | 44 | zone := plugin.Zones(c.Zones).Matches(qname) 45 | if zone == "" { 46 | klog.Infof("Request does not match configured zones %v", c.Zones) 47 | return plugin.NextOrFailure(c.Name(), c.Next, ctx, state.W, r) 48 | } 49 | 50 | klog.Infof("Request received for %q", qname) 51 | if state.QType() != dns.TypeA && state.QType() != dns.TypeAAAA && state.QType() != dns.TypeSRV { 52 | msg := fmt.Sprintf("Query of type %d is not supported", state.QType()) 53 | klog.Info(msg) 54 | return plugin.NextOrFailure(c.Name(), c.Next, ctx, state.W, r) 55 | } 56 | zone = qname[len(qname)-len(zone):] // maintain case of original query 57 | state.Zone = zone 58 | 59 | pReq, pErr := parseRequest(state) 60 | 61 | if pErr != nil || pReq.podOrSvc != Svc { 62 | // We only support svc type queries i.e. *.svc.* 63 | klog.Infof("Request type %q is not a 'svc' type query - err was %v", pReq.podOrSvc, pErr) 64 | return plugin.NextOrFailure(c.Name(), c.Next, ctx, state.W, r) 65 | } 66 | 67 | return c.getDNSRecord(ctx, zone, state, w, r, pReq) 68 | } 69 | 70 | func (c *CrossDNS) getDNSRecord(ctx context.Context, _ string, state *request.Request, w dns.ResponseWriter, 71 | r *dns.Msg, pReq *recordRequest, 72 | ) (int, error) { 73 | // wait for endpoint slice synced. 74 | if !cache.WaitForCacheSync(ctx.Done(), c.epsSynced, c.SISynced) { 75 | klog.Fatal("unable to sync caches for endpointslices or service import") 76 | } 77 | 78 | si, errGetSI := c.SILister.ServiceImports(pReq.namespace).Get(pReq.service) 79 | if errGetSI != nil { 80 | klog.Errorf("Failed to get service import %v", errGetSI) 81 | return dns.RcodeServerFailure, errors.New("failed to write response") 82 | } 83 | 84 | var dnsRecords []DNSRecord 85 | var err error 86 | var srcEndpointSliceList []*v1.EndpointSlice 87 | if si.Spec.Type == v1alpha1.ClusterSetIP { 88 | if len(si.Spec.IPs) != 0 { 89 | record := DNSRecord{ 90 | IP: si.Spec.IPs[0], 91 | } 92 | dnsRecords = append(dnsRecords, record) 93 | } 94 | } else { 95 | if pReq.cluster != "" { 96 | srcEndpointSliceList, err = c.endpointSlicesLister.EndpointSlices(pReq.namespace).List( 97 | labels.SelectorFromSet( 98 | labels.Set{ 99 | known.LabelServiceNameSpace: pReq.namespace, 100 | known.LabelServiceName: pReq.service, 101 | known.LabelClusterID: pReq.cluster, 102 | })) 103 | } else { 104 | srcEndpointSliceList, err = c.endpointSlicesLister.EndpointSlices(pReq.namespace).List( 105 | labels.SelectorFromSet( 106 | labels.Set{ 107 | known.LabelServiceNameSpace: pReq.namespace, 108 | known.LabelServiceName: pReq.service, 109 | })) 110 | } 111 | if err != nil { 112 | klog.Errorf("Failed to write message %v", err) 113 | return dns.RcodeServerFailure, errors.New("failed to write response") 114 | } 115 | record := c.getAllRecordsFromEndpointslice(srcEndpointSliceList) 116 | dnsRecords = append(dnsRecords, record...) 117 | } 118 | if len(dnsRecords) == 0 { 119 | klog.Infof("Couldn't find a connected cluster or valid IPs for %q", state.QName()) 120 | return c.emptyResponse(state) 121 | } 122 | 123 | records := make([]dns.RR, 0) 124 | 125 | if state.QType() == dns.TypeA { 126 | records = c.createARecords(dnsRecords, state) 127 | } 128 | 129 | a := new(dns.Msg) 130 | a.SetReply(r) 131 | a.Authoritative = true 132 | a.Answer = append(a.Answer, records...) 133 | klog.Infof("Responding to query with '%s'", a.Answer) 134 | 135 | wErr := w.WriteMsg(a) 136 | if wErr != nil { 137 | // Error writing reply msg 138 | klog.Errorf("Failed to write message %#v: %v", a, wErr) 139 | return dns.RcodeServerFailure, errors.New("failed to write response") 140 | } 141 | 142 | return dns.RcodeSuccess, nil 143 | } 144 | 145 | func (c CrossDNS) getAllRecordsFromEndpointslice(slices []*v1.EndpointSlice) []DNSRecord { 146 | records := make([]DNSRecord, 0) 147 | for _, eps := range slices { 148 | for _, endpoint := range eps.Endpoints { 149 | record := DNSRecord{ 150 | IP: endpoint.Addresses[0], 151 | ClusterName: eps.GetLabels()[known.LabelClusterID], 152 | } 153 | records = append(records, record) 154 | } 155 | } 156 | return records 157 | } 158 | 159 | func (c CrossDNS) Name() string { 160 | return "crossdns" 161 | } 162 | 163 | func (c CrossDNS) emptyResponse(state *request.Request) (int, error) { 164 | a := new(dns.Msg) 165 | a.SetReply(state.Req) 166 | 167 | return writeResponse(state, a) 168 | } 169 | 170 | func writeResponse(state *request.Request, a *dns.Msg) (int, error) { 171 | a.Authoritative = true 172 | 173 | wErr := state.W.WriteMsg(a) 174 | if wErr != nil { 175 | klog.Errorf("Failed to write message %#v: %v", a, wErr) 176 | return dns.RcodeServerFailure, errors.New("failed to write response") 177 | } 178 | 179 | return dns.RcodeSuccess, nil 180 | } 181 | 182 | func (c CrossDNS) createARecords(dnsrecords []DNSRecord, state *request.Request) []dns.RR { 183 | records := make([]dns.RR, 0) 184 | 185 | for _, record := range dnsrecords { 186 | dnsRecord := &dns.A{Hdr: dns.RR_Header{ 187 | Name: state.QName(), Rrtype: dns.TypeA, Class: state.QClass(), 188 | Ttl: uint32(5), 189 | }, A: net.ParseIP(record.IP).To4()} 190 | records = append(records, dnsRecord) 191 | } 192 | 193 | return records 194 | } 195 | 196 | var _ plugin.Handler = &CrossDNS{} 197 | -------------------------------------------------------------------------------- /pkg/plugin/parse.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/coredns/coredns/plugin/pkg/dnsutil" 7 | "github.com/coredns/coredns/request" 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | const ( 12 | Svc = "svc" 13 | Pod = "pod" 14 | defaultTTL = uint32(5) 15 | ) 16 | 17 | var errInvalidRequest = errors.New("invalid query name") 18 | 19 | // NOTE: This is taken from github.com/coredns/plugin/kubernetes/parse.go with changes to support use cases in 20 | // https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns 21 | type recordRequest struct { 22 | // The named port from the kubernetes DNS spec, this is the service part (think _https) from a well formed 23 | // SRV record. 24 | port string 25 | // The protocol is usually _udp or _tcp (if set), and comes from the protocol part of a well formed 26 | // SRV record. 27 | protocol string 28 | // The hostname referring to individual pod backing a headless multiclusterservice. 29 | hostname string 30 | // The cluster referring to cluster exporting a multicluster service 31 | cluster string 32 | // The servicename used in Kubernetes. 33 | service string 34 | // The namespace used in Kubernetes. 35 | namespace string 36 | // A each name can be for a pod or a service, here we track what we've seen, either "pod" or "service". 37 | podOrSvc string 38 | } 39 | 40 | // parseRequest parses the qname to find all the elements we need for querying lighthouse. 41 | // 3 Possible cases: 42 | // 1. (host): host.cluster.service.namespace.pod|svc.zone 43 | // 2. (cluster): cluster.service.namespace.pod|svc.zone 44 | // 3. (service): service.namespace.pod|svc.zone 45 | // 46 | // Federations are handled in the federation plugin. And aren't parsed here. 47 | func parseRequest(state *request.Request) (*recordRequest, error) { 48 | r := &recordRequest{} 49 | 50 | base, _ := dnsutil.TrimZone(state.Name(), state.Zone) 51 | // return NODATA for apex queries 52 | if base == "" || base == Svc || base == Pod { 53 | return r, nil 54 | } 55 | 56 | segs := dns.SplitDomainName(base) 57 | // for r.name, r.namespace and r.cluster, we need to know if they have been set or not... 58 | // For cluster: if empty we should skip the cluster check in k.get(). Hence we cannot set if to "*". 59 | // For name: myns.svc.cluster.local != *.myns.svc.cluster.local 60 | // For namespace: svc.cluster.local != *.svc.cluster.local 61 | 62 | // start at the right and fill out recordRequest with the bits we find, so we look for 63 | // pod|svc.namespace.service and then either 64 | // * cluster 65 | // * hostname.cluster 66 | last := len(segs) - 1 67 | 68 | if last < 0 { 69 | return r, nil 70 | } 71 | 72 | r.podOrSvc = segs[last] 73 | if r.podOrSvc != Pod && r.podOrSvc != Svc { 74 | return r, errInvalidRequest 75 | } 76 | 77 | last-- 78 | if last < 0 { 79 | return r, nil 80 | } 81 | 82 | r.namespace = segs[last] 83 | 84 | last-- 85 | if last < 0 { 86 | return r, nil 87 | } 88 | 89 | r.service = segs[last] 90 | 91 | last-- 92 | if last < 0 { 93 | return r, nil 94 | } 95 | 96 | return parseSegments(segs, last, r, state.QType()) 97 | } 98 | 99 | // String return a string representation of r, it just returns all fields concatenated with dots. 100 | // This is mostly used in tests. 101 | func (r *recordRequest) String() string { 102 | s := r.hostname 103 | s += "." + r.cluster 104 | s += "." + r.service 105 | s += "." + r.namespace 106 | s += "." + r.podOrSvc 107 | 108 | return s 109 | } 110 | 111 | func parseSegments(segs []string, count int, r *recordRequest, qType uint16) (*recordRequest, error) { 112 | // Because of ambiguity we check the labels left: 1: a cluster. 2: hostname and cluster. 113 | // Anything else is a query that is too long to answer and can safely be delegated to return an nxdomain. 114 | if qType == dns.TypeA { 115 | switch count { 116 | case 0: // cluster only 117 | r.cluster = segs[count] 118 | case 1: // cluster and hostname 119 | r.cluster = segs[count] 120 | r.hostname = segs[count-1] 121 | default: // too long 122 | return r, errInvalidRequest 123 | } 124 | } else if qType == dns.TypeSRV { 125 | switch count { 126 | case 0: // cluster only 127 | r.cluster = segs[count] 128 | case 1: // endpoint only 129 | r.protocol = stripUnderscore(segs[count]) 130 | r.port = stripUnderscore(segs[count-1]) 131 | 132 | case 2: // service and port 133 | r.cluster = segs[count] 134 | r.protocol = stripUnderscore(segs[count-1]) 135 | r.port = stripUnderscore(segs[count-2]) 136 | default: // too long 137 | return r, errInvalidRequest 138 | } 139 | } 140 | 141 | return r, nil 142 | } 143 | 144 | // stripUnderscore removes a prefixed underscore from s. 145 | func stripUnderscore(s string) string { 146 | if s[0] != '_' { 147 | return s 148 | } 149 | 150 | return s[1:] 151 | } 152 | -------------------------------------------------------------------------------- /pkg/plugin/setup.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "flag" 5 | 6 | "k8s.io/client-go/kubernetes" 7 | "k8s.io/klog/v2" 8 | mcsclientset "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned" 9 | mcsInformers "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions" 10 | 11 | "github.com/coredns/caddy" 12 | "github.com/coredns/coredns/core/dnsserver" 13 | "github.com/coredns/coredns/plugin" 14 | "github.com/fleetboard-io/fleetboard/pkg/known" 15 | "github.com/pkg/errors" 16 | kubeinformers "k8s.io/client-go/informers" 17 | "k8s.io/client-go/tools/clientcmd" 18 | ) 19 | 20 | var ( 21 | masterURL string 22 | kubeconfig string 23 | ) 24 | 25 | // Hook for unit tests. 26 | var buildKubeConfigFunc = clientcmd.BuildConfigFromFlags 27 | 28 | // init registers this plugin within the Caddy plugin framework. It uses "example" as the 29 | // name, and couples it to the Action "setup". 30 | func init() { 31 | caddy.RegisterPlugin("crossdns", caddy.Plugin{ 32 | ServerType: "dns", 33 | Action: setup, 34 | }) 35 | } 36 | 37 | func setup(c *caddy.Controller) error { 38 | klog.Infof("In setup") 39 | 40 | cd, err := CrossDNSParse(c) 41 | if err != nil { 42 | return plugin.Error("crossdns", err) 43 | } 44 | 45 | dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { 46 | cd.Next = next 47 | return cd 48 | }) 49 | 50 | return nil 51 | } 52 | 53 | func CrossDNSParse(c *caddy.Controller) (*CrossDNS, error) { 54 | cfg, err := buildKubeConfigFunc(masterURL, kubeconfig) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "error building kubeconfig") 57 | } 58 | 59 | stopChannel := make(chan struct{}) 60 | cd := &CrossDNS{} 61 | 62 | kubeClient := kubernetes.NewForConfigOrDie(cfg) 63 | mcsClientSet := mcsclientset.NewForConfigOrDie(cfg) 64 | kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, known.DefaultResync) 65 | mcsInformerFactory := mcsInformers.NewSharedInformerFactory(mcsClientSet, known.DefaultResync) 66 | endpointSlicesInformer := kubeInformerFactory.Discovery().V1().EndpointSlices() 67 | siInformer := mcsInformerFactory.Multicluster().V1alpha1().ServiceImports() 68 | 69 | cd.endpointSlicesLister = endpointSlicesInformer.Lister() 70 | cd.SILister = siInformer.Lister() 71 | cd.epsSynced = endpointSlicesInformer.Informer().HasSynced 72 | cd.SISynced = siInformer.Informer().HasSynced 73 | 74 | kubeInformerFactory.Start(stopChannel) 75 | mcsInformerFactory.Start(stopChannel) 76 | 77 | c.OnShutdown(func() error { 78 | close(stopChannel) 79 | return nil 80 | }) 81 | 82 | if err != nil { 83 | klog.Fatalf("failed to add event handler for service import: %v", err) 84 | return nil, err 85 | } 86 | 87 | if c.Next() { 88 | cd.Zones = c.RemainingArgs() 89 | if len(cd.Zones) == 0 { 90 | cd.Zones = make([]string, len(c.ServerBlockKeys)) 91 | copy(cd.Zones, c.ServerBlockKeys) 92 | } 93 | 94 | for i, str := range cd.Zones { 95 | cd.Zones[i] = plugin.Host(str).Normalize() 96 | } 97 | 98 | for c.NextBlock() { 99 | switch c.Val() { 100 | case "fallthrough": 101 | cd.Fall.SetZonesFromArgs(c.RemainingArgs()) 102 | default: 103 | if c.Val() != "}" { 104 | return nil, c.Errf("unknown property '%s'", c.Val()) 105 | } 106 | } 107 | } 108 | } 109 | return cd, nil 110 | } 111 | 112 | func init() { 113 | flag.StringVar(&kubeconfig, "kubeconfig", "", 114 | "Path to a kubeconfig. Only required if out-of-cluster.") 115 | flag.StringVar(&masterURL, "master", "", 116 | "The address of the Kubernetes API server."+ 117 | " Overrides any value in kubeconfig. Only required if out-of-cluster.") 118 | } 119 | -------------------------------------------------------------------------------- /pkg/proxy/ipvs/netlink.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ipvs 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/util/sets" 21 | ) 22 | 23 | // NetLinkHandle for revoke netlink interface 24 | type NetLinkHandle interface { 25 | // EnsureAddressBind checks if address is bound to the interface and, if not, binds it. 26 | // If the address is already bound, return true. 27 | EnsureAddressBind(address, devName string) (exist bool, err error) 28 | // UnbindAddress unbind address from the interface 29 | UnbindAddress(address, devName string) error 30 | // EnsureDummyDevice checks if dummy device is exist and, if not, create one. 31 | // If the dummy device is already exist, return true. 32 | EnsureDummyDevice(devName string) (exist bool, err error) 33 | // DeleteDummyDevice deletes the given dummy device by name. 34 | DeleteDummyDevice(devName string) error 35 | // ListBindAddress will list all IP addresses which are bound in a given interface 36 | ListBindAddress(devName string) ([]string, error) 37 | // GetAllLocalAddresses return all local addresses on the node. 38 | // Only the addresses of the current family are returned. 39 | // IPv6 link-local and loopback addresses are excluded. 40 | GetAllLocalAddresses() (sets.Set[string], error) 41 | // GetLocalAddresses return all local addresses for an interface. 42 | // Only the addresses of the current family are returned. 43 | // IPv6 link-local and loopback addresses are excluded. 44 | GetLocalAddresses(dev string) (sets.Set[string], error) 45 | // GetAllLocalAddressesExcept return all local addresses on the node, except from the passed dev. 46 | // This is not the same as to take the diff between GetAllLocalAddresses and GetLocalAddresses 47 | // since an address can be assigned to many interfaces. This problem raised 48 | // https://github.com/kubernetes/kubernetes/issues/114815 49 | GetAllLocalAddressesExcept(dev string) (sets.Set[string], error) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/proxy/ipvs/netlink_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | /* 4 | Copyright 2017 The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package ipvs 20 | 21 | import ( 22 | "errors" 23 | "fmt" 24 | "net" 25 | 26 | "k8s.io/apimachinery/pkg/util/sets" 27 | "k8s.io/klog/v2" 28 | proxyutil "k8s.io/kubernetes/pkg/proxy/util" 29 | netutils "k8s.io/utils/net" 30 | 31 | "github.com/vishvananda/netlink" 32 | "golang.org/x/sys/unix" 33 | ) 34 | 35 | type netlinkHandle struct { 36 | netlink.Handle 37 | isIPv6 bool 38 | } 39 | 40 | // NewNetLinkHandle will create a new NetLinkHandle 41 | func NewNetLinkHandle(isIPv6 bool) NetLinkHandle { 42 | return &netlinkHandle{netlink.Handle{}, isIPv6} 43 | } 44 | 45 | // EnsureAddressBind checks if address is bound to the interface and, if not, binds it. 46 | // If the address is already bound, return true. 47 | func (h *netlinkHandle) EnsureAddressBind(address, devName string) (exist bool, err error) { 48 | dev, err := h.LinkByName(devName) 49 | if err != nil { 50 | return false, fmt.Errorf("error get interface: %s, err: %v", devName, err) 51 | } 52 | addr := netutils.ParseIPSloppy(address) 53 | if addr == nil { 54 | return false, fmt.Errorf("error parse ip address: %s", address) 55 | } 56 | if err := h.AddrAdd(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil { 57 | // "EEXIST" will be returned if the address is already bound to device 58 | if errors.Is(err, unix.EEXIST) { 59 | return true, nil 60 | } 61 | return false, fmt.Errorf("error bind address: %s to interface: %s, err: %v", address, devName, err) 62 | } 63 | return false, nil 64 | } 65 | 66 | // UnbindAddress makes sure IP address is unbound from the network interface. 67 | func (h *netlinkHandle) UnbindAddress(address, devName string) error { 68 | dev, err := h.LinkByName(devName) 69 | if err != nil { 70 | return fmt.Errorf("error get interface: %s, err: %v", devName, err) 71 | } 72 | addr := netutils.ParseIPSloppy(address) 73 | if addr == nil { 74 | return fmt.Errorf("error parse ip address: %s", address) 75 | } 76 | if err := h.AddrDel(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil { 77 | if !errors.Is(err, unix.ENXIO) { 78 | return fmt.Errorf("error unbind address: %s from interface: %s, err: %v", address, devName, err) 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // EnsureDummyDevice is part of interface 85 | func (h *netlinkHandle) EnsureDummyDevice(devName string) (bool, error) { 86 | _, err := h.LinkByName(devName) 87 | if err == nil { 88 | // found dummy device 89 | return true, nil 90 | } 91 | dummy := &netlink.Dummy{ 92 | LinkAttrs: netlink.LinkAttrs{Name: devName}, 93 | } 94 | return false, h.LinkAdd(dummy) 95 | } 96 | 97 | // DeleteDummyDevice is part of interface. 98 | func (h *netlinkHandle) DeleteDummyDevice(devName string) error { 99 | link, err := h.LinkByName(devName) 100 | if err != nil { 101 | var linkNotFoundError netlink.LinkNotFoundError 102 | ok := errors.As(err, &linkNotFoundError) 103 | if ok { 104 | return nil 105 | } 106 | return fmt.Errorf("error deleting a non-exist dummy device: %s, %v", devName, err) 107 | } 108 | dummy, ok := link.(*netlink.Dummy) 109 | if !ok { 110 | return fmt.Errorf("expect dummy device, got device type: %s", link.Type()) 111 | } 112 | return h.LinkDel(dummy) 113 | } 114 | 115 | // ListBindAddress will list all IP addresses which are bound in a given interface 116 | func (h *netlinkHandle) ListBindAddress(devName string) ([]string, error) { 117 | dev, err := h.LinkByName(devName) 118 | if err != nil { 119 | return nil, fmt.Errorf("error get interface: %s, err: %v", devName, err) 120 | } 121 | addrs, err := h.AddrList(dev, 0) 122 | if err != nil { 123 | return nil, fmt.Errorf("error list bound address of interface: %s, err: %v", devName, err) 124 | } 125 | var ips []string 126 | for _, addr := range addrs { 127 | ips = append(ips, addr.IP.String()) 128 | } 129 | return ips, nil 130 | } 131 | 132 | // GetAllLocalAddresses return all local addresses on the node. 133 | // Only the addresses of the current family are returned. 134 | // IPv6 link-local and loopback addresses are excluded. 135 | func (h *netlinkHandle) GetAllLocalAddresses() (sets.Set[string], error) { 136 | addr, err := net.InterfaceAddrs() 137 | if err != nil { 138 | return nil, fmt.Errorf("could not get addresses: %v", err) 139 | } 140 | return proxyutil.AddressSet(h.isValidForSet, addr), nil 141 | } 142 | 143 | // GetLocalAddresses return all local addresses for an interface. 144 | // Only the addresses of the current family are returned. 145 | // IPv6 link-local and loopback addresses are excluded. 146 | func (h *netlinkHandle) GetLocalAddresses(dev string) (sets.Set[string], error) { 147 | ifi, err := net.InterfaceByName(dev) 148 | if err != nil { 149 | return nil, fmt.Errorf("could not get interface %s: %v", dev, err) 150 | } 151 | addr, err := ifi.Addrs() 152 | if err != nil { 153 | return nil, fmt.Errorf("can't get addresses from %s: %v", ifi.Name, err) 154 | } 155 | return proxyutil.AddressSet(h.isValidForSet, addr), nil 156 | } 157 | 158 | func (h *netlinkHandle) isValidForSet(ip net.IP) bool { 159 | if h.isIPv6 != netutils.IsIPv6(ip) { 160 | return false 161 | } 162 | if h.isIPv6 && ip.IsLinkLocalUnicast() { 163 | return false 164 | } 165 | if ip.IsLoopback() { 166 | return false 167 | } 168 | return true 169 | } 170 | 171 | // GetAllLocalAddressesExcept return all local addresses on the node, 172 | // except from the passed dev. This is not the same as to take the 173 | // diff between GetAllLocalAddresses and GetLocalAddresses since an 174 | // address can be assigned to many interfaces. This problem raised 175 | // https://github.com/kubernetes/kubernetes/issues/114815 176 | func (h *netlinkHandle) GetAllLocalAddressesExcept(dev string) (sets.Set[string], error) { 177 | ifaces, err := net.Interfaces() 178 | if err != nil { 179 | return nil, err 180 | } 181 | var addr []net.Addr 182 | for _, iface := range ifaces { 183 | if iface.Name == dev { 184 | continue 185 | } 186 | ifadr, err := iface.Addrs() 187 | if err != nil { 188 | // This may happen if the interface was deleted. Ignore 189 | // but log the error. 190 | klog.ErrorS(err, "Reading addresses", "interface", iface.Name) 191 | continue 192 | } 193 | addr = append(addr, ifadr...) 194 | } 195 | return proxyutil.AddressSet(h.isValidForSet, addr), nil 196 | } 197 | -------------------------------------------------------------------------------- /pkg/proxy/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package proxy 18 | 19 | import ( 20 | "fmt" 21 | "net" 22 | 23 | v1 "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/types" 25 | "k8s.io/apimachinery/pkg/util/sets" 26 | 27 | "github.com/fleetboard-io/fleetboard/pkg/proxy/config" 28 | ) 29 | 30 | // Provider is the interface provided by proxier implementations. 31 | type Provider interface { 32 | config.EndpointSliceHandler 33 | config.ServiceImportHandler 34 | 35 | // Sync immediately synchronizes the Provider's current state to proxy rules. 36 | Sync() 37 | // SyncLoop runs periodic work. 38 | // This is expected to run as a goroutine or as the main loop of the app. 39 | // It does not return. 40 | SyncLoop() 41 | } 42 | 43 | // ServicePortName carries a namespace + name + portname. This is the unique 44 | // identifier for a load-balanced service. 45 | type ServicePortName struct { 46 | types.NamespacedName 47 | Port string 48 | Protocol v1.Protocol 49 | } 50 | 51 | func (spn ServicePortName) String() string { 52 | return fmt.Sprintf("%s%s", spn.NamespacedName.String(), fmtPortName(spn.Port)) 53 | } 54 | 55 | func fmtPortName(in string) string { 56 | if in == "" { 57 | return "" 58 | } 59 | return fmt.Sprintf(":%s", in) 60 | } 61 | 62 | // ServicePort is an interface which abstracts information about a service. 63 | type ServicePort interface { 64 | // String returns service string. An example format can be: `IP:Port/Protocol`. 65 | String() string 66 | // ClusterIP returns service cluster IP in net.IP format. 67 | ClusterIP() net.IP 68 | // Port returns service port if present. If return 0 means not present. 69 | Port() int 70 | // SessionAffinityType returns service session affinity type 71 | SessionAffinityType() v1.ServiceAffinity 72 | // StickyMaxAgeSeconds returns service max connection age 73 | StickyMaxAgeSeconds() int 74 | // ExternalIPStrings returns service ExternalIPs as a string array. 75 | ExternalIPStrings() []string 76 | // LoadBalancerIPStrings returns service LoadBalancerIPs as a string array. 77 | LoadBalancerIPStrings() []string 78 | // Protocol returns service protocol. 79 | Protocol() v1.Protocol 80 | // LoadBalancerSourceRanges returns service LoadBalancerSourceRanges if present empty array if not 81 | LoadBalancerSourceRanges() []string 82 | // HealthCheckNodePort returns service health check node port if present. If return 0, it means not present. 83 | HealthCheckNodePort() int 84 | // NodePort returns a service Node port if present. If return 0, it means not present. 85 | NodePort() int 86 | // ExternalPolicyLocal returns if a service has only node local endpoints for external traffic. 87 | ExternalPolicyLocal() bool 88 | // InternalPolicyLocal returns if a service has only node local endpoints for internal traffic. 89 | InternalPolicyLocal() bool 90 | // InternalTrafficPolicy returns service InternalTrafficPolicy 91 | InternalTrafficPolicy() *v1.ServiceInternalTrafficPolicy 92 | // HintsAnnotation returns the value of the v1.DeprecatedAnnotationTopologyAwareHints annotation. 93 | HintsAnnotation() string 94 | // ExternallyAccessible returns true if the service port is reachable via something 95 | // other than ClusterIP (NodePort/ExternalIP/LoadBalancer) 96 | ExternallyAccessible() bool 97 | // UsesClusterEndpoints returns true if the service port ever sends traffic to 98 | // endpoints based on "Cluster" traffic policy 99 | UsesClusterEndpoints() bool 100 | // UsesLocalEndpoints returns true if the service port ever sends traffic to 101 | // endpoints based on "Local" traffic policy 102 | UsesLocalEndpoints() bool 103 | } 104 | 105 | // Endpoint in an interface which abstracts information about an endpoint. 106 | // TODO: Rename functions to be consistent with ServicePort. 107 | type Endpoint interface { 108 | // String returns endpoint string. An example format can be: `IP:Port`. 109 | // We take the returned value as ServiceEndpoint.Endpoint. 110 | String() string 111 | // GetIsLocal returns true if the endpoint is running in same host as kube-proxy, otherwise returns false. 112 | GetIsLocal() bool 113 | // IsReady returns true if an endpoint is ready and not terminating. 114 | // This is only set when watching EndpointSlices. If using Endpoints, this is always 115 | // true since only ready endpoints are read from Endpoints. 116 | IsReady() bool 117 | // IsServing returns true if an endpoint is ready. It does not account 118 | // for terminating state. 119 | // This is only set when watching EndpointSlices. If using Endpoints, this is always 120 | // true since only ready endpoints are read from Endpoints. 121 | IsServing() bool 122 | // IsTerminating returns true if an endpoint is terminating. For pods, 123 | // that is any pod with a deletion timestamp. 124 | // This is only set when watching EndpointSlices. If using Endpoints, this is always 125 | // false since terminating endpoints are always excluded from Endpoints. 126 | IsTerminating() bool 127 | // GetZoneHints returns the zone hint for the endpoint. This is based on 128 | // endpoint.hints.forZones[0].name in the EndpointSlice API. 129 | GetZoneHints() sets.Set[string] 130 | // IP returns IP part of the endpoint. 131 | IP() string 132 | // Port returns the Port part of the endpoint. 133 | Port() (int, error) 134 | // Equal checks if two endpoints are equal. 135 | Equal(Endpoint) bool 136 | // GetNodeName returns the node name for the endpoint 137 | GetNodeName() string 138 | // GetZone returns the zone for the endpoint 139 | GetZone() string 140 | } 141 | 142 | // ServiceEndpoint is used to identify a service and one of its endpoint pair. 143 | type ServiceEndpoint struct { 144 | Endpoint string 145 | ServicePortName ServicePortName 146 | } 147 | -------------------------------------------------------------------------------- /pkg/tunnel/options.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | 6 | cliflag "k8s.io/component-base/cli/flag" 7 | "k8s.io/component-base/config" 8 | "k8s.io/component-base/logs" 9 | logsapi "k8s.io/component-base/logs/api/v1" 10 | 11 | "github.com/fleetboard-io/fleetboard/pkg/known" 12 | ) 13 | 14 | type Specification struct { 15 | Options 16 | known.EnvConfig 17 | } 18 | 19 | type Options struct { 20 | // hub secret located 21 | HubSecretNamespace string 22 | // hub secret name 23 | HubSecretName string 24 | // used to share endpoint slices in hub 25 | ShareNamespace string 26 | // true means run as hub 27 | AsHub bool 28 | // true means run as cluster 29 | AsCluster bool 30 | // cidr means which cidr this cluster will use, it is usually empty if is not hub. 31 | CIDR string 32 | // hub url is the service url for hub cluster api-server 33 | HubURL string 34 | 35 | Logs *logs.Options 36 | // ClientConnection specifies the kubeconfig file and client connection 37 | // settings for the proxy server to use when communicating with the apiserver. 38 | ClientConnection config.ClientConnectionConfiguration 39 | } 40 | 41 | // NewOptions creates a new Options object with default parameters 42 | func NewOptions() *Options { 43 | o := Options{ 44 | ClientConnection: config.ClientConnectionConfiguration{}, 45 | Logs: logs.NewOptions(), 46 | } 47 | o.Logs.Verbosity = logsapi.VerbosityLevel(2) 48 | 49 | return &o 50 | } 51 | 52 | func (o *Options) Validate() []error { 53 | var allErrors []error 54 | if o.AsHub && len(o.CIDR) == 0 { 55 | allErrors = append(allErrors, fmt.Errorf("--cidr must be specified when run as hub")) 56 | } 57 | if !o.AsHub && len(o.HubURL) == 0 { 58 | allErrors = append(allErrors, fmt.Errorf("--hub-url must be specified when run as cluster")) 59 | } 60 | 61 | if len(o.ShareNamespace) == 0 { 62 | allErrors = append(allErrors, fmt.Errorf("--shared-namespace must be specified")) 63 | } 64 | 65 | if o.AsCluster && len(o.HubSecretNamespace) == 0 { 66 | allErrors = append(allErrors, fmt.Errorf("--hub-secret-namespace must be specified when run as cluser")) 67 | } 68 | 69 | if o.AsCluster && len(o.HubSecretName) == 0 { 70 | allErrors = append(allErrors, fmt.Errorf("--hub-secret-name must be specified when run as cluser")) 71 | } 72 | 73 | return allErrors 74 | } 75 | 76 | func (o *Options) Complete() error { 77 | if o.AsHub { 78 | if len(o.CIDR) == 0 { 79 | o.CIDR = "20.112.0.0/12" 80 | } 81 | } 82 | return nil 83 | } 84 | 85 | // Flags returns flags for a specific APIServer by section name 86 | func (o *Options) Flags() (fss cliflag.NamedFlagSets) { 87 | logsapi.AddFlags(o.Logs, fss.FlagSet("logs")) 88 | 89 | fs := fss.FlagSet("misc") 90 | fs.StringVar(&o.ClientConnection.Kubeconfig, "kubeconfig", o.ClientConnection.Kubeconfig, 91 | "Path to a kubeconfig file pointing at the 'core' kubernetes server. Only required if out-of-cluster.") 92 | 93 | fs.BoolVar(&o.AsHub, "as-hub", false, "If true, run as hub. [default=false]") 94 | 95 | fs.BoolVar(&o.AsCluster, "as-cluster", false, "If true, run as cluster. [default=false]") 96 | 97 | fs.StringVar(&o.CIDR, "cidr", o.CIDR, "usually global cidr used in multi-cluster ipam,"+ 98 | " or your cluster local ip range") 99 | 100 | fs.StringVar(&o.HubURL, "hub-url", o.HubURL, "hub public url, used by cluster.") 101 | 102 | fs.StringVar(&o.HubSecretNamespace, "hub-secret-namespace", o.HubSecretNamespace, 103 | "hub secret locate namespace to access peer crd.") 104 | 105 | fs.StringVar(&o.HubSecretName, "hub-secret-name", o.HubSecretName, 106 | "hub secret locate name to access peer crd.") 107 | 108 | fs.StringVar(&o.ShareNamespace, "shared-namespace", o.ShareNamespace, 109 | "shared namespace in hub used to share endpoint slices across clusters") 110 | 111 | return fss 112 | } 113 | -------------------------------------------------------------------------------- /pkg/tunnel/tunnel.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "sync" 8 | 9 | "golang.zx2c4.com/wireguard/wgctrl" 10 | "golang.zx2c4.com/wireguard/wgctrl/wgtypes" 11 | v1 "k8s.io/api/core/v1" 12 | "k8s.io/client-go/kubernetes" 13 | "k8s.io/klog/v2" 14 | "k8s.io/utils/ptr" 15 | 16 | "github.com/fleetboard-io/fleetboard/pkg/apis/fleetboard.io/v1alpha1" 17 | "github.com/fleetboard-io/fleetboard/pkg/known" 18 | "github.com/fleetboard-io/fleetboard/utils" 19 | "github.com/pkg/errors" 20 | "github.com/vishvananda/netlink" 21 | ) 22 | 23 | type managedKeys struct { 24 | psk wgtypes.Key 25 | privateKey wgtypes.Key 26 | PublicKey wgtypes.Key 27 | } 28 | 29 | type DaemonCNFTunnelConfig struct { 30 | NodeID string 31 | PodID string 32 | endpointIP string 33 | SecondaryCIDR []string 34 | ServiceCIDR []string 35 | port int 36 | PublicKey []string `json:"public_key"` // wire-guard public key 37 | } 38 | 39 | type Wireguard struct { 40 | interConnections map[string]*v1alpha1.Peer // clusterID -> remote ep connection 41 | innerConnections map[string]*DaemonCNFTunnelConfig // NodeID -> inner cluster connection 42 | sync.Mutex 43 | link netlink.Link // your link 44 | Spec *Specification 45 | client *wgctrl.Client 46 | Keys *managedKeys 47 | } 48 | 49 | func (w *Wireguard) GetAllExistingInnerConnection() map[string]*DaemonCNFTunnelConfig { 50 | return w.innerConnections 51 | } 52 | 53 | func (w *Wireguard) GetAllExistingInterConnection() map[string]*v1alpha1.Peer { 54 | return w.interConnections 55 | } 56 | 57 | func (w *Wireguard) GetExistingInnerConnection(nodeID string) (*DaemonCNFTunnelConfig, bool) { 58 | w.Lock() 59 | defer w.Unlock() 60 | config, found := w.innerConnections[nodeID] 61 | return config, found 62 | } 63 | 64 | func (w *Wireguard) DeleteExistingInnerConnection(nodeID string) { 65 | w.Lock() 66 | defer w.Unlock() 67 | delete(w.innerConnections, nodeID) 68 | } 69 | 70 | func DaemonConfigFromPod(pod *v1.Pod, isLeader bool) *DaemonCNFTunnelConfig { 71 | daemonConfig := &DaemonCNFTunnelConfig{ 72 | NodeID: pod.Spec.NodeName, 73 | PodID: pod.Name, 74 | endpointIP: utils.GetEth0IP(pod), 75 | SecondaryCIDR: utils.GetSpecificAnnotation(pod, known.FleetboardNodeCIDR), 76 | ServiceCIDR: utils.GetSpecificAnnotation(pod, known.FleetboardServiceCIDR), 77 | port: known.UDPPort, 78 | PublicKey: utils.GetSpecificAnnotation(pod, known.PublicKey), 79 | } 80 | if !isLeader { 81 | daemonConfig.SecondaryCIDR = utils.GetSpecificAnnotation(pod, known.FleetboardTunnelCIDR) 82 | } 83 | return daemonConfig 84 | } 85 | 86 | func NewTunnel(spec *Specification) (*Wireguard, error) { 87 | var err error 88 | 89 | w := &Wireguard{ 90 | interConnections: make(map[string]*v1alpha1.Peer), 91 | innerConnections: make(map[string]*DaemonCNFTunnelConfig), 92 | Keys: &managedKeys{}, 93 | Spec: spec, 94 | } 95 | 96 | if err = w.setWGLink(); err != nil { 97 | return nil, errors.Wrap(err, "failed to add WireGuard link") 98 | } 99 | 100 | // Create the wireguard controller. 101 | if w.client, err = wgctrl.New(); err != nil { 102 | if os.IsNotExist(err) { 103 | return nil, fmt.Errorf("wgctrl is not available on this system") 104 | } 105 | 106 | return nil, errors.Wrap(err, "failed to open wgctl client") 107 | } 108 | 109 | defer func() { 110 | if err != nil { 111 | if e := w.client.Close(); e != nil { 112 | klog.Errorf("failed to close wgctrl client: %v", e) 113 | } 114 | w.client = nil 115 | } 116 | }() 117 | 118 | // set wire-guard Keys. 119 | if err = w.setKeyPair(); err != nil { 120 | return nil, err 121 | } 122 | // Configure the device - still not up. 123 | peerConfigs := make([]wgtypes.PeerConfig, 0) 124 | cfg := wgtypes.Config{ 125 | PrivateKey: &w.Keys.privateKey, 126 | ListenPort: ptr.To(known.UDPPort), 127 | FirewallMark: nil, 128 | ReplacePeers: true, 129 | Peers: peerConfigs, 130 | } 131 | 132 | if err = w.client.ConfigureDevice(known.DefaultDeviceName, cfg); err != nil { 133 | return nil, errors.Wrap(err, "failed to configure WireGuard device") 134 | } 135 | 136 | return w, err 137 | } 138 | 139 | func (w *Wireguard) Init(client *kubernetes.Clientset) error { 140 | w.Lock() 141 | defer w.Unlock() 142 | 143 | klog.Info("Initializing WireGuard device...") 144 | 145 | l, err := net.InterfaceByName(known.DefaultDeviceName) 146 | if err != nil { 147 | return errors.Wrapf(err, "cannot get wireguard link by name %s", known.DefaultDeviceName) 148 | } 149 | 150 | d, err := w.client.Device(known.DefaultDeviceName) 151 | if err != nil { 152 | return errors.Wrap(err, "wgctrl cannot find WireGuard device") 153 | } 154 | 155 | // IP link set $DefaultDeviceName up. 156 | if upErr := netlink.LinkSetUp(w.link); upErr != nil { 157 | return errors.Wrap(upErr, "failed to bring up WireGuard device") 158 | } 159 | 160 | klog.Infof("WireGuard device %s, is up on i/f number %d, listening on port :%d, with key %s", 161 | w.link.Attrs().Name, l.Index, d.ListenPort, d.PublicKey) 162 | 163 | return utils.AddAnnotationToSelf(client, known.PublicKey, w.Keys.PublicKey.String(), true) 164 | } 165 | 166 | func CreateAndUpTunnel(k8sClient *kubernetes.Clientset, agentSpec *Specification) (*Wireguard, error) { 167 | w, err := NewTunnel(agentSpec) 168 | if err != nil { 169 | klog.Fatal(err) 170 | return nil, err 171 | } 172 | // up the interface. 173 | if errInit := w.Init(k8sClient); errInit != nil { 174 | klog.Fatal(errInit) 175 | return nil, errInit 176 | } 177 | return w, nil 178 | } 179 | 180 | func (w *Wireguard) Cleanup() error { 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /utils/deploy.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | pkgruntime "k8s.io/apimachinery/pkg/runtime" 8 | "k8s.io/klog/v2" 9 | 10 | "github.com/fleetboard-io/fleetboard/pkg/known" 11 | "github.com/mattbaird/jsonpatch" 12 | ) 13 | 14 | // current is deployed resource, modified is changed resource. 15 | // ignoreAdd is true if you want to ignore add action. 16 | // The function will return the bool value to indicate whether to sync back the current object. 17 | func ResourceNeedResync(current pkgruntime.Object, modified pkgruntime.Object, ignoreAdd bool) bool { 18 | currentBytes, err := json.Marshal(current) 19 | if err != nil { 20 | klog.ErrorDepth(5, fmt.Sprintf("Error marshal json: %v", err)) 21 | return false 22 | } 23 | 24 | modifiedBytes, err := json.Marshal(modified) 25 | if err != nil { 26 | klog.ErrorDepth(5, fmt.Sprintf("Error marshal json: %v", err)) 27 | return false 28 | } 29 | 30 | patch, err := jsonpatch.CreatePatch(currentBytes, modifiedBytes) 31 | if err != nil { 32 | klog.ErrorDepth(5, fmt.Sprintf("Error creating JSON patch: %v", err)) 33 | return false 34 | } 35 | for _, operation := range patch { 36 | // filter ignored paths 37 | if shouldPatchBeIgnored(operation) { 38 | continue 39 | } 40 | 41 | switch operation.Operation { 42 | case "add": 43 | if ignoreAdd { 44 | continue 45 | } else { 46 | return true 47 | } 48 | case "remove", "replace": 49 | return true 50 | default: 51 | // skip other operations, like "copy", "move" and "test" 52 | continue 53 | } 54 | } 55 | 56 | return false 57 | } 58 | 59 | // shouldPatchBeIgnored used to decide if this patch operation should be ignored. 60 | func shouldPatchBeIgnored(operation jsonpatch.JsonPatchOperation) bool { 61 | // some fields need to be ignore like meta.selfLink, meta.resourceVersion. 62 | if ContainsString(fieldsToBeIgnored(), operation.Path) { 63 | return true 64 | } 65 | // some sections like status section need to be ignored. 66 | if ContainsPrefix(sectionToBeIgnored(), operation.Path) { 67 | return true 68 | } 69 | 70 | return false 71 | } 72 | 73 | func sectionToBeIgnored() []string { 74 | return []string{ 75 | known.SectionStatus, 76 | } 77 | } 78 | 79 | func fieldsToBeIgnored() []string { 80 | return []string{ 81 | known.MetaGeneration, 82 | known.CreationTimestamp, 83 | known.ManagedFields, 84 | known.MetaUID, 85 | known.MetaSelflink, 86 | known.MetaResourceVersion, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /utils/kubeconfig.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/client-go/rest" 7 | "k8s.io/client-go/tools/clientcmd" 8 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 9 | ) 10 | 11 | // createBasicKubeConfig creates a basic, general KubeConfig object that then can be extended 12 | func createBasicKubeConfig(serverURL, clusterName, userName string, caCert []byte) *clientcmdapi.Config { 13 | // Use the cluster and the username as the context name 14 | contextName := fmt.Sprintf("%s@%s", userName, clusterName) 15 | 16 | var insecureSkipTLSVerify bool 17 | if caCert == nil { 18 | insecureSkipTLSVerify = true 19 | } 20 | 21 | return &clientcmdapi.Config{ 22 | Clusters: map[string]*clientcmdapi.Cluster{ 23 | clusterName: { 24 | Server: serverURL, 25 | InsecureSkipTLSVerify: insecureSkipTLSVerify, 26 | CertificateAuthorityData: caCert, 27 | }, 28 | }, 29 | Contexts: map[string]*clientcmdapi.Context{ 30 | contextName: { 31 | Cluster: clusterName, 32 | AuthInfo: userName, 33 | }, 34 | }, 35 | AuthInfos: map[string]*clientcmdapi.AuthInfo{}, 36 | CurrentContext: contextName, 37 | } 38 | } 39 | 40 | // CreateKubeConfigWithToken creates a KubeConfig object with access to the API server with a token 41 | func CreateKubeConfigWithToken(serverURL, token string, caCert []byte) *clientcmdapi.Config { 42 | // user_name and cluster_name is mutable, so not necessary to be regarded as constants 43 | userName := "fleetboard" 44 | clusterName := "fleetboard-cluster" 45 | config := createBasicKubeConfig(serverURL, clusterName, userName, caCert) 46 | config.AuthInfos[userName] = &clientcmdapi.AuthInfo{ 47 | Token: token, 48 | } 49 | return config 50 | } 51 | 52 | // GenerateKubeConfigFromToken composes a kubeconfig from token 53 | func GenerateKubeConfigFromToken(serverURL, token string, caCert []byte) (*rest.Config, error) { 54 | clientConfig := CreateKubeConfigWithToken(serverURL, token, caCert) 55 | config, err := clientcmd.NewDefaultClientConfig(*clientConfig, &clientcmd.ConfigOverrides{}).ClientConfig() 56 | if err != nil { 57 | return nil, fmt.Errorf("error while creating kubeconfig: %v", err) 58 | } 59 | return config, nil 60 | } 61 | -------------------------------------------------------------------------------- /utils/netutil.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | "net" 8 | 9 | v1 "k8s.io/api/core/v1" 10 | ) 11 | 12 | // GetIndexIPFromCIDR return index ip in the cidr, index start from 1 not 0, because 0 is not a valid ip. 13 | func GetIndexIPFromCIDR(cidr string, index int) (string, error) { 14 | ip, ipnet, err := net.ParseCIDR(cidr) 15 | if err != nil { 16 | return "", err 17 | } 18 | ipA := ip.Mask(ipnet.Mask) 19 | start := 0 20 | for start < index && ipnet.Contains(ipA) { 21 | start++ 22 | inc(ipA) 23 | } 24 | if start != index { 25 | return "", errors.New("your index is out of the cidr") 26 | } 27 | // remove network address and broadcast address 28 | return ipA.String(), nil 29 | } 30 | 31 | func inc(ipA net.IP) { 32 | for j := len(ipA) - 1; j >= 0; j-- { 33 | ipA[j]++ 34 | if ipA[j] > 0 { 35 | break 36 | } 37 | } 38 | } 39 | 40 | func FindTunnelAvailableCIDR(tunnelCIDR string, existingCIDRs []string) (string, error) { 41 | networkBits, err := divideTunnelNetwork(tunnelCIDR) 42 | if err != nil { 43 | return "", err 44 | } 45 | return findAvailableCIDR(tunnelCIDR, existingCIDRs, networkBits) 46 | } 47 | 48 | func FindClusterAvailableCIDR(clusterCIDR string, existingCIDRs []string) (string, error) { 49 | networkBits, err := divideClusterNetwork(clusterCIDR) 50 | if err != nil { 51 | return "", err 52 | } 53 | return findAvailableCIDR(clusterCIDR, existingCIDRs, networkBits) 54 | } 55 | 56 | /* 57 | divideTunnelNetwork and divideClusterNetwork divide network cidr for peer clusters and nodes in cluster 58 | as dynamically as possibly. 59 | Generally speaking, divided into 3 parts by cidr size for clusters, nodes per cluster, and pods per node. 60 | 61 | dividing table is as follows: 62 | | network-cidr | host-bits | peer-cluster-bits | peer-cluster-cidr | node-pod-bits | node-cidr | cluster-node-bits | 63 | | -----------: | --------: | ----------------: | ----------------: | ------------: | --------: | ----------------: | 64 | | /16~/13 | 16~19 | 2 | /18~/15 | 8 | /24 | 6~9 | 65 | | /12~/9 | 20-23 | 4 | /16~/13 | 8 | /24 | 6~11 | 66 | | >=/8 | >=24 | 4 | >=/12 | 10 | /22 | 10~18 | 67 | */ 68 | func divideTunnelNetwork(networkCIDR string) (subnetNetworkBits int, err error) { 69 | _, network, err := net.ParseCIDR(networkCIDR) 70 | if err != nil { 71 | return 0, err 72 | } 73 | 74 | networkBits, _ := network.Mask.Size() 75 | switch { 76 | case networkBits >= 13 && networkBits <= 16: 77 | subnetNetworkBits = networkBits + 2 78 | case networkBits >= 9 && networkBits <= 12: 79 | subnetNetworkBits = networkBits + 4 80 | case networkBits <= 8: 81 | subnetNetworkBits = networkBits + 4 82 | 83 | default: 84 | err = errors.New("network cidr is too small") 85 | } 86 | return 87 | } 88 | func divideClusterNetwork(networkCIDR string) (subnetNetworkBits int, err error) { 89 | _, network, err := net.ParseCIDR(networkCIDR) 90 | if err != nil { 91 | return 0, err 92 | } 93 | 94 | networkBits, _ := network.Mask.Size() 95 | switch { 96 | case networkBits > 13 && networkBits <= 18: // overlapping tunnel cidr but same node cidr 97 | subnetNetworkBits = 24 98 | case networkBits <= 12: 99 | subnetNetworkBits = 22 100 | 101 | default: 102 | err = errors.New("network cidr is too small") 103 | } 104 | return 105 | } 106 | 107 | func findAvailableCIDR(networkCIDR string, existingCIDRs []string, networkBits int) (string, error) { 108 | // Split networkCIDR into 16 size blocks 109 | hostBits := 32 - networkBits // 主机位数 110 | _, network, err := net.ParseCIDR(networkCIDR) 111 | 112 | if err != nil { 113 | return "", err 114 | } 115 | 116 | // Create a map to store existing CIDRs 117 | existingCIDRSet := make(map[string]bool) 118 | for _, cidr := range existingCIDRs { 119 | // Trim existing CIDR to 16 bits network 120 | if len(cidr) == 0 { 121 | continue 122 | } 123 | _, ipNet, _ := net.ParseCIDR(cidr) 124 | ipNet.IP = ipNet.IP.Mask(net.CIDRMask(networkBits, 32)) 125 | existingCIDRSet[ipNet.String()] = true 126 | } 127 | 128 | // Iterate over available blocks and find an unused one 129 | for i := 0; i <= (1<