Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Split controller and node mode, add max volumes per node flag #137

Merged
merged 5 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ updates:
update-types: ["version-update:semver-minor"]
- dependency-name: "k8s.io/apimachinery"
update-types: ["version-update:semver-minor"]
- dependency-name: "k8s.io/component-base"
update-types: ["version-update:semver-minor"]
- dependency-name: "k8s.io/client-go"
update-types: ["version-update:semver-minor"]
- dependency-name: "k8s.io/mount-utils"
Expand Down
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ issues:
exclude-use-default: true
max-issues-per-linter: 50
max-same-issues: 0 # disable
exclude-rules:
- path: _test.go
linters:
- funlen
- nestif

linters-settings:
gci:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CMDS=cloudstack-csi-driver cloudstack-csi-sc-syncer

PKG=github.com/leaseweb/cloudstack-csi-driver
# Revision that gets built into each binary via the main.version
# string. Uses the `git describe` output based on the most recent
# version tag with a short revision suffix or, if nothing has been
Expand All @@ -8,10 +9,12 @@ CMDS=cloudstack-csi-driver cloudstack-csi-sc-syncer
# Beware that tags may also be missing in shallow clones as done by
# some CI systems (like TravisCI, which pulls only 50 commits).
REV=$(shell git describe --long --tags --match='v*' --dirty 2>/dev/null || git rev-list -n1 HEAD)
GIT_COMMIT?=$(shell git rev-parse HEAD)
BUILD_DATE?=$(shell date -u -Iseconds)

DOCKER?=docker

IMPORTPATH_LDFLAGS = -X main.version=$(REV)
IMPORTPATH_LDFLAGS = -X ${PKG}/pkg/driver.driverVersion=$(REV) -X ${PKG}/pkg/driver.gitCommit=${GIT_COMMIT} -X ${PKG}/pkg/driver.buildDate=${BUILD_DATE}
LDFLAGS = -s -w
FULL_LDFLAGS = $(LDFLAGS) $(IMPORTPATH_LDFLAGS)

Expand Down
3 changes: 2 additions & 1 deletion charts/cloudstack-csi/templates/csi-controller-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ spec:
image: "{{ .Values.controller.csiDriverController.image.repository }}:{{ .Values.controller.csiDriverController.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.controller.csiDriverController.image.pullPolicy }}
args:
- "controller"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloudstackconfig=$(CLOUD_CONFIG)"
- "--cloudstack-config=$(CLOUD_CONFIG)"
- "--logging-format={{ .Values.logFormat }}"
- "--v={{ .Values.logVerbosityLevel }}"
{{- if .Values.controller.csiDriverController.extraArgs }}
Expand Down
5 changes: 3 additions & 2 deletions charts/cloudstack-csi/templates/csi-node-ds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ spec:
image: "{{ .Values.node.csiDriver.image.repository }}:{{ .Values.node.csiDriver.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.node.csiDriver.image.pullPolicy }}
args:
- "node"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloudstackconfig=$(CLOUD_CONFIG)"
- "--cloudstack-config=$(CLOUD_CONFIG)"
- "--logging-format={{ .Values.logFormat }}"
- "--nodeName=$(NODE_NAME)"
- "--node-name=$(NODE_NAME)"
- "--v={{ .Values.logVerbosityLevel }}"
{{- if .Values.node.csiDriver.extraArgs }}
{{- with .Values.node.csiDriver.extraArgs }}
Expand Down
63 changes: 44 additions & 19 deletions cmd/cloudstack-csi-driver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ package main

