Skip to content

Commit

Permalink
Merge pull request #58 from praveingk/newdp
Browse files Browse the repository at this point in the history
Custom Go-based dataplane for xDS control plane APIs
  • Loading branch information
praveingk authored Oct 17, 2023
2 parents 3776ffa + 0faaeb2 commit e39de07
Show file tree
Hide file tree
Showing 18 changed files with 913 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/pr-e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ jobs:
run: make docker-build
- name: Run e2e connectivity test on a kind cluster
run: ./tests/k8s.sh
- name: Run e2e connectivity test on a kind cluster with go dataplane
run: ./tests/k8s.sh go
- name: Run end-to-end tests
run: make tests-e2e
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ build:
$(GO) build -o ./bin/dataplane ./cmd/dataplane/main.go
$(GO) build -o ./bin/cl-controlplane ./cmd/cl-controlplane
$(GO) build -o ./bin/cl-dataplane ./cmd/cl-dataplane
$(GO) build -o ./bin/cl-go-dataplane ./cmd/cl-go-dataplane
$(GO) build -o ./bin/cl-adm ./cmd/cl-adm


docker-build: build
docker build --progress=plain --rm --tag mbg .
docker build --progress=plain --rm --tag cl-controlplane -f ./cmd/cl-controlplane/Dockerfile .
docker build --progress=plain --rm --tag cl-dataplane -f ./cmd/cl-dataplane/Dockerfile .
docker build --progress=plain --rm --tag cl-go-dataplane -f ./cmd/cl-go-dataplane/Dockerfile .
docker build --progress=plain --rm --tag gwctl -f ./cmd/gwctl/Dockerfile .

build-image:
Expand Down
24 changes: 22 additions & 2 deletions cmd/cl-adm/cmd/create/create_peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ type PeerOptions struct {
Name string
// Dataplanes is the number of dataplanes to create.
Dataplanes uint16
// DataplaneType is the type of dataplane to create (envoy or go-based)
DataplaneType string
}

// AddFlags adds flags to fs and binds them to options.
func (o *PeerOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.Name, "name", "", "Peer name.")
fs.Uint16Var(&o.Dataplanes, "dataplanes", 1, "Number of dataplanes.")
fs.StringVar(&o.DataplaneType, "dataplane-type", "envoy", "Type of dataplane, Supported values: \"envoy\" (default), \"go\"")
}

// RequiredFlags are the names of flags that must be explicitly specified.
Expand Down Expand Up @@ -104,6 +107,10 @@ func (o *PeerOptions) Run() error {
return err
}

if err := verifyDataplaneType(o.DataplaneType); err != nil {
return err
}

peerDirectory := config.PeerDirectory(o.Name)
if err := os.Mkdir(peerDirectory, 0755); err != nil {
return err
Expand Down Expand Up @@ -136,8 +143,9 @@ func (o *PeerOptions) Run() error {

// deployment configuration
args, err := templates.Config{
Peer: o.Name,
Dataplanes: o.Dataplanes,
Peer: o.Name,
Dataplanes: o.Dataplanes,
DataplaneType: o.DataplaneType,
}.TemplateArgs()
if err != nil {
return err
Expand Down Expand Up @@ -191,3 +199,15 @@ func verifyNotExists(path string) error {

return nil
}

// verifyDataplaneType checks if the given dataplane type is valid
func verifyDataplaneType(dType string) error {
switch dType {
case templates.DataplaneTypeEnvoy:
return nil
case templates.DataplaneTypeGo:
return nil
default:
return fmt.Errorf("undefined dataplane-type %s", dType)
}
}
15 changes: 13 additions & 2 deletions cmd/cl-adm/templates/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,18 @@ type Config struct {

// Dataplanes is the number of dataplane servers to run.
Dataplanes uint16

// DataplaneType is the type of dataplane to create (envoy or go-based)
DataplaneType string
}

const (
// DataplaneTypeEnvoy represents an envoy-type dataplane.
DataplaneTypeEnvoy = "envoy"
// DataplaneTypeGo represents a go-type dataplane.
DataplaneTypeGo = "go"
)

// TemplateArgs returns arguments for instantiating a text/template
func (c Config) TemplateArgs() (map[string]interface{}, error) {
fabricCA, err := os.ReadFile(filepath.Join(config.FabricDirectory(), config.CertificateFileName))
Expand Down Expand Up @@ -64,8 +74,9 @@ func (c Config) TemplateArgs() (map[string]interface{}, error) {
}

return map[string]interface{}{
"peer": c.Peer,
"dataplanes": c.Dataplanes,
"peer": c.Peer,
"dataplanes": c.Dataplanes,
"dataplaneType": c.DataplaneType,

"fabricCA": base64.StdEncoding.EncodeToString(fabricCA),
"peerCA": base64.StdEncoding.EncodeToString(peerCA),
Expand Down
5 changes: 3 additions & 2 deletions cmd/cl-adm/templates/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ docker run -itd \
-v $FABRIC_DIR/{{.dataplaneCertPath}}:{{.dataplaneCertMountPath}} \
-v $FABRIC_DIR/{{.dataplaneKeyPath}}:{{.dataplaneKeyMountPath}} \
-v $FABRIC_DIR/{{.dataplanePersistencyDirectory}}:{{.persistencyDirectoryMountPath}} \
cl-dataplane \
cl-dataplane \
{{ if (eq .dataplaneType .dataplaneTypeEnvoy) }}cl-dataplane \
cl-dataplane \{{ else }}cl-go-dataplane \
cl-go-dataplane \{{ end }}
--controlplane-host {{.peer}}-controlplane \
--log-level info \
--log-file {{.persistencyDirectoryMountPath}}/log.log
Expand Down
2 changes: 1 addition & 1 deletion cmd/cl-adm/templates/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ spec:
secretName: cl-dataplane
containers:
- name: dataplane
image: cl-dataplane
{{ if (eq .dataplaneType .dataplaneTypeEnvoy) }}image: cl-dataplane{{ else }}image: cl-go-dataplane{{ end }}
imagePullPolicy: IfNotPresent
args: ["--controlplane-host", "cl-controlplane", "--log-level", "info"]
ports:
Expand Down
13 changes: 13 additions & 0 deletions cmd/cl-go-dataplane/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM alpine:3.14

# Copy binary
RUN mkdir -p /usr/local/bin
COPY ./bin/cl-go-dataplane /usr/local/bin/cl-go-dataplane

# Create directory for private keys
RUN mkdir -p /etc/ssl/private

# Create directory for certificates
RUN mkdir -p /etc/ssl/certs

ENTRYPOINT ["/usr/local/bin/cl-go-dataplane"]
156 changes: 156 additions & 0 deletions cmd/cl-go-dataplane/app/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package app

import (
"fmt"
"net"
"os"
"strconv"

"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

cpapi "github.com/clusterlink-net/clusterlink/pkg/controlplane/api"
"github.com/clusterlink-net/clusterlink/pkg/dataplane/api"
dpclient "github.com/clusterlink-net/clusterlink/pkg/dataplane/client"
dpserver "github.com/clusterlink-net/clusterlink/pkg/dataplane/server"
"github.com/clusterlink-net/clusterlink/pkg/util"
)

const (
// logLevel is the default log level.
logLevel = "warn"

// CAFile is the path to the certificate authority file.
CAFile = "/etc/ssl/certs/clink_ca.pem"
// CertificateFile is the path to the certificate file.
CertificateFile = "/etc/ssl/certs/clink-dataplane.pem"
// KeyFile is the path to the private-key file.
KeyFile = "/etc/ssl/private/clink-dataplane.pem"

// dataplaneServerAddress is the address of the dataplane HTTP server for accepting ingress dataplane connections.
dataplaneServerAddress = "127.0.0.1:8443"
)

// Options contains everything necessary to create and run a dataplane.
type Options struct {
// ControlplaneHost is the IP/hostname of the controlplane.
ControlplaneHost string
// LogFile is the path to file where logs will be written.
LogFile string
// LogLevel is the log level.
LogLevel string
}

// AddFlags adds flags to fs and binds them to options.
func (o *Options) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.ControlplaneHost, "controlplane-host", "",
"The controlplane IP/hostname.")
fs.StringVar(&o.LogFile, "log-file", "",
"Path to a file where logs will be written. If not specified, logs will be printed to stderr.")
fs.StringVar(&o.LogLevel, "log-level", logLevel,
"The log level. One of fatal, error, warn, info, debug.")
}

// RequiredFlags are the names of flags that must be explicitly specified.
func (o *Options) RequiredFlags() []string {
return []string{"controlplane-host"}
}

// Run the go dataplane.
func (o *Options) runGoDataplane(peerName, dataplaneID string, parsedCertData *util.ParsedCertData) error {
controlplaneTarget := net.JoinHostPort(o.ControlplaneHost, strconv.Itoa(cpapi.ListenPort))

log.Infof("Starting go dataplane, Name: %s, ID: %s", peerName, dataplaneID)

dataplane := dpserver.NewDataplane(dataplaneID, controlplaneTarget, peerName, parsedCertData)
go func() {
err := dataplane.StartDataplaneServer(dataplaneServerAddress)
log.Errorf("Failed to start dataplane server: %v.", err)
}()

go func() {
err := dataplane.StartSNIServer(dataplaneServerAddress)
log.Error("Failed to start dataplane server", err)
}()

// Start xDS client, if it fails to start we keep retrying to connect to the controlplane host
tlsConfig := parsedCertData.ClientConfig(cpapi.GRPCServerName(peerName))
xdsClient := dpclient.NewXDSClient(dataplane, controlplaneTarget, tlsConfig)
err := xdsClient.Run()
return fmt.Errorf("xDS Client stopped: %v", err)
}

// Run the dataplane.
func (o *Options) Run() error {
// set log file
if o.LogFile != "" {
f, err := os.OpenFile(o.LogFile, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return fmt.Errorf("unable to open log file: %v", err)
}

defer func() {
if err := f.Close(); err != nil {
log.Errorf("Cannot close log file: %v", err)
}
}()

log.SetOutput(f)
}

// set log level
logLevel, err := log.ParseLevel(o.LogLevel)
if err != nil {
return fmt.Errorf("unable to set log level: %v", err)
}
log.SetLevel(logLevel)

// parse TLS files
parsedCertData, err := util.ParseTLSFiles(CAFile, CertificateFile, KeyFile)
if err != nil {
return err
}

dnsNames := parsedCertData.DNSNames()
if len(dnsNames) != 1 {
return fmt.Errorf("expected peer certificate to contain a single DNS name, but got %d", len(dnsNames))
}

peerName, err := api.StripServerPrefix(dnsNames[0])
if err != nil {
return err
}

// generate random dataplane ID
dataplaneID := uuid.New().String()
log.Infof("Dataplane ID: %s.", dataplaneID)

return o.runGoDataplane(peerName, dataplaneID, parsedCertData)
}

// NewCLGoDataplaneCommand creates a *cobra.Command object with default parameters.
func NewCLGoDataplaneCommand() *cobra.Command {
opts := &Options{}

cmd := &cobra.Command{
Use: "cl-go-dataplane",
Long: `cl-go-dataplane: dataplane agent for allowing network connectivity of remote clients and services`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return opts.Run()
},
}

opts.AddFlags(cmd.Flags())

for _, flag := range opts.RequiredFlags() {
if err := cmd.MarkFlagRequired(flag); err != nil {
fmt.Printf("Error marking required flag '%s': %v\n", flag, err)
os.Exit(1)
}
}

return cmd
}
15 changes: 15 additions & 0 deletions cmd/cl-go-dataplane/cl-go-dataplane.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// The cl-dataplane binary runs an instance of a clink dataplane.
package main

import (
"os"

"github.com/clusterlink-net/clusterlink/cmd/cl-go-dataplane/app"
)

func main() {
command := app.NewCLGoDataplaneCommand()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
1 change: 0 additions & 1 deletion cmd/dataplane/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ func main() {

// Set Dataplane
dp := dp.NewDataplane(&store.Store{ID: id, CertAuthority: ca, Cert: cert, Key: key, Dataplane: dataplane}, controlplane)

dp.StartServer(port)
log.Infof("Dataplane main process is finished")
}
Loading

0 comments on commit e39de07

Please sign in to comment.