import (
"context"
"flag"
"fmt"
"os"
"path"
"strings"

flag "github.com/spf13/pflag"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
Expand All @@ -22,30 +22,51 @@ import (
"github.com/leaseweb/cloudstack-csi-driver/pkg/driver"
)

var (
endpoint = flag.String("endpoint", "unix:///tmp/csi.sock", "CSI endpoint")
cloudstackconfig = flag.String("cloudstackconfig", "./cloud-config", "CloudStack configuration file")
nodeName = flag.String("nodeName", "", "Node name")
showVersion = flag.Bool("version", false, "Show version")

// Version is set by the build process.
version = ""
)

func main() {
fs := flag.NewFlagSet("cloudstack-csi-driver", flag.ExitOnError)
if err := logsapi.RegisterLogFormat(logsapi.JSONLogFormat, json.Factory{}, logsapi.LoggingBetaOptions); err != nil {
klog.ErrorS(err, "failed to register JSON log format")
}

var (
showVersion = fs.Bool("version", false, "Show version")
args = os.Args[1:]
cmd = string(driver.AllMode)
options = driver.Options{}
)
fg := featuregate.NewFeatureGate()
err := logsapi.AddFeatureGates(fg)
if err != nil {
klog.ErrorS(err, "failed to add feature gates")
}

c := logsapi.NewLoggingConfiguration()
logsapi.AddGoFlags(c, flag.CommandLine)
flag.Parse()
logsapi.AddFlags(c, fs)

if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") {
cmd = os.Args[1]
args = os.Args[2:]
}

switch cmd {
case string(driver.ControllerMode), string(driver.NodeMode), string(driver.AllMode):
options.Mode = driver.Mode(cmd)
default:
klog.Errorf("Unknown driver mode %s: Expected %s, %s, or %s", cmd, driver.ControllerMode, driver.NodeMode, driver.AllMode)
klog.FlushAndExit(klog.ExitFlushTimeout, 0)
}

options.AddFlags(fs)

if err = fs.Parse(args); err != nil {
klog.ErrorS(err, "Failed to parse options")
klog.FlushAndExit(klog.ExitFlushTimeout, 0)
}
if err = options.Validate(); err != nil {
klog.ErrorS(err, "Invalid options")
klog.FlushAndExit(klog.ExitFlushTimeout, 0)
}

logs.InitLogs()
logger := klog.Background()
if err = logsapi.ValidateAndApply(c, fg); err != nil {
Expand All @@ -54,23 +75,27 @@ func main() {
}

if *showVersion {
baseName := path.Base(os.Args[0])
fmt.Println(baseName, version) //nolint:forbidigo
versionInfo, versionErr := driver.GetVersionJSON()
if versionErr != nil {
logger.Error(err, "failed to get version")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
fmt.Println(versionInfo) //nolint:forbidigo
os.Exit(0)
}

// Setup cloud connector.
config, err := cloud.ReadConfig(*cloudstackconfig)
config, err := cloud.ReadConfig(options.CloudStackConfig)
if err != nil {
logger.Error(err, "Cannot read CloudStack configuration")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
logger.Info("Successfully read CloudStack configuration", "cloudstackconfig", *cloudstackconfig)
logger.Info("Successfully read CloudStack configuration", "cloudstackconfig", options.CloudStackConfig)

ctx := klog.NewContext(context.Background(), logger)
csConnector := cloud.New(config)

d, err := driver.New(*endpoint, csConnector, nil, *nodeName, version)
d, err := driver.New(ctx, csConnector, &options, nil)
if err != nil {
logger.Error(err, "Failed to initialize driver")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
Expand Down
9 changes: 5 additions & 4 deletions deploy/k8s/controller-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ spec:
image: cloudstack-csi-driver
imagePullPolicy: Always
args:
- "-endpoint=$(CSI_ENDPOINT)"
- "-cloudstackconfig=/etc/cloudstack-csi-driver/cloud-config"
- "-logging-format=text"
- "-v=4"
- "controller"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloudstack-config=/etc/cloudstack-csi-driver/cloud-config"
- "--logging-format=text"
- "--v=4"
env:
- name: CSI_ENDPOINT
value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
Expand Down
11 changes: 6 additions & 5 deletions deploy/k8s/node-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ spec:
image: cloudstack-csi-driver
imagePullPolicy: Always
args:
- "-endpoint=$(CSI_ENDPOINT)"
- "-cloudstackconfig=/etc/cloudstack-csi-driver/cloud-config"
- "-logging-format=text"
- "-nodeName=$(NODE_NAME)"
- "-v=4"
- "node"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloudstack-config=/etc/cloudstack-csi-driver/cloud-config"
- "--logging-format=text"
- "--node-name=$(NODE_NAME)"
- "--v=4"
env:
- name: CSI_ENDPOINT
value: unix:///csi/csi.sock
Expand Down
32 changes: 32 additions & 0 deletions pkg/driver/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@ package driver
// DriverName is the name of the CSI plugin.
const DriverName = "csi.cloudstack.apache.org"

// Mode is the operating mode of the CSI driver.
type Mode string

// Driver operating modes.
const (
// ControllerMode is the mode that only starts the controller service.
ControllerMode Mode = "controller"
// NodeMode is the mode that only starts the node service.
NodeMode Mode = "node"
// AllMode is the mode that only starts both the controller and the node service.
AllMode Mode = "all"
)

// constants for default command line flag values.
const (
// DefaultCSIEndpoint is the default CSI endpoint for the driver.
DefaultCSIEndpoint = "unix://tmp/csi.sock"
DefaultMaxVolAttachLimit int64 = 256
)

// Filesystem types.
const (
// FSTypeExt2 represents the ext2 filesystem type.
FSTypeExt2 = "ext2"
// FSTypeExt3 represents the ext3 filesystem type.
FSTypeExt3 = "ext3"
// FSTypeExt4 represents the ext4 filesystem type.
FSTypeExt4 = "ext4"
// FSTypeXfs represents the xfs filesystem type.
FSTypeXfs = "xfs"
)

// Topology keys.
const (
ZoneKey = "topology." + DriverName + "/zone"
Expand Down
100 changes: 82 additions & 18 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ package driver

import (
"context"
"fmt"
"net"

"github.com/container-storage-interface/spec/lib/go/csi"
"google.golang.org/grpc"
"k8s.io/klog/v2"

"github.com/leaseweb/cloudstack-csi-driver/pkg/cloud"
"github.com/leaseweb/cloudstack-csi-driver/pkg/mount"
"github.com/leaseweb/cloudstack-csi-driver/pkg/util"
)

// Interface is the CloudStack CSI driver interface.
Expand All @@ -17,29 +24,86 @@ type Interface interface {
}

type cloudstackDriver struct {
endpoint string
nodeName string
version string

connector cloud.Interface
mounter mount.Interface
controller csi.ControllerServer
node csi.NodeServer
options *Options
}

// New instantiates a new CloudStack CSI driver.
func New(endpoint string, csConnector cloud.Interface, mounter mount.Interface, nodeName string, version string) (Interface, error) {
return &cloudstackDriver{
endpoint: endpoint,
nodeName: nodeName,
version: version,
connector: csConnector,
mounter: mounter,
}, nil
func New(ctx context.Context, csConnector cloud.Interface, options *Options, mounter mount.Interface) (Interface, error) {
logger := klog.FromContext(ctx)
logger.Info("Driver starting", "Driver", DriverName, "Version", driverVersion)

if err := validateMode(options.Mode); err != nil {
return nil, fmt.Errorf("invalid driver options: %w", err)
}

driver := &cloudstackDriver{
options: options,
}

switch options.Mode {
case ControllerMode:
driver.controller = NewControllerServer(csConnector)
case NodeMode:
driver.node = NewNodeServer(csConnector, mounter, options)
case AllMode:
driver.controller = NewControllerServer(csConnector)
driver.node = NewNodeServer(csConnector, mounter, options)
default:
return nil, fmt.Errorf("unknown mode: %s", options.Mode)
}

return driver, nil
}

func (cs *cloudstackDriver) Run(ctx context.Context) error {
ids := NewIdentityServer(cs.version)
ctrls := NewControllerServer(cs.connector)
ns := NewNodeServer(cs.connector, cs.mounter, cs.nodeName)
logger := klog.FromContext(ctx)
scheme, addr, err := util.ParseEndpoint(cs.options.Endpoint)
if err != nil {
return err
}

listener, err := net.Listen(scheme, addr)
if err != nil {
return fmt.Errorf("failed to listen: %w", err)
}

// Log every request and payloads (request + response)
opts := []grpc.ServerOption{
grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(klog.NewContext(ctx, logger), req)
if err != nil {
logger.Error(err, "GRPC method failed", "method", info.FullMethod)
}

return resp, err
}),
}
grpcServer := grpc.NewServer(opts...)

csi.RegisterIdentityServer(grpcServer, cs)
switch cs.options.Mode {
case ControllerMode:
csi.RegisterControllerServer(grpcServer, cs.controller)
case NodeMode:
csi.RegisterNodeServer(grpcServer, cs.node)
case AllMode:
csi.RegisterControllerServer(grpcServer, cs.controller)
csi.RegisterNodeServer(grpcServer, cs.node)
default:
return fmt.Errorf("unknown mode: %s", cs.options.Mode)
}

logger.Info("Listening for connections", "address", listener.Addr())

return grpcServer.Serve(listener)
}

func validateMode(mode Mode) error {
if mode != AllMode && mode != ControllerMode && mode != NodeMode {
return fmt.Errorf("mode is not supported (actual: %s, supported: %v)", mode, []Mode{AllMode, ControllerMode, NodeMode})
}

return cs.serve(ctx, ids, ctrls, ns)
return nil
}
Loading
Loading