diff --git a/cmd/directpv/main.go b/cmd/directpv/main.go
index cba0b817..d7cf3db1 100644
--- a/cmd/directpv/main.go
+++ b/cmd/directpv/main.go
@@ -25,10 +25,10 @@ import (
"syscall"
"time"
+ "github.com/minio/directpv/pkg/admin/installer"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/installer"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
diff --git a/cmd/kubectl-directpv/clean.go b/cmd/kubectl-directpv/clean.go
index 4afd69d3..a572a07a 100644
--- a/cmd/kubectl-directpv/clean.go
+++ b/cmd/kubectl-directpv/clean.go
@@ -1,5 +1,5 @@
// This file is part of MinIO DirectPV
-// Copyright (c) 2021, 2022 MinIO, Inc.
+// Copyright (c) 2024 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -19,20 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
- "github.com/minio/directpv/pkg/types"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- corev1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var cleanCmd = &cobra.Command{
@@ -68,7 +60,7 @@ var cleanCmd = &cobra.Command{
volumeNameArgs = args
if err := validateCleanCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -138,65 +130,21 @@ func validateCleanCmd() error {
}
func cleanMain(ctx context.Context) {
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := volume.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- DriveIDSelector(toLabelValues(driveIDArgs)).
- PodNameSelector(toLabelValues(podNameArgs)).
- PodNSSelector(toLabelValues(podNSArgs)).
- StatusSelector(volumeStatusSelectors).
- VolumeNameSelector(volumeNameArgs).
- List(ctx)
-
- matchFunc := func(volume *types.Volume) bool {
- pv, err := k8s.KubeClient().CoreV1().PersistentVolumes().Get(ctx, volume.Name, metav1.GetOptions{})
- if err != nil {
- if apierrors.IsNotFound(err) {
- return true
- }
- utils.Eprintf(quietFlag, true, "unable to get PV for volume %v; %v\n", volume.Name, err)
- return false
- }
- switch pv.Status.Phase {
- case corev1.VolumeReleased, corev1.VolumeFailed:
- return true
- default:
- return false
- }
- }
-
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- if !matchFunc(&result.Volume) {
- continue
- }
-
- result.Volume.RemovePVProtection()
-
- if dryRunFlag {
- continue
- }
-
- if _, err := client.VolumeClient().Update(ctx, &result.Volume, metav1.UpdateOptions{
- TypeMeta: types.NewVolumeTypeMeta(),
- }); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
- }
- if err := client.VolumeClient().Delete(ctx, result.Volume.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Println("Removing volume", result.Volume.Name)
- }
+ _, err := adminClient.Clean(
+ ctx,
+ admin.CleanArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveIDs: driveIDArgs,
+ PodNames: podNameArgs,
+ PodNamespaces: podNSArgs,
+ VolumeStatus: volumeStatusSelectors,
+ VolumeNames: volumeNameArgs,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(true, "%v\n", err)
+ os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/cordon.go b/cmd/kubectl-directpv/cordon.go
index bb80f4a5..752e5b30 100644
--- a/cmd/kubectl-directpv/cordon.go
+++ b/cmd/kubectl-directpv/cordon.go
@@ -19,18 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var cordonCmd = &cobra.Command{
@@ -60,7 +54,7 @@ var cordonCmd = &cobra.Command{
driveIDArgs = args
if err := validateCordonCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -116,64 +110,19 @@ func validateCordonCmd() error {
}
func cordonMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- StatusSelector(driveStatusSelectors).
- DriveIDSelector(driveIDSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if result.Drive.IsUnschedulable() {
- continue
- }
-
- volumes := result.Drive.GetVolumes()
- if len(volumes) != 0 {
- for vresult := range volume.NewLister().VolumeNameSelector(volumes).IgnoreNotFound(true).List(ctx) {
- if vresult.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", vresult.Err)
- os.Exit(1)
- }
-
- if vresult.Volume.Status.Status == directpvtypes.VolumeStatusPending {
- utils.Eprintf(quietFlag, true, "unable to cordon drive %v; pending volumes found\n", result.Drive.GetDriveID())
- os.Exit(1)
- }
- }
- }
-
- result.Drive.Unschedulable()
- if !dryRunFlag {
- if _, err := client.DriveClient().Update(ctx, &result.Drive, metav1.UpdateOptions{}); err != nil {
- utils.Eprintf(quietFlag, true, "unable to cordon drive %v; %v\n", result.Drive.GetDriveID(), err)
- os.Exit(1)
- }
- }
-
- if !quietFlag {
- fmt.Printf("Drive %v/%v cordoned\n", result.Drive.GetNodeID(), result.Drive.GetDriveName())
- }
- }
-
- if !processed {
- if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
- } else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
- }
-
+ _, err := adminClient.Cordon(
+ ctx,
+ admin.CordonArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ Status: driveStatusSelectors,
+ DriveIDs: driveIDSelectors,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/discover.go b/cmd/kubectl-directpv/discover.go
index 39322c60..61cb6bf9 100644
--- a/cmd/kubectl-directpv/discover.go
+++ b/cmd/kubectl-directpv/discover.go
@@ -28,16 +28,13 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
+ "github.com/minio/directpv/pkg/admin"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/node"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
- "k8s.io/client-go/util/retry"
)
var (
@@ -71,7 +68,7 @@ var discoverCmd = &cobra.Command{
),
Run: func(c *cobra.Command, _ []string) {
if err := validateDiscoverCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
discoverMain(c.Context())
@@ -95,33 +92,6 @@ func validateDiscoverCmd() error {
return validateDriveNameArgs()
}
-func toInitConfig(resultMap map[directpvtypes.NodeID][]types.Device) InitConfig {
- nodeInfo := []NodeInfo{}
- initConfig := NewInitConfig()
- for node, devices := range resultMap {
- driveInfo := []DriveInfo{}
- for _, device := range devices {
- if device.DeniedReason != "" {
- continue
- }
- driveInfo = append(driveInfo, DriveInfo{
- ID: device.ID,
- Name: device.Name,
- Size: device.Size,
- Make: device.Make,
- FS: device.FSType,
- Select: driveSelectedValue,
- })
- }
- nodeInfo = append(nodeInfo, NodeInfo{
- Name: node,
- Drives: driveInfo,
- })
- }
- initConfig.Nodes = nodeInfo
- return initConfig
-}
-
func showDevices(resultMap map[directpvtypes.NodeID][]types.Device) error {
writer := newTableWriter(
table.Row{
@@ -182,14 +152,14 @@ func showDevices(resultMap map[directpvtypes.NodeID][]types.Device) error {
}
if writer.Length() == 0 || !foundAvailableDrive {
- utils.Eprintf(false, false, "%v\n", color.HiYellowString("No drives are available to initialize"))
+ eprintf(false, color.HiYellowString("No drives are available to initialize")+"\n")
return errDiscoveryFailed
}
return nil
}
-func writeInitConfig(config InitConfig) error {
+func writeInitConfig(config admin.InitConfig) error {
f, err := os.OpenFile(outputFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return err
@@ -198,48 +168,101 @@ func writeInitConfig(config InitConfig) error {
return config.Write(f)
}
-func discoverDevices(ctx context.Context, nodes []types.Node, teaProgram *tea.Program) (devices map[directpvtypes.NodeID][]types.Device, err error) {
- var nodeNames []string
- nodeClient := client.NodeClient()
- totalNodeCount := len(nodes)
- discoveryProgressMap := make(map[string]progressLog, totalNodeCount)
- for i := range nodes {
- nodeNames = append(nodeNames, nodes[i].Name)
- updateFunc := func() error {
- node, err := nodeClient.Get(ctx, nodes[i].Name, metav1.GetOptions{})
- if err != nil {
- return err
- }
- node.Spec.Refresh = true
- if _, err := nodeClient.Update(ctx, node, metav1.UpdateOptions{TypeMeta: types.NewNodeTypeMeta()}); err != nil {
- return err
+func discoverMain(ctx context.Context) {
+ var teaProgram *tea.Program
+ var wg sync.WaitGroup
+ if !quietFlag {
+ m := newProgressModel(false)
+ teaProgram = tea.NewProgram(m)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := teaProgram.Run(); err != nil {
+ fmt.Println("error running program:", err)
+ os.Exit(1)
}
- if teaProgram != nil {
- discoveryProgressMap[node.Name] = progressLog{
- log: fmt.Sprintf("Discovering node '%v'", node.Name),
+ }()
+ }
+
+ nodeCh, errCh, err := adminClient.RefreshNodes(ctx, nodesArgs)
+ if err != nil {
+ eprintf(true, "discovery failed; %v\n", err)
+ os.Exit(1)
+ }
+
+ var nodeNames []string
+ discoveryProgressMap := make(map[string]progressLog)
+ var refreshwg sync.WaitGroup
+ refreshwg.Add(1)
+ go func() {
+ defer refreshwg.Done()
+ for {
+ select {
+ case nodeID, ok := <-nodeCh:
+ if !ok {
+ return
+ }
+ nodeNames = append(nodeNames, string(nodeID))
+ if teaProgram != nil {
+ discoveryProgressMap[string(nodeID)] = progressLog{
+ log: fmt.Sprintf("Discovering node '%v'", nodeID),
+ }
+ teaProgram.Send(progressNotification{
+ progressLogs: toProgressLogs(discoveryProgressMap),
+ })
+ }
+ case err, ok := <-errCh:
+ if !ok {
+ return
}
- teaProgram.Send(progressNotification{
- progressLogs: toProgressLogs(discoveryProgressMap),
- })
+ eprintf(true, "discovery failed; %v\n", err)
+ os.Exit(1)
+ case <-ctx.Done():
+ eprintf(true, ctx.Err().Error())
+ os.Exit(1)
}
- return nil
}
- if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
- return nil, err
+ }()
+ refreshwg.Wait()
+
+ resultMap, err := discoverDevices(ctx, nodeNames, drivesArgs, teaProgram)
+ if err != nil {
+ eprintf(true, "discovery failed; %v\n", err)
+ os.Exit(1)
+ }
+ if teaProgram != nil {
+ teaProgram.Send(progressNotification{
+ done: true,
+ err: err,
+ })
+ wg.Wait()
+ }
+ if err := showDevices(resultMap); err != nil {
+ if !errors.Is(err, errDiscoveryFailed) {
+ eprintf(true, "%v\n", err)
}
+ os.Exit(1)
}
+ if err := writeInitConfig(admin.ToInitConfig(resultMap)); err != nil {
+ eprintf(true, "unable to write init config; %v\n", err)
+ } else if !quietFlag {
+ color.HiGreen("Generated '%s' successfully.", outputFile)
+ }
+}
+func discoverDevices(ctx context.Context, nodes, drives []string, teaProgram *tea.Program) (devices map[directpvtypes.NodeID][]types.Device, err error) {
ctx, cancel := context.WithTimeout(ctx, nodeListTimeout)
defer cancel()
- eventCh, stop, err := node.NewLister().
- NodeSelector(toLabelValues(nodeNames)).
+ eventCh, stop, err := adminClient.NewNodeLister().
+ NodeSelector(utils.ToLabelValues(nodes)).
Watch(ctx)
if err != nil {
return nil, err
}
defer stop()
+ discoveryProgressMap := make(map[string]progressLog)
devices = map[directpvtypes.NodeID][]types.Device{}
for {
select {
@@ -253,9 +276,9 @@ func discoverDevices(ctx context.Context, nodes []types.Node, teaProgram *tea.Pr
}
switch event.Type {
case watch.Modified, watch.Added:
- node := event.Node
+ node := event.Item
if !node.Spec.Refresh {
- devices[directpvtypes.NodeID(node.Name)] = node.GetDevicesByNames(drivesArgs)
+ devices[directpvtypes.NodeID(node.Name)] = node.GetDevicesByNames(drives)
if teaProgram != nil {
discoveryProgressMap[node.Name] = progressLog{
log: fmt.Sprintf("Discovered node '%v'", node.Name),
@@ -274,109 +297,8 @@ func discoverDevices(ctx context.Context, nodes []types.Node, teaProgram *tea.Pr
default:
}
case <-ctx.Done():
- utils.Eprintf(quietFlag, true, "unable to complete the discovery; %v\n", ctx.Err())
+ err = fmt.Errorf("unable to complete the discovery; %v", ctx.Err())
return
}
}
}
-
-func syncNodes(ctx context.Context) (err error) {
- csiNodes, err := getCSINodes(ctx)
- if err != nil {
- return fmt.Errorf("unable to get CSI nodes; %w", err)
- }
-
- nodes, err := node.NewLister().Get(ctx)
- if err != nil {
- return fmt.Errorf("unable to get nodes; %w", err)
- }
-
- var nodeNames []string
- for _, node := range nodes {
- nodeNames = append(nodeNames, node.Name)
- }
-
- // Add missing nodes.
- for _, csiNode := range csiNodes {
- if !utils.Contains(nodeNames, csiNode) {
- node := types.NewNode(directpvtypes.NodeID(csiNode), []types.Device{})
- node.Spec.Refresh = true
- if _, err = client.NodeClient().Create(ctx, node, metav1.CreateOptions{}); err != nil {
- return fmt.Errorf("unable to create node %v; %w", csiNode, err)
- }
- }
- }
-
- // Remove non-existing nodes.
- for _, nodeName := range nodeNames {
- if !utils.Contains(csiNodes, nodeName) {
- if err = client.NodeClient().Delete(ctx, nodeName, metav1.DeleteOptions{}); err != nil {
- return fmt.Errorf("unable to remove non-existing node %v; %w", nodeName, err)
- }
- }
- }
-
- return nil
-}
-
-func discoverMain(ctx context.Context) {
- if err := syncNodes(ctx); err != nil {
- utils.Eprintf(quietFlag, true, "sync failed; %v\n", err)
- os.Exit(1)
- }
-
- nodes, err := node.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- Get(ctx)
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
- }
- if len(nodesArgs) != 0 && len(nodes) == 0 {
- suffix := ""
- if len(nodesArgs) > 1 {
- suffix = "s"
- }
- utils.Eprintf(quietFlag, true, "node%v %v not found\n", suffix, nodesArgs)
- os.Exit(1)
- }
-
- var teaProgram *tea.Program
- var wg sync.WaitGroup
- if !quietFlag {
- m := newProgressModel(false)
- teaProgram = tea.NewProgram(m)
- wg.Add(1)
- go func() {
- defer wg.Done()
- if _, err := teaProgram.Run(); err != nil {
- fmt.Println("error running program:", err)
- os.Exit(1)
- }
- }()
- }
- resultMap, err := discoverDevices(ctx, nodes, teaProgram)
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
- }
- if teaProgram != nil {
- teaProgram.Send(progressNotification{
- done: true,
- err: err,
- })
- wg.Wait()
- }
- if err := showDevices(resultMap); err != nil {
- if !errors.Is(err, errDiscoveryFailed) {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- }
- os.Exit(1)
- }
-
- if err := writeInitConfig(toInitConfig(resultMap)); err != nil {
- utils.Eprintf(quietFlag, true, "unable to write init config; %v\n", err)
- } else if !quietFlag {
- color.HiGreen("Generated '%s' successfully.", outputFile)
- }
-}
diff --git a/cmd/kubectl-directpv/info.go b/cmd/kubectl-directpv/info.go
index d512fa84..c33fd2f9 100644
--- a/cmd/kubectl-directpv/info.go
+++ b/cmd/kubectl-directpv/info.go
@@ -20,18 +20,12 @@ import (
"context"
"fmt"
"os"
- "strings"
"github.com/dustin/go-humanize"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/k8s"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var infoCmd = &cobra.Command{
@@ -45,50 +39,11 @@ var infoCmd = &cobra.Command{
}
func infoMain(ctx context.Context) {
- crds, err := k8s.CRDClient().List(ctx, metav1.ListOptions{})
+ nodeInfoMap, err := adminClient.Info(ctx)
if err != nil {
- utils.Eprintf(quietFlag, true, "unable to list CRDs; %v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
-
- drivesFound := false
- volumesFound := false
- for _, crd := range crds.Items {
- if strings.Contains(crd.Name, consts.DriveResource+"."+consts.GroupName) {
- drivesFound = true
- }
- if strings.Contains(crd.Name, consts.VolumeResource+"."+consts.GroupName) {
- volumesFound = true
- }
- }
- if !drivesFound || !volumesFound {
- utils.Eprintf(quietFlag, false, "%v installation not found\n", consts.AppPrettyName)
- os.Exit(1)
- }
-
- nodeList, err := getCSINodes(ctx)
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
- }
-
- if len(nodeList) == 0 {
- utils.Eprintf(quietFlag, true, "%v not installed\n", consts.AppPrettyName)
- os.Exit(1)
- }
-
- drives, err := drive.NewLister().Get(ctx)
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to get drive list; %v\n", err)
- os.Exit(1)
- }
-
- volumes, err := volume.NewLister().Get(ctx)
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to get volume list; %v\n", err)
- os.Exit(1)
- }
-
writer := newTableWriter(
table.Row{"NODE", "CAPACITY", "ALLOCATED", "VOLUMES", "DRIVES"},
[]table.SortBy{
@@ -118,30 +73,14 @@ func infoMain(ctx context.Context) {
var totalDriveSize uint64
var totalVolumeSize uint64
- for _, n := range nodeList {
- driveCount := 0
- driveSize := uint64(0)
- for _, d := range drives {
- if string(d.GetNodeID()) == n {
- driveCount++
- driveSize += uint64(d.Status.TotalCapacity)
- }
- }
- totalDriveSize += driveSize
-
- volumeCount := 0
- volumeSize := uint64(0)
- for _, v := range volumes {
- if string(v.GetNodeID()) == n {
- if v.IsPublished() {
- volumeCount++
- volumeSize += uint64(v.Status.TotalCapacity)
- }
- }
- }
- totalVolumeSize += volumeSize
-
- if driveCount == 0 {
+ var totalDriveCount int
+ var totalVolumeCount int
+ for n, info := range nodeInfoMap {
+ totalDriveSize += info.DriveSize
+ totalVolumeSize += info.VolumeSize
+ totalDriveCount += info.DriveCount
+ totalVolumeCount += info.VolumeCount
+ if info.DriveCount == 0 {
writer.AppendRow([]interface{}{
fmt.Sprintf("%s %s", color.HiYellowString(dot), n),
"-",
@@ -152,23 +91,23 @@ func infoMain(ctx context.Context) {
} else {
writer.AppendRow([]interface{}{
fmt.Sprintf("%s %s", color.GreenString(dot), n),
- humanize.IBytes(driveSize),
- humanize.IBytes(volumeSize),
- fmt.Sprintf("%d", volumeCount),
- fmt.Sprintf("%d", driveCount),
+ humanize.IBytes(info.DriveSize),
+ humanize.IBytes(info.VolumeSize),
+ fmt.Sprintf("%d", info.VolumeCount),
+ fmt.Sprintf("%d", info.DriveCount),
})
}
}
if !quietFlag {
writer.Render()
- if len(drives) > 0 {
+ if len(nodeInfoMap) > 0 {
fmt.Printf(
"\n%s/%s used, %s volumes, %s drives\n",
humanize.IBytes(totalVolumeSize),
humanize.IBytes(totalDriveSize),
- color.HiWhiteString("%d", len(volumes)),
- color.HiWhiteString("%d", len(drives)),
+ color.HiWhiteString("%d", totalVolumeCount),
+ color.HiWhiteString("%d", totalDriveCount),
)
}
}
diff --git a/cmd/kubectl-directpv/init.go b/cmd/kubectl-directpv/init.go
index 66d762ba..e0441541 100644
--- a/cmd/kubectl-directpv/init.go
+++ b/cmd/kubectl-directpv/init.go
@@ -26,12 +26,10 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
- "github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
+ "github.com/minio/directpv/pkg/admin"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/initrequest"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
@@ -63,15 +61,15 @@ var initCmd = &cobra.Command{
switch len(args) {
case 1:
case 0:
- utils.Eprintf(quietFlag, true, "Please provide the input file. Check `--help` for usage.\n")
+ eprintf(true, "Please provide the input file. Check `--help` for usage.\n")
os.Exit(-1)
default:
- utils.Eprintf(quietFlag, true, "Too many input args. Check `--help` for usage.\n")
+ eprintf(true, "Too many input args. Check `--help` for usage.\n")
os.Exit(-1)
}
if !dangerousFlag {
- utils.Eprintf(quietFlag, true, "Initializing the drives will permanently erase existing data. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag.\n")
+ eprintf(true, "Initializing the drives will permanently erase existing data. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag.\n")
os.Exit(1)
}
@@ -86,26 +84,6 @@ func init() {
addDangerousFlag(initCmd, "Perform initialization of drives which will permanently erase existing data")
}
-func toInitRequestObjects(config *InitConfig, requestID string) (initRequests []types.InitRequest) {
- for _, node := range config.Nodes {
- initDevices := []types.InitDevice{}
- for _, device := range node.Drives {
- if strings.ToLower(device.Select) != driveSelectedValue {
- continue
- }
- initDevices = append(initDevices, types.InitDevice{
- ID: device.ID,
- Name: device.Name,
- Force: device.FS != "",
- })
- }
- if len(initDevices) > 0 {
- initRequests = append(initRequests, *types.NewInitRequest(requestID, node.Name, initDevices))
- }
- }
- return
-}
-
func showResults(results []initResult) {
if len(results) == 0 {
return
@@ -164,20 +142,13 @@ func showResults(results []initResult) {
writer.Render()
}
-func toProgressLogs(progressMap map[string]progressLog) (logs []progressLog) {
- for _, v := range progressMap {
- logs = append(logs, v)
- }
- return
-}
-
func initDevices(ctx context.Context, initRequests []types.InitRequest, requestID string, teaProgram *tea.Program) (results []initResult, err error) {
totalReqCount := len(initRequests)
totalTasks := totalReqCount * 2
var completedTasks int
initProgressMap := make(map[string]progressLog, totalReqCount)
for i := range initRequests {
- initReq, err := client.InitRequestClient().Create(ctx, &initRequests[i], metav1.CreateOptions{TypeMeta: types.NewInitRequestTypeMeta()})
+ initReq, err := adminClient.InitRequest().Create(ctx, &initRequests[i], metav1.CreateOptions{TypeMeta: types.NewInitRequestTypeMeta()})
if err != nil {
return nil, err
}
@@ -195,8 +166,8 @@ func initDevices(ctx context.Context, initRequests []types.InitRequest, requestI
ctx, cancel := context.WithTimeout(ctx, initRequestListTimeout)
defer cancel()
- eventCh, stop, err := initrequest.NewLister().
- RequestIDSelector(toLabelValues([]string{requestID})).
+ eventCh, stop, err := adminClient.NewInitRequestLister().
+ RequestIDSelector(utils.ToLabelValues([]string{requestID})).
Watch(ctx)
if err != nil {
return nil, err
@@ -216,7 +187,7 @@ func initDevices(ctx context.Context, initRequests []types.InitRequest, requestI
}
switch event.Type {
case watch.Modified, watch.Added:
- initReq := event.InitRequest
+ initReq := event.Item
if initReq.Status.Status != directpvtypes.InitStatusPending {
results = append(results, initResult{
requestID: initReq.Name,
@@ -250,32 +221,23 @@ func initDevices(ctx context.Context, initRequests []types.InitRequest, requestI
}
}
-func readInitConfig(inputFile string) (*InitConfig, error) {
- f, err := os.Open(inputFile)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return parseInitConfig(f)
-}
-
func initMain(ctx context.Context, inputFile string) {
- initConfig, err := readInitConfig(inputFile)
+ initConfig, err := admin.ReadInitConfig(inputFile)
if err != nil {
- utils.Eprintf(quietFlag, true, "unable to read the input file; %v", err.Error())
+ eprintf(true, "unable to read the input file; %v", err.Error())
os.Exit(1)
}
- requestID := uuid.New().String()
- initRequests := toInitRequestObjects(initConfig, requestID)
+
+ initRequests, requestID := initConfig.ToInitRequestObjects()
if len(initRequests) == 0 {
- utils.Eprintf(false, false, "%v\n", color.HiYellowString("No drives are available to init"))
+ eprintf(false, "%v\n", color.HiYellowString("No drives are available to init"))
os.Exit(1)
}
defer func() {
labelMap := map[directpvtypes.LabelKey][]directpvtypes.LabelValue{
- directpvtypes.RequestIDLabelKey: toLabelValues([]string{requestID}),
+ directpvtypes.RequestIDLabelKey: utils.ToLabelValues([]string{requestID}),
}
- client.InitRequestClient().DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{
+ adminClient.InitRequest().DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{
LabelSelector: directpvtypes.ToLabelSelector(labelMap),
})
}()
@@ -295,7 +257,7 @@ func initMain(ctx context.Context, inputFile string) {
}
results, err := initDevices(ctx, initRequests, requestID, teaProgram)
if err != nil && quietFlag {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
if teaProgram != nil {
diff --git a/cmd/kubectl-directpv/init_config.go b/cmd/kubectl-directpv/init_config.go
deleted file mode 100644
index 770dd8ac..00000000
--- a/cmd/kubectl-directpv/init_config.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// This file is part of MinIO DirectPV
-// Copyright (c) 2021, 2022 MinIO, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package main
-
-import (
- "errors"
- "io"
-
- "gopkg.in/yaml.v3"
-)
-
-var errUnsupportedInitConfigVersion = errors.New("unsupported init config version")
-
-const latestInitConfigVersion = "v1"
-
-// InitConfig holds the latest config version
-type InitConfig = InitConfigV1
-
-// NodeInfo holds the latest node info
-type NodeInfo = NodeInfoV1
-
-// DriveInfo holds the latest drive info
-type DriveInfo = DriveInfoV1
-
-// NewInitConfig initializes an init config.
-func NewInitConfig() InitConfig {
- return InitConfig{
- Version: latestInitConfigVersion,
- }
-}
-
-func parseInitConfig(r io.Reader) (*InitConfig, error) {
- var config InitConfig
- if err := yaml.NewDecoder(r).Decode(&config); err != nil {
- return nil, err
- }
- if config.Version != latestInitConfigVersion {
- return nil, errUnsupportedInitConfigVersion
- }
- return &config, nil
-}
-
-func (config InitConfig) Write(w io.Writer) error {
- encoder := yaml.NewEncoder(w)
- defer encoder.Close()
- return encoder.Encode(config)
-}
diff --git a/cmd/kubectl-directpv/install.go b/cmd/kubectl-directpv/install.go
index 4de78fd4..3e2887ba 100644
--- a/cmd/kubectl-directpv/install.go
+++ b/cmd/kubectl-directpv/install.go
@@ -27,15 +27,14 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
- directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/admin"
+ "github.com/minio/directpv/pkg/admin/installer"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/installer"
+ "github.com/minio/directpv/pkg/k8s"
legacyclient "github.com/minio/directpv/pkg/legacy/client"
"github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/version"
)
@@ -88,7 +87,7 @@ var installCmd = &cobra.Command{
),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := validateInstallCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
disableInit = dryRunPrinter != nil
@@ -125,87 +124,19 @@ func init() {
installCmd.PersistentFlags().BoolVar(&openshiftFlag, "openshift", openshiftFlag, "Use OpenShift specific installation")
}
-func validateNodeSelectorArgs() error {
- nodeSelector = map[string]string{}
- for _, value := range nodeSelectorArgs {
- tokens := strings.Split(value, "=")
- if len(tokens) != 2 {
- return fmt.Errorf("invalid node selector value %v", value)
- }
- if tokens[0] == "" {
- return fmt.Errorf("invalid key in node selector value %v", value)
- }
- nodeSelector[tokens[0]] = tokens[1]
- }
- return nil
-}
-
-func validateTolerationsArgs() error {
- for _, value := range tolerationArgs {
- var k, v, e string
- tokens := strings.SplitN(value, "=", 2)
- switch len(tokens) {
- case 1:
- k = tokens[0]
- tokens = strings.Split(k, ":")
- switch len(tokens) {
- case 1:
- case 2:
- k, e = tokens[0], tokens[1]
- default:
- if len(tokens) != 2 {
- return fmt.Errorf("invalid toleration %v", value)
- }
- }
- case 2:
- k, v = tokens[0], tokens[1]
- default:
- if len(tokens) != 2 {
- return fmt.Errorf("invalid toleration %v", value)
- }
- }
- if k == "" {
- return fmt.Errorf("invalid key in toleration %v", value)
- }
- if v != "" {
- if tokens = strings.Split(v, ":"); len(tokens) != 2 {
- return fmt.Errorf("invalid value in toleration %v", value)
- }
- v, e = tokens[0], tokens[1]
- }
- effect := corev1.TaintEffect(e)
- switch effect {
- case corev1.TaintEffectNoSchedule, corev1.TaintEffectPreferNoSchedule, corev1.TaintEffectNoExecute:
- default:
- return fmt.Errorf("invalid toleration effect in toleration %v", value)
- }
- operator := corev1.TolerationOpExists
- if v != "" {
- operator = corev1.TolerationOpEqual
- }
- tolerations = append(tolerations, corev1.Toleration{
- Key: k,
- Operator: operator,
- Value: v,
- Effect: effect,
- })
- }
-
- return nil
-}
-
-func validateInstallCmd() error {
- if err := validateNodeSelectorArgs(); err != nil {
+func validateInstallCmd() (err error) {
+ nodeSelector, err = k8s.ParseNodeSelector(nodeSelectorArgs)
+ if err != nil {
return fmt.Errorf("%v; format of '--node-selector' flag value must be [=]", err)
}
- if err := validateTolerationsArgs(); err != nil {
+ tolerations, err = k8s.ParseTolerations(tolerationArgs)
+ if err != nil {
return fmt.Errorf("%v; format of '--tolerations' flag value must be [=value]:", err)
}
- if err := validateOutputFormat(false); err != nil {
+ if err = validateOutputFormat(false); err != nil {
return err
}
if dryRunPrinter != nil && k8sVersion != "" {
- var err error
if kubeVersion, err = version.ParseSemantic(k8sVersion); err != nil {
return fmt.Errorf("invalid kubernetes version %v; %v", k8sVersion, err)
}
@@ -213,111 +144,61 @@ func validateInstallCmd() error {
return nil
}
-func getLegacyFlag(ctx context.Context) bool {
- if dryRunPrinter != nil {
- return legacyFlag
- }
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := volume.NewLister().
- LabelSelector(
- map[directpvtypes.LabelKey]directpvtypes.LabelValue{
- directpvtypes.MigratedLabelKey: "true",
- },
- ).
- IgnoreNotFound(true).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "unable to get volumes; %v", result.Err)
- break
- }
-
- return true
- }
-
- legacyclient.Init()
-
- for result := range legacyclient.ListVolumes(ctx) {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "unable to get legacy volumes; %v", result.Err)
- break
- }
-
- return true
- }
-
- return false
-}
-
func installMain(ctx context.Context) {
- legacyFlag = getLegacyFlag(ctx)
-
+ pluginVersion := "dev"
+ if Version != "" {
+ pluginVersion = Version
+ }
+ dryRun := dryRunPrinter != nil
var file *utils.SafeFile
var err error
- if dryRunPrinter == nil && !declarativeFlag {
+ if !dryRun && !declarativeFlag {
auditFile := fmt.Sprintf("install.log.%v", time.Now().UTC().Format(time.RFC3339Nano))
file, err = openAuditFile(auditFile)
if err != nil {
- utils.Eprintf(quietFlag, true, "unable to open audit file %v; %v\n", auditFile, err)
- utils.Eprintf(false, false, "%v\n", color.HiYellowString("Skipping audit logging"))
+ eprintf(true, "unable to open audit file %v; %v\n", auditFile, err)
+ eprintf(false, "%v\n", color.HiYellowString("Skipping audit logging"))
}
-
defer func() {
if file != nil {
if err := file.Close(); err != nil {
- utils.Eprintf(quietFlag, true, "unable to close audit file; %v\n", err)
+ eprintf(true, "unable to close audit file; %v\n", err)
}
}
}()
}
- args := installer.NewArgs(image)
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
- os.Exit(1)
+ args := admin.InstallArgs{
+ Image: image,
+ Registry: registry,
+ Org: org,
+ ImagePullSecrets: imagePullSecrets,
+ NodeSelector: nodeSelector,
+ Tolerations: tolerations,
+ SeccompProfile: seccompProfile,
+ AppArmorProfile: apparmorProfile,
+ EnableLegacy: legacyFlag,
+ PluginVersion: pluginVersion,
+ Quiet: quietFlag,
+ KubeVersion: kubeVersion,
+ DryRun: dryRunPrinter != nil,
+ OutputFormat: outputFormat,
+ Declarative: declarativeFlag,
+ Openshift: openshiftFlag,
+ AuditWriter: file,
}
- pluginVersion := "dev"
- if Version != "" {
- pluginVersion = Version
- }
-
- args.Registry = registry
- args.Org = org
- args.ImagePullSecrets = imagePullSecrets
- args.NodeSelector = nodeSelector
- args.Tolerations = tolerations
- args.SeccompProfile = seccompProfile
- args.AppArmorProfile = apparmorProfile
- args.Quiet = quietFlag
- args.KubeVersion = kubeVersion
- args.Legacy = legacyFlag
- args.PluginVersion = pluginVersion
- if file != nil {
- args.ObjectWriter = file
- }
- if dryRunPrinter != nil {
- args.DryRun = true
- if outputFormat == "yaml" {
- args.ObjectMarshaler = func(obj runtime.Object) ([]byte, error) {
- return utils.ToYAML(obj)
- }
- } else {
- args.ObjectMarshaler = func(obj runtime.Object) ([]byte, error) {
- return utils.ToJSON(obj)
- }
- }
- }
- args.Declarative = declarativeFlag
- args.Openshift = openshiftFlag
-
var failed bool
- var installedComponents []installer.Component
var wg sync.WaitGroup
- if dryRunPrinter == nil && !declarativeFlag && !quietFlag {
+ var installedComponents []installer.Component
+ legacyClient, err := legacyclient.NewClient(adminClient.K8s())
+ if err != nil {
+ fmt.Println("error creating legacy client:", err)
+ return
+ }
+ installerTasks := installer.GetDefaultTasks(adminClient.Client, legacyClient)
+ enableProgress := !dryRun && !declarativeFlag && !quietFlag
+ if enableProgress {
m := newProgressModel(true)
teaProgram := tea.NewProgram(m)
wg.Add(1)
@@ -325,15 +206,15 @@ func installMain(ctx context.Context) {
defer wg.Done()
if _, err := teaProgram.Run(); err != nil {
fmt.Println("error running program:", err)
- os.Exit(1)
+ return
}
}()
- wg.Add(1)
var totalSteps, step, completedTasks int
var currentPercent float64
- totalTasks := len(installer.Tasks)
+ totalTasks := len(installerTasks)
weightagePerTask := 1.0 / totalTasks
progressCh := make(chan installer.Message)
+ wg.Add(1)
go func() {
defer wg.Done()
defer close(args.ProgressCh)
@@ -401,8 +282,9 @@ func installMain(ctx context.Context) {
}()
args.ProgressCh = progressCh
}
- if err := installer.Install(ctx, args); err != nil && args.ProgressCh == nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+
+ if err := adminClient.Install(ctx, args, installerTasks); err != nil && args.ProgressCh == nil {
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
if args.ProgressCh != nil {
diff --git a/cmd/kubectl-directpv/label.go b/cmd/kubectl-directpv/label.go
index 5000fffd..5462336b 100644
--- a/cmd/kubectl-directpv/label.go
+++ b/cmd/kubectl-directpv/label.go
@@ -21,24 +21,12 @@ import (
"fmt"
"strings"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/spf13/cobra"
)
-type label struct {
- key types.LabelKey
- value types.LabelValue
- remove bool
-}
-
-func (l label) String() string {
- if l.value == "" {
- return string(l.key)
- }
- return string(l.key) + ":" + string(l.value)
-}
-
-var labels []label
+var labels []admin.Label
var labelCmd = &cobra.Command{
Use: "label",
@@ -71,28 +59,28 @@ func validateLabelCmd() error {
return validateDriveNameArgs()
}
-func validateLabelCmdArgs(args []string) (labels []label, err error) {
+func validateLabelCmdArgs(args []string) (labels []admin.Label, err error) {
if len(args) == 0 {
return nil, errors.New("at least one label must be provided")
}
for _, arg := range args {
- var label label
+ var label admin.Label
tokens := strings.Split(arg, "=")
switch len(tokens) {
case 1:
if !strings.HasSuffix(arg, "-") {
return nil, fmt.Errorf("argument %v must end with '-' to remove label", arg)
}
- label.remove = true
- if label.key, err = types.NewLabelKey(arg[:len(arg)-1]); err != nil {
+ label.Remove = true
+ if label.Key, err = types.NewLabelKey(arg[:len(arg)-1]); err != nil {
return nil, err
}
case 2:
- if label.key, err = types.NewLabelKey(tokens[0]); err != nil {
+ if label.Key, err = types.NewLabelKey(tokens[0]); err != nil {
return nil, err
}
- if label.value, err = types.NewLabelValue(tokens[1]); err != nil {
+ if label.Value, err = types.NewLabelValue(tokens[1]); err != nil {
return nil, err
}
default:
diff --git a/cmd/kubectl-directpv/label_drives.go b/cmd/kubectl-directpv/label_drives.go
index cf52a4d8..ac41e923 100644
--- a/cmd/kubectl-directpv/label_drives.go
+++ b/cmd/kubectl-directpv/label_drives.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var labelDrivesCmd = &cobra.Command{
@@ -53,7 +48,7 @@ var labelDrivesCmd = &cobra.Command{
Run: func(c *cobra.Command, args []string) {
driveIDArgs = idArgs
if err := validateLabelDrivesCmd(args); err != nil {
- utils.Eprintf(quietFlag, true, "%s; Check `--help` for usage\n", err.Error())
+ eprintf(true, "%s; Check `--help` for usage\n", err.Error())
os.Exit(1)
}
labelDrivesMain(c.Context())
@@ -97,47 +92,21 @@ func init() {
}
func labelDrivesMain(ctx context.Context) {
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- StatusSelector(driveStatusSelectors).
- DriveIDSelector(driveIDSelectors).
- LabelSelector(labelSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
- drive := &result.Drive
- var verb string
- for i := range labels {
- updateFunc := func() (err error) {
- if labels[i].remove {
- if ok := drive.RemoveLabel(labels[i].key); !ok {
- return
- }
- verb = "removed from"
- } else {
- if ok := drive.SetLabel(labels[i].key, labels[i].value); !ok {
- return
- }
- verb = "set on"
- }
- if !dryRunFlag {
- drive, err = client.DriveClient().Update(ctx, drive, metav1.UpdateOptions{})
- }
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v/%v: %v\n", drive.GetNodeID(), drive.GetDriveName(), err)
- } else if !quietFlag {
- fmt.Printf("Label '%s' successfully %s %v/%v\n", labels[i].String(), verb, drive.GetNodeID(), drive.GetDriveName())
- }
- return
- }
- retry.RetryOnConflict(retry.DefaultRetry, updateFunc)
- }
+ _, err := adminClient.LabelDrives(
+ ctx,
+ admin.LabelDriveArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveStatus: driveStatusSelectors,
+ DriveIDs: driveIDSelectors,
+ LabelSelectors: labelSelectors,
+ DryRun: dryRunFlag,
+ },
+ labels,
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
+ os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/label_volumes.go b/cmd/kubectl-directpv/label_volumes.go
index c45c9cfb..a3cd8927 100644
--- a/cmd/kubectl-directpv/label_volumes.go
+++ b/cmd/kubectl-directpv/label_volumes.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var labelVolumesCmd = &cobra.Command{
@@ -53,7 +48,7 @@ var labelVolumesCmd = &cobra.Command{
Run: func(c *cobra.Command, args []string) {
volumeNameArgs = idArgs
if err := validateLabelVolumesCmd(args); err != nil {
- utils.Eprintf(quietFlag, true, "%s; Check `--help` for usage\n", err.Error())
+ eprintf(true, "%s; Check `--help` for usage\n", err.Error())
os.Exit(1)
}
labelVolumesMain(c.Context())
@@ -106,51 +101,23 @@ func init() {
}
func labelVolumesMain(ctx context.Context) {
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := volume.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- DriveIDSelector(toLabelValues(driveIDArgs)).
- PodNameSelector(toLabelValues(podNameArgs)).
- PodNSSelector(toLabelValues(podNSArgs)).
- StatusSelector(volumeStatusSelectors).
- VolumeNameSelector(volumeNameArgs).
- LabelSelector(labelSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- var verb string
- volume := &result.Volume
- for i := range labels {
- updateFunc := func() (err error) {
- if labels[i].remove {
- if ok := volume.RemoveLabel(labels[i].key); !ok {
- return
- }
- verb = "removed from"
- } else {
- if ok := volume.SetLabel(labels[i].key, labels[i].value); !ok {
- return
- }
- verb = "set on"
- }
- if !dryRunFlag {
- volume, err = client.VolumeClient().Update(ctx, volume, metav1.UpdateOptions{})
- }
- if err != nil {
- utils.Eprintf(quietFlag, true, "%v: %v\n", volume.Name, err)
- } else if !quietFlag {
- fmt.Printf("Label '%s' successfully %s %v\n", labels[i].String(), verb, volume.Name)
- }
- return
- }
- retry.RetryOnConflict(retry.DefaultRetry, updateFunc)
- }
+ _, err := adminClient.LabelVolumes(
+ ctx,
+ admin.LabelVolumeArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveIDs: driveIDArgs,
+ PodNames: podNameArgs,
+ PodNamespaces: podNSArgs,
+ VolumeStatus: volumeStatusSelectors,
+ VolumeNames: volumeNameArgs,
+ LabelSelectors: labelSelectors,
+ },
+ labels,
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
+ os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/list_drives.go b/cmd/kubectl-directpv/list_drives.go
index 5f6dcd9a..7556d53b 100644
--- a/cmd/kubectl-directpv/list_drives.go
+++ b/cmd/kubectl-directpv/list_drives.go
@@ -25,7 +25,6 @@ import (
"github.com/jedib0t/go-pretty/v6/table"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
@@ -68,7 +67,7 @@ var listDrivesCmd = &cobra.Command{
Run: func(c *cobra.Command, args []string) {
driveIDArgs = args
if err := validateListDrivesArgs(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -117,15 +116,15 @@ func validateListDrivesArgs() error {
}
func listDrivesMain(ctx context.Context) {
- drives, err := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
+ drives, err := adminClient.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(nodesArgs)).
+ DriveNameSelector(utils.ToLabelValues(drivesArgs)).
StatusSelector(driveStatusSelectors).
DriveIDSelector(driveIDSelectors).
LabelSelector(labelSelectors).
Get(ctx)
if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
@@ -225,9 +224,9 @@ func listDrivesMain(ctx context.Context) {
}
if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
+ eprintf(false, "No resources found\n")
} else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
+ eprintf(false, "No matching resources found\n")
}
os.Exit(1)
diff --git a/cmd/kubectl-directpv/list_volumes.go b/cmd/kubectl-directpv/list_volumes.go
index b86bd694..870e4fb8 100644
--- a/cmd/kubectl-directpv/list_volumes.go
+++ b/cmd/kubectl-directpv/list_volumes.go
@@ -27,7 +27,6 @@ import (
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -76,7 +75,7 @@ var listVolumesCmd = &cobra.Command{
Run: func(c *cobra.Command, args []string) {
volumeNameArgs = args
if err := validateListVolumesArgs(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -155,18 +154,18 @@ func getPVCName(ctx context.Context, volume types.Volume) string {
}
func listVolumesMain(ctx context.Context) {
- volumes, err := volume.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- DriveIDSelector(toLabelValues(driveIDArgs)).
- PodNameSelector(toLabelValues(podNameArgs)).
- PodNSSelector(toLabelValues(podNSArgs)).
+ volumes, err := adminClient.NewVolumeLister().
+ NodeSelector(utils.ToLabelValues(nodesArgs)).
+ DriveNameSelector(utils.ToLabelValues(drivesArgs)).
+ DriveIDSelector(utils.ToLabelValues(driveIDArgs)).
+ PodNameSelector(utils.ToLabelValues(podNameArgs)).
+ PodNSSelector(utils.ToLabelValues(podNSArgs)).
StatusSelector(volumeStatusSelectors).
VolumeNameSelector(volumeNameArgs).
LabelSelector(labelSelectors).
Get(ctx)
if err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
@@ -276,9 +275,9 @@ func listVolumesMain(ctx context.Context) {
}
if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
+ eprintf(false, "No resources found\n")
} else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
+ eprintf(false, "No matching resources found\n")
}
os.Exit(1)
diff --git a/cmd/kubectl-directpv/main.go b/cmd/kubectl-directpv/main.go
index 314fe92b..505fd85e 100644
--- a/cmd/kubectl-directpv/main.go
+++ b/cmd/kubectl-directpv/main.go
@@ -23,11 +23,12 @@ import (
"os/signal"
"syscall"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/utils"
+ "github.com/minio/directpv/pkg/k8s"
"github.com/spf13/cobra"
"github.com/spf13/viper"
+ "k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
@@ -35,7 +36,10 @@ import (
// e.g. $ go build -ldflags="-X main.Version=v4.0.1"
var Version string
-var disableInit bool
+var (
+ disableInit bool
+ adminClient *admin.Client
+)
var mainCmd = &cobra.Command{
Use: consts.AppName,
@@ -48,7 +52,15 @@ var mainCmd = &cobra.Command{
Version: Version,
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
if !disableInit {
- client.Init()
+ kubeConfig, err := k8s.GetKubeConfig()
+ if err != nil {
+ klog.Fatalf("unable to get kubernetes configuration; %v", err)
+ }
+ kubeConfig.WarningHandler = rest.NoWarnings{}
+ adminClient, err = admin.NewClient(kubeConfig)
+ if err != nil {
+ klog.Fatalf("unable to create admin client; %v", err)
+ }
}
return nil
},
@@ -170,7 +182,7 @@ func main() {
go func() {
select {
case signal := <-signalCh:
- utils.Eprintf(quietFlag, false, "\nExiting on signal %v\n", signal)
+ eprintf(false, "\nExiting on signal %v\n", signal)
cancelFunc()
os.Exit(1)
case <-ctx.Done():
@@ -178,7 +190,7 @@ func main() {
}()
if err := mainCmd.ExecuteContext(ctx); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/migrate.go b/cmd/kubectl-directpv/migrate.go
index 4de0ee78..a9cfc610 100644
--- a/cmd/kubectl-directpv/migrate.go
+++ b/cmd/kubectl-directpv/migrate.go
@@ -18,14 +18,12 @@ package main
import (
"context"
- "fmt"
"os"
"strings"
"time"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/installer"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
)
@@ -53,41 +51,14 @@ func init() {
}
func migrateMain(ctx context.Context) {
- if err := installer.Migrate(ctx, &installer.Args{
- Quiet: quietFlag,
- Legacy: true,
- }, false); err != nil {
- utils.Eprintf(quietFlag, true, "migration failed; %v", err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Println("Migration successful; Please restart the pods in '" + consts.AppName + "' namespace.")
- }
-
- if retainFlag {
- return
- }
-
suffix := time.Now().Format(time.RFC3339)
-
- drivesBackupFile := "directcsidrives-" + suffix + ".yaml"
- backupCreated, err := installer.RemoveLegacyDrives(ctx, drivesBackupFile)
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to remove legacy drive CRDs; %v", err)
- os.Exit(1)
- }
- if backupCreated && !quietFlag {
- fmt.Println("Legacy drive CRDs backed up to", drivesBackupFile)
- }
-
- volumesBackupFile := "directcsivolumes-" + suffix + ".yaml"
- backupCreated, err = installer.RemoveLegacyVolumes(ctx, volumesBackupFile)
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to remove legacy volume CRDs; %v", err)
+ if err := adminClient.Migrate(ctx, admin.MigrateArgs{
+ Quiet: quietFlag,
+ Retain: retainFlag,
+ DrivesBackupFile: "directcsidrives-" + suffix + ".yaml",
+ VolumesBackupFile: "directcsivolumes-" + suffix + ".yaml",
+ }); err != nil {
+ eprintf(true, "migration failed; %v", err)
os.Exit(1)
}
- if backupCreated && !quietFlag {
- fmt.Println("Legacy volume CRDs backed up to", volumesBackupFile)
- }
}
diff --git a/cmd/kubectl-directpv/move.go b/cmd/kubectl-directpv/move.go
index a1f7eea7..5a8c8854 100644
--- a/cmd/kubectl-directpv/move.go
+++ b/cmd/kubectl-directpv/move.go
@@ -18,18 +18,13 @@ package main
import (
"context"
- "fmt"
"os"
"strings"
+ "github.com/minio/directpv/pkg/admin"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/types"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var moveCmd = &cobra.Command{
@@ -46,147 +41,37 @@ var moveCmd = &cobra.Command{
),
Run: func(c *cobra.Command, args []string) {
if len(args) != 2 {
- utils.Eprintf(quietFlag, true, "only one source and one destination drive must be provided\n")
+ eprintf(true, "only one source and one destination drive must be provided\n")
os.Exit(-1)
}
src := strings.TrimSpace(args[0])
if src == "" {
- utils.Eprintf(quietFlag, true, "empty source drive\n")
+ eprintf(true, "empty source drive\n")
os.Exit(-1)
}
dest := strings.TrimSpace(args[1])
if dest == "" {
- utils.Eprintf(quietFlag, true, "empty destination drive\n")
+ eprintf(true, "empty destination drive\n")
os.Exit(-1)
}
- moveMain(c.Context(), src, dest)
+ moveMain(c.Context(), directpvtypes.DriveID(src), directpvtypes.DriveID(dest))
},
}
-func moveMain(ctx context.Context, src, dest string) {
- if src == dest {
- utils.Eprintf(quietFlag, true, "source and destination drives are same\n")
- os.Exit(1)
- }
-
- srcDrive, err := client.DriveClient().Get(ctx, src, metav1.GetOptions{})
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to get source drive; %v\n", err)
- os.Exit(1)
- }
-
- if !srcDrive.IsUnschedulable() {
- utils.Eprintf(quietFlag, true, "source drive is not cordoned\n")
- os.Exit(1)
- }
-
- sourceVolumeNames := srcDrive.GetVolumes()
- if len(sourceVolumeNames) == 0 {
- utils.Eprintf(quietFlag, false, "No volumes found in source drive %v\n", src)
- return
- }
-
- var requiredCapacity int64
- var volumes []types.Volume
- for result := range volume.NewLister().VolumeNameSelector(sourceVolumeNames).List(ctx) {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- if result.Volume.IsPublished() {
- utils.Eprintf(quietFlag, true, "cannot move published volume %v\n", result.Volume.Name)
- os.Exit(1)
- }
-
- requiredCapacity += result.Volume.Status.TotalCapacity
- volumes = append(volumes, result.Volume)
- }
-
- if len(volumes) == 0 {
- utils.Eprintf(quietFlag, false, "No volumes found in source drive %v\n", src)
- return
- }
-
- destDrive, err := client.DriveClient().Get(ctx, dest, metav1.GetOptions{})
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to get destination drive; %v\n", err)
- os.Exit(1)
- }
-
- if destDrive.GetNodeID() != srcDrive.GetNodeID() {
- utils.Eprintf(
- quietFlag,
- true,
- "source and destination drives must be in same node; source node %v; desination node %v\n",
- srcDrive.GetNodeID(),
- destDrive.GetNodeID(),
- )
- os.Exit(1)
- }
-
- if !destDrive.IsUnschedulable() {
- utils.Eprintf(quietFlag, true, "destination drive is not cordoned\n")
- os.Exit(1)
- }
-
- if destDrive.Status.Status != directpvtypes.DriveStatusReady {
- utils.Eprintf(quietFlag, true, "destination drive is not in ready state\n")
- os.Exit(1)
- }
-
- if srcDrive.GetAccessTier() != destDrive.GetAccessTier() {
- utils.Eprintf(
- quietFlag,
- true,
- "source drive access-tier %v and destination drive access-tier %v differ\n",
- srcDrive.GetAccessTier(),
- destDrive.GetAccessTier(),
- )
- os.Exit(1)
- }
-
- if destDrive.Status.FreeCapacity < requiredCapacity {
- utils.Eprintf(
- quietFlag,
- true,
- "insufficient free capacity on destination drive; required=%v free=%v\n",
- printableBytes(requiredCapacity),
- printableBytes(destDrive.Status.FreeCapacity),
- )
- os.Exit(1)
- }
-
- for _, volume := range volumes {
- if destDrive.AddVolumeFinalizer(volume.Name) {
- destDrive.Status.FreeCapacity -= volume.Status.TotalCapacity
- destDrive.Status.AllocatedCapacity += volume.Status.TotalCapacity
- }
- }
- destDrive.Status.Status = directpvtypes.DriveStatusMoving
- _, err = client.DriveClient().Update(
- ctx, destDrive, metav1.UpdateOptions{TypeMeta: types.NewDriveTypeMeta()},
- )
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to move volumes to destination drive; %v\n", err)
- os.Exit(1)
- }
-
- for _, volume := range volumes {
- if !quietFlag {
- fmt.Println("Moving volume", volume.Name)
- }
- }
-
- srcDrive.ResetFinalizers()
- _, err = client.DriveClient().Update(
- ctx, srcDrive, metav1.UpdateOptions{TypeMeta: types.NewDriveTypeMeta()},
+func moveMain(ctx context.Context, src, dest directpvtypes.DriveID) {
+ err := adminClient.Move(
+ ctx,
+ admin.MoveArgs{
+ Source: src,
+ Destination: dest,
+ },
+ logFunc,
)
if err != nil {
- utils.Eprintf(quietFlag, true, "unable to remove volume references in source drive; %v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/progress_model.go b/cmd/kubectl-directpv/progress_model.go
index 4bfe2079..da491ded 100644
--- a/cmd/kubectl-directpv/progress_model.go
+++ b/cmd/kubectl-directpv/progress_model.go
@@ -171,3 +171,10 @@ func (m progressModel) View() (str string) {
}
return str + pad
}
+
+func toProgressLogs(progressMap map[string]progressLog) (logs []progressLog) {
+ for _, v := range progressMap {
+ logs = append(logs, v)
+ }
+ return
+}
diff --git a/cmd/kubectl-directpv/remove.go b/cmd/kubectl-directpv/remove.go
index 39c9d69e..ae50d8a5 100644
--- a/cmd/kubectl-directpv/remove.go
+++ b/cmd/kubectl-directpv/remove.go
@@ -23,13 +23,9 @@ import (
"os"
"strings"
- directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var removeCmd = &cobra.Command{
@@ -59,7 +55,7 @@ var removeCmd = &cobra.Command{
driveIDArgs = args
if err := validateRemoveCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -115,59 +111,19 @@ func validateRemoveCmd() error {
}
func removeMain(ctx context.Context) {
- var processed bool
- var failed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- StatusSelector(driveStatusSelectors).
- DriveIDSelector(driveIDSelectors).
- IgnoreNotFound(true).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
- switch result.Drive.Status.Status {
- case directpvtypes.DriveStatusRemoved:
- default:
- volumeCount := result.Drive.GetVolumeCount()
- if volumeCount > 0 {
- failed = true
- } else {
- result.Drive.Status.Status = directpvtypes.DriveStatusRemoved
- var err error
- if !dryRunFlag {
- _, err = client.DriveClient().Update(ctx, &result.Drive, metav1.UpdateOptions{})
- }
- if err != nil {
- failed = true
- utils.Eprintf(quietFlag, true, "%v/%v: %v\n", result.Drive.GetNodeID(), result.Drive.GetDriveName(), err)
- } else if !quietFlag {
- fmt.Printf("Removing %v/%v\n", result.Drive.GetNodeID(), result.Drive.GetDriveName())
- }
- }
- }
- }
-
- if !processed {
- if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
- } else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
- }
-
- os.Exit(1)
- }
-
- if failed {
+ _, err := adminClient.Remove(
+ ctx,
+ admin.RemoveArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveStatus: driveStatusSelectors,
+ DriveIDs: driveIDSelectors,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/resume_drives.go b/cmd/kubectl-directpv/resume_drives.go
index 5d503c48..852ffe6e 100644
--- a/cmd/kubectl-directpv/resume_drives.go
+++ b/cmd/kubectl-directpv/resume_drives.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var resumeDrivesCmd = &cobra.Command{
@@ -53,7 +48,7 @@ var resumeDrivesCmd = &cobra.Command{
driveIDArgs = args
if err := validateResumeDrivesCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -91,60 +86,18 @@ func validateResumeDrivesCmd() error {
}
func resumeDrivesMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- DriveIDSelector(driveIDSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if !result.Drive.IsSuspended() {
- // only suspended drives can be resumed.
- continue
- }
-
- driveClient := client.DriveClient()
- updateFunc := func() error {
- drive, err := driveClient.Get(ctx, result.Drive.Name, metav1.GetOptions{})
- if err != nil {
- return err
- }
- drive.Resume()
- if !dryRunFlag {
- if _, err := driveClient.Update(ctx, drive, metav1.UpdateOptions{}); err != nil {
- return err
- }
- }
- return nil
- }
- if err := retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
- utils.Eprintf(quietFlag, true, "unable to resume drive %v; %v\n", result.Drive.GetDriveID(), err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Printf("Drive %v/%v resumed\n", result.Drive.GetNodeID(), result.Drive.GetDriveName())
- }
- }
-
- if !processed {
- if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
- } else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
- }
-
+ _, err := adminClient.ResumeDrives(
+ ctx,
+ admin.ResumeDriveArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveIDSelectors: driveIDSelectors,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/resume_volumes.go b/cmd/kubectl-directpv/resume_volumes.go
index 04ff4dff..b271b697 100644
--- a/cmd/kubectl-directpv/resume_volumes.go
+++ b/cmd/kubectl-directpv/resume_volumes.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var resumeVolumesCmd = &cobra.Command{
@@ -53,7 +48,7 @@ var resumeVolumesCmd = &cobra.Command{
volumeNameArgs = args
if err := validateResumeVolumesCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -101,57 +96,20 @@ func validateResumeVolumesCmd() error {
}
func resumeVolumesMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := volume.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- PodNameSelector(toLabelValues(podNameArgs)).
- PodNSSelector(toLabelValues(podNSArgs)).
- VolumeNameSelector(volumeNameArgs).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if !result.Volume.IsSuspended() {
- // only suspended drives can be resumed.
- continue
- }
-
- volumeClient := client.VolumeClient()
- updateFunc := func() error {
- volume, err := volumeClient.Get(ctx, result.Volume.Name, metav1.GetOptions{})
- if err != nil {
- return err
- }
- volume.Resume()
- if !dryRunFlag {
- if _, err := volumeClient.Update(ctx, volume, metav1.UpdateOptions{}); err != nil {
- return err
- }
- }
- return nil
- }
- if err := retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
- utils.Eprintf(quietFlag, true, "unable to resume volume %v; %v\n", result.Volume.Name, err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Printf("Volume %v/%v resumed\n", result.Volume.GetNodeID(), result.Volume.Name)
- }
- }
-
- if !processed {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
+ _, err := adminClient.ResumeVolumes(
+ ctx,
+ admin.ResumeVolumeArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ PodNames: podNameArgs,
+ PodNamespaces: podNSArgs,
+ VolumeNames: volumeNameArgs,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/suspend_drives.go b/cmd/kubectl-directpv/suspend_drives.go
index e83648d9..ce38c7e9 100644
--- a/cmd/kubectl-directpv/suspend_drives.go
+++ b/cmd/kubectl-directpv/suspend_drives.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var suspendDrivesCmd = &cobra.Command{
@@ -54,12 +49,12 @@ var suspendDrivesCmd = &cobra.Command{
driveIDArgs = args
if err := validateSuspendDrivesCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
if !dangerousFlag {
- utils.Eprintf(quietFlag, true, "Suspending the drives will make the corresponding volumes as read-only. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag..\n")
+ eprintf(true, "Suspending the drives will make the corresponding volumes as read-only. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag..\n")
os.Exit(1)
}
@@ -98,54 +93,18 @@ func validateSuspendDrivesCmd() error {
}
func suspendDrivesMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- DriveIDSelector(driveIDSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if result.Drive.IsSuspended() {
- continue
- }
-
- driveClient := client.DriveClient()
- updateFunc := func() error {
- drive, err := driveClient.Get(ctx, result.Drive.Name, metav1.GetOptions{})
- if err != nil {
- return err
- }
- drive.Suspend()
- if !dryRunFlag {
- if _, err := driveClient.Update(ctx, drive, metav1.UpdateOptions{}); err != nil {
- return err
- }
- }
- return nil
- }
- if err := retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
- utils.Eprintf(quietFlag, true, "unable to suspend drive %v; %v\n", result.Drive.GetDriveID(), err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Printf("Drive %v/%v suspended\n", result.Drive.GetNodeID(), result.Drive.GetDriveName())
- }
- }
-
- if !processed {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
+ _, err := adminClient.SuspendDrives(
+ ctx,
+ admin.SuspendDriveArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ DriveIDSelectors: driveIDSelectors,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/suspend_volumes.go b/cmd/kubectl-directpv/suspend_volumes.go
index 85761410..781fc2ce 100644
--- a/cmd/kubectl-directpv/suspend_volumes.go
+++ b/cmd/kubectl-directpv/suspend_volumes.go
@@ -19,17 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/utils"
- "github.com/minio/directpv/pkg/volume"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/util/retry"
)
var suspendVolumesCmd = &cobra.Command{
@@ -54,12 +49,12 @@ var suspendVolumesCmd = &cobra.Command{
volumeNameArgs = args
if err := validateSuspendVolumesCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
if !dangerousFlag {
- utils.Eprintf(quietFlag, true, "Suspending the volumes will make them as read-only. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag.\n")
+ eprintf(true, "Suspending the volumes will make them as read-only. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag.\n")
os.Exit(1)
}
@@ -108,56 +103,20 @@ func validateSuspendVolumesCmd() error {
}
func suspendVolumesMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := volume.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- PodNameSelector(toLabelValues(podNameArgs)).
- PodNSSelector(toLabelValues(podNSArgs)).
- VolumeNameSelector(volumeNameArgs).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if result.Volume.IsSuspended() {
- continue
- }
-
- volumeClient := client.VolumeClient()
- updateFunc := func() error {
- volume, err := volumeClient.Get(ctx, result.Volume.Name, metav1.GetOptions{})
- if err != nil {
- return err
- }
- volume.Suspend()
- if !dryRunFlag {
- if _, err := volumeClient.Update(ctx, volume, metav1.UpdateOptions{}); err != nil {
- return err
- }
- }
- return nil
- }
- if err := retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
- utils.Eprintf(quietFlag, true, "unable to suspend volume %v; %v\n", result.Volume.Name, err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Printf("Volume %v/%v suspended\n", result.Volume.GetNodeID(), result.Volume.Name)
- }
- }
-
- if !processed {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
+ _, err := adminClient.SuspendVolumes(
+ ctx,
+ admin.SuspendVolumeArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ PodNames: podNameArgs,
+ PodNamespaces: podNSArgs,
+ VolumeNames: volumeNameArgs,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/uncordon.go b/cmd/kubectl-directpv/uncordon.go
index 5d1e337e..1374db14 100644
--- a/cmd/kubectl-directpv/uncordon.go
+++ b/cmd/kubectl-directpv/uncordon.go
@@ -19,16 +19,12 @@ package main
import (
"context"
"errors"
- "fmt"
"os"
"strings"
- "github.com/minio/directpv/pkg/client"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var uncordonCmd = &cobra.Command{
@@ -58,7 +54,7 @@ var uncordonCmd = &cobra.Command{
driveIDArgs = args
if err := validateUncordonCmd(); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ eprintf(true, "%v\n", err)
os.Exit(-1)
}
@@ -114,52 +110,19 @@ func validateUncordonCmd() error {
}
func uncordonMain(ctx context.Context) {
- var processed bool
-
- ctx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
-
- resultCh := drive.NewLister().
- NodeSelector(toLabelValues(nodesArgs)).
- DriveNameSelector(toLabelValues(drivesArgs)).
- StatusSelector(driveStatusSelectors).
- DriveIDSelector(driveIDSelectors).
- List(ctx)
- for result := range resultCh {
- if result.Err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", result.Err)
- os.Exit(1)
- }
-
- processed = true
-
- if !result.Drive.IsUnschedulable() {
- continue
- }
-
- result.Drive.Schedulable()
- var err error
- if !dryRunFlag {
- _, err = client.DriveClient().Update(ctx, &result.Drive, metav1.UpdateOptions{})
- }
-
- if err != nil {
- utils.Eprintf(quietFlag, true, "unable to uncordon drive %v; %v\n", result.Drive.GetDriveID(), err)
- os.Exit(1)
- }
-
- if !quietFlag {
- fmt.Printf("Drive %v/%v uncordoned\n", result.Drive.GetNodeID(), result.Drive.GetDriveName())
- }
- }
-
- if !processed {
- if allFlag {
- utils.Eprintf(quietFlag, false, "No resources found\n")
- } else {
- utils.Eprintf(quietFlag, false, "No matching resources found\n")
- }
-
+ _, err := adminClient.Uncordon(
+ ctx,
+ admin.UncordonArgs{
+ Nodes: nodesArgs,
+ Drives: drivesArgs,
+ Status: driveStatusSelectors,
+ DriveIDs: driveIDSelectors,
+ DryRun: dryRunFlag,
+ },
+ logFunc,
+ )
+ if err != nil {
+ eprintf(!errors.Is(err, admin.ErrNoMatchingResourcesFound), "%v\n", err)
os.Exit(1)
}
}
diff --git a/cmd/kubectl-directpv/uninstall.go b/cmd/kubectl-directpv/uninstall.go
index b77496a7..eee22205 100644
--- a/cmd/kubectl-directpv/uninstall.go
+++ b/cmd/kubectl-directpv/uninstall.go
@@ -21,9 +21,8 @@ import (
"fmt"
"os"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/installer"
- "github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
)
@@ -45,8 +44,15 @@ func init() {
}
func uninstallMain(ctx context.Context) {
- if err := installer.Uninstall(ctx, quietFlag, dangerousFlag); err != nil {
- utils.Eprintf(quietFlag, true, "%v\n", err)
+ err := adminClient.Uninstall(
+ ctx,
+ admin.UninstallArgs{
+ Quiet: quietFlag,
+ Dangerous: dangerousFlag,
+ },
+ )
+ if err != nil {
+ eprintf(true, "%v\n", err)
os.Exit(1)
}
if !quietFlag {
diff --git a/cmd/kubectl-directpv/utils.go b/cmd/kubectl-directpv/utils.go
index 4817706a..355c678e 100644
--- a/cmd/kubectl-directpv/utils.go
+++ b/cmd/kubectl-directpv/utils.go
@@ -17,24 +17,17 @@
package main
import (
- "context"
"errors"
"fmt"
"os"
"path"
- "time"
"github.com/dustin/go-humanize"
"github.com/jedib0t/go-pretty/v6/table"
- directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/admin"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/utils"
"github.com/mitchellh/go-homedir"
- storagev1 "k8s.io/api/storage/v1"
- storagev1beta1 "k8s.io/api/storage/v1beta1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
)
@@ -58,25 +51,6 @@ func printJSON(obj interface{}) {
fmt.Print(string(data))
}
-func getDefaultAuditDir() (string, error) {
- homeDir, err := homedir.Dir()
- if err != nil {
- return "", err
- }
- return path.Join(homeDir, "."+consts.AppName, "audit"), nil
-}
-
-func openAuditFile(auditFile string) (*utils.SafeFile, error) {
- defaultAuditDir, err := getDefaultAuditDir()
- if err != nil {
- return nil, fmt.Errorf("unable to get default audit directory; %w", err)
- }
- if err := os.MkdirAll(defaultAuditDir, 0o700); err != nil {
- return nil, fmt.Errorf("unable to create default audit directory; %w", err)
- }
- return utils.NewSafeFile(path.Join(defaultAuditDir, auditFile))
-}
-
func printableString(s string) string {
if s == "" {
return "-"
@@ -107,13 +81,6 @@ func newTableWriter(header table.Row, sortBy []table.SortBy, noHeader bool) tabl
return writer
}
-func toLabelValues(slice []string) (values []directpvtypes.LabelValue) {
- for _, s := range slice {
- values = append(values, directpvtypes.ToLabelValue(s))
- }
- return
-}
-
func validateOutputFormat(isWideSupported bool) error {
switch outputFormat {
case "":
@@ -135,54 +102,29 @@ func validateOutputFormat(isWideSupported bool) error {
return nil
}
-func getCSINodes(ctx context.Context) (nodes []string, err error) {
- storageClient, gvk, err := k8s.GetClientForNonCoreGroupVersionKind("storage.k8s.io", "CSINode", "v1", "v1beta1", "v1alpha1")
+func openAuditFile(auditFile string) (*utils.SafeFile, error) {
+ defaultAuditDir, err := getDefaultAuditDir()
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("unable to get default audit directory; %w", err)
}
+ if err := os.MkdirAll(defaultAuditDir, 0o700); err != nil {
+ return nil, fmt.Errorf("unable to create default audit directory; %w", err)
+ }
+ return utils.NewSafeFile(path.Join(defaultAuditDir, auditFile))
+}
- switch gvk.Version {
- case "v1apha1":
- err = fmt.Errorf("unsupported CSINode storage.k8s.io/v1alpha1")
- case "v1":
- result := &storagev1.CSINodeList{}
- if err = storageClient.Get().
- Resource("csinodes").
- VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec).
- Timeout(10 * time.Second).
- Do(ctx).
- Into(result); err != nil {
- err = fmt.Errorf("unable to get csinodes; %w", err)
- break
- }
- for _, csiNode := range result.Items {
- for _, driver := range csiNode.Spec.Drivers {
- if driver.Name == consts.Identity {
- nodes = append(nodes, csiNode.Name)
- break
- }
- }
- }
- case "v1beta1":
- result := &storagev1beta1.CSINodeList{}
- if err = storageClient.Get().
- Resource(gvk.Kind).
- VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec).
- Timeout(10 * time.Second).
- Do(ctx).
- Into(result); err != nil {
- err = fmt.Errorf("unable to get csinodes; %w", err)
- break
- }
- for _, csiNode := range result.Items {
- for _, driver := range csiNode.Spec.Drivers {
- if driver.Name == consts.Identity {
- nodes = append(nodes, csiNode.Name)
- break
- }
- }
- }
+func getDefaultAuditDir() (string, error) {
+ homeDir, err := homedir.Dir()
+ if err != nil {
+ return "", err
}
+ return path.Join(homeDir, "."+consts.AppName, "audit"), nil
+}
+
+func eprintf(isErr bool, format string, args ...any) {
+ utils.Eprintf(quietFlag, isErr, format, args...)
+}
- return nodes, err
+func logFunc(log admin.LogMessage) {
+ eprintf(log.Type == admin.ErrorLogType, log.FormattedMessage)
}
diff --git a/codegen.sh b/codegen.sh
index 4281d88d..8ef70c54 100755
--- a/codegen.sh
+++ b/codegen.sh
@@ -107,8 +107,8 @@ client-gen \
--input-base "${REPOSITORY}/pkg/apis"
echo "Running controller-gen ..."
-controller-gen crd:crdVersions=v1 paths=./... output:dir=pkg/installer
-rm -f pkg/installer/direct.csi.min.io_directcsidrives.yaml pkg/installer/direct.csi.min.io_directcsivolumes.yaml
+controller-gen crd:crdVersions=v1 paths=./... output:dir=pkg/admin/installer
+rm -f pkg/admin/installer/direct.csi.min.io_directcsidrives.yaml pkg/admin/installer/direct.csi.min.io_directcsivolumes.yaml
echo "Running conversion-gen ..."
conversion-gen \
diff --git a/docs/admin.md b/docs/admin.md
new file mode 100644
index 00000000..e5b18a59
--- /dev/null
+++ b/docs/admin.md
@@ -0,0 +1,373 @@
+# DirectPV Admin Client APIs
+
+The DirectPV Admin Golang Client SDK provides APIs to manage DirectPV drives and volumes.
+
+This quickstart guide will show you how to use DirectPV Admin client SDK to list the initialized drives.
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/minio/directpv/pkg/admin"
+ "k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/clientcmd"
+)
+
+const (
+ MaxThreadCount = 200
+)
+
+func getKubeConfig() (*rest.Config, error) {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return nil, err
+ }
+ kubeConfig := filepath.Join(home, ".kube", "config")
+ config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
+ if err != nil {
+ if config, err = rest.InClusterConfig(); err != nil {
+ return nil, err
+ }
+ }
+ config.QPS = float32(MaxThreadCount / 2)
+ config.Burst = MaxThreadCount
+ return config, nil
+}
+
+func main() {
+ kubeConfig, err := getKubeConfig()
+ if err != nil {
+ fmt.Printf("%s: Could not connect to kubernetes. %s=%s\n", "Error", "KUBECONFIG", kubeConfig)
+ os.Exit(1)
+ }
+ adminClient, err := admin.NewClient(kubeConfig)
+ if err != nil {
+ log.Fatalf("unable to initialize client; %v", err)
+ }
+ drives, err := adminClient.NewDriveLister().Get(context.Background())
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ for _, drive := range drives {
+ fmt.Printf("\n DeviceName: %v", drive.GetDriveName())
+ fmt.Printf("\n Node: %v", drive.GetNodeID())
+ fmt.Printf("\n Make: %v", drive.Status.Make)
+ fmt.Println()
+ }
+}
+```
+
+## Install DirectPV
+
+Installs DirectPV components
+
+### Install(ctx context.Context, args InstallArgs, installerTasks []installer.Task) error
+
+__Example__
+
+```go
+args := admin.InstallArgs{
+ Image: image,
+ Registry: registry,
+ Org: org,
+ ImagePullSecrets: imagePullSecrets,
+ NodeSelector: nodeSelector,
+ Tolerations: tolerations,
+ SeccompProfile: seccompProfile,
+ AppArmorProfile: apparmorProfile,
+ EnableLegacy: legacyFlag,
+ PluginVersion: pluginVersion,
+ Quiet: quietFlag,
+ KubeVersion: kubeVersion,
+ DryRun: dryRunPrinter != nil,
+ OutputFormat: outputFormat,
+ Declarative: declarativeFlag,
+ Openshift: openshiftFlag,
+ AuditWriter: file,
+}
+var installedComponents []installer.Component
+legacyClient, err := legacyclient.NewClient(adminClient.K8s())
+if err != nil {
+ log.Fatalf("error creating legacy client:", err)
+}
+installerTasks := installer.GetDefaultTasks(adminClient.Client, legacyClient)
+if err := adminClient.Install(ctx, args, installerTasks); err != nil {
+ log.Fatalf("unable to complete installation; %v", err)
+}
+```
+
+## Refresh Nodes
+
+Refreshes the nodes to get the latest devices list, this is used for discovering the nodes and devices present in the cluster.
+
+### RefreshNodes(ctx context.Context, selectedNodes []string) (<-chan directpvtypes.NodeID, <-chan error, error)
+
+__Example__
+
+```go
+nodeCh, errCh, err := adminClient.RefreshNodes(ctx, []string{"praveen-thinkpad-x1-carbon-6th"})
+if err != nil {
+ log.Fatalln(err)
+}
+
+for {
+ select {
+ case nodeID, ok := <-nodeCh:
+ if !ok {
+ return
+ }
+ log.Println("Refreshing node ", nodeID)
+ case err, ok := <-errCh:
+ if !ok {
+ return
+ }
+ log.Fatalln(err)
+ case <-ctx.Done():
+ return
+ }
+}
+```
+
+## DirectPV Info
+
+Returns the overall information about DirectPV installation
+
+### Info(ctx context.Context) (map[string]NodeLevelInfo, error)
+
+__Example__
+
+```go
+nodeInfoMap, err := adminClient.Info(context.Background())
+if err != nil {
+ log.Fatalf("unable to get info; %v", err)
+}
+```
+
+## Label DirectPV drives
+
+Label the directpv drives
+
+### LabelDrives(ctx context.Context, args LabelDriveArgs, labels []Label, log logFn) (results []LabelDriveResult, err error)
+
+__Example__
+
+```go
+labels := []admin.Label{
+ {
+ Key: directpvtypes.LabelKey("example-key"),
+ Value: directpvtypes.LabelValue("example-value"),
+ },
+}
+if _, err := adminClient.LabelDrives(context.Background(), admin.LabelDriveArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, labels, log); err != nil {
+ log.Fatalf("unable to label the drive; %v", err)
+}
+fmt.Println("successfully labeled the drive(s)")
+```
+
+## Label DirectPV volumes
+
+Label the directpv volumes
+
+### LabelVolumes(ctx context.Context, args LabelVolumeArgs, labels []Label, log logFn) (results []LabelVolumeResult, err error)
+
+__Example__
+
+```go
+labels := []admin.Label{
+ {
+ Key: directpvtypes.LabelKey("example-key"),
+ Value: directpvtypes.LabelValue("example-value"),
+ },
+}
+if _, err := adminClient.LabelVolumes(context.Background(), admin.LabelVolumeArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, labels, log); err != nil {
+ log.Fatalf("unable to label the volume; %v", err)
+}
+```
+
+## Cordon the drive
+
+Cordon the drive to make it unschedulable
+
+### Cordon(ctx context.Context, args CordonArgs, log logFn) (results []CordonResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.Cordon(context.Background(), admin.CordonArgs{
+ Drives: []string{"dm-1"},
+}, log); err != nil {
+ log.Fatalf("unable to cordon the drive; %v", err)
+}
+```
+
+## Uncordon the drive
+
+Mark drives as schedulable
+
+### Uncordon(ctx context.Context, args UncordonArgs, log logFn) (results []UncordonResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.Uncordon(context.Background(), admin.UncordonArgs{
+ Drives: []string{"dm-1"},
+}, log); err != nil {
+ log.Fatalf("unable to uncordon the drive; %v", err)
+}
+```
+
+## Migrate the legacy drives and volumes
+
+Migrates the legacy direct-csi drives and volumes
+
+### Migrate(ctx context.Context, args MigrateArgs) error
+
+__Example__
+
+```go
+suffix := time.Now().Format(time.RFC3339)
+if err := adminClient.Migrate(ctx, admin.MigrateArgs{
+ DrivesBackupFile: "directcsidrives-" + suffix + ".yaml",
+ VolumesBackupFile: "directcsivolumes-" + suffix + ".yaml",
+}); err != nil {
+ log.Fatalf("migration failed; %v", err)
+}
+```
+
+## Move the volume references from one drive to another
+
+Move volumes excluding data from source drive to destination drive on a same node
+
+### Move(ctx context.Context, args MoveArgs, log logFn) error
+
+__Example__
+
+```go
+if err := adminClient.Move(context.Background(), admin.MoveArgs{
+ Source: directpvtypes.DriveID("2786de98-2a84-40d4-8cee-8f73686928f8"),
+ Destination: directpvtypes.DriveID("b35f1f8e-6bf3-4747-9976-192b23c1a019"),
+}, log); err != nil {
+ log.Fatalf("unable to move the drive; %v", err)
+}
+fmt.Println("successfully moved the drive")
+```
+
+## Cleanup volumes
+
+Cleanup stale volumes
+
+### Clean(ctx context.Context, args CleanArgs, log logFn) (removedVolumes []string, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.Clean(context.Background(), admin.CleanArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to clean the volume; %v", err)
+}
+fmt.Println("successfully cleaned the volume(s)")
+```
+
+## Suspend drives
+
+Suspend the drives (CAUTION: This will make the corresponding volumes as read-only)
+
+### SuspendDrives(ctx context.Context, args SuspendDriveArgs, log logFn) (results []SuspendDriveResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.SuspendDrives(context.Background(), admin.SuspendDriveArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to suspend the drive; %v", err)
+}
+fmt.Println("successfully suspended the drive(s)")
+```
+
+## Suspend volumes
+
+Suspend the volumes (CAUTION: This will make the corresponding volumes as read-only)
+
+### SuspendVolumes(ctx context.Context, args SuspendVolumeArgs, log logFn) (results []SuspendVolumeResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.SuspendVolumes(context.Background(), admin.SuspendVolumeArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to suspend the volume; %v", err)
+}
+fmt.Println("successfully suspended the volume(s)")
+```
+
+## Resume drives
+
+Resume suspended drives
+
+### ResumeDrives(ctx context.Context, args ResumeDriveArgs, log logFn) (results []ResumeDriveResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.ResumeDrives(context.Background(), admin.SuspendDriveArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to resume the drive; %v", err)
+}
+fmt.Println("successfully resumed the drive(s)")
+```
+
+## Resume volumes
+
+Resume suspended volumes
+
+### ResumeVolumes(ctx context.Context, args ResumeVolumeArgs, log logFn) (results []ResumeVolumeResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.ResumeVolumes(context.Background(), admin.ResumeVolumeArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to resume the volume; %v", err)
+}
+fmt.Println("successfully resumed the volume(s)")
+```
+
+## Remove drives
+
+Remove unused drives from DirectPV
+
+### Remove(ctx context.Context, args RemoveArgs, log logFn) (results []RemoveResult, err error)
+
+__Example__
+
+```go
+if _, err := adminClient.Remove(context.Background(), admin.RemoveArgs{
+ Nodes: []string{"praveen-thinkpad-x1-carbon-6th"},
+ Drives: []string{"dm-0"},
+}, log); err != nil {
+ log.Fatalf("unable to remove the drive; %v", err)
+}
+fmt.Println("successfully removed the drive(s)")
+```
diff --git a/docs/command-reference.md b/docs/command-reference.md
index ba3183fd..0e558b04 100644
--- a/docs/command-reference.md
+++ b/docs/command-reference.md
@@ -62,7 +62,7 @@ FLAGS:
--apparmor-profile string Set path to Apparmor profile
--seccomp-profile string Set path to Seccomp profile
-o, --output string Generate installation manifest. One of: yaml|json
- --kube-version string Select the kubernetes version for manifest generation (default "1.27.0")
+ --kube-version string Select the kubernetes version for manifest generation (default "1.29.0")
--legacy Enable legacy mode (Used with '-o')
--openshift Use OpenShift specific installation
-h, --help help for install
diff --git a/pkg/admin/clean.go b/pkg/admin/clean.go
new file mode 100644
index 00000000..08a9fb84
--- /dev/null
+++ b/pkg/admin/clean.go
@@ -0,0 +1,121 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/types"
+ "github.com/minio/directpv/pkg/utils"
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// CleanArgs represents the arguments to clean the volumes
+type CleanArgs struct {
+ Nodes []string
+ Drives []string
+ DriveIDs []string
+ PodNames []string
+ PodNamespaces []string
+ VolumeStatus []directpvtypes.VolumeStatus
+ VolumeNames []string
+ DryRun bool
+}
+
+// Clean removes the stale/abandoned volumes
+func (client *Client) Clean(ctx context.Context, args CleanArgs, log LogFunc) (removedVolumes []string, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewVolumeLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ DriveIDSelector(utils.ToLabelValues(args.DriveIDs)).
+ PodNameSelector(utils.ToLabelValues(args.PodNames)).
+ PodNSSelector(utils.ToLabelValues(args.PodNamespaces)).
+ StatusSelector(args.VolumeStatus).
+ VolumeNameSelector(args.VolumeNames).
+ List(ctx)
+
+ matchFunc := func(volume *types.Volume) bool {
+ pv, err := client.Kube().CoreV1().PersistentVolumes().Get(ctx, volume.Name, metav1.GetOptions{})
+ if err != nil {
+ if apierrors.IsNotFound(err) {
+ return true
+ }
+ log(
+ LogMessage{
+ Type: ErrorLogType,
+ Err: err,
+ Message: "unable to get PV for volume",
+ Values: map[string]any{"volume": volume.Name},
+ FormattedMessage: fmt.Sprintf("unable to get PV for volume %v; %v\n", volume.Name, err),
+ },
+ )
+ return false
+ }
+ switch pv.Status.Phase {
+ case corev1.VolumeReleased, corev1.VolumeFailed:
+ return true
+ default:
+ return false
+ }
+ }
+
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ if !matchFunc(&result.Volume) {
+ continue
+ }
+ result.Volume.RemovePVProtection()
+ if args.DryRun {
+ continue
+ }
+ if _, err = client.Volume().Update(ctx, &result.Volume, metav1.UpdateOptions{
+ TypeMeta: types.NewVolumeTypeMeta(),
+ }); err != nil {
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "removing volume",
+ Values: map[string]any{"volume": result.Volume.Name},
+ FormattedMessage: fmt.Sprintf("Removing volume %v\n", result.Volume.Name),
+ },
+ )
+
+ if err = client.Volume().Delete(ctx, result.Volume.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
+ return
+ }
+ removedVolumes = append(removedVolumes, result.Volume.Name)
+ }
+
+ return
+}
diff --git a/pkg/admin/client.go b/pkg/admin/client.go
new file mode 100644
index 00000000..3a3057f9
--- /dev/null
+++ b/pkg/admin/client.go
@@ -0,0 +1,38 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "github.com/minio/directpv/pkg/client"
+ "k8s.io/client-go/rest"
+)
+
+// Client represents the admin clientset
+type Client struct {
+ *client.Client
+}
+
+// NewClient returns a new admin client
+func NewClient(c *rest.Config) (*Client, error) {
+ directpvClientSet, err := client.NewClient(c)
+ if err != nil {
+ return nil, err
+ }
+ return &Client{
+ Client: directpvClientSet,
+ }, nil
+}
diff --git a/pkg/admin/cordon.go b/pkg/admin/cordon.go
new file mode 100644
index 00000000..1db313fe
--- /dev/null
+++ b/pkg/admin/cordon.go
@@ -0,0 +1,114 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// CordonResult represents the
+type CordonResult struct {
+ NodeID directpvtypes.NodeID
+ DriveName directpvtypes.DriveName
+}
+
+// CordonArgs represents the args to Cordon the drive
+type CordonArgs struct {
+ Nodes []string
+ Drives []string
+ Status []directpvtypes.DriveStatus
+ DriveIDs []directpvtypes.DriveID
+ DryRun bool
+}
+
+// Cordon makes a drive unschedulable
+func (client *Client) Cordon(ctx context.Context, args CordonArgs, log LogFunc) (results []CordonResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ StatusSelector(args.Status).
+ DriveIDSelector(args.DriveIDs).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+
+ processed = true
+
+ if result.Drive.IsUnschedulable() {
+ continue
+ }
+
+ volumes := result.Drive.GetVolumes()
+ if len(volumes) != 0 {
+ for vresult := range client.NewVolumeLister().VolumeNameSelector(volumes).IgnoreNotFound(true).List(ctx) {
+ if vresult.Err != nil {
+ err = vresult.Err
+ return
+ }
+
+ if vresult.Volume.Status.Status == directpvtypes.VolumeStatusPending {
+ err = fmt.Errorf("unable to cordon drive %v; pending volumes found", result.Drive.GetDriveID())
+ return
+ }
+ }
+ }
+
+ result.Drive.Unschedulable()
+ if !args.DryRun {
+ if _, err = client.Drive().Update(ctx, &result.Drive, metav1.UpdateOptions{}); err != nil {
+ err = fmt.Errorf("unable to cordon drive %v; %v", result.Drive.GetDriveID(), err)
+ return
+ }
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "drive cordoned",
+ Values: map[string]any{"nodeId": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Drive %v/%v cordoned\n", result.Drive.GetNodeID(), result.Drive.GetDriveName()),
+ },
+ )
+
+ results = append(results, CordonResult{
+ NodeID: result.Drive.GetNodeID(),
+ DriveName: result.Drive.GetDriveName(),
+ })
+ }
+
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/info.go b/pkg/admin/info.go
new file mode 100644
index 00000000..b746d209
--- /dev/null
+++ b/pkg/admin/info.go
@@ -0,0 +1,98 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/minio/directpv/pkg/consts"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// NodeLevelInfo represents the node level information
+type NodeLevelInfo struct {
+ DriveSize uint64
+ VolumeSize uint64
+ DriveCount int
+ VolumeCount int
+}
+
+// Info returns the overall info of the directpv installation
+func (client *Client) Info(ctx context.Context) (map[string]NodeLevelInfo, error) {
+ crds, err := client.CRD().List(ctx, metav1.ListOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("unable to list CRDs; %v", err)
+ }
+ drivesFound := false
+ volumesFound := false
+ for _, crd := range crds.Items {
+ if strings.Contains(crd.Name, consts.DriveResource+"."+consts.GroupName) {
+ drivesFound = true
+ }
+ if strings.Contains(crd.Name, consts.VolumeResource+"."+consts.GroupName) {
+ volumesFound = true
+ }
+ }
+ if !drivesFound || !volumesFound {
+ return nil, fmt.Errorf("%v installation not found", consts.AppPrettyName)
+ }
+ nodeList, err := client.K8s().GetCSINodes(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get CSI nodes; %v", err)
+ }
+ if len(nodeList) == 0 {
+ return nil, fmt.Errorf("%v not installed", consts.AppPrettyName)
+ }
+ drives, err := client.NewDriveLister().Get(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get drive list; %v", err)
+ }
+ volumes, err := client.NewVolumeLister().Get(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get volume list; %v", err)
+ }
+ nodeInfo := make(map[string]NodeLevelInfo, len(nodeList))
+ for _, n := range nodeList {
+ driveCount := 0
+ driveSize := uint64(0)
+ for _, d := range drives {
+ if string(d.GetNodeID()) == n {
+ driveCount++
+ driveSize += uint64(d.Status.TotalCapacity)
+ }
+ }
+ volumeCount := 0
+ volumeSize := uint64(0)
+ for _, v := range volumes {
+ if string(v.GetNodeID()) == n {
+ if v.IsPublished() {
+ volumeCount++
+ volumeSize += uint64(v.Status.TotalCapacity)
+ }
+ }
+ }
+ nodeInfo[n] = NodeLevelInfo{
+ DriveSize: driveSize,
+ VolumeSize: volumeSize,
+ DriveCount: driveCount,
+ VolumeCount: volumeCount,
+ }
+ }
+ return nodeInfo, nil
+}
diff --git a/pkg/admin/init_config.go b/pkg/admin/init_config.go
new file mode 100644
index 00000000..a4699a58
--- /dev/null
+++ b/pkg/admin/init_config.go
@@ -0,0 +1,132 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "errors"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/google/uuid"
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/types"
+ "gopkg.in/yaml.v3"
+)
+
+const (
+ // DriveSelectedValue denotes the option in InitConfig
+ DriveSelectedValue = "yes"
+)
+
+var errUnsupportedInitConfigVersion = errors.New("unsupported init config version")
+
+const latestInitConfigVersion = "v1"
+
+// InitConfig holds the latest config version
+type InitConfig = InitConfigV1
+
+// NodeInfo holds the latest node info
+type NodeInfo = NodeInfoV1
+
+// DriveInfo holds the latest drive info
+type DriveInfo = DriveInfoV1
+
+// NewInitConfig initializes an init config.
+func NewInitConfig() InitConfig {
+ return InitConfig{
+ Version: latestInitConfigVersion,
+ }
+}
+
+// ReadInitConfig reads the init config from a file
+func ReadInitConfig(inputFile string) (*InitConfig, error) {
+ f, err := os.Open(inputFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return parseInitConfig(f)
+}
+
+func parseInitConfig(r io.Reader) (*InitConfig, error) {
+ var config InitConfig
+ if err := yaml.NewDecoder(r).Decode(&config); err != nil {
+ return nil, err
+ }
+ if config.Version != latestInitConfigVersion {
+ return nil, errUnsupportedInitConfigVersion
+ }
+ return &config, nil
+}
+
+// Write encodes the YAML to the stream provided
+func (config InitConfig) Write(w io.Writer) error {
+ encoder := yaml.NewEncoder(w)
+ defer encoder.Close()
+ return encoder.Encode(config)
+}
+
+// ToInitConfig converts the map to InitConfig
+func ToInitConfig(resultMap map[directpvtypes.NodeID][]types.Device) InitConfig {
+ nodeInfo := []NodeInfo{}
+ initConfig := NewInitConfig()
+ for node, devices := range resultMap {
+ driveInfo := []DriveInfo{}
+ for _, device := range devices {
+ if device.DeniedReason != "" {
+ continue
+ }
+ driveInfo = append(driveInfo, DriveInfo{
+ ID: device.ID,
+ Name: device.Name,
+ Size: device.Size,
+ Make: device.Make,
+ FS: device.FSType,
+ Select: DriveSelectedValue,
+ })
+ }
+ nodeInfo = append(nodeInfo, NodeInfo{
+ Name: node,
+ Drives: driveInfo,
+ })
+ }
+ initConfig.Nodes = nodeInfo
+ return initConfig
+}
+
+// ToInitRequestObjects converts initConfig to init request objects.
+func (config *InitConfig) ToInitRequestObjects() (initRequests []types.InitRequest, requestID string) {
+ requestID = uuid.New().String()
+ for _, node := range config.Nodes {
+ initDevices := []types.InitDevice{}
+ for _, device := range node.Drives {
+ if strings.ToLower(device.Select) != DriveSelectedValue {
+ continue
+ }
+ initDevices = append(initDevices, types.InitDevice{
+ ID: device.ID,
+ Name: device.Name,
+ Force: device.FS != "",
+ })
+ }
+ if len(initDevices) > 0 {
+ initRequests = append(initRequests, *types.NewInitRequest(requestID, node.Name, initDevices))
+ }
+ }
+ return
+}
diff --git a/cmd/kubectl-directpv/init_config_v1.go b/pkg/admin/init_config_v1.go
similarity index 64%
rename from cmd/kubectl-directpv/init_config_v1.go
rename to pkg/admin/init_config_v1.go
index 0943ac6f..5a0b9d16 100644
--- a/cmd/kubectl-directpv/init_config_v1.go
+++ b/pkg/admin/init_config_v1.go
@@ -1,5 +1,5 @@
// This file is part of MinIO DirectPV
-// Copyright (c) 2021, 2022 MinIO, Inc.
+// Copyright (c) 2024 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -14,32 +14,28 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package main
+package admin
import directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
-const (
- driveSelectedValue = "yes"
-)
-
// InitConfigV1 defines the config to initialize the devices
type InitConfigV1 struct {
- Version string `yaml:"version"`
- Nodes []NodeInfoV1 `yaml:"nodes,omitempty"`
+ Version string `yaml:"version" json:"version"`
+ Nodes []NodeInfoV1 `yaml:"nodes,omitempty" json:"nodes,omitempty"`
}
// NodeInfoV1 holds the node information
type NodeInfoV1 struct {
- Name directpvtypes.NodeID `yaml:"name"`
- Drives []DriveInfoV1 `yaml:"drives,omitempty"`
+ Name directpvtypes.NodeID `yaml:"name" json:"name"`
+ Drives []DriveInfoV1 `yaml:"drives,omitempty" json:"drives,omitempty"`
}
// DriveInfoV1 represents the drives that are to be initialized
type DriveInfoV1 struct {
- ID string `yaml:"id"`
- Name string `yaml:"name"`
- Size uint64 `yaml:"size"`
- Make string `yaml:"make"`
- FS string `yaml:"fs,omitempty"`
- Select string `yaml:"select,omitempty"`
+ ID string `yaml:"id" json:"id"`
+ Name string `yaml:"name" json:"name"`
+ Size uint64 `yaml:"size" json:"size"`
+ Make string `yaml:"make" json:"make"`
+ FS string `yaml:"fs,omitempty" json:"fs,omitempty"`
+ Select string `yaml:"select,omitempty" json:"select,omitempty"`
}
diff --git a/pkg/admin/install.go b/pkg/admin/install.go
new file mode 100644
index 00000000..c17843a2
--- /dev/null
+++ b/pkg/admin/install.go
@@ -0,0 +1,190 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/minio/directpv/pkg/admin/installer"
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ legacyclient "github.com/minio/directpv/pkg/legacy/client"
+ "github.com/minio/directpv/pkg/utils"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ versionpkg "k8s.io/apimachinery/pkg/util/version"
+ "k8s.io/klog/v2"
+)
+
+// ErrInstallationIncomplete denotes that the installation couldn't complete
+var ErrInstallationIncomplete = errors.New("unable to complete the installation")
+
+// InstallArgs represents the arguments required for installation
+type InstallArgs struct {
+ // Image of the DirectPV
+ Image string
+ // Registry denotes the private registry
+ Registry string
+ // Org denotes the organization name
+ Org string
+ // ImagePullSecrets for the images from priv registries
+ ImagePullSecrets []string
+ // NodeSelector denotes the nodeSelector to be set for the node-server
+ NodeSelector map[string]string
+ // Tolerations denotes the tolerations to be set for the node-server
+ Tolerations []corev1.Toleration
+ // SeccompProfile denotes the seccomp profile name to be set on the node-server
+ SeccompProfile string
+ // AppArmorProfile denotes the apparmor profile name to be set on the node-server
+ AppArmorProfile string
+ // EnableLegacy to run in legacy mode
+ EnableLegacy bool
+ // PluginVersion denotes the plugin version; this will be set in node-server's annotations
+ PluginVersion string
+ // Quiet enables quiet mode
+ Quiet bool
+ // KubeVersion is required for declarative and dryrun manifests
+ KubeVersion *versionpkg.Version
+ // DryRun when set, runs in dryrun mode and generates the manifests
+ DryRun bool
+ // OutputFormat denotes the output format (yaml|json) for the manifests; to be used for DryRun
+ OutputFormat string
+ // Declarative when set, generates yaml manifests
+ Declarative bool
+ // Openshift when set, runs openshift specific installation
+ Openshift bool
+ // ProgressCh represents the progress channel
+ ProgressCh chan<- installer.Message
+ // AuditWriter denotes the writer passed to record the audit log
+ AuditWriter io.Writer
+}
+
+// Validate - validates the args
+func (args *InstallArgs) Validate() error {
+ if args.DryRun || args.Declarative {
+ switch args.OutputFormat {
+ case "yaml", "json":
+ case "":
+ args.OutputFormat = "yaml"
+ }
+ }
+ return nil
+}
+
+// Install - installs directpv with the provided arguments
+func (client *Client) Install(ctx context.Context, args InstallArgs, installerTasks []installer.Task) error {
+ var err error
+ if err := args.Validate(); err != nil {
+ return err
+ }
+ installerArgs := installer.NewArgs(args.Image)
+ var version string
+ if args.PluginVersion != "" {
+ version = args.PluginVersion
+ }
+ installerArgs.Registry = args.Registry
+ installerArgs.Org = args.Org
+ installerArgs.ImagePullSecrets = args.ImagePullSecrets
+ installerArgs.NodeSelector = args.NodeSelector
+ installerArgs.Tolerations = args.Tolerations
+ installerArgs.SeccompProfile = args.SeccompProfile
+ installerArgs.AppArmorProfile = args.AppArmorProfile
+ installerArgs.Quiet = args.Quiet
+ installerArgs.KubeVersion = args.KubeVersion
+ installerArgs.Legacy = client.isLegacyEnabled(ctx, args)
+ installerArgs.PluginVersion = version
+ if args.AuditWriter != nil {
+ installerArgs.ObjectWriter = args.AuditWriter
+ }
+ if args.DryRun {
+ installerArgs.DryRun = true
+ if args.OutputFormat == "yaml" {
+ installerArgs.ObjectMarshaler = func(obj runtime.Object) ([]byte, error) {
+ return utils.ToYAML(obj)
+ }
+ } else {
+ installerArgs.ObjectMarshaler = func(obj runtime.Object) ([]byte, error) {
+ return utils.ToJSON(obj)
+ }
+ }
+ if installerArgs.KubeVersion == nil {
+ // default higher version
+ if installerArgs.KubeVersion, err = versionpkg.ParseSemantic("1.29.0"); err != nil {
+ klog.Fatalf("this should not happen; %v", err)
+ }
+ }
+ } else {
+ major, minor, err := client.K8s().GetKubeVersion()
+ if err != nil {
+ return err
+ }
+ installerArgs.KubeVersion, err = versionpkg.ParseSemantic(fmt.Sprintf("%v.%v.0", major, minor))
+ if err != nil {
+ klog.Fatalf("this should not happen; %v", err)
+ }
+ }
+ installerArgs.Declarative = args.Declarative
+ installerArgs.Openshift = args.Openshift
+ installerArgs.ProgressCh = args.ProgressCh
+
+ return installer.Install(ctx, installerArgs, installerTasks)
+}
+
+func (client Client) isLegacyEnabled(ctx context.Context, args InstallArgs) bool {
+ if args.DryRun {
+ return args.EnableLegacy
+ }
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewVolumeLister().
+ LabelSelector(
+ map[directpvtypes.LabelKey]directpvtypes.LabelValue{
+ directpvtypes.MigratedLabelKey: "true",
+ },
+ ).
+ IgnoreNotFound(true).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ utils.Eprintf(args.Quiet, true, "unable to get volumes; %v", result.Err)
+ break
+ }
+
+ return true
+ }
+
+ legacyClient, err := legacyclient.NewClient(client.K8s())
+ if err != nil {
+ utils.Eprintf(args.Quiet, true, "unable to create legacy client; %v", err)
+ return false
+ }
+
+ for result := range legacyClient.ListVolumes(ctx) {
+ if result.Err != nil {
+ utils.Eprintf(args.Quiet, true, "unable to get legacy volumes; %v", result.Err)
+ break
+ }
+
+ return true
+ }
+
+ return false
+}
diff --git a/pkg/installer/args.go b/pkg/admin/installer/args.go
similarity index 98%
rename from pkg/installer/args.go
rename to pkg/admin/installer/args.go
index 31bde459..10f4867d 100644
--- a/pkg/installer/args.go
+++ b/pkg/admin/installer/args.go
@@ -117,6 +117,10 @@ func (args *Args) validate() error {
return errors.New("object converter must be provided")
}
+ if args.KubeVersion == nil {
+ return errors.New("kubeversion is not set")
+ }
+
return nil
}
diff --git a/pkg/installer/consts.go b/pkg/admin/installer/consts.go
similarity index 100%
rename from pkg/installer/consts.go
rename to pkg/admin/installer/consts.go
diff --git a/pkg/installer/crd.go b/pkg/admin/installer/crd.go
similarity index 77%
rename from pkg/installer/crd.go
rename to pkg/admin/installer/crd.go
index b68636d5..5a69013f 100644
--- a/pkg/installer/crd.go
+++ b/pkg/admin/installer/crd.go
@@ -24,11 +24,6 @@ import (
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
- "github.com/minio/directpv/pkg/initrequest"
- "github.com/minio/directpv/pkg/k8s"
- "github.com/minio/directpv/pkg/node"
- "github.com/minio/directpv/pkg/volume"
"k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -49,7 +44,9 @@ var nodesYAML []byte
//go:embed directpv.min.io_directpvinitrequests.yaml
var initrequestsYAML []byte
-type crdTask struct{}
+type crdTask struct {
+ client *client.Client
+}
func (crdTask) Name() string {
return "CRD"
@@ -69,12 +66,12 @@ func (crdTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (crdTask) Execute(ctx context.Context, args *Args) error {
- return createCRDs(ctx, args)
+func (t crdTask) Execute(ctx context.Context, args *Args) error {
+ return t.createCRDs(ctx, args)
}
-func (c crdTask) Delete(ctx context.Context, args *Args) error {
- return deleteCRDs(ctx, args.ForceUninstall)
+func (t crdTask) Delete(ctx context.Context, args *Args) error {
+ return t.deleteCRDs(ctx, args.ForceUninstall)
}
func setNoneConversionStrategy(crd *apiextensions.CustomResourceDefinition) {
@@ -148,7 +145,7 @@ func updateCRD(
return existingCRD, false, nil
}
-func createCRDs(ctx context.Context, args *Args) (err error) {
+func (t crdTask) createCRDs(ctx context.Context, args *Args) (err error) {
register := func(data []byte, step int) error {
object := map[string]interface{}{}
if err := yaml.Unmarshal(data, &object); err != nil {
@@ -171,7 +168,7 @@ func createCRDs(ctx context.Context, args *Args) (err error) {
return args.writeObject(&crd)
}
- existingCRD, err := k8s.CRDClient().Get(ctx, crd.Name, metav1.GetOptions{})
+ existingCRD, err := t.client.CRD().Get(ctx, crd.Name, metav1.GetOptions{})
if err != nil {
if !apierrors.IsNotFound(err) {
return err
@@ -182,7 +179,7 @@ func createCRDs(ctx context.Context, args *Args) (err error) {
}
if !args.Declarative {
- _, err := k8s.CRDClient().Create(ctx, &crd, metav1.CreateOptions{})
+ _, err := t.client.CRD().Create(ctx, &crd, metav1.CreateOptions{})
if err != nil {
return err
}
@@ -204,7 +201,7 @@ func createCRDs(ctx context.Context, args *Args) (err error) {
}
if !args.Declarative && !isLatest {
- updatedCRD, err = k8s.CRDClient().Update(ctx, updatedCRD, metav1.UpdateOptions{})
+ updatedCRD, err = t.client.CRD().Update(ctx, updatedCRD, metav1.UpdateOptions{})
if err != nil {
return err
}
@@ -233,11 +230,11 @@ func createCRDs(ctx context.Context, args *Args) (err error) {
return register(initrequestsYAML, 4)
}
-func removeVolumes(ctx context.Context) error {
+func (t crdTask) removeVolumes(ctx context.Context) error {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
- for result := range volume.NewLister().List(ctx) {
+ for result := range t.client.NewVolumeLister().List(ctx) {
if result.Err != nil {
if apierrors.IsNotFound(result.Err) {
break
@@ -248,12 +245,12 @@ func removeVolumes(ctx context.Context) error {
result.Volume.RemovePVProtection()
result.Volume.RemovePurgeProtection()
- _, err := client.VolumeClient().Update(ctx, &result.Volume, metav1.UpdateOptions{})
+ _, err := t.client.Volume().Update(ctx, &result.Volume, metav1.UpdateOptions{})
if err != nil {
return err
}
- err = client.VolumeClient().Delete(ctx, result.Volume.Name, metav1.DeleteOptions{})
+ err = t.client.Volume().Delete(ctx, result.Volume.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
@@ -262,11 +259,11 @@ func removeVolumes(ctx context.Context) error {
return nil
}
-func removeDrives(ctx context.Context) error {
+func (t crdTask) removeDrives(ctx context.Context) error {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
- for result := range drive.NewLister().List(ctx) {
+ for result := range t.client.NewDriveLister().List(ctx) {
if result.Err != nil {
if apierrors.IsNotFound(result.Err) {
break
@@ -274,12 +271,12 @@ func removeDrives(ctx context.Context) error {
return result.Err
}
result.Drive.Finalizers = []string{}
- _, err := client.DriveClient().Update(ctx, &result.Drive, metav1.UpdateOptions{})
+ _, err := t.client.Drive().Update(ctx, &result.Drive, metav1.UpdateOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = client.DriveClient().Delete(ctx, result.Drive.Name, metav1.DeleteOptions{})
+ err = t.client.Drive().Delete(ctx, result.Drive.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
@@ -288,11 +285,11 @@ func removeDrives(ctx context.Context) error {
return nil
}
-func removeNodes(ctx context.Context) error {
+func (t crdTask) removeNodes(ctx context.Context) error {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
- for result := range node.NewLister().List(ctx) {
+ for result := range t.client.NewNodeLister().List(ctx) {
if result.Err != nil {
if apierrors.IsNotFound(result.Err) {
break
@@ -300,11 +297,11 @@ func removeNodes(ctx context.Context) error {
return result.Err
}
result.Node.Finalizers = []string{}
- _, err := client.NodeClient().Update(ctx, &result.Node, metav1.UpdateOptions{})
+ _, err := t.client.Node().Update(ctx, &result.Node, metav1.UpdateOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = client.NodeClient().Delete(ctx, result.Node.Name, metav1.DeleteOptions{})
+ err = t.client.Node().Delete(ctx, result.Node.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
@@ -313,11 +310,11 @@ func removeNodes(ctx context.Context) error {
return nil
}
-func removeInitRequests(ctx context.Context) error {
+func (t crdTask) removeInitRequests(ctx context.Context) error {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
- for result := range initrequest.NewLister().List(ctx) {
+ for result := range t.client.NewInitRequestLister().List(ctx) {
if result.Err != nil {
if apierrors.IsNotFound(result.Err) {
break
@@ -325,11 +322,11 @@ func removeInitRequests(ctx context.Context) error {
return result.Err
}
result.InitRequest.Finalizers = []string{}
- _, err := client.InitRequestClient().Update(ctx, &result.InitRequest, metav1.UpdateOptions{})
+ _, err := t.client.InitRequest().Update(ctx, &result.InitRequest, metav1.UpdateOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = client.InitRequestClient().Delete(ctx, result.InitRequest.Name, metav1.DeleteOptions{})
+ err = t.client.InitRequest().Delete(ctx, result.InitRequest.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
@@ -338,47 +335,47 @@ func removeInitRequests(ctx context.Context) error {
return nil
}
-func deleteCRDs(ctx context.Context, force bool) error {
+func (t crdTask) deleteCRDs(ctx context.Context, force bool) error {
if !force {
return nil
}
- if err := removeVolumes(ctx); err != nil {
+ if err := t.removeVolumes(ctx); err != nil {
return err
}
- if err := removeDrives(ctx); err != nil {
+ if err := t.removeDrives(ctx); err != nil {
return err
}
- if err := removeNodes(ctx); err != nil {
+ if err := t.removeNodes(ctx); err != nil {
return err
}
- if err := removeInitRequests(ctx); err != nil {
+ if err := t.removeInitRequests(ctx); err != nil {
return err
}
driveCRDName := consts.DriveResource + "." + consts.GroupName
- err := k8s.CRDClient().Delete(ctx, driveCRDName, metav1.DeleteOptions{})
+ err := t.client.CRD().Delete(ctx, driveCRDName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
volumeCRDName := consts.VolumeResource + "." + consts.GroupName
- err = k8s.CRDClient().Delete(ctx, volumeCRDName, metav1.DeleteOptions{})
+ err = t.client.CRD().Delete(ctx, volumeCRDName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
nodeCRDName := consts.NodeResource + "." + consts.GroupName
- err = k8s.CRDClient().Delete(ctx, nodeCRDName, metav1.DeleteOptions{})
+ err = t.client.CRD().Delete(ctx, nodeCRDName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
initRequestCRDName := consts.InitRequestResource + "." + consts.GroupName
- err = k8s.CRDClient().Delete(ctx, initRequestCRDName, metav1.DeleteOptions{})
+ err = t.client.CRD().Delete(ctx, initRequestCRDName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
diff --git a/pkg/installer/csidriver.go b/pkg/admin/installer/csidriver.go
similarity index 74%
rename from pkg/installer/csidriver.go
rename to pkg/admin/installer/csidriver.go
index ba888cdd..7cce21ac 100644
--- a/pkg/installer/csidriver.go
+++ b/pkg/admin/installer/csidriver.go
@@ -22,8 +22,8 @@ import (
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
legacyclient "github.com/minio/directpv/pkg/legacy/client"
storagev1 "k8s.io/api/storage/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
@@ -31,7 +31,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
-type csiDriverTask struct{}
+type csiDriverTask struct {
+ client *client.Client
+}
func (csiDriverTask) Name() string {
return "CSIDriver"
@@ -55,17 +57,17 @@ func (csiDriverTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (csiDriverTask) Execute(ctx context.Context, args *Args) error {
- return createCSIDriver(ctx, args)
+func (t csiDriverTask) Execute(ctx context.Context, args *Args) error {
+ return t.createCSIDriver(ctx, args)
}
-func (csiDriverTask) Delete(ctx context.Context, _ *Args) error {
- return deleteCSIDriver(ctx)
+func (t csiDriverTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteCSIDriver(ctx)
}
var errCSIDriverVersionUnsupported = errors.New("unsupported CSIDriver version found")
-func doCreateCSIDriver(ctx context.Context, args *Args, version string, legacy bool, step int) (err error) {
+func (t csiDriverTask) doCreateCSIDriver(ctx context.Context, args *Args, version string, legacy bool, step int) (err error) {
name := consts.Identity
if legacy {
name = legacyclient.Identity
@@ -109,7 +111,7 @@ func doCreateCSIDriver(ctx context.Context, args *Args, version string, legacy b
}
if !args.DryRun && !args.Declarative {
- _, err := k8s.KubeClient().StorageV1().CSIDrivers().Create(ctx, csiDriver, metav1.CreateOptions{})
+ _, err := t.client.Kube().StorageV1().CSIDrivers().Create(ctx, csiDriver, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
@@ -142,7 +144,7 @@ func doCreateCSIDriver(ctx context.Context, args *Args, version string, legacy b
}
if !args.DryRun && !args.Declarative {
- _, err := k8s.KubeClient().StorageV1beta1().CSIDrivers().Create(ctx, csiDriver, metav1.CreateOptions{})
+ _, err := t.client.Kube().StorageV1beta1().CSIDrivers().Create(ctx, csiDriver, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
@@ -155,26 +157,26 @@ func doCreateCSIDriver(ctx context.Context, args *Args, version string, legacy b
}
}
-func createCSIDriver(ctx context.Context, args *Args) (err error) {
+func (t csiDriverTask) createCSIDriver(ctx context.Context, args *Args) (err error) {
version := "v1"
if args.DryRun {
if args.KubeVersion.Major() >= 1 && args.KubeVersion.Minor() < 19 {
version = "v1beta1"
}
} else {
- gvk, err := k8s.GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
+ gvk, err := t.client.K8s().GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
if err != nil {
return err
}
version = gvk.Version
}
- if err := doCreateCSIDriver(ctx, args, version, false, 1); err != nil {
+ if err := t.doCreateCSIDriver(ctx, args, version, false, 1); err != nil {
return err
}
if args.Legacy {
- if err := doCreateCSIDriver(ctx, args, version, true, 2); err != nil {
+ if err := t.doCreateCSIDriver(ctx, args, version, true, 2); err != nil {
return err
}
}
@@ -182,14 +184,14 @@ func createCSIDriver(ctx context.Context, args *Args) (err error) {
return nil
}
-func doDeleteCSIDriver(ctx context.Context, version, name string) (err error) {
+func (t csiDriverTask) doDeleteCSIDriver(ctx context.Context, version, name string) (err error) {
switch version {
case "v1":
- err = k8s.KubeClient().StorageV1().CSIDrivers().Delete(
+ err = t.client.Kube().StorageV1().CSIDrivers().Delete(
ctx, name, metav1.DeleteOptions{},
)
case "v1beta1":
- err = k8s.KubeClient().StorageV1beta1().CSIDrivers().Delete(
+ err = t.client.Kube().StorageV1beta1().CSIDrivers().Delete(
ctx, name, metav1.DeleteOptions{},
)
default:
@@ -203,15 +205,15 @@ func doDeleteCSIDriver(ctx context.Context, version, name string) (err error) {
return nil
}
-func deleteCSIDriver(ctx context.Context) error {
- gvk, err := k8s.GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
+func (t csiDriverTask) deleteCSIDriver(ctx context.Context) error {
+ gvk, err := t.client.K8s().GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
if err != nil {
return err
}
- if err = doDeleteCSIDriver(ctx, gvk.Version, consts.Identity); err != nil {
+ if err = t.doDeleteCSIDriver(ctx, gvk.Version, consts.Identity); err != nil {
return err
}
- return doDeleteCSIDriver(ctx, gvk.Version, legacyclient.Identity)
+ return t.doDeleteCSIDriver(ctx, gvk.Version, legacyclient.Identity)
}
diff --git a/pkg/installer/daemonset.go b/pkg/admin/installer/daemonset.go
similarity index 92%
rename from pkg/installer/daemonset.go
rename to pkg/admin/installer/daemonset.go
index f3fd048a..4ca0fbae 100644
--- a/pkg/installer/daemonset.go
+++ b/pkg/admin/installer/daemonset.go
@@ -21,8 +21,8 @@ import (
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
legacyclient "github.com/minio/directpv/pkg/legacy/client"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@@ -48,7 +48,9 @@ const (
totalDaemonsetSteps = 2
)
-type daemonsetTask struct{}
+type daemonsetTask struct {
+ client *client.Client
+}
func (daemonsetTask) Name() string {
return "Daemonset"
@@ -72,12 +74,12 @@ func (daemonsetTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (daemonsetTask) Execute(ctx context.Context, args *Args) error {
- return createDaemonset(ctx, args)
+func (t daemonsetTask) Execute(ctx context.Context, args *Args) error {
+ return t.createDaemonset(ctx, args)
}
-func (daemonsetTask) Delete(ctx context.Context, _ *Args) error {
- return deleteDaemonset(ctx)
+func (t daemonsetTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteDaemonset(ctx)
}
func newSecurityContext(seccompProfile string) *corev1.SecurityContext {
@@ -249,7 +251,7 @@ func newDaemonset(podSpec corev1.PodSpec, name, selectorValue string, args *Args
}
}
-func doCreateDaemonset(ctx context.Context, args *Args) (err error) {
+func (t daemonsetTask) doCreateDaemonset(ctx context.Context, args *Args) (err error) {
securityContext := newSecurityContext(args.SeccompProfile)
pluginSocketDir := newPluginsSocketDir(kubeletDirPath, consts.Identity)
volumes, volumeMounts := getVolumesAndMounts(pluginSocketDir)
@@ -286,7 +288,7 @@ func doCreateDaemonset(ctx context.Context, args *Args) (err error) {
var selectorValue string
if !args.DryRun {
- daemonset, err := k8s.KubeClient().AppsV1().DaemonSets(namespace).Get(
+ daemonset, err := t.client.Kube().AppsV1().DaemonSets(namespace).Get(
ctx, consts.NodeServerName, metav1.GetOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
@@ -309,7 +311,7 @@ func doCreateDaemonset(ctx context.Context, args *Args) (err error) {
daemonset := newDaemonset(podSpec, consts.NodeServerName, selectorValue, args)
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().AppsV1().DaemonSets(namespace).Create(
+ _, err = t.client.Kube().AppsV1().DaemonSets(namespace).Create(
ctx, daemonset, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -320,7 +322,7 @@ func doCreateDaemonset(ctx context.Context, args *Args) (err error) {
return args.writeObject(daemonset)
}
-func doCreateLegacyDaemonset(ctx context.Context, args *Args) (err error) {
+func (t daemonsetTask) doCreateLegacyDaemonset(ctx context.Context, args *Args) (err error) {
securityContext := newSecurityContext(args.SeccompProfile)
pluginSocketDir := newPluginsSocketDir(kubeletDirPath, legacyclient.Identity)
volumes, volumeMounts := getVolumesAndMounts(pluginSocketDir)
@@ -349,7 +351,7 @@ func doCreateLegacyDaemonset(ctx context.Context, args *Args) (err error) {
var selectorValue string
if !args.DryRun {
- daemonset, err := k8s.KubeClient().AppsV1().DaemonSets(namespace).Get(
+ daemonset, err := t.client.Kube().AppsV1().DaemonSets(namespace).Get(
ctx, consts.LegacyNodeServerName, metav1.GetOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
@@ -372,7 +374,7 @@ func doCreateLegacyDaemonset(ctx context.Context, args *Args) (err error) {
daemonset := newDaemonset(podSpec, consts.LegacyNodeServerName, selectorValue, args)
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().AppsV1().DaemonSets(namespace).Create(
+ _, err = t.client.Kube().AppsV1().DaemonSets(namespace).Create(
ctx, daemonset, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -383,11 +385,11 @@ func doCreateLegacyDaemonset(ctx context.Context, args *Args) (err error) {
return args.writeObject(daemonset)
}
-func createDaemonset(ctx context.Context, args *Args) (err error) {
+func (t daemonsetTask) createDaemonset(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, fmt.Sprintf("Creating %s Daemonset", consts.NodeServerName), 1, nil) {
return errSendProgress
}
- if err := doCreateDaemonset(ctx, args); err != nil {
+ if err := t.doCreateDaemonset(ctx, args); err != nil {
return err
}
if !sendProgressMessage(ctx, args.ProgressCh, fmt.Sprintf("Created %s Daemonset", consts.NodeServerName), 1, daemonsetComponent(consts.NodeServerName)) {
@@ -401,7 +403,7 @@ func createDaemonset(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, fmt.Sprintf("Creating %s Daemonset", consts.LegacyNodeServerName), 2, nil) {
return errSendProgress
}
- if err := doCreateLegacyDaemonset(ctx, args); err != nil {
+ if err := t.doCreateLegacyDaemonset(ctx, args); err != nil {
return err
}
if !sendProgressMessage(ctx, args.ProgressCh, fmt.Sprintf("Created %s Daemonset", consts.LegacyNodeServerName), 2, daemonsetComponent(consts.LegacyNodeServerName)) {
@@ -411,15 +413,15 @@ func createDaemonset(ctx context.Context, args *Args) (err error) {
return nil
}
-func deleteDaemonset(ctx context.Context) error {
- err := k8s.KubeClient().AppsV1().DaemonSets(namespace).Delete(
+func (t daemonsetTask) deleteDaemonset(ctx context.Context) error {
+ err := t.client.Kube().AppsV1().DaemonSets(namespace).Delete(
ctx, consts.NodeServerName, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().AppsV1().DaemonSets(namespace).Delete(
+ err = t.client.Kube().AppsV1().DaemonSets(namespace).Delete(
ctx, consts.LegacyNodeServerName, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
diff --git a/pkg/installer/deployment.go b/pkg/admin/installer/deployment.go
similarity index 87%
rename from pkg/installer/deployment.go
rename to pkg/admin/installer/deployment.go
index 2229e9c8..b6d74d44 100644
--- a/pkg/installer/deployment.go
+++ b/pkg/admin/installer/deployment.go
@@ -21,8 +21,8 @@ import (
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -31,7 +31,9 @@ import (
const deploymentFinalizer = consts.Identity + "/delete-protection"
-type deploymentTask struct{}
+type deploymentTask struct {
+ client *client.Client
+}
func (deploymentTask) Name() string {
return "Deployment"
@@ -55,15 +57,15 @@ func (deploymentTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (deploymentTask) Execute(ctx context.Context, args *Args) error {
- return createDeployment(ctx, args)
+func (t deploymentTask) Execute(ctx context.Context, args *Args) error {
+ return t.createDeployment(ctx, args)
}
-func (deploymentTask) Delete(ctx context.Context, _ *Args) error {
- return deleteDeployment(ctx)
+func (t deploymentTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteDeployment(ctx)
}
-func doCreateDeployment(ctx context.Context, args *Args, legacy bool, step int) (err error) {
+func (t deploymentTask) doCreateDeployment(ctx context.Context, args *Args, legacy bool, step int) (err error) {
name := consts.ControllerServerName
containerArgs := []string{name, fmt.Sprintf("--identity=%s", consts.Identity)}
if legacy {
@@ -179,7 +181,7 @@ func doCreateDeployment(ctx context.Context, args *Args, legacy bool, step int)
var selectorValue string
if !args.DryRun {
- deployment, err := k8s.KubeClient().AppsV1().Deployments(namespace).Get(
+ deployment, err := t.client.Kube().AppsV1().Deployments(namespace).Get(
ctx, name, metav1.GetOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
@@ -232,7 +234,7 @@ func doCreateDeployment(ctx context.Context, args *Args, legacy bool, step int)
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().AppsV1().Deployments(namespace).Create(
+ _, err = t.client.Kube().AppsV1().Deployments(namespace).Create(
ctx, deployment, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -243,13 +245,13 @@ func doCreateDeployment(ctx context.Context, args *Args, legacy bool, step int)
return args.writeObject(deployment)
}
-func createDeployment(ctx context.Context, args *Args) (err error) {
- if err := doCreateDeployment(ctx, args, false, 1); err != nil {
+func (t deploymentTask) createDeployment(ctx context.Context, args *Args) (err error) {
+ if err := t.doCreateDeployment(ctx, args, false, 1); err != nil {
return err
}
if args.Legacy {
- if err := doCreateDeployment(ctx, args, true, 2); err != nil {
+ if err := t.doCreateDeployment(ctx, args, true, 2); err != nil {
return err
}
}
@@ -271,8 +273,8 @@ func removeFinalizer(objectMeta *metav1.ObjectMeta, finalizer string) []string {
return finalizers
}
-func doDeleteDeployment(ctx context.Context, name string) error {
- deploymentClient := k8s.KubeClient().AppsV1().Deployments(namespace)
+func (t deploymentTask) doDeleteDeployment(ctx context.Context, name string) error {
+ deploymentClient := t.client.Kube().AppsV1().Deployments(namespace)
deployment, err := deploymentClient.Get(ctx, name, metav1.GetOptions{})
if err != nil {
@@ -294,10 +296,10 @@ func doDeleteDeployment(ctx context.Context, name string) error {
return nil
}
-func deleteDeployment(ctx context.Context) error {
- if err := doDeleteDeployment(ctx, consts.ControllerServerName); err != nil {
+func (t deploymentTask) deleteDeployment(ctx context.Context) error {
+ if err := t.doDeleteDeployment(ctx, consts.ControllerServerName); err != nil {
return err
}
- return doDeleteDeployment(ctx, consts.LegacyControllerServerName)
+ return t.doDeleteDeployment(ctx, consts.LegacyControllerServerName)
}
diff --git a/pkg/installer/directpv.min.io_directpvdrives.yaml b/pkg/admin/installer/directpv.min.io_directpvdrives.yaml
similarity index 100%
rename from pkg/installer/directpv.min.io_directpvdrives.yaml
rename to pkg/admin/installer/directpv.min.io_directpvdrives.yaml
diff --git a/pkg/installer/directpv.min.io_directpvinitrequests.yaml b/pkg/admin/installer/directpv.min.io_directpvinitrequests.yaml
similarity index 100%
rename from pkg/installer/directpv.min.io_directpvinitrequests.yaml
rename to pkg/admin/installer/directpv.min.io_directpvinitrequests.yaml
diff --git a/pkg/installer/directpv.min.io_directpvnodes.yaml b/pkg/admin/installer/directpv.min.io_directpvnodes.yaml
similarity index 100%
rename from pkg/installer/directpv.min.io_directpvnodes.yaml
rename to pkg/admin/installer/directpv.min.io_directpvnodes.yaml
diff --git a/pkg/installer/directpv.min.io_directpvvolumes.yaml b/pkg/admin/installer/directpv.min.io_directpvvolumes.yaml
similarity index 100%
rename from pkg/installer/directpv.min.io_directpvvolumes.yaml
rename to pkg/admin/installer/directpv.min.io_directpvvolumes.yaml
diff --git a/pkg/installer/installer.go b/pkg/admin/installer/installer.go
similarity index 51%
rename from pkg/installer/installer.go
rename to pkg/admin/installer/installer.go
index cf80e38b..de9614cb 100644
--- a/pkg/installer/installer.go
+++ b/pkg/admin/installer/installer.go
@@ -18,61 +18,30 @@ package installer
import (
"context"
- "fmt"
- "strconv"
- "strings"
"github.com/fatih/color"
- "github.com/minio/directpv/pkg/k8s"
+ "github.com/minio/directpv/pkg/client"
+ legacyclient "github.com/minio/directpv/pkg/legacy/client"
"github.com/minio/directpv/pkg/utils"
- "k8s.io/apimachinery/pkg/util/version"
- "k8s.io/klog/v2"
)
-// Tasks is a list of tasks to performed during installation and uninstallation
-var Tasks = []Task{
- namespaceTask{},
- rbacTask{},
- pspTask{},
- crdTask{},
- migrateTask{},
- csiDriverTask{},
- storageClassTask{},
- daemonsetTask{},
- deploymentTask{},
-}
-
-func getKubeVersion() (major, minor uint, err error) {
- versionInfo, err := k8s.DiscoveryClient().ServerVersion()
- if err != nil {
- return 0, 0, err
- }
-
- var u64 uint64
- if u64, err = strconv.ParseUint(versionInfo.Major, 10, 64); err != nil {
- return 0, 0, fmt.Errorf("unable to parse major version %v; %v", versionInfo.Major, err)
- }
- major = uint(u64)
-
- minorString := versionInfo.Minor
- if strings.Contains(versionInfo.GitVersion, "-eks-") {
- // Do trimming only for EKS.
- // Refer https://github.com/aws/containers-roadmap/issues/1404
- i := strings.IndexFunc(minorString, func(r rune) bool { return r < '0' || r > '9' })
- if i > -1 {
- minorString = minorString[:i]
- }
+// GetDefaultTasks returns the installer tasks to be run
+func GetDefaultTasks(client *client.Client, legacyClient *legacyclient.Client) []Task {
+ return []Task{
+ namespaceTask{client},
+ rbacTask{client},
+ pspTask{client},
+ crdTask{client},
+ migrateTask{client, legacyClient},
+ csiDriverTask{client},
+ storageClassTask{client},
+ daemonsetTask{client},
+ deploymentTask{client},
}
- if u64, err = strconv.ParseUint(minorString, 10, 64); err != nil {
- return 0, 0, fmt.Errorf("unable to parse minor version %v; %v", minor, err)
- }
- minor = uint(u64)
-
- return major, minor, nil
}
// Install performs DirectPV installation on kubernetes.
-func Install(ctx context.Context, args *Args) (err error) {
+func Install(ctx context.Context, args *Args, tasks []Task) (err error) {
defer func() {
if !sendDoneMessage(ctx, args.ProgressCh, err) {
err = errSendProgress
@@ -84,25 +53,6 @@ func Install(ctx context.Context, args *Args) (err error) {
return err
}
- switch {
- case args.DryRun:
- if args.KubeVersion == nil {
- // default higher version
- if args.KubeVersion, err = version.ParseSemantic("1.29.0"); err != nil {
- klog.Fatalf("this should not happen; %v", err)
- }
- }
- default:
- major, minor, err := getKubeVersion()
- if err != nil {
- return err
- }
- args.KubeVersion, err = version.ParseSemantic(fmt.Sprintf("%v.%v.0", major, minor))
- if err != nil {
- klog.Fatalf("this should not happen; %v", err)
- }
- }
-
if args.KubeVersion.Major() == 1 {
if args.KubeVersion.Minor() < 20 {
args.csiProvisionerImage = csiProvisionerImageV2_2_0
@@ -127,7 +77,7 @@ func Install(ctx context.Context, args *Args) (err error) {
}
}
- for _, task := range Tasks {
+ for _, task := range tasks {
if err := task.Start(ctx, args); err != nil {
return err
}
@@ -141,12 +91,12 @@ func Install(ctx context.Context, args *Args) (err error) {
}
// Uninstall removes DirectPV from kubernetes.
-func Uninstall(ctx context.Context, quiet, force bool) (err error) {
+func Uninstall(ctx context.Context, quiet, force bool, tasks []Task) (err error) {
args := &Args{
ForceUninstall: force,
Quiet: quiet,
}
- for _, task := range Tasks {
+ for _, task := range tasks {
if err := task.Delete(ctx, args); err != nil {
return err
}
diff --git a/pkg/installer/installer_test.go b/pkg/admin/installer/installer_test.go
similarity index 60%
rename from pkg/installer/installer_test.go
rename to pkg/admin/installer/installer_test.go
index bdbae0d0..2230f66f 100644
--- a/pkg/installer/installer_test.go
+++ b/pkg/admin/installer/installer_test.go
@@ -23,12 +23,15 @@ import (
"github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/k8s"
+ legacyclient "github.com/minio/directpv/pkg/legacy/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ versionpkg "k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/version"
)
func init() {
client.FakeInit()
+ legacyclient.FakeInit()
}
var (
@@ -93,57 +96,15 @@ func getDiscoveryGroupsAndMethods() ([]*metav1.APIGroup, []*metav1.APIResourceLi
return apiGroups, apiResourceList, nil
}
-func TestGetKubeVersion(t *testing.T) {
- testCases := []struct {
- info version.Info
- major uint
- minor uint
- expectErr bool
- }{
- {version.Info{Major: "a", Minor: "0"}, 0, 0, true}, // invalid major
- {version.Info{Major: "-1", Minor: "0"}, 0, 0, true}, // invalid major
- {version.Info{Major: "0", Minor: "a"}, 0, 0, true}, // invalid minor
- {version.Info{Major: "0", Minor: "-1"}, 0, 0, true}, // invalid minor
- {version.Info{Major: "0", Minor: "-1", GitVersion: "commit-eks-id"}, 0, 0, true}, // invalid minor for eks
- {version.Info{Major: "0", Minor: "incompat", GitVersion: "commit-eks-"}, 0, 0, true}, // invalid minor for eks
- {version.Info{Major: "0", Minor: "0"}, 0, 0, false},
- {version.Info{Major: "1", Minor: "0"}, 1, 0, false},
- {version.Info{Major: "0", Minor: "1"}, 0, 1, false},
- {version.Info{Major: "1", Minor: "18"}, 1, 18, false},
- {version.Info{Major: "1", Minor: "18+", GitVersion: "commit-eks-id"}, 1, 18, false},
- {version.Info{Major: "1", Minor: "18-", GitVersion: "commit-eks-id"}, 1, 18, false},
- {version.Info{Major: "1", Minor: "18incompat", GitVersion: "commit-eks-id"}, 1, 18, false},
- {version.Info{Major: "1", Minor: "18-incompat", GitVersion: "commit-eks-id"}, 1, 18, false},
- }
-
- for i, testCase := range testCases {
- k8s.SetDiscoveryInterface(getDiscoveryGroupsAndMethods, &testCase.info)
- major, minor, err := getKubeVersion()
- if testCase.expectErr {
- if err == nil {
- t.Fatalf("case %v: expected error, but succeeded", i+1)
- }
- continue
- }
-
- if err != nil {
- t.Fatalf("case %v: unexpected error: %v", i+1, err)
- }
-
- if major != testCase.major {
- t.Fatalf("case %v: major: expected: %v, got: %v", i+1, testCase.major, major)
- }
-
- if minor != testCase.minor {
- t.Fatalf("case %v: minor: expected: %v, got: %v", i+1, testCase.minor, minor)
- }
- }
-}
-
func TestInstallUinstall(t *testing.T) {
+ kversion, err := versionpkg.ParseSemantic("1.26.0")
+ if err != nil {
+ t.Fatalf("unable to parse version; %v", err)
+ }
args := Args{
image: "directpv-0.0.0dev0",
ObjectWriter: io.Discard,
+ KubeVersion: kversion,
}
testVersions := []version.Info{
@@ -161,16 +122,18 @@ func TestInstallUinstall(t *testing.T) {
}
for i, testVersion := range testVersions {
- k8s.SetDiscoveryInterface(getDiscoveryGroupsAndMethods, &testVersion)
+ client := client.GetClient()
+ legacyClient := legacyclient.GetClient()
+ client.K8sClient.DiscoveryClient = k8s.NewFakeDiscovery(getDiscoveryGroupsAndMethods, &testVersion)
ctx := context.TODO()
args := args
- if err := Install(ctx, &args); err != nil {
+ tasks := GetDefaultTasks(client, legacyClient)
+ if err := Install(ctx, &args, tasks); err != nil {
t.Fatalf("case %v: unexpected error; %v", i+1, err)
}
- if err := Uninstall(ctx, false, true); err != nil {
+ if err := Uninstall(ctx, false, true, tasks); err != nil {
t.Fatalf("csae %v: unexpected error; %v", i+1, err)
}
-
_, err := k8s.KubeClient().CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
if err == nil {
t.Fatalf("case %v: uninstall on kube version v%v.%v not removed namespace", i+1, testVersion.Major, testVersion.Minor)
diff --git a/pkg/installer/migrate.go b/pkg/admin/installer/migrate.go
similarity index 74%
rename from pkg/installer/migrate.go
rename to pkg/admin/installer/migrate.go
index bc53f77b..8b3b1a83 100644
--- a/pkg/installer/migrate.go
+++ b/pkg/admin/installer/migrate.go
@@ -19,7 +19,6 @@ package installer
import (
"context"
"fmt"
- "os"
"strings"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
@@ -47,7 +46,10 @@ const (
legacyPurgeProtection = legacyclient.GroupName + "/purge-protection"
)
-type migrateTask struct{}
+type migrateTask struct {
+ client *client.Client
+ legacyClient *legacyclient.Client
+}
func (migrateTask) Name() string {
return "Migration"
@@ -67,15 +69,15 @@ func (migrateTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (migrateTask) Execute(ctx context.Context, args *Args) error {
- return Migrate(ctx, args, true)
+func (t migrateTask) Execute(ctx context.Context, args *Args) error {
+ return t.migrate(ctx, args, true)
}
func (migrateTask) Delete(_ context.Context, _ *Args) error {
return nil
}
-func migrateDrives(ctx context.Context, dryRun bool, progressCh chan<- Message) (driveMap map[string]string, legacyDriveErrors, driveErrors map[string]error, err error) {
+func (t migrateTask) migrateDrives(ctx context.Context, dryRun bool, progressCh chan<- Message) (driveMap map[string]string, legacyDriveErrors, driveErrors map[string]error, err error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
@@ -84,7 +86,7 @@ func migrateDrives(ctx context.Context, dryRun bool, progressCh chan<- Message)
driveErrors = map[string]error{}
fsUUIDs := make(utils.StringSet)
- for result := range legacyclient.ListDrives(ctx) {
+ for result := range t.legacyClient.ListDrives(ctx) {
if result.Err != nil {
return nil, legacyDriveErrors, driveErrors, fmt.Errorf(
"unable to get legacy drives; %v", result.Err,
@@ -181,13 +183,13 @@ func migrateDrives(ctx context.Context, dryRun bool, progressCh chan<- Message)
}
}
- existingDrive, err := client.DriveClient().Get(ctx, string(driveID), metav1.GetOptions{})
+ existingDrive, err := t.client.Drive().Get(ctx, string(driveID), metav1.GetOptions{})
if err != nil {
switch {
case apierrors.IsNotFound(err):
if !dryRun {
sendProgressMessage(ctx, progressCh, fmt.Sprintf("Migrating directcsidrive %s to directpvdrive %s", result.Drive.Name, drive.Name), 1, nil)
- _, err = client.DriveClient().Create(ctx, drive, metav1.CreateOptions{})
+ _, err = t.client.Drive().Create(ctx, drive, metav1.CreateOptions{})
if err != nil {
legacyDriveErrors[result.Drive.Name] = fmt.Errorf(
"unable to create drive %v by migrating legacy drive %v; %w",
@@ -219,14 +221,14 @@ func migrateDrives(ctx context.Context, dryRun bool, progressCh chan<- Message)
return driveMap, legacyDriveErrors, driveErrors, nil
}
-func migrateVolumes(ctx context.Context, driveMap map[string]string, dryRun bool, progressCh chan<- Message) (legacyVolumeErrors, volumeErrors map[string]error, err error) {
+func (t migrateTask) migrateVolumes(ctx context.Context, driveMap map[string]string, dryRun bool, progressCh chan<- Message) (legacyVolumeErrors, volumeErrors map[string]error, err error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
legacyVolumeErrors = map[string]error{}
volumeErrors = map[string]error{}
- for result := range legacyclient.ListVolumes(ctx) {
+ for result := range t.legacyClient.ListVolumes(ctx) {
if result.Err != nil {
return legacyVolumeErrors, volumeErrors, fmt.Errorf(
"unable to get legacy volumes; %v", result.Err,
@@ -296,13 +298,13 @@ func migrateVolumes(ctx context.Context, driveMap map[string]string, dryRun bool
}
}
- existingVolume, err := client.VolumeClient().Get(ctx, name, metav1.GetOptions{})
+ existingVolume, err := t.client.Volume().Get(ctx, name, metav1.GetOptions{})
if err != nil {
switch {
case apierrors.IsNotFound(err):
if !dryRun {
sendProgressMessage(ctx, progressCh, fmt.Sprintf("Migrating directcsivolume %s to directpvvolume %s", result.Volume.Name, volume.Name), 2, nil)
- _, err = client.VolumeClient().Create(ctx, volume, metav1.CreateOptions{})
+ _, err = t.client.Volume().Create(ctx, volume, metav1.CreateOptions{})
if err != nil {
legacyVolumeErrors[result.Volume.Name] = fmt.Errorf(
"unable to create volume %v by migrating legacy volume %v; %w",
@@ -332,14 +334,14 @@ func migrateVolumes(ctx context.Context, driveMap map[string]string, dryRun bool
}
// Migrate migrates legacy drives and volumes.
-func Migrate(ctx context.Context, args *Args, installer bool) (err error) {
+func (t migrateTask) migrate(ctx context.Context, args *Args, installer bool) (err error) {
if (installer && args.DryRun) || args.Declarative || !args.Legacy {
return nil
}
legacyclient.Init()
- version, _, err := legacyclient.GetGroupVersion("DirectCSIDrive")
+ version, _, err := legacyclient.GetGroupVersion(t.legacyClient.K8sClient, "DirectCSIDrive")
if err != nil {
return fmt.Errorf("unable to probe DirectCSIDrive version; %w", err)
}
@@ -350,7 +352,7 @@ func Migrate(ctx context.Context, args *Args, installer bool) (err error) {
return fmt.Errorf("migration does not support DirectCSIDrive version %v", version)
}
- version, _, err = legacyclient.GetGroupVersion("DirectCSIVolume")
+ version, _, err = legacyclient.GetGroupVersion(t.legacyClient.K8sClient, "DirectCSIVolume")
if err != nil {
return fmt.Errorf("unable to probe DirectCSIVolume version; %w", err)
}
@@ -361,14 +363,14 @@ func Migrate(ctx context.Context, args *Args, installer bool) (err error) {
return fmt.Errorf("migration does not support DirectCSIVolume version %v", version)
}
- driveMap, legacyDriveErrors, driveErrors, err := migrateDrives(ctx, args.DryRun, args.ProgressCh)
+ driveMap, legacyDriveErrors, driveErrors, err := t.migrateDrives(ctx, args.DryRun, args.ProgressCh)
if err != nil {
return err
}
if !sendProgressMessage(ctx, args.ProgressCh, "Migrated the drives", 1, nil) {
return errSendProgress
}
- legacyVolumeErrors, volumeErrors, err := migrateVolumes(ctx, driveMap, args.DryRun, args.ProgressCh)
+ legacyVolumeErrors, volumeErrors, err := t.migrateVolumes(ctx, driveMap, args.DryRun, args.ProgressCh)
if err != nil {
return err
}
@@ -403,84 +405,7 @@ func Migrate(ctx context.Context, args *Args, installer bool) (err error) {
return nil
}
-// RemoveLegacyDrives removes legacy drive CRDs.
-func RemoveLegacyDrives(ctx context.Context, backupFile string) (backupCreated bool, err error) {
- var drives []directv1beta5.DirectCSIDrive
- for result := range legacyclient.ListDrives(ctx) {
- if result.Err != nil {
- return false, fmt.Errorf("unable to get legacy drives; %w", result.Err)
- }
- drives = append(drives, result.Drive)
- }
- if len(drives) == 0 {
- return false, nil
- }
-
- data, err := utils.ToYAML(directv1beta5.DirectCSIDriveList{
- TypeMeta: metav1.TypeMeta{
- Kind: "List",
- APIVersion: "v1",
- },
- Items: drives,
- })
- if err != nil {
- return false, fmt.Errorf("unable to generate legacy drives YAML; %w", err)
- }
-
- if err = os.WriteFile(backupFile, data, os.ModePerm); err != nil {
- return false, fmt.Errorf("unable to write legacy drives YAML; %w", err)
- }
-
- for _, drive := range drives {
- drive.Finalizers = []string{}
- if _, err := legacyclient.DriveClient().Update(ctx, &drive, metav1.UpdateOptions{}); err != nil {
- return false, fmt.Errorf("unable to update legacy drive %v; %w", drive.Name, err)
- }
- if err := legacyclient.DriveClient().Delete(ctx, drive.Name, metav1.DeleteOptions{}); err != nil {
- return false, fmt.Errorf("unable to remove legacy drive %v; %w", drive.Name, err)
- }
- }
-
- return true, nil
-}
-
-// RemoveLegacyVolumes removes legacy volume CRDs.
-func RemoveLegacyVolumes(ctx context.Context, backupFile string) (backupCreated bool, err error) {
- var volumes []directv1beta5.DirectCSIVolume
- for result := range legacyclient.ListVolumes(ctx) {
- if result.Err != nil {
- return false, fmt.Errorf("unable to get legacy volumes; %w", result.Err)
- }
- volumes = append(volumes, result.Volume)
- }
- if len(volumes) == 0 {
- return false, nil
- }
-
- data, err := utils.ToYAML(directv1beta5.DirectCSIVolumeList{
- TypeMeta: metav1.TypeMeta{
- Kind: "List",
- APIVersion: "v1",
- },
- Items: volumes,
- })
- if err != nil {
- return false, fmt.Errorf("unable to generate legacy volumes YAML; %w", err)
- }
-
- if err = os.WriteFile(backupFile, data, os.ModePerm); err != nil {
- return false, fmt.Errorf("unable to write legacy volumes YAML; %w", err)
- }
-
- for _, volume := range volumes {
- volume.Finalizers = nil
- if _, err := legacyclient.VolumeClient().Update(ctx, &volume, metav1.UpdateOptions{}); err != nil {
- return false, fmt.Errorf("unable to update legacy volume %v; %w", volume.Name, err)
- }
- if err := legacyclient.VolumeClient().Delete(ctx, volume.Name, metav1.DeleteOptions{}); err != nil {
- return false, fmt.Errorf("unable to remove legacy volume %v; %w", volume.Name, err)
- }
- }
-
- return true, nil
+// Migrate migrates the resources using the provided clients
+func Migrate(ctx context.Context, args *Args, client *client.Client, legacyClient *legacyclient.Client) error {
+ return migrateTask{client, legacyClient}.migrate(ctx, args, false)
}
diff --git a/pkg/installer/migrate_test.go b/pkg/admin/installer/migrate_test.go
similarity index 94%
rename from pkg/installer/migrate_test.go
rename to pkg/admin/installer/migrate_test.go
index 5aa2ab0e..c52e62f3 100644
--- a/pkg/installer/migrate_test.go
+++ b/pkg/admin/installer/migrate_test.go
@@ -41,7 +41,8 @@ func TestMigrateDrivesError(t *testing.T) {
}
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset(drive))
- driveMap, legacyDriveErrors, driveErrors, err := migrateDrives(context.TODO(), false, nil)
+ task := migrateTask{client.GetClient(), legacyclient.GetClient()}
+ driveMap, legacyDriveErrors, driveErrors, err := task.migrateDrives(context.TODO(), false, nil)
if len(driveMap) == 0 && len(legacyDriveErrors) == 0 && len(driveErrors) == 0 && err == nil {
t.Fatalf("expected error, but succeeded\n")
}
@@ -55,7 +56,7 @@ func TestMigrateDrivesError(t *testing.T) {
},
}
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset(drive))
- driveMap, legacyDriveErrors, driveErrors, err = migrateDrives(context.TODO(), false, nil)
+ driveMap, legacyDriveErrors, driveErrors, err = task.migrateDrives(context.TODO(), false, nil)
if len(driveMap) == 0 && len(legacyDriveErrors) == 0 && len(driveErrors) == 0 && err == nil {
t.Fatalf("expected error, but succeeded\n")
}
@@ -78,7 +79,7 @@ func TestMigrateDrivesError(t *testing.T) {
},
}
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset(drive1, drive2))
- driveMap, legacyDriveErrors, driveErrors, err = migrateDrives(context.TODO(), false, nil)
+ driveMap, legacyDriveErrors, driveErrors, err = task.migrateDrives(context.TODO(), false, nil)
if len(driveMap) == 0 && len(legacyDriveErrors) == 0 && len(driveErrors) == 0 && err == nil {
t.Fatalf("expected error, but succeeded\n")
}
@@ -88,8 +89,8 @@ func TestMigrateNoDrives(t *testing.T) {
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset())
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
-
- _, legacyDriveErrors, driveErrors, err := migrateDrives(context.TODO(), false, nil)
+ task := migrateTask{client.GetClient(), legacyclient.GetClient()}
+ _, legacyDriveErrors, driveErrors, err := task.migrateDrives(context.TODO(), false, nil)
if len(legacyDriveErrors) != 0 || len(driveErrors) != 0 || err != nil {
t.Fatalf("unexpected error; %v\n", err)
}
@@ -115,7 +116,7 @@ func TestMigrateNoDrives(t *testing.T) {
clientset = types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- _, legacyDriveErrors, driveErrors, err = migrateDrives(context.TODO(), false, nil)
+ _, legacyDriveErrors, driveErrors, err = task.migrateDrives(context.TODO(), false, nil)
if len(legacyDriveErrors) != 0 || len(driveErrors) != 0 || err != nil {
t.Fatalf("unexpected error; %v\n", err)
}
@@ -204,8 +205,8 @@ func TestMigrateReadyDrive(t *testing.T) {
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset(drive))
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
-
- driveMap, legacyDriveErrors, driveErrors, err := migrateDrives(context.TODO(), false, nil)
+ task := migrateTask{client.GetClient(), legacyclient.GetClient()}
+ driveMap, legacyDriveErrors, driveErrors, err := task.migrateDrives(context.TODO(), false, nil)
if len(legacyDriveErrors) != 0 || len(driveErrors) != 0 || err != nil {
t.Fatalf("unexpected error; %v, %v, %v\n", legacyDriveErrors, driveErrors, err)
}
@@ -340,8 +341,8 @@ func TestMigrateInUseDrive(t *testing.T) {
legacyclient.SetDriveClient(legacyclientsetfake.NewSimpleClientset(drive))
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
-
- driveMap, legacyDriveErrors, driveErrors, err := migrateDrives(context.TODO(), false, nil)
+ task := migrateTask{client.GetClient(), legacyclient.GetClient()}
+ driveMap, legacyDriveErrors, driveErrors, err := task.migrateDrives(context.TODO(), false, nil)
if len(legacyDriveErrors) != 0 || len(driveErrors) != 0 || err != nil {
t.Fatalf("unexpected error; %v, %v, %v\n", legacyDriveErrors, driveErrors, err)
}
@@ -451,8 +452,8 @@ func TestMigrateVolumes(t *testing.T) {
legacyclient.SetVolumeClient(legacyclientsetfake.NewSimpleClientset(volume))
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
client.SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
-
- legacyVolumeErrors, volumeErrors, err := migrateVolumes(
+ task := migrateTask{client.GetClient(), legacyclient.GetClient()}
+ legacyVolumeErrors, volumeErrors, err := task.migrateVolumes(
context.TODO(),
map[string]string{"08450612-7ab3-40f9-ab83-38645fba6d29": "a9908089-96dd-4e8b-8f06-0c0b5e391f39"},
false,
@@ -492,7 +493,7 @@ func TestMigrateVolumes(t *testing.T) {
// no fsuuid found error
legacyclient.SetVolumeClient(legacyclientsetfake.NewSimpleClientset(volume))
- legacyVolumeErrors, volumeErrors, err = migrateVolumes(context.TODO(), map[string]string{}, false, nil)
+ legacyVolumeErrors, volumeErrors, err = task.migrateVolumes(context.TODO(), map[string]string{}, false, nil)
if len(legacyVolumeErrors) == 0 && len(volumeErrors) == 0 && err == nil {
t.Fatalf("expected error; but succeeded\n")
}
diff --git a/pkg/installer/namespace.go b/pkg/admin/installer/namespace.go
similarity index 82%
rename from pkg/installer/namespace.go
rename to pkg/admin/installer/namespace.go
index feeb624d..5e7a1fb8 100644
--- a/pkg/installer/namespace.go
+++ b/pkg/admin/installer/namespace.go
@@ -20,14 +20,16 @@ import (
"context"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/k8s"
+ "github.com/minio/directpv/pkg/client"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
podsecurityadmissionapi "k8s.io/pod-security-admission/api"
)
-type namespaceTask struct{}
+type namespaceTask struct {
+ client *client.Client
+}
func (namespaceTask) Name() string {
return "Namespace"
@@ -40,8 +42,8 @@ func (namespaceTask) Start(ctx context.Context, args *Args) error {
return nil
}
-func (namespaceTask) Execute(ctx context.Context, args *Args) error {
- return createNamespace(ctx, args)
+func (t namespaceTask) Execute(ctx context.Context, args *Args) error {
+ return t.createNamespace(ctx, args)
}
func (namespaceTask) End(ctx context.Context, args *Args, err error) error {
@@ -51,11 +53,11 @@ func (namespaceTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (namespaceTask) Delete(ctx context.Context, _ *Args) error {
- return deleteNamespace(ctx)
+func (t namespaceTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteNamespace(ctx)
}
-func createNamespace(ctx context.Context, args *Args) (err error) {
+func (t namespaceTask) createNamespace(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating namespace", 1, nil) {
return errSendProgress
}
@@ -99,7 +101,7 @@ func createNamespace(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
+ _, err = t.client.Kube().CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
@@ -108,9 +110,9 @@ func createNamespace(ctx context.Context, args *Args) (err error) {
return args.writeObject(ns)
}
-func deleteNamespace(ctx context.Context) error {
+func (t namespaceTask) deleteNamespace(ctx context.Context) error {
propagationPolicy := metav1.DeletePropagationForeground
- err := k8s.KubeClient().CoreV1().Namespaces().Delete(
+ err := t.client.Kube().CoreV1().Namespaces().Delete(
ctx, namespace, metav1.DeleteOptions{PropagationPolicy: &propagationPolicy},
)
if err != nil {
diff --git a/pkg/installer/progress.go b/pkg/admin/installer/progress.go
similarity index 100%
rename from pkg/installer/progress.go
rename to pkg/admin/installer/progress.go
diff --git a/pkg/installer/psp.go b/pkg/admin/installer/psp.go
similarity index 82%
rename from pkg/installer/psp.go
rename to pkg/admin/installer/psp.go
index 9af06d56..9efa3e77 100644
--- a/pkg/installer/psp.go
+++ b/pkg/admin/installer/psp.go
@@ -21,8 +21,8 @@ import (
"errors"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
corev1 "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
rbac "k8s.io/api/rbac/v1"
@@ -35,7 +35,9 @@ const pspClusterRoleBindingName = "psp-" + consts.Identity
var errPSPUnsupported = errors.New("pod security policy is not supported in your kubernetes version")
-type pspTask struct{}
+type pspTask struct {
+ client *client.Client
+}
func (pspTask) Name() string {
return "PSP"
@@ -55,12 +57,12 @@ func (pspTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (pspTask) Execute(ctx context.Context, args *Args) error {
- return createPSP(ctx, args)
+func (t pspTask) Execute(ctx context.Context, args *Args) error {
+ return t.createPSP(ctx, args)
}
-func (pspTask) Delete(ctx context.Context, _ *Args) error {
- major, minor, err := getKubeVersion()
+func (t pspTask) Delete(ctx context.Context, _ *Args) error {
+ major, minor, err := t.client.K8s().GetKubeVersion()
if err != nil {
return err
}
@@ -68,10 +70,10 @@ func (pspTask) Delete(ctx context.Context, _ *Args) error {
if podSecurityAdmission {
return nil
}
- return deletePSP(ctx)
+ return t.deletePSP(ctx)
}
-func createPSPClusterRoleBinding(ctx context.Context, args *Args) (err error) {
+func (t pspTask) createPSPClusterRoleBinding(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating psp cluster role binding", 2, nil) {
return errSendProgress
}
@@ -110,7 +112,7 @@ func createPSPClusterRoleBinding(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().RbacV1().ClusterRoleBindings().Create(ctx, crb, metav1.CreateOptions{})
+ _, err = t.client.Kube().RbacV1().ClusterRoleBindings().Create(ctx, crb, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
@@ -119,7 +121,7 @@ func createPSPClusterRoleBinding(ctx context.Context, args *Args) (err error) {
return args.writeObject(crb)
}
-func createPodSecurityPolicy(ctx context.Context, args *Args) (err error) {
+func (t pspTask) createPodSecurityPolicy(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating pod security policy", 1, nil) {
return errSendProgress
}
@@ -173,7 +175,7 @@ func createPodSecurityPolicy(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().PolicyV1beta1().PodSecurityPolicies().Create(
+ _, err = t.client.Kube().PolicyV1beta1().PodSecurityPolicies().Create(
ctx, psp, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -184,14 +186,14 @@ func createPodSecurityPolicy(ctx context.Context, args *Args) (err error) {
return args.writeObject(psp)
}
-func createPSP(ctx context.Context, args *Args) error {
+func (t pspTask) createPSP(ctx context.Context, args *Args) error {
if args.podSecurityAdmission {
return nil
}
var gvk *schema.GroupVersionKind
if !args.DryRun {
var err error
- if gvk, err = k8s.GetGroupVersionKind("policy", "PodSecurityPolicy", "v1beta1"); err != nil {
+ if gvk, err = t.client.K8s().GetGroupVersionKind("policy", "PodSecurityPolicy", "v1beta1"); err != nil {
return err
}
} else {
@@ -199,24 +201,24 @@ func createPSP(ctx context.Context, args *Args) error {
}
if gvk.Version == "v1beta1" {
- if err := createPodSecurityPolicy(ctx, args); err != nil {
+ if err := t.createPodSecurityPolicy(ctx, args); err != nil {
return err
}
- return createPSPClusterRoleBinding(ctx, args)
+ return t.createPSPClusterRoleBinding(ctx, args)
}
return errPSPUnsupported
}
-func deletePSP(ctx context.Context) error {
- err := k8s.KubeClient().RbacV1().ClusterRoleBindings().Delete(
+func (t pspTask) deletePSP(ctx context.Context) error {
+ err := t.client.Kube().RbacV1().ClusterRoleBindings().Delete(
ctx, pspClusterRoleBindingName, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().PolicyV1beta1().PodSecurityPolicies().Delete(
+ err = t.client.Kube().PolicyV1beta1().PodSecurityPolicies().Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
diff --git a/pkg/installer/rbac.go b/pkg/admin/installer/rbac.go
similarity index 85%
rename from pkg/installer/rbac.go
rename to pkg/admin/installer/rbac.go
index 190b653c..4bd1773b 100644
--- a/pkg/installer/rbac.go
+++ b/pkg/admin/installer/rbac.go
@@ -20,8 +20,8 @@ import (
"context"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -50,7 +50,9 @@ func newPolicyRule(resources []string, apiGroups []string, verbs ...string) rbac
}
}
-type rbacTask struct{}
+type rbacTask struct {
+ client *client.Client
+}
func (rbacTask) Name() string {
return "RBAC"
@@ -70,15 +72,15 @@ func (rbacTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (rbacTask) Execute(ctx context.Context, args *Args) error {
- return createRBAC(ctx, args)
+func (t rbacTask) Execute(ctx context.Context, args *Args) error {
+ return t.createRBAC(ctx, args)
}
-func (rbacTask) Delete(ctx context.Context, _ *Args) error {
- return deleteRBAC(ctx)
+func (t rbacTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteRBAC(ctx)
}
-func createServiceAccount(ctx context.Context, args *Args) (err error) {
+func (t rbacTask) createServiceAccount(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating service account", 1, nil) {
return errSendProgress
}
@@ -108,7 +110,7 @@ func createServiceAccount(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().CoreV1().ServiceAccounts(namespace).Create(
+ _, err = t.client.Kube().CoreV1().ServiceAccounts(namespace).Create(
ctx, serviceAccount, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -119,7 +121,7 @@ func createServiceAccount(ctx context.Context, args *Args) (err error) {
return args.writeObject(serviceAccount)
}
-func createClusterRole(ctx context.Context, args *Args) (err error) {
+func (t rbacTask) createClusterRole(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating cluster role", 2, nil) {
return errSendProgress
}
@@ -175,7 +177,7 @@ func createClusterRole(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().RbacV1().ClusterRoles().Create(
+ _, err = t.client.Kube().RbacV1().ClusterRoles().Create(
ctx, clusterRole, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -186,7 +188,7 @@ func createClusterRole(ctx context.Context, args *Args) (err error) {
return args.writeObject(clusterRole)
}
-func createClusterRoleBinding(ctx context.Context, args *Args) (err error) {
+func (t rbacTask) createClusterRoleBinding(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating cluster role binding", 3, nil) {
return errSendProgress
}
@@ -226,7 +228,7 @@ func createClusterRoleBinding(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().RbacV1().ClusterRoleBindings().Create(
+ _, err = t.client.Kube().RbacV1().ClusterRoleBindings().Create(
ctx, clusterRoleBinding, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -237,7 +239,7 @@ func createClusterRoleBinding(ctx context.Context, args *Args) (err error) {
return args.writeObject(clusterRoleBinding)
}
-func createRole(ctx context.Context, args *Args) (err error) {
+func (t rbacTask) createRole(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating role", 4, nil) {
return errSendProgress
}
@@ -268,7 +270,7 @@ func createRole(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().RbacV1().Roles(namespace).Create(
+ _, err = t.client.Kube().RbacV1().Roles(namespace).Create(
ctx, role, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -279,7 +281,7 @@ func createRole(ctx context.Context, args *Args) (err error) {
return args.writeObject(role)
}
-func createRoleBinding(ctx context.Context, args *Args) (err error) {
+func (t rbacTask) createRoleBinding(ctx context.Context, args *Args) (err error) {
if !sendProgressMessage(ctx, args.ProgressCh, "Creating role binding", 5, nil) {
return errSendProgress
}
@@ -319,7 +321,7 @@ func createRoleBinding(ctx context.Context, args *Args) (err error) {
}
if !args.DryRun && !args.Declarative {
- _, err = k8s.KubeClient().RbacV1().RoleBindings(namespace).Create(
+ _, err = t.client.Kube().RbacV1().RoleBindings(namespace).Create(
ctx, roleBinding, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -330,52 +332,52 @@ func createRoleBinding(ctx context.Context, args *Args) (err error) {
return args.writeObject(roleBinding)
}
-func createRBAC(ctx context.Context, args *Args) (err error) {
- if err = createServiceAccount(ctx, args); err != nil {
+func (t rbacTask) createRBAC(ctx context.Context, args *Args) (err error) {
+ if err = t.createServiceAccount(ctx, args); err != nil {
return err
}
- if err = createClusterRole(ctx, args); err != nil {
+ if err = t.createClusterRole(ctx, args); err != nil {
return err
}
- if err = createClusterRoleBinding(ctx, args); err != nil {
+ if err = t.createClusterRoleBinding(ctx, args); err != nil {
return err
}
- if err = createRole(ctx, args); err != nil {
+ if err = t.createRole(ctx, args); err != nil {
return err
}
- return createRoleBinding(ctx, args)
+ return t.createRoleBinding(ctx, args)
}
-func deleteRBAC(ctx context.Context) error {
- err := k8s.KubeClient().CoreV1().ServiceAccounts(namespace).Delete(
+func (t rbacTask) deleteRBAC(ctx context.Context) error {
+ err := t.client.Kube().CoreV1().ServiceAccounts(namespace).Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().RbacV1().ClusterRoles().Delete(
+ err = t.client.Kube().RbacV1().ClusterRoles().Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().RbacV1().ClusterRoleBindings().Delete(
+ err = t.client.Kube().RbacV1().ClusterRoleBindings().Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().RbacV1().Roles(namespace).Delete(
+ err = t.client.Kube().RbacV1().Roles(namespace).Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
- err = k8s.KubeClient().RbacV1().RoleBindings(namespace).Delete(
+ err = t.client.Kube().RbacV1().RoleBindings(namespace).Delete(
ctx, consts.Identity, metav1.DeleteOptions{},
)
if err != nil && !apierrors.IsNotFound(err) {
diff --git a/pkg/installer/storageclass.go b/pkg/admin/installer/storageclass.go
similarity index 77%
rename from pkg/installer/storageclass.go
rename to pkg/admin/installer/storageclass.go
index d25bf600..8c4fce60 100644
--- a/pkg/installer/storageclass.go
+++ b/pkg/admin/installer/storageclass.go
@@ -22,8 +22,8 @@ import (
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/k8s"
legacyclient "github.com/minio/directpv/pkg/legacy/client"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
@@ -34,7 +34,9 @@ import (
var errStorageClassVersionUnsupported = errors.New("unsupported StorageClass version found")
-type storageClassTask struct{}
+type storageClassTask struct {
+ client *client.Client
+}
func (storageClassTask) Name() string {
return "StorageClass"
@@ -58,15 +60,15 @@ func (storageClassTask) End(ctx context.Context, args *Args, err error) error {
return nil
}
-func (storageClassTask) Execute(ctx context.Context, args *Args) error {
- return createStorageClass(ctx, args)
+func (t storageClassTask) Execute(ctx context.Context, args *Args) error {
+ return t.createStorageClass(ctx, args)
}
-func (storageClassTask) Delete(ctx context.Context, _ *Args) error {
- return deleteStorageClass(ctx)
+func (t storageClassTask) Delete(ctx context.Context, _ *Args) error {
+ return t.deleteStorageClass(ctx)
}
-func doCreateStorageClass(ctx context.Context, args *Args, version string, legacy bool, step int) (err error) {
+func (t storageClassTask) doCreateStorageClass(ctx context.Context, args *Args, version string, legacy bool, step int) (err error) {
name := consts.Identity
if legacy {
name = legacyclient.Identity
@@ -118,7 +120,7 @@ func doCreateStorageClass(ctx context.Context, args *Args, version string, legac
}
if !args.DryRun && !args.Declarative {
- _, err := k8s.KubeClient().StorageV1().StorageClasses().Create(
+ _, err := t.client.Kube().StorageV1().StorageClasses().Create(
ctx, storageClass, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -152,7 +154,7 @@ func doCreateStorageClass(ctx context.Context, args *Args, version string, legac
}
if !args.DryRun && !args.Declarative {
- _, err := k8s.KubeClient().StorageV1beta1().StorageClasses().Create(
+ _, err := t.client.Kube().StorageV1beta1().StorageClasses().Create(
ctx, storageClass, metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
@@ -167,7 +169,7 @@ func doCreateStorageClass(ctx context.Context, args *Args, version string, legac
}
}
-func createStorageClass(ctx context.Context, args *Args) (err error) {
+func (t storageClassTask) createStorageClass(ctx context.Context, args *Args) (err error) {
version := "v1"
switch {
case args.DryRun:
@@ -175,19 +177,19 @@ func createStorageClass(ctx context.Context, args *Args) (err error) {
version = "v1beta1"
}
default:
- gvk, err := k8s.GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
+ gvk, err := t.client.K8s().GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
if err != nil {
return err
}
version = gvk.Version
}
- if err := doCreateStorageClass(ctx, args, version, false, 1); err != nil {
+ if err := t.doCreateStorageClass(ctx, args, version, false, 1); err != nil {
return err
}
if args.Legacy {
- if err := doCreateStorageClass(ctx, args, version, true, 2); err != nil {
+ if err := t.doCreateStorageClass(ctx, args, version, true, 2); err != nil {
return err
}
}
@@ -195,14 +197,14 @@ func createStorageClass(ctx context.Context, args *Args) (err error) {
return nil
}
-func doDeleteStorageClass(ctx context.Context, version, name string) (err error) {
+func (t storageClassTask) doDeleteStorageClass(ctx context.Context, version, name string) (err error) {
switch version {
case "v1":
- err = k8s.KubeClient().StorageV1().StorageClasses().Delete(
+ err = t.client.Kube().StorageV1().StorageClasses().Delete(
ctx, name, metav1.DeleteOptions{},
)
case "v1beta1":
- err = k8s.KubeClient().StorageV1beta1().StorageClasses().Delete(
+ err = t.client.Kube().StorageV1beta1().StorageClasses().Delete(
ctx, name, metav1.DeleteOptions{},
)
default:
@@ -216,15 +218,15 @@ func doDeleteStorageClass(ctx context.Context, version, name string) (err error)
return nil
}
-func deleteStorageClass(ctx context.Context) error {
- gvk, err := k8s.GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
+func (t storageClassTask) deleteStorageClass(ctx context.Context) error {
+ gvk, err := t.client.K8s().GetGroupVersionKind("storage.k8s.io", "CSIDriver", "v1", "v1beta1")
if err != nil {
return err
}
- if err = doDeleteStorageClass(ctx, gvk.Version, consts.StorageClassName); err != nil {
+ if err = t.doDeleteStorageClass(ctx, gvk.Version, consts.StorageClassName); err != nil {
return err
}
- return doDeleteStorageClass(ctx, gvk.Version, legacyclient.Identity)
+ return t.doDeleteStorageClass(ctx, gvk.Version, legacyclient.Identity)
}
diff --git a/pkg/installer/utils.go b/pkg/admin/installer/utils.go
similarity index 100%
rename from pkg/installer/utils.go
rename to pkg/admin/installer/utils.go
diff --git a/pkg/installer/vars.go b/pkg/admin/installer/vars.go
similarity index 100%
rename from pkg/installer/vars.go
rename to pkg/admin/installer/vars.go
diff --git a/pkg/admin/label_drives.go b/pkg/admin/label_drives.go
new file mode 100644
index 00000000..6b036a44
--- /dev/null
+++ b/pkg/admin/label_drives.go
@@ -0,0 +1,133 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// Label represents the label to be set
+type Label struct {
+ Key directpvtypes.LabelKey
+ Value directpvtypes.LabelValue
+ Remove bool
+}
+
+func (l Label) String() string {
+ if l.Value == "" {
+ return string(l.Key)
+ }
+ return string(l.Key) + ":" + string(l.Value)
+}
+
+// LabelDriveResult represents the labeled drives
+type LabelDriveResult struct {
+ NodeID directpvtypes.NodeID
+ DriveName directpvtypes.DriveName
+}
+
+// LabelDriveArgs represents the arguments for adding/removing labels on/from the drives
+type LabelDriveArgs struct {
+ Nodes []string
+ Drives []string
+ DriveStatus []directpvtypes.DriveStatus
+ DriveIDs []directpvtypes.DriveID
+ LabelSelectors map[directpvtypes.LabelKey]directpvtypes.LabelValue
+ DryRun bool
+}
+
+// LabelDrives sets/removes labels on/from the drives
+func (client *Client) LabelDrives(ctx context.Context, args LabelDriveArgs, labels []Label, log LogFunc) (results []LabelDriveResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ StatusSelector(args.DriveStatus).
+ DriveIDSelector(args.DriveIDs).
+ LabelSelector(args.LabelSelectors).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ processed = true
+ drive := &result.Drive
+ var verb string
+ for i := range labels {
+ updateFunc := func() (err error) {
+ if labels[i].Remove {
+ if ok := drive.RemoveLabel(labels[i].Key); !ok {
+ return
+ }
+ verb = "removed from"
+ } else {
+ if ok := drive.SetLabel(labels[i].Key, labels[i].Value); !ok {
+ return
+ }
+ verb = "set on"
+ }
+ if !args.DryRun {
+ drive, err = client.Drive().Update(ctx, drive, metav1.UpdateOptions{})
+ }
+ if err != nil {
+ log(
+ LogMessage{
+ Type: ErrorLogType,
+ Err: err,
+ Message: "unable to " + verb + " label to drive",
+ Values: map[string]any{"node": drive.GetNodeID(), "driveName": drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("%v/%v: %v\n", drive.GetNodeID(), drive.GetDriveName(), err),
+ },
+ )
+ } else {
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "label successfully " + verb + " label to drive",
+ Values: map[string]any{"label": labels[i].String(), "verb": verb, "node": drive.GetNodeID(), "driveName": drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Label '%s' successfully %s %v/%v\n", labels[i].String(), verb, drive.GetNodeID(), drive.GetDriveName()),
+ },
+ )
+ }
+ results = append(results, LabelDriveResult{
+ NodeID: drive.GetNodeID(),
+ DriveName: drive.GetDriveName(),
+ })
+ return
+ }
+ retry.RetryOnConflict(retry.DefaultRetry, updateFunc)
+ }
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/label_volumes.go b/pkg/admin/label_volumes.go
new file mode 100644
index 00000000..ddf887f6
--- /dev/null
+++ b/pkg/admin/label_volumes.go
@@ -0,0 +1,125 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// LabelVolumeArgs represents the arguments for adding/removing labels on/from the volumes
+type LabelVolumeArgs struct {
+ Nodes []string
+ Drives []string
+ DriveIDs []string
+ PodNames []string
+ PodNamespaces []string
+ VolumeStatus []directpvtypes.VolumeStatus
+ VolumeNames []string
+ LabelSelectors map[directpvtypes.LabelKey]directpvtypes.LabelValue
+ DryRun bool
+}
+
+// LabelVolumeResult represents the labeled volume
+type LabelVolumeResult struct {
+ NodeID directpvtypes.NodeID
+ VolumeName string
+}
+
+// LabelVolumes sets/removes labels on/from the volumes
+func (client *Client) LabelVolumes(ctx context.Context, args LabelVolumeArgs, labels []Label, log LogFunc) (results []LabelVolumeResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ var processed bool
+ resultCh := client.NewVolumeLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ DriveIDSelector(utils.ToLabelValues(args.DriveIDs)).
+ PodNameSelector(utils.ToLabelValues(args.PodNames)).
+ PodNSSelector(utils.ToLabelValues(args.PodNamespaces)).
+ StatusSelector(args.VolumeStatus).
+ VolumeNameSelector(args.VolumeNames).
+ LabelSelector(args.LabelSelectors).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ var verb string
+ processed = true
+ volume := &result.Volume
+ for i := range labels {
+ updateFunc := func() (err error) {
+ if labels[i].Remove {
+ if ok := volume.RemoveLabel(labels[i].Key); !ok {
+ return
+ }
+ verb = "removed from"
+ } else {
+ if ok := volume.SetLabel(labels[i].Key, labels[i].Value); !ok {
+ return
+ }
+ verb = "set on"
+ }
+ if !args.DryRun {
+ volume, err = client.Volume().Update(ctx, volume, metav1.UpdateOptions{})
+ }
+ if err != nil {
+ log(
+ LogMessage{
+ Type: ErrorLogType,
+ Err: err,
+ Message: "unable to " + verb + " label to volume",
+ Values: map[string]any{"verb": verb, "volume": volume.Name},
+ FormattedMessage: fmt.Sprintf("%v: %v\n", volume.Name, err),
+ },
+ )
+ } else {
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "label successfully " + verb + " label to volume",
+ Values: map[string]any{"label": labels[i].String(), "verb": verb, "volume": volume.Name},
+ FormattedMessage: fmt.Sprintf("Label '%s' successfully %s %v\n", labels[i].String(), verb, volume.Name),
+ },
+ )
+ }
+ results = append(results, LabelVolumeResult{
+ NodeID: volume.GetNodeID(),
+ VolumeName: volume.Name,
+ })
+ return
+ }
+ retry.RetryOnConflict(retry.DefaultRetry, updateFunc)
+ }
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/logger.go b/pkg/admin/logger.go
new file mode 100644
index 00000000..8b57daf4
--- /dev/null
+++ b/pkg/admin/logger.go
@@ -0,0 +1,44 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+// LogType represents the type of the log
+type LogType int
+
+const (
+ // UnknownLogType denotes the dummy LogType
+ UnknownLogType LogType = iota
+ // ErrorLogType denotes the error LogType
+ ErrorLogType
+ // InfoLogType denotes the non-error info LogType
+ InfoLogType
+)
+
+// LogMessage represents the log message
+type LogMessage struct {
+ Type LogType
+ Err error
+ Code string
+ Message string
+ Values map[string]any
+ FormattedMessage string
+}
+
+// LogFunc represents the logger
+type LogFunc func(LogMessage)
+
+func nullLogger(_ LogMessage) {}
diff --git a/pkg/admin/migrate.go b/pkg/admin/migrate.go
new file mode 100644
index 00000000..96977a75
--- /dev/null
+++ b/pkg/admin/migrate.go
@@ -0,0 +1,78 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/minio/directpv/pkg/admin/installer"
+ "github.com/minio/directpv/pkg/consts"
+ legacyclient "github.com/minio/directpv/pkg/legacy/client"
+)
+
+// MigrateArgs denotest the migrate arguments
+type MigrateArgs struct {
+ Quiet bool
+ Retain bool
+ DrivesBackupFile string
+ VolumesBackupFile string
+}
+
+// Migrate migrates the directpv resources
+func (client *Client) Migrate(ctx context.Context, args MigrateArgs) error {
+ if !args.Retain {
+ if args.DrivesBackupFile == "" || args.VolumesBackupFile == "" {
+ return errors.New("backup file should not be empty")
+ }
+ if args.DrivesBackupFile == args.VolumesBackupFile {
+ return errors.New("backup filenames are same")
+ }
+ }
+ legacyClient, err := legacyclient.NewClient(client.K8s())
+ if err != nil {
+ return fmt.Errorf("unable to create legacy client; %v", err)
+ }
+ if err := installer.Migrate(ctx, &installer.Args{
+ Quiet: args.Quiet,
+ Legacy: true,
+ }, client.Client, legacyClient); err != nil {
+ return fmt.Errorf("migration failed; %v", err)
+ }
+ if !args.Quiet {
+ fmt.Println("Migration successful; Please restart the pods in '" + consts.AppName + "' namespace.")
+ }
+ if args.Retain {
+ return nil
+ }
+ backupCreated, err := legacyClient.RemoveAllDrives(ctx, args.DrivesBackupFile)
+ if err != nil {
+ return fmt.Errorf("unable to remove legacy drive CRDs; %v", err)
+ }
+ if backupCreated && !args.Quiet {
+ fmt.Println("Legacy drive CRDs backed up to", args.DrivesBackupFile)
+ }
+ backupCreated, err = legacyClient.RemoveAllVolumes(ctx, args.VolumesBackupFile)
+ if err != nil {
+ return fmt.Errorf("unable to remove legacy volume CRDs; %v", err)
+ }
+ if backupCreated && !args.Quiet {
+ fmt.Println("Legacy volume CRDs backed up to", args.VolumesBackupFile)
+ }
+ return nil
+}
diff --git a/pkg/admin/move.go b/pkg/admin/move.go
new file mode 100644
index 00000000..3d5c52bc
--- /dev/null
+++ b/pkg/admin/move.go
@@ -0,0 +1,138 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/dustin/go-humanize"
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/types"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// MoveArgs represents the args for moving
+type MoveArgs struct {
+ Source directpvtypes.DriveID
+ Destination directpvtypes.DriveID
+}
+
+// Move - moves the volume references from source to destination
+func (client *Client) Move(ctx context.Context, args MoveArgs, log LogFunc) error {
+ if log == nil {
+ log = nullLogger
+ }
+
+ if args.Source == args.Destination {
+ return errors.New("source and destination drives are same")
+ }
+
+ srcDrive, err := client.Drive().Get(ctx, string(args.Source), metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("unable to get source drive; %v", err)
+ }
+
+ if !srcDrive.IsUnschedulable() {
+ return errors.New("source drive is not cordoned")
+ }
+
+ sourceVolumeNames := srcDrive.GetVolumes()
+ if len(sourceVolumeNames) == 0 {
+ return fmt.Errorf("no volumes found in source drive %v", args.Source)
+ }
+
+ var requiredCapacity int64
+ var volumes []types.Volume
+ for result := range client.NewVolumeLister().VolumeNameSelector(sourceVolumeNames).List(ctx) {
+ if result.Err != nil {
+ return result.Err
+ }
+ if result.Volume.IsPublished() {
+ return fmt.Errorf("cannot move published volume %v", result.Volume.Name)
+ }
+ requiredCapacity += result.Volume.Status.TotalCapacity
+ volumes = append(volumes, result.Volume)
+ }
+
+ if len(volumes) == 0 {
+ return fmt.Errorf("no volumes found in source drive %v", args.Source)
+ }
+
+ destDrive, err := client.Drive().Get(ctx, string(args.Destination), metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("unable to get destination drive %v; %v", args.Destination, err)
+ }
+ if destDrive.GetNodeID() != srcDrive.GetNodeID() {
+ return fmt.Errorf("source and destination drives must be in same node; source node %v; desination node %v",
+ srcDrive.GetNodeID(),
+ destDrive.GetNodeID())
+ }
+ if !destDrive.IsUnschedulable() {
+ return errors.New("destination drive is not cordoned")
+ }
+ if destDrive.Status.Status != directpvtypes.DriveStatusReady {
+ return errors.New("destination drive is not in ready state")
+ }
+
+ if srcDrive.GetAccessTier() != destDrive.GetAccessTier() {
+ return fmt.Errorf("source drive access-tier %v and destination drive access-tier %v differ",
+ srcDrive.GetAccessTier(),
+ destDrive.GetAccessTier())
+ }
+
+ if destDrive.Status.FreeCapacity < requiredCapacity {
+ return fmt.Errorf("insufficient free capacity on destination drive; required=%v free=%v",
+ humanize.IBytes(uint64(requiredCapacity)),
+ humanize.IBytes(uint64(destDrive.Status.FreeCapacity)))
+ }
+
+ for _, volume := range volumes {
+ if destDrive.AddVolumeFinalizer(volume.Name) {
+ destDrive.Status.FreeCapacity -= volume.Status.TotalCapacity
+ destDrive.Status.AllocatedCapacity += volume.Status.TotalCapacity
+ }
+ }
+ destDrive.Status.Status = directpvtypes.DriveStatusMoving
+ _, err = client.Drive().Update(
+ ctx, destDrive, metav1.UpdateOptions{TypeMeta: types.NewDriveTypeMeta()},
+ )
+ if err != nil {
+ return fmt.Errorf("unable to move volumes to destination drive; %v", err)
+ }
+
+ for _, volume := range volumes {
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "moving volume",
+ Values: map[string]any{"volume": volume.Name},
+ FormattedMessage: fmt.Sprintf("Moving volume %v\n", volume.Name),
+ },
+ )
+ }
+
+ srcDrive.ResetFinalizers()
+ _, err = client.Drive().Update(
+ ctx, srcDrive, metav1.UpdateOptions{TypeMeta: types.NewDriveTypeMeta()},
+ )
+ if err != nil {
+ return fmt.Errorf("unable to remove volume references in source drive; %v", err)
+ }
+ return nil
+}
diff --git a/pkg/admin/refresh.go b/pkg/admin/refresh.go
new file mode 100644
index 00000000..9e53c176
--- /dev/null
+++ b/pkg/admin/refresh.go
@@ -0,0 +1,84 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// RefreshNodes refreshes the nodes provided in the input
+func (client *Client) RefreshNodes(ctx context.Context, selectedNodes []string) (<-chan directpvtypes.NodeID, <-chan error, error) {
+ if err := client.SyncNodes(ctx); err != nil {
+ return nil, nil, err
+ }
+
+ nodes, err := client.NewNodeLister().
+ NodeSelector(utils.ToLabelValues(selectedNodes)).
+ Get(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(selectedNodes) != 0 && len(nodes) == 0 {
+ suffix := ""
+ if len(selectedNodes) > 1 {
+ suffix = "s"
+ }
+ return nil, nil, fmt.Errorf("node%v %v not found", suffix, strings.Join(selectedNodes, ","))
+ }
+
+ nodeCh := make(chan directpvtypes.NodeID)
+ errCh := make(chan error)
+
+ go func() {
+ defer close(nodeCh)
+ defer close(errCh)
+
+ nodeClient := client.Node()
+ for i := range nodes {
+ updateFunc := func() error {
+ node, err := nodeClient.Get(ctx, nodes[i].Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ select {
+ case nodeCh <- directpvtypes.NodeID(node.Name):
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+ node.Spec.Refresh = true
+ if _, err := nodeClient.Update(ctx, node, metav1.UpdateOptions{TypeMeta: types.NewNodeTypeMeta()}); err != nil {
+ return err
+ }
+ return nil
+ }
+ if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
+ errCh <- err
+ return
+ }
+ }
+ }()
+
+ return nodeCh, errCh, nil
+}
diff --git a/pkg/admin/remove.go b/pkg/admin/remove.go
new file mode 100644
index 00000000..314cb236
--- /dev/null
+++ b/pkg/admin/remove.go
@@ -0,0 +1,118 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// RemoveArgs represents the arguments to remove a drive
+type RemoveArgs struct {
+ Nodes []string
+ Drives []string
+ DriveStatus []directpvtypes.DriveStatus
+ DriveIDs []directpvtypes.DriveID
+ DryRun bool
+}
+
+// RemoveResult represents the removed drive
+type RemoveResult struct {
+ NodeID directpvtypes.NodeID
+ DriveName directpvtypes.DriveName
+}
+
+// Remove removes the initialized drive(s)
+func (client *Client) Remove(ctx context.Context, args RemoveArgs, log LogFunc) (results []RemoveResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+ var failed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ StatusSelector(args.DriveStatus).
+ DriveIDSelector(args.DriveIDs).
+ IgnoreNotFound(true).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+
+ processed = true
+ switch result.Drive.Status.Status {
+ case directpvtypes.DriveStatusRemoved:
+ default:
+ volumeCount := result.Drive.GetVolumeCount()
+ if volumeCount > 0 {
+ failed = true
+ } else {
+ result.Drive.Status.Status = directpvtypes.DriveStatusRemoved
+ var err error
+ if !args.DryRun {
+ _, err = client.Drive().Update(ctx, &result.Drive, metav1.UpdateOptions{})
+ }
+ if err != nil {
+ failed = true
+ log(
+ LogMessage{
+ Type: ErrorLogType,
+ Err: err,
+ Message: "unable to remove drive",
+ Values: map[string]any{"node": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("%v/%v: %v\n", result.Drive.GetNodeID(), result.Drive.GetDriveName(), err),
+ },
+ )
+ } else {
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "removing drive",
+ Values: map[string]any{"node": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Removing %v/%v\n", result.Drive.GetNodeID(), result.Drive.GetDriveName()),
+ },
+ )
+ }
+ results = append(results, RemoveResult{
+ NodeID: result.Drive.GetNodeID(),
+ DriveName: result.Drive.GetDriveName(),
+ })
+ }
+ }
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ if failed {
+ err = errors.New("unable to remove drive(s)")
+ return
+ }
+ return
+}
diff --git a/pkg/admin/resume_drives.go b/pkg/admin/resume_drives.go
new file mode 100644
index 00000000..d26acff1
--- /dev/null
+++ b/pkg/admin/resume_drives.go
@@ -0,0 +1,97 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// ResumeDriveArgs represents the args to be passed for resuming the drive
+type ResumeDriveArgs = SuspendDriveArgs
+
+// ResumeDriveResult represents the resumed drive
+type ResumeDriveResult = SuspendDriveResult
+
+// ResumeDrives will resume the suspended drives
+func (client *Client) ResumeDrives(ctx context.Context, args ResumeDriveArgs, log LogFunc) (results []ResumeDriveResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ DriveIDSelector(args.DriveIDSelectors).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ processed = true
+ if !result.Drive.IsSuspended() {
+ // only suspended drives can be resumed.
+ continue
+ }
+ driveClient := client.Drive()
+ updateFunc := func() error {
+ drive, err := driveClient.Get(ctx, result.Drive.Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ drive.Resume()
+ if !args.DryRun {
+ if _, err := driveClient.Update(ctx, drive, metav1.UpdateOptions{}); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
+ err = fmt.Errorf("unable to resume drive %v; %v", result.Drive.GetDriveID(), err)
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "drive resumed",
+ Values: map[string]any{"node": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Drive %v/%v resumed\n", result.Drive.GetNodeID(), result.Drive.GetDriveName()),
+ },
+ )
+
+ results = append(results, ResumeDriveResult{
+ NodeID: result.Drive.GetNodeID(),
+ DriveName: result.Drive.GetDriveName(),
+ })
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/resume_volumes.go b/pkg/admin/resume_volumes.go
new file mode 100644
index 00000000..c425758c
--- /dev/null
+++ b/pkg/admin/resume_volumes.go
@@ -0,0 +1,100 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// ResumeVolumeArgs represents the args to be passed for resuming the volume
+type ResumeVolumeArgs = SuspendVolumeArgs
+
+// ResumeVolumeResult represents the suspended volume
+type ResumeVolumeResult = SuspendVolumeResult
+
+// ResumeVolumes will resume the suspended volumes
+func (client *Client) ResumeVolumes(ctx context.Context, args ResumeVolumeArgs, log LogFunc) (results []ResumeVolumeResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewVolumeLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ PodNameSelector(utils.ToLabelValues(args.PodNames)).
+ PodNSSelector(utils.ToLabelValues(args.PodNamespaces)).
+ VolumeNameSelector(args.VolumeNames).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ processed = true
+ if !result.Volume.IsSuspended() {
+ // only suspended drives can be resumed.
+ continue
+ }
+ volumeClient := client.Volume()
+ updateFunc := func() error {
+ volume, err := volumeClient.Get(ctx, result.Volume.Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ volume.Resume()
+ if !args.DryRun {
+ if _, err := volumeClient.Update(ctx, volume, metav1.UpdateOptions{}); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
+ err = fmt.Errorf("unable to resume volume %v; %v", result.Volume.Name, err)
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "volume resumed",
+ Values: map[string]any{"node": result.Volume.GetNodeID(), "volume": result.Volume.Name},
+ FormattedMessage: fmt.Sprintf("Volume %v/%v resumed\n", result.Volume.GetNodeID(), result.Volume.Name),
+ },
+ )
+
+ results = append(results, ResumeVolumeResult{
+ NodeID: result.Volume.GetNodeID(),
+ VolumeName: result.Volume.Name,
+ })
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/suspend_drives.go b/pkg/admin/suspend_drives.go
new file mode 100644
index 00000000..f2a6b9f5
--- /dev/null
+++ b/pkg/admin/suspend_drives.go
@@ -0,0 +1,114 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// ErrNoMatchingResourcesFound denotes that no matching resources are found for processing
+var ErrNoMatchingResourcesFound = errors.New("no matching resources found")
+
+// SuspendDriveArgs denotes the args for suspending the drive
+type SuspendDriveArgs struct {
+ Nodes []string
+ Drives []string
+ DriveIDSelectors []directpvtypes.DriveID
+ DryRun bool
+}
+
+// SuspendDriveResult represents the suspended drive
+type SuspendDriveResult struct {
+ NodeID directpvtypes.NodeID
+ DriveName directpvtypes.DriveName
+}
+
+// SuspendDrives suspends the drive
+func (client *Client) SuspendDrives(ctx context.Context, args SuspendDriveArgs, log LogFunc) (results []SuspendDriveResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ DriveIDSelector(args.DriveIDSelectors).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+
+ processed = true
+
+ if result.Drive.IsSuspended() {
+ continue
+ }
+
+ driveClient := client.Drive()
+ updateFunc := func() error {
+ drive, err := driveClient.Get(ctx, result.Drive.Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ drive.Suspend()
+ if !args.DryRun {
+ if _, err := driveClient.Update(ctx, drive, metav1.UpdateOptions{}); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
+ err = fmt.Errorf("unable to suspend drive %v; %v", result.Drive.GetDriveID(), err)
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "drive suspended",
+ Values: map[string]any{"node": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Drive %v/%v suspended\n", result.Drive.GetNodeID(), result.Drive.GetDriveName()),
+ },
+ )
+
+ results = append(results, SuspendDriveResult{
+ NodeID: result.Drive.GetNodeID(),
+ DriveName: result.Drive.GetDriveName(),
+ })
+ }
+
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+
+ return results, nil
+}
diff --git a/pkg/admin/suspend_volumes.go b/pkg/admin/suspend_volumes.go
new file mode 100644
index 00000000..13a6a113
--- /dev/null
+++ b/pkg/admin/suspend_volumes.go
@@ -0,0 +1,109 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/util/retry"
+)
+
+// SuspendVolumeArgs denotes the args for suspending the volume
+type SuspendVolumeArgs struct {
+ Nodes []string
+ Drives []string
+ PodNames []string
+ PodNamespaces []string
+ VolumeNames []string
+ DryRun bool
+}
+
+// SuspendVolumeResult represents the suspended volume
+type SuspendVolumeResult struct {
+ NodeID directpvtypes.NodeID
+ VolumeName string
+}
+
+// SuspendVolumes suspends the volume
+func (client *Client) SuspendVolumes(ctx context.Context, args SuspendVolumeArgs, log LogFunc) (results []SuspendVolumeResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewVolumeLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ PodNameSelector(utils.ToLabelValues(args.PodNames)).
+ PodNSSelector(utils.ToLabelValues(args.PodNamespaces)).
+ VolumeNameSelector(args.VolumeNames).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+ processed = true
+ if result.Volume.IsSuspended() {
+ continue
+ }
+ volumeClient := client.Volume()
+ updateFunc := func() error {
+ volume, err := volumeClient.Get(ctx, result.Volume.Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ volume.Suspend()
+ if !args.DryRun {
+ if _, err := volumeClient.Update(ctx, volume, metav1.UpdateOptions{}); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
+ err = fmt.Errorf("unable to suspend volume %v; %v", result.Volume.Name, err)
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "volume suspended",
+ Values: map[string]any{"node": result.Volume.GetNodeID(), "volume": result.Volume.Name},
+ FormattedMessage: fmt.Sprintf("Volume %v/%v suspended\n", result.Volume.GetNodeID(), result.Volume.Name),
+ },
+ )
+
+ results = append(results, SuspendVolumeResult{
+ NodeID: result.Volume.GetNodeID(),
+ VolumeName: result.Volume.Name,
+ })
+ }
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/uncordon.go b/pkg/admin/uncordon.go
new file mode 100644
index 00000000..2341b7b7
--- /dev/null
+++ b/pkg/admin/uncordon.go
@@ -0,0 +1,90 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// UncordonArgs represents the args for uncordoning a drive
+type UncordonArgs = CordonArgs
+
+// UncordonResult represents the uncordoned drive
+type UncordonResult = CordonResult
+
+// Uncordon makes the drive schedulable again
+func (client *Client) Uncordon(ctx context.Context, args UncordonArgs, log LogFunc) (results []UncordonResult, err error) {
+ if log == nil {
+ log = nullLogger
+ }
+
+ var processed bool
+
+ ctx, cancelFunc := context.WithCancel(ctx)
+ defer cancelFunc()
+
+ resultCh := client.NewDriveLister().
+ NodeSelector(utils.ToLabelValues(args.Nodes)).
+ DriveNameSelector(utils.ToLabelValues(args.Drives)).
+ StatusSelector(args.Status).
+ DriveIDSelector(args.DriveIDs).
+ List(ctx)
+ for result := range resultCh {
+ if result.Err != nil {
+ err = result.Err
+ return
+ }
+
+ processed = true
+
+ if !result.Drive.IsUnschedulable() {
+ continue
+ }
+
+ result.Drive.Schedulable()
+ if !args.DryRun {
+ _, err = client.Drive().Update(ctx, &result.Drive, metav1.UpdateOptions{})
+ }
+ if err != nil {
+ err = fmt.Errorf("unable to uncordon drive %v; %v", result.Drive.GetDriveID(), err)
+ return
+ }
+
+ log(
+ LogMessage{
+ Type: InfoLogType,
+ Message: "drive uncordoned",
+ Values: map[string]any{"node": result.Drive.GetNodeID(), "driveName": result.Drive.GetDriveName()},
+ FormattedMessage: fmt.Sprintf("Drive %v/%v uncordoned\n", result.Drive.GetNodeID(), result.Drive.GetDriveName()),
+ },
+ )
+
+ results = append(results, UncordonResult{
+ NodeID: result.Drive.GetNodeID(),
+ DriveName: result.Drive.GetDriveName(),
+ })
+ }
+
+ if !processed {
+ return nil, ErrNoMatchingResourcesFound
+ }
+ return
+}
diff --git a/pkg/admin/uninstall.go b/pkg/admin/uninstall.go
new file mode 100644
index 00000000..91538f97
--- /dev/null
+++ b/pkg/admin/uninstall.go
@@ -0,0 +1,39 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package admin
+
+import (
+ "context"
+
+ "github.com/minio/directpv/pkg/admin/installer"
+ legacyclient "github.com/minio/directpv/pkg/legacy/client"
+)
+
+// UninstallArgs represents the args to uninstall
+type UninstallArgs struct {
+ Quiet bool
+ Dangerous bool
+}
+
+// Uninstall uninstalls directpv
+func (client Client) Uninstall(ctx context.Context, args UninstallArgs) error {
+ legacyClient, err := legacyclient.NewClient(client.K8s())
+ if err != nil {
+ return err
+ }
+ return installer.Uninstall(ctx, args.Quiet, args.Dangerous, installer.GetDefaultTasks(client.Client, legacyClient))
+}
diff --git a/pkg/client/clients.go b/pkg/client/clients.go
index 4179b83e..fa24e8c7 100644
--- a/pkg/client/clients.go
+++ b/pkg/client/clients.go
@@ -22,36 +22,56 @@ import (
)
var (
- initialized int32
- clientsetInterface types.ExtClientsetInterface
- restClient rest.Interface
- driveClient types.LatestDriveInterface
- volumeClient types.LatestVolumeInterface
- nodeClient types.LatestNodeInterface
- initRequestClient types.LatestInitRequestInterface
+ initialized int32
+ client *Client
)
+// GetClient returns the client
+func GetClient() *Client {
+ return client
+}
+
// RESTClient gets latest versioned REST client.
func RESTClient() rest.Interface {
- return restClient
+ return client.REST()
}
// DriveClient gets latest versioned drive interface.
func DriveClient() types.LatestDriveInterface {
- return driveClient
+ return client.Drive()
}
// VolumeClient gets latest versioned volume interface.
func VolumeClient() types.LatestVolumeInterface {
- return volumeClient
+ return client.Volume()
}
// NodeClient gets latest versioned node interface.
func NodeClient() types.LatestNodeInterface {
- return nodeClient
+ return client.Node()
}
// InitRequestClient gets latest versioned init request interface.
func InitRequestClient() types.LatestInitRequestInterface {
- return initRequestClient
+ return client.InitRequest()
+}
+
+// NewDriveLister returns the new drive lister
+func NewDriveLister() *DriveLister {
+ return client.NewDriveLister()
+}
+
+// NewVolumeLister returns the new volume lister
+func NewVolumeLister() *VolumeLister {
+ return client.NewVolumeLister()
+}
+
+// NewNodeLister returns the new node lister
+func NewNodeLister() *NodeLister {
+ return client.NewNodeLister()
+}
+
+// NewInitRequestLister returns the new initrequest lister
+func NewInitRequestLister() *InitRequestLister {
+ return client.NewInitRequestLister()
}
diff --git a/pkg/client/discovery.go b/pkg/client/discovery.go
new file mode 100644
index 00000000..afbd3b53
--- /dev/null
+++ b/pkg/client/discovery.go
@@ -0,0 +1,66 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package client
+
+import (
+ "context"
+ "fmt"
+
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/types"
+ "github.com/minio/directpv/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// SyncNodes compares the csinodes with directpvnode list and syncs them
+// It adds the missing nodes and deletes the non-existing nodes
+func (c Client) SyncNodes(ctx context.Context) (err error) {
+ csiNodes, err := c.K8s().GetCSINodes(ctx)
+ if err != nil {
+ return fmt.Errorf("unable to get CSI nodes; %w", err)
+ }
+ nodes, err := c.NewNodeLister().Get(ctx)
+ if err != nil {
+ return fmt.Errorf("unable to get nodes; %w", err)
+ }
+
+ var nodeNames []string
+ for _, node := range nodes {
+ nodeNames = append(nodeNames, node.Name)
+ }
+
+ // Add missing nodes.
+ for _, csiNode := range csiNodes {
+ if !utils.Contains(nodeNames, csiNode) {
+ node := types.NewNode(directpvtypes.NodeID(csiNode), []types.Device{})
+ node.Spec.Refresh = true
+ if _, err = c.Node().Create(ctx, node, metav1.CreateOptions{}); err != nil {
+ return fmt.Errorf("unable to create node %v; %w", csiNode, err)
+ }
+ }
+ }
+
+ // Remove non-existing nodes.
+ for _, nodeName := range nodeNames {
+ if !utils.Contains(csiNodes, nodeName) {
+ if err = c.Node().Delete(ctx, nodeName, metav1.DeleteOptions{}); err != nil {
+ return fmt.Errorf("unable to remove non-existing node %v; %w", nodeName, err)
+ }
+ }
+ }
+ return nil
+}
diff --git a/pkg/drive/list.go b/pkg/client/drive_lister.go
similarity index 77%
rename from pkg/drive/list.go
rename to pkg/client/drive_lister.go
index 7130c462..20f6d448 100644
--- a/pkg/drive/list.go
+++ b/pkg/client/drive_lister.go
@@ -1,5 +1,5 @@
// This file is part of MinIO DirectPV
-// Copyright (c) 2021, 2022 MinIO, Inc.
+// Copyright (c) 2021-2024 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -14,13 +14,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package drive
+package client
import (
"context"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
@@ -34,8 +33,8 @@ type ListDriveResult struct {
Err error
}
-// Lister is drive lister.
-type Lister struct {
+// DriveLister is lister wrapper for DirectPVDrive listing.
+type DriveLister struct {
nodes []directpvtypes.LabelValue
driveNames []directpvtypes.LabelValue
accessTiers []directpvtypes.LabelValue
@@ -44,59 +43,61 @@ type Lister struct {
labels map[directpvtypes.LabelKey]directpvtypes.LabelValue
maxObjects int64
ignoreNotFound bool
+ driveClient types.LatestDriveInterface
}
-// NewLister creates new drive lister.
-func NewLister() *Lister {
- return &Lister{
- maxObjects: k8s.MaxThreadCount,
+// NewDriveLister creates new drive lister.
+func (client Client) NewDriveLister() *DriveLister {
+ return &DriveLister{
+ maxObjects: k8s.MaxThreadCount,
+ driveClient: client.Drive(),
}
}
// NodeSelector adds filter listing by nodes.
-func (lister *Lister) NodeSelector(nodes []directpvtypes.LabelValue) *Lister {
+func (lister *DriveLister) NodeSelector(nodes []directpvtypes.LabelValue) *DriveLister {
lister.nodes = nodes
return lister
}
// DriveNameSelector adds filter listing by drive names.
-func (lister *Lister) DriveNameSelector(driveNames []directpvtypes.LabelValue) *Lister {
+func (lister *DriveLister) DriveNameSelector(driveNames []directpvtypes.LabelValue) *DriveLister {
lister.driveNames = driveNames
return lister
}
// StatusSelector adds filter listing by drive status.
-func (lister *Lister) StatusSelector(statusList []directpvtypes.DriveStatus) *Lister {
+func (lister *DriveLister) StatusSelector(statusList []directpvtypes.DriveStatus) *DriveLister {
lister.statusList = statusList
return lister
}
// DriveIDSelector adds filter listing by drive IDs.
-func (lister *Lister) DriveIDSelector(driveIDs []directpvtypes.DriveID) *Lister {
+func (lister *DriveLister) DriveIDSelector(driveIDs []directpvtypes.DriveID) *DriveLister {
lister.driveIDs = driveIDs
return lister
}
// LabelSelector adds filter listing by labels.
-func (lister *Lister) LabelSelector(labels map[directpvtypes.LabelKey]directpvtypes.LabelValue) *Lister {
+func (lister *DriveLister) LabelSelector(labels map[directpvtypes.LabelKey]directpvtypes.LabelValue) *DriveLister {
lister.labels = labels
return lister
}
// MaxObjects controls number of items to be fetched in every iteration.
-func (lister *Lister) MaxObjects(n int64) *Lister {
+func (lister *DriveLister) MaxObjects(n int64) *DriveLister {
lister.maxObjects = n
return lister
}
// IgnoreNotFound controls listing to ignore drive not found error.
-func (lister *Lister) IgnoreNotFound(b bool) *Lister {
+func (lister *DriveLister) IgnoreNotFound(b bool) *DriveLister {
lister.ignoreNotFound = b
return lister
}
// List returns channel to loop through drive items.
-func (lister *Lister) List(ctx context.Context) <-chan ListDriveResult {
+func (lister *DriveLister) List(ctx context.Context) <-chan ListDriveResult {
getOnly := len(lister.nodes) == 0 &&
len(lister.driveNames) == 0 &&
len(lister.accessTiers) == 0 &&
@@ -133,7 +134,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListDriveResult {
LabelSelector: labelSelector,
}
for {
- result, err := client.DriveClient().List(ctx, options)
+ result, err := lister.driveClient.List(ctx, options)
if err != nil {
if apierrors.IsNotFound(err) && lister.ignoreNotFound {
break
@@ -170,7 +171,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListDriveResult {
}
for _, driveID := range lister.driveIDs {
- drive, err := client.DriveClient().Get(ctx, string(driveID), metav1.GetOptions{})
+ drive, err := lister.driveClient.Get(ctx, string(driveID), metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) && lister.ignoreNotFound {
continue
@@ -190,7 +191,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListDriveResult {
}
// Get returns list of drives.
-func (lister *Lister) Get(ctx context.Context) ([]types.Drive, error) {
+func (lister *DriveLister) Get(ctx context.Context) ([]types.Drive, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
diff --git a/pkg/drive/list_test.go b/pkg/client/drive_lister_test.go
similarity index 84%
rename from pkg/drive/list_test.go
rename to pkg/client/drive_lister_test.go
index 70e6115d..27aa4f18 100644
--- a/pkg/drive/list_test.go
+++ b/pkg/client/drive_lister_test.go
@@ -14,14 +14,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package drive
+package client
import (
"context"
"fmt"
"testing"
- "github.com/minio/directpv/pkg/client"
clientsetfake "github.com/minio/directpv/pkg/clientset/fake"
"github.com/minio/directpv/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,8 +29,8 @@ import (
func TestGetDriveList(t *testing.T) {
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- drives, err := NewLister().Get(context.TODO())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ drives, err := client.NewDriveLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -47,8 +46,8 @@ func TestGetDriveList(t *testing.T) {
}
clientset = types.NewExtFakeClientset(clientsetfake.NewSimpleClientset(objects...))
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- drives, err = NewLister().Get(context.TODO())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ drives, err = client.NewDriveLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/pkg/client/dynamic.go b/pkg/client/dynamic.go
index 2e994191..8a0e90a2 100644
--- a/pkg/client/dynamic.go
+++ b/pkg/client/dynamic.go
@@ -31,7 +31,6 @@ import (
apimachinerytypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
- "k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
@@ -40,12 +39,12 @@ type dynamicInterface struct {
groupVersion schema.GroupVersion
}
-func dynamicInterfaceForConfig(config *rest.Config, kind, resource string) (*dynamicInterface, error) {
- gvk, err := k8s.GetGroupVersionKind(consts.GroupName, kind, types.Versions...)
+func dynamicInterfaceForConfig(k8sClient *k8s.Client, kind, resource string) (*dynamicInterface, error) {
+ gvk, err := k8sClient.GetGroupVersionKind(consts.GroupName, kind, types.Versions...)
if err != nil && !meta.IsNoMatchError(err) {
return nil, err
}
- resourceInterface, err := dynamic.NewForConfig(config)
+ resourceInterface, err := dynamic.NewForConfig(k8sClient.KubeConfig)
if err != nil {
return nil, err
}
@@ -229,8 +228,8 @@ type latestDriveClient struct {
}
// latestDriveClientForConfig creates new dynamic drive interface.
-func latestDriveClientForConfig(config *rest.Config) (*latestDriveClient, error) {
- inter, err := dynamicInterfaceForConfig(config, consts.DriveKind, consts.DriveResource)
+func latestDriveClientForConfig(k8sClient *k8s.Client) (*latestDriveClient, error) {
+ inter, err := dynamicInterfaceForConfig(k8sClient, consts.DriveKind, consts.DriveResource)
if err != nil {
return nil, err
}
@@ -351,8 +350,8 @@ type latestVolumeClient struct {
}
// latestVolumeClientForConfig creates new dynamic volume interface.
-func latestVolumeClientForConfig(config *rest.Config) (*latestVolumeClient, error) {
- inter, err := dynamicInterfaceForConfig(config, consts.VolumeKind, consts.VolumeResource)
+func latestVolumeClientForConfig(k8sClient *k8s.Client) (*latestVolumeClient, error) {
+ inter, err := dynamicInterfaceForConfig(k8sClient, consts.VolumeKind, consts.VolumeResource)
if err != nil {
return nil, err
}
@@ -473,8 +472,8 @@ type latestNodeClient struct {
}
// latestNodeClientForConfig creates new dynamic node interface.
-func latestNodeClientForConfig(config *rest.Config) (*latestNodeClient, error) {
- inter, err := dynamicInterfaceForConfig(config, consts.NodeKind, consts.NodeResource)
+func latestNodeClientForConfig(k8sClient *k8s.Client) (*latestNodeClient, error) {
+ inter, err := dynamicInterfaceForConfig(k8sClient, consts.NodeKind, consts.NodeResource)
if err != nil {
return nil, err
}
@@ -597,8 +596,8 @@ type latestInitRequestClient struct {
}
// latestInitRequestClientForConfig creates new dynamic initrequest interface.
-func latestInitRequestClientForConfig(config *rest.Config) (*latestInitRequestClient, error) {
- inter, err := dynamicInterfaceForConfig(config, consts.InitRequestKind, consts.InitRequestResource)
+func latestInitRequestClientForConfig(k8sClient *k8s.Client) (*latestInitRequestClient, error) {
+ inter, err := dynamicInterfaceForConfig(k8sClient, consts.InitRequestKind, consts.InitRequestResource)
if err != nil {
return nil, err
}
diff --git a/pkg/client/fake.go b/pkg/client/fake.go
index 09c48e13..360419db 100644
--- a/pkg/client/fake.go
+++ b/pkg/client/fake.go
@@ -25,36 +25,46 @@ import (
// FakeInit initializes fake clients.
func FakeInit() {
k8s.FakeInit()
+ k8sClient := k8s.GetClient()
+ clientsetInterface := types.NewExtFakeClientset(fake.NewSimpleClientset())
+ driveClient := clientsetInterface.DirectpvLatest().DirectPVDrives()
+ volumeClient := clientsetInterface.DirectpvLatest().DirectPVVolumes()
+ nodeClient := clientsetInterface.DirectpvLatest().DirectPVNodes()
+ initRequestClient := clientsetInterface.DirectpvLatest().DirectPVInitRequests()
+ restClient := clientsetInterface.DirectpvLatest().RESTClient()
- clientsetInterface = types.NewExtFakeClientset(fake.NewSimpleClientset())
- driveClient = clientsetInterface.DirectpvLatest().DirectPVDrives()
- volumeClient = clientsetInterface.DirectpvLatest().DirectPVVolumes()
- nodeClient = clientsetInterface.DirectpvLatest().DirectPVNodes()
- initRequestClient = clientsetInterface.DirectpvLatest().DirectPVInitRequests()
-
- initEvent(k8s.KubeClient())
+ initEvent(k8sClient.KubeClient)
+ client = &Client{
+ K8sClient: k8sClient,
+ ClientsetInterface: clientsetInterface,
+ RESTClient: restClient,
+ DriveClient: driveClient,
+ VolumeClient: volumeClient,
+ NodeClient: nodeClient,
+ InitRequestClient: initRequestClient,
+ }
}
// SetDriveInterface sets latest drive interface.
// Note: To be used for writing test cases only
func SetDriveInterface(i types.LatestDriveInterface) {
- driveClient = i
+ client.DriveClient = i
}
// SetVolumeInterface sets the latest volume interface.
// Note: To be used for writing test cases only
func SetVolumeInterface(i types.LatestVolumeInterface) {
- volumeClient = i
+ client.VolumeClient = i
}
// SetNodeInterface sets latest node interface.
// Note: To be used for writing test cases only
func SetNodeInterface(i types.LatestNodeInterface) {
- nodeClient = i
+ client.NodeClient = i
}
// SetInitRequestInterface sets latest initrequest interface.
// Note: To be used for writing test cases only
func SetInitRequestInterface(i types.LatestInitRequestInterface) {
- initRequestClient = i
+ client.InitRequestClient = i
}
diff --git a/pkg/client/init.go b/pkg/client/init.go
index 38965350..289d8672 100644
--- a/pkg/client/init.go
+++ b/pkg/client/init.go
@@ -17,12 +17,22 @@
package client
import (
+ "fmt"
"sync/atomic"
"github.com/minio/directpv/pkg/clientset"
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
+ apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
+ "k8s.io/client-go/discovery"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
"k8s.io/klog/v2"
+
+ // support gcp, azure, and oidc client auth
+ _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
+ _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
+ _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
// Init initializes various clients.
@@ -30,32 +40,123 @@ func Init() {
if atomic.AddInt32(&initialized, 1) != 1 {
return
}
-
- k8s.Init()
-
- cs, err := clientset.NewForConfig(k8s.KubeConfig())
+ var err error
+ if err = k8s.Init(); err != nil {
+ klog.Fatalf("unable to initialize k8s clients; %v", err)
+ }
+ client, err = newClient(k8s.GetClient())
if err != nil {
- klog.Fatalf("unable to create new clientset interface; %v", err)
+ klog.Fatalf("unable to initialize client; %v", err)
}
- clientsetInterface = types.NewExtClientset(cs)
+ initEvent(k8s.KubeClient())
+}
- restClient = clientsetInterface.DirectpvLatest().RESTClient()
+// Client represents the directpv client set
+type Client struct {
+ ClientsetInterface types.ExtClientsetInterface
+ RESTClient rest.Interface
+ DriveClient types.LatestDriveInterface
+ VolumeClient types.LatestVolumeInterface
+ NodeClient types.LatestNodeInterface
+ InitRequestClient types.LatestInitRequestInterface
+ K8sClient *k8s.Client
+}
- if driveClient, err = latestDriveClientForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new drive interface; %v", err)
- }
+// REST returns the REST client
+func (c Client) REST() rest.Interface {
+ return c.RESTClient
+}
- if volumeClient, err = latestVolumeClientForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new volume interface; %v", err)
- }
+// Drive returns the DirectPV Drive interface
+func (c Client) Drive() types.LatestDriveInterface {
+ return c.DriveClient
+}
- if nodeClient, err = latestNodeClientForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new node interface; %v", err)
- }
+// Volume returns the DirectPV Volume interface
+func (c Client) Volume() types.LatestVolumeInterface {
+ return c.VolumeClient
+}
+
+// Node returns the DirectPV Node interface
+func (c Client) Node() types.LatestNodeInterface {
+ return c.NodeClient
+}
+
+// InitRequest returns the DirectPV InitRequest interface
+func (c Client) InitRequest() types.LatestInitRequestInterface {
+ return c.InitRequestClient
+}
+
+// K8s returns the kubernetes client
+func (c Client) K8s() *k8s.Client {
+ return c.K8sClient
+}
+
+// KubeConfig returns the kubeconfig
+func (c Client) KubeConfig() *rest.Config {
+ return c.K8sClient.KubeConfig
+}
- if initRequestClient, err = latestInitRequestClientForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new initrequest interface; %v", err)
+// Kube returns the kube client
+func (c Client) Kube() kubernetes.Interface {
+ return c.K8sClient.KubeClient
+}
+
+// APIextensions returns the APIextensionsClient
+func (c Client) APIextensions() apiextensions.ApiextensionsV1Interface {
+ return c.K8sClient.APIextensionsClient
+}
+
+// CRD returns the CRD client
+func (c Client) CRD() apiextensions.CustomResourceDefinitionInterface {
+ return c.K8sClient.CRDClient
+}
+
+// Discovery returns the discovery client
+func (c Client) Discovery() discovery.DiscoveryInterface {
+ return c.K8sClient.DiscoveryClient
+}
+
+// NewClient returns the directpv client
+func NewClient(c *rest.Config) (*Client, error) {
+ k8sClient, err := k8s.NewClient(c)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create kubernetes client; %v", err)
}
+ return newClient(k8sClient)
+}
- initEvent(k8s.KubeClient())
+// newClient returns the directpv client
+func newClient(k8sClient *k8s.Client) (*Client, error) {
+ cs, err := clientset.NewForConfig(k8sClient.KubeConfig)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new clientset interface; %v", err)
+ }
+ clientsetInterface := types.NewExtClientset(cs)
+ restClient := clientsetInterface.DirectpvLatest().RESTClient()
+ driveClient, err := latestDriveClientForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new drive interface; %v", err)
+ }
+ volumeClient, err := latestVolumeClientForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new volume interface; %v", err)
+ }
+ nodeClient, err := latestNodeClientForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new node interface; %v", err)
+ }
+ initRequestClient, err := latestInitRequestClientForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new initrequest interface; %v", err)
+ }
+ return &Client{
+ ClientsetInterface: clientsetInterface,
+ RESTClient: restClient,
+ DriveClient: driveClient,
+ VolumeClient: volumeClient,
+ NodeClient: nodeClient,
+ InitRequestClient: initRequestClient,
+ K8sClient: k8sClient,
+ }, nil
}
diff --git a/pkg/initrequest/list.go b/pkg/client/initrequest_lister.go
similarity index 73%
rename from pkg/initrequest/list.go
rename to pkg/client/initrequest_lister.go
index 6ca663b5..213529aa 100644
--- a/pkg/initrequest/list.go
+++ b/pkg/client/initrequest_lister.go
@@ -14,21 +14,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package initrequest
+package client
import (
"context"
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/watch"
)
// ListInitRequestResult denotes list of initrequest result.
@@ -37,54 +35,56 @@ type ListInitRequestResult struct {
Err error
}
-// Lister is initRequest lister.
-type Lister struct {
- nodes []directpvtypes.LabelValue
- requestIDs []directpvtypes.LabelValue
- initRequestNames []string
- maxObjects int64
- ignoreNotFound bool
+// InitRequestLister is initRequest lister.
+type InitRequestLister struct {
+ nodes []directpvtypes.LabelValue
+ requestIDs []directpvtypes.LabelValue
+ initRequestNames []string
+ maxObjects int64
+ ignoreNotFound bool
+ initRequestClient types.LatestInitRequestInterface
}
-// NewLister creates new volume lister.
-func NewLister() *Lister {
- return &Lister{
- maxObjects: k8s.MaxThreadCount,
+// NewInitRequestLister creates new volume lister.
+func (client Client) NewInitRequestLister() *InitRequestLister {
+ return &InitRequestLister{
+ maxObjects: k8s.MaxThreadCount,
+ initRequestClient: client.InitRequest(),
}
}
// NodeSelector adds filter listing by nodes.
-func (lister *Lister) NodeSelector(nodes []directpvtypes.LabelValue) *Lister {
+func (lister *InitRequestLister) NodeSelector(nodes []directpvtypes.LabelValue) *InitRequestLister {
lister.nodes = nodes
return lister
}
// RequestIDSelector adds filter listing by its request IDs.
-func (lister *Lister) RequestIDSelector(requestIDs []directpvtypes.LabelValue) *Lister {
+func (lister *InitRequestLister) RequestIDSelector(requestIDs []directpvtypes.LabelValue) *InitRequestLister {
lister.requestIDs = requestIDs
return lister
}
// InitRequestNameSelector adds filter listing by InitRequestNames.
-func (lister *Lister) InitRequestNameSelector(initRequestNames []string) *Lister {
+func (lister *InitRequestLister) InitRequestNameSelector(initRequestNames []string) *InitRequestLister {
lister.initRequestNames = initRequestNames
return lister
}
// MaxObjects controls number of items to be fetched in every iteration.
-func (lister *Lister) MaxObjects(n int64) *Lister {
+func (lister *InitRequestLister) MaxObjects(n int64) *InitRequestLister {
lister.maxObjects = n
return lister
}
// IgnoreNotFound controls listing to ignore not found error.
-func (lister *Lister) IgnoreNotFound(b bool) *Lister {
+func (lister *InitRequestLister) IgnoreNotFound(b bool) *InitRequestLister {
lister.ignoreNotFound = b
return lister
}
// List returns channel to loop through initrequest items.
-func (lister *Lister) List(ctx context.Context) <-chan ListInitRequestResult {
+func (lister *InitRequestLister) List(ctx context.Context) <-chan ListInitRequestResult {
getOnly := len(lister.nodes) == 0 &&
len(lister.requestIDs) == 0 &&
len(lister.initRequestNames) != 0
@@ -114,7 +114,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListInitRequestResult {
LabelSelector: labelSelector,
}
for {
- result, err := client.InitRequestClient().List(ctx, options)
+ result, err := lister.initRequestClient.List(ctx, options)
if err != nil {
send(ListInitRequestResult{Err: err})
return
@@ -148,7 +148,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListInitRequestResult {
}
for _, initRequestName := range lister.initRequestNames {
- initRequest, err := client.InitRequestClient().Get(ctx, initRequestName, metav1.GetOptions{})
+ initRequest, err := lister.initRequestClient.Get(ctx, initRequestName, metav1.GetOptions{})
if err != nil {
send(ListInitRequestResult{Err: err})
return
@@ -163,7 +163,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListInitRequestResult {
}
// Get returns list of initrequest.
-func (lister *Lister) Get(ctx context.Context) ([]types.InitRequest, error) {
+func (lister *InitRequestLister) Get(ctx context.Context) ([]types.InitRequest, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
@@ -178,20 +178,13 @@ func (lister *Lister) Get(ctx context.Context) ([]types.InitRequest, error) {
return initRequestList, nil
}
-// WatchEvent represents the initrequest events.
-type WatchEvent struct {
- Type watch.EventType
- InitRequest *types.InitRequest
- Err error
-}
-
// Watch looks for changes in InitRequestList and reports them.
-func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), error) {
+func (lister *InitRequestLister) Watch(ctx context.Context) (<-chan WatchEvent[*types.InitRequest], func(), error) {
labelMap := map[directpvtypes.LabelKey][]directpvtypes.LabelValue{
directpvtypes.NodeLabelKey: lister.nodes,
directpvtypes.RequestIDLabelKey: lister.requestIDs,
}
- initRequestWatchInterface, err := client.InitRequestClient().Watch(ctx, metav1.ListOptions{
+ initRequestWatchInterface, err := lister.initRequestClient.Watch(ctx, metav1.ListOptions{
LabelSelector: directpvtypes.ToLabelSelector(labelMap),
})
if err != nil {
@@ -199,7 +192,7 @@ func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), err
}
stopFn := initRequestWatchInterface.Stop
- watchCh := make(chan WatchEvent)
+ watchCh := make(chan WatchEvent[*types.InitRequest])
go func() {
defer close(watchCh)
resultCh := initRequestWatchInterface.ResultChan()
@@ -212,7 +205,7 @@ func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), err
var initRequest types.InitRequest
err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, &initRequest)
if err != nil {
- watchCh <- WatchEvent{
+ watchCh <- WatchEvent[*types.InitRequest]{
Type: result.Type,
Err: fmt.Errorf("unable to convert unstructured object %s; %v", unstructured.GetName(), err),
}
@@ -221,9 +214,9 @@ func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), err
if len(lister.initRequestNames) > 0 && !utils.Contains(lister.initRequestNames, initRequest.Name) {
continue
}
- watchCh <- WatchEvent{
- Type: result.Type,
- InitRequest: &initRequest,
+ watchCh <- WatchEvent[*types.InitRequest]{
+ Type: result.Type,
+ Item: &initRequest,
}
}
}()
diff --git a/pkg/node/list.go b/pkg/client/node_lister.go
similarity index 78%
rename from pkg/node/list.go
rename to pkg/client/node_lister.go
index 1af5d920..9ce5edac 100644
--- a/pkg/node/list.go
+++ b/pkg/client/node_lister.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package node
+package client
import (
"context"
@@ -22,7 +22,6 @@ import (
"fmt"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -37,47 +36,49 @@ type ListNodeResult struct {
Err error
}
-// Lister is node lister.
-type Lister struct {
+// NodeLister is node lister.
+type NodeLister struct {
nodes []directpvtypes.LabelValue
nodeNames []string
maxObjects int64
ignoreNotFound bool
+ nodeClient types.LatestNodeInterface
}
-// NewLister creates new volume lister.
-func NewLister() *Lister {
- return &Lister{
+// NewNodeLister creates new volume lister.
+func (client Client) NewNodeLister() *NodeLister {
+ return &NodeLister{
maxObjects: k8s.MaxThreadCount,
+ nodeClient: client.Node(),
}
}
// NodeSelector adds filter listing by nodes.
-func (lister *Lister) NodeSelector(nodes []directpvtypes.LabelValue) *Lister {
+func (lister *NodeLister) NodeSelector(nodes []directpvtypes.LabelValue) *NodeLister {
lister.nodes = nodes
return lister
}
// NodeNameSelector adds filter listing by node names.
-func (lister *Lister) NodeNameSelector(nodeNames []string) *Lister {
+func (lister *NodeLister) NodeNameSelector(nodeNames []string) *NodeLister {
lister.nodeNames = nodeNames
return lister
}
// MaxObjects controls number of items to be fetched in every iteration.
-func (lister *Lister) MaxObjects(n int64) *Lister {
+func (lister *NodeLister) MaxObjects(n int64) *NodeLister {
lister.maxObjects = n
return lister
}
// IgnoreNotFound controls listing to ignore node not found error.
-func (lister *Lister) IgnoreNotFound(b bool) *Lister {
+func (lister *NodeLister) IgnoreNotFound(b bool) *NodeLister {
lister.ignoreNotFound = b
return lister
}
// List returns channel to loop through node items.
-func (lister *Lister) List(ctx context.Context) <-chan ListNodeResult {
+func (lister *NodeLister) List(ctx context.Context) <-chan ListNodeResult {
getOnly := len(lister.nodes) == 0 &&
len(lister.nodeNames) != 0
@@ -105,7 +106,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListNodeResult {
LabelSelector: labelSelector,
}
for {
- result, err := client.NodeClient().List(ctx, options)
+ result, err := lister.nodeClient.List(ctx, options)
if err != nil {
send(ListNodeResult{Err: err})
return
@@ -139,7 +140,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListNodeResult {
}
for _, nodeName := range lister.nodeNames {
- node, err := client.NodeClient().Get(ctx, nodeName, metav1.GetOptions{})
+ node, err := lister.nodeClient.Get(ctx, nodeName, metav1.GetOptions{})
if err != nil {
send(ListNodeResult{Err: err})
return
@@ -154,7 +155,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListNodeResult {
}
// Get returns list of nodes.
-func (lister *Lister) Get(ctx context.Context) ([]types.Node, error) {
+func (lister *NodeLister) Get(ctx context.Context) ([]types.Node, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
@@ -169,22 +170,22 @@ func (lister *Lister) Get(ctx context.Context) ([]types.Node, error) {
return nodeList, nil
}
-// WatchEvent represents the node events.
-type WatchEvent struct {
+// WatchEvent represents the events used in the Lister.
+type WatchEvent[I any] struct {
Type watch.EventType
- Node *types.Node
+ Item I
Err error
}
// Watch looks for changes in NodeList and reports them.
-func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), error) {
+func (lister *NodeLister) Watch(ctx context.Context) (<-chan WatchEvent[*types.Node], func(), error) {
if len(lister.nodeNames) > 0 {
return nil, nil, errors.New("unsupported selector")
}
labelMap := map[directpvtypes.LabelKey][]directpvtypes.LabelValue{
directpvtypes.NodeLabelKey: lister.nodes,
}
- nodeWatchInterface, err := client.NodeClient().Watch(ctx, metav1.ListOptions{
+ nodeWatchInterface, err := lister.nodeClient.Watch(ctx, metav1.ListOptions{
LabelSelector: directpvtypes.ToLabelSelector(labelMap),
})
if err != nil {
@@ -192,7 +193,7 @@ func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), err
}
stopFn := nodeWatchInterface.Stop
- watchCh := make(chan WatchEvent)
+ watchCh := make(chan WatchEvent[*types.Node])
go func() {
defer close(watchCh)
resultCh := nodeWatchInterface.ResultChan()
@@ -205,15 +206,15 @@ func (lister *Lister) Watch(ctx context.Context) (<-chan WatchEvent, func(), err
var node types.Node
err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, &node)
if err != nil {
- watchCh <- WatchEvent{
+ watchCh <- WatchEvent[*types.Node]{
Type: result.Type,
Err: fmt.Errorf("unable to convert unstructured object %s; %v", unstructured.GetName(), err),
}
continue
}
- watchCh <- WatchEvent{
+ watchCh <- WatchEvent[*types.Node]{
Type: result.Type,
- Node: &node,
+ Item: &node,
}
}
}()
diff --git a/pkg/volume/list.go b/pkg/client/volume_lister.go
similarity index 76%
rename from pkg/volume/list.go
rename to pkg/client/volume_lister.go
index 2ac5e518..cf9aadf9 100644
--- a/pkg/volume/list.go
+++ b/pkg/client/volume_lister.go
@@ -1,5 +1,5 @@
// This file is part of MinIO DirectPV
-// Copyright (c) 2021, 2022 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -14,13 +14,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package volume
+package client
import (
"context"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
- "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/k8s"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
@@ -34,8 +33,8 @@ type ListVolumeResult struct {
Err error
}
-// Lister is volume lister.
-type Lister struct {
+// VolumeLister is volume lister.
+type VolumeLister struct {
nodes []directpvtypes.LabelValue
driveNames []directpvtypes.LabelValue
driveIDs []directpvtypes.LabelValue
@@ -46,77 +45,79 @@ type Lister struct {
labels map[directpvtypes.LabelKey]directpvtypes.LabelValue
maxObjects int64
ignoreNotFound bool
+ volumeClient types.LatestVolumeInterface
}
-// NewLister creates new volume lister.
-func NewLister() *Lister {
- return &Lister{
- maxObjects: k8s.MaxThreadCount,
+// NewVolumeLister creates new volume lister.
+func (client Client) NewVolumeLister() *VolumeLister {
+ return &VolumeLister{
+ maxObjects: k8s.MaxThreadCount,
+ volumeClient: client.Volume(),
}
}
// NodeSelector adds filter listing by nodes.
-func (lister *Lister) NodeSelector(nodes []directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) NodeSelector(nodes []directpvtypes.LabelValue) *VolumeLister {
lister.nodes = nodes
return lister
}
// DriveNameSelector adds filter listing by drive names.
-func (lister *Lister) DriveNameSelector(driveNames []directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) DriveNameSelector(driveNames []directpvtypes.LabelValue) *VolumeLister {
lister.driveNames = driveNames
return lister
}
// DriveIDSelector adds filter listing by drive IDs.
-func (lister *Lister) DriveIDSelector(driveIDs []directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) DriveIDSelector(driveIDs []directpvtypes.LabelValue) *VolumeLister {
lister.driveIDs = driveIDs
return lister
}
// PodNameSelector adds filter listing by pod names.
-func (lister *Lister) PodNameSelector(podNames []directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) PodNameSelector(podNames []directpvtypes.LabelValue) *VolumeLister {
lister.podNames = podNames
return lister
}
// PodNSSelector adds filter listing by pod namespaces.
-func (lister *Lister) PodNSSelector(podNSs []directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) PodNSSelector(podNSs []directpvtypes.LabelValue) *VolumeLister {
lister.podNSs = podNSs
return lister
}
// StatusSelector adds filter listing by volume status.
-func (lister *Lister) StatusSelector(statusList []directpvtypes.VolumeStatus) *Lister {
+func (lister *VolumeLister) StatusSelector(statusList []directpvtypes.VolumeStatus) *VolumeLister {
lister.statusList = statusList
return lister
}
// VolumeNameSelector adds filter listing by volume names.
-func (lister *Lister) VolumeNameSelector(volumeNames []string) *Lister {
+func (lister *VolumeLister) VolumeNameSelector(volumeNames []string) *VolumeLister {
lister.volumeNames = volumeNames
return lister
}
// LabelSelector adds filter listing by labels.
-func (lister *Lister) LabelSelector(labels map[directpvtypes.LabelKey]directpvtypes.LabelValue) *Lister {
+func (lister *VolumeLister) LabelSelector(labels map[directpvtypes.LabelKey]directpvtypes.LabelValue) *VolumeLister {
lister.labels = labels
return lister
}
// MaxObjects controls number of items to be fetched in every iteration.
-func (lister *Lister) MaxObjects(n int64) *Lister {
+func (lister *VolumeLister) MaxObjects(n int64) *VolumeLister {
lister.maxObjects = n
return lister
}
// IgnoreNotFound controls listing to ignore drive not found error.
-func (lister *Lister) IgnoreNotFound(b bool) *Lister {
+func (lister *VolumeLister) IgnoreNotFound(b bool) *VolumeLister {
lister.ignoreNotFound = b
return lister
}
// List returns channel to loop through volume items.
-func (lister *Lister) List(ctx context.Context) <-chan ListVolumeResult {
+func (lister *VolumeLister) List(ctx context.Context) <-chan ListVolumeResult {
getOnly := len(lister.nodes) == 0 &&
len(lister.driveNames) == 0 &&
len(lister.driveIDs) == 0 &&
@@ -158,7 +159,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListVolumeResult {
}
for {
- result, err := client.VolumeClient().List(ctx, options)
+ result, err := lister.volumeClient.List(ctx, options)
if err != nil {
if apierrors.IsNotFound(err) && lister.ignoreNotFound {
break
@@ -196,7 +197,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListVolumeResult {
}
for _, volumeName := range lister.volumeNames {
- volume, err := client.VolumeClient().Get(ctx, volumeName, metav1.GetOptions{})
+ volume, err := lister.volumeClient.Get(ctx, volumeName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) && lister.ignoreNotFound {
continue
@@ -215,7 +216,7 @@ func (lister *Lister) List(ctx context.Context) <-chan ListVolumeResult {
}
// Get returns list of volumes.
-func (lister *Lister) Get(ctx context.Context) ([]types.Volume, error) {
+func (lister *VolumeLister) Get(ctx context.Context) ([]types.Volume, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
diff --git a/pkg/volume/list_test.go b/pkg/client/volume_lister_test.go
similarity index 78%
rename from pkg/volume/list_test.go
rename to pkg/client/volume_lister_test.go
index d5e43c62..529df48c 100644
--- a/pkg/volume/list_test.go
+++ b/pkg/client/volume_lister_test.go
@@ -14,14 +14,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-package volume
+package client
import (
"context"
"fmt"
"testing"
- "github.com/minio/directpv/pkg/client"
clientsetfake "github.com/minio/directpv/pkg/clientset/fake"
"github.com/minio/directpv/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,10 +29,10 @@ import (
func TestGetVolumeList(t *testing.T) {
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- client.SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
- volumes, err := NewLister().Get(context.TODO())
+ volumes, err := client.NewVolumeLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -49,10 +48,10 @@ func TestGetVolumeList(t *testing.T) {
}
clientset = types.NewExtFakeClientset(clientsetfake.NewSimpleClientset(objects...))
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- client.SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
- volumes, err = NewLister().Get(context.TODO())
+ volumes, err = client.NewVolumeLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -63,10 +62,10 @@ func TestGetVolumeList(t *testing.T) {
func TestGetSortedVolumeList(t *testing.T) {
clientset := types.NewExtFakeClientset(clientsetfake.NewSimpleClientset())
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- client.SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
- volumes, err := NewLister().Get(context.TODO())
+ volumes, err := client.NewVolumeLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -92,10 +91,10 @@ func TestGetSortedVolumeList(t *testing.T) {
}
clientset = types.NewExtFakeClientset(clientsetfake.NewSimpleClientset(objects...))
- client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- client.SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
+ SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
+ SetVolumeInterface(clientset.DirectpvLatest().DirectPVVolumes())
- volumes, err = NewLister().Get(context.TODO())
+ volumes, err = client.NewVolumeLister().Get(context.TODO())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/pkg/csi/controller/server_test.go b/pkg/csi/controller/server_test.go
index 72ea13f6..eba3ef8d 100644
--- a/pkg/csi/controller/server_test.go
+++ b/pkg/csi/controller/server_test.go
@@ -27,7 +27,6 @@ import (
"github.com/minio/directpv/pkg/client"
clientsetfake "github.com/minio/directpv/pkg/clientset/fake"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
"github.com/minio/directpv/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -177,7 +176,7 @@ func TestCreateAndDeleteVolumeRPCs(t *testing.T) {
// Fetch the drive objects
client.SetDriveInterface(clientset.DirectpvLatest().DirectPVDrives())
- driveList, err := drive.NewLister().Get(ctx)
+ driveList, err := client.NewDriveLister().Get(ctx)
if err != nil {
t.Errorf("Listing drives failed: %v", err)
}
diff --git a/pkg/csi/controller/utils.go b/pkg/csi/controller/utils.go
index cd370f7c..059380ca 100644
--- a/pkg/csi/controller/utils.go
+++ b/pkg/csi/controller/utils.go
@@ -25,8 +25,8 @@ import (
"github.com/container-storage-interface/spec/lib/go/csi"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
+ "github.com/minio/directpv/pkg/client"
"github.com/minio/directpv/pkg/consts"
- "github.com/minio/directpv/pkg/drive"
"github.com/minio/directpv/pkg/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -107,7 +107,7 @@ func getFilteredDrives(ctx context.Context, req *csi.CreateVolumeRequest) (drive
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
- for result := range drive.NewLister().List(ctx) {
+ for result := range client.NewDriveLister().List(ctx) {
if result.Err != nil {
return nil, result.Err
}
diff --git a/pkg/csi/node/server_test.go b/pkg/csi/node/server_test.go
index 51db5b2f..0e68f4ac 100644
--- a/pkg/csi/node/server_test.go
+++ b/pkg/csi/node/server_test.go
@@ -27,6 +27,10 @@ import (
"github.com/minio/directpv/pkg/xfs"
)
+func init() {
+ client.FakeInit()
+}
+
func TestNodeExpandVolume(t *testing.T) {
volumeID := "volume-id-1"
volume := types.NewVolume(volumeID, "fsuuid1", "node-1", "drive-1", "sda", 100*MiB)
diff --git a/pkg/device/sync.go b/pkg/device/sync.go
index 4a14c6ef..c4915f2f 100644
--- a/pkg/device/sync.go
+++ b/pkg/device/sync.go
@@ -24,7 +24,6 @@ import (
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/minio/directpv/pkg/client"
- "github.com/minio/directpv/pkg/drive"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/minio/directpv/pkg/xfs"
@@ -220,7 +219,7 @@ func Sync(ctx context.Context, nodeID directpvtypes.NodeID) error {
if err != nil {
return err
}
- drives, err := drive.NewLister().NodeSelector([]directpvtypes.LabelValue{directpvtypes.ToLabelValue(string(nodeID))}).Get(ctx)
+ drives, err := client.NewDriveLister().NodeSelector([]directpvtypes.LabelValue{directpvtypes.ToLabelValue(string(nodeID))}).Get(ctx)
if err != nil {
return err
}
diff --git a/pkg/k8s/clients.go b/pkg/k8s/clients.go
index 372d5e09..0ade6408 100644
--- a/pkg/k8s/clients.go
+++ b/pkg/k8s/clients.go
@@ -27,30 +27,31 @@ import (
const MaxThreadCount = 200
var (
- initialized int32
- kubeConfig *rest.Config
- kubeClient kubernetes.Interface
- apiextensionsClient apiextensions.ApiextensionsV1Interface
- crdClient apiextensions.CustomResourceDefinitionInterface
- discoveryClient discovery.DiscoveryInterface
+ initialized int32
+ client *Client
)
+// GetClient returns kubernetes client.
+func GetClient() *Client {
+ return client
+}
+
// KubeConfig gets kubernetes client configuration.
func KubeConfig() *rest.Config {
- return kubeConfig
+ return client.KubeConfig
}
// KubeClient gets kubernetes client.
func KubeClient() kubernetes.Interface {
- return kubeClient
+ return client.KubeClient
}
// CRDClient gets kubernetes CRD client.
func CRDClient() apiextensions.CustomResourceDefinitionInterface {
- return crdClient
+ return client.CRDClient
}
// DiscoveryClient gets kubernetes discovery client.
func DiscoveryClient() discovery.DiscoveryInterface {
- return discoveryClient
+ return client.DiscoveryClient
}
diff --git a/pkg/k8s/fake.go b/pkg/k8s/fake.go
index cbbf1e0a..4917c0d9 100644
--- a/pkg/k8s/fake.go
+++ b/pkg/k8s/fake.go
@@ -47,28 +47,34 @@ func (fd *FakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*meta
// FakeInit initializes fake clients.
func FakeInit() {
+ var kubeClient kubernetes.Interface
kubeClient = kubernetesfake.NewSimpleClientset()
- crdClient = &apiextensionsv1fake.FakeCustomResourceDefinitions{
+ crdClient := &apiextensionsv1fake.FakeCustomResourceDefinitions{
Fake: &apiextensionsv1fake.FakeApiextensionsV1{
Fake: &kubeClient.(*kubernetesfake.Clientset).Fake,
},
}
- discoveryClient = &discoveryfake.FakeDiscovery{}
+ discoveryClient := &discoveryfake.FakeDiscovery{}
scheme := runtime.NewScheme()
_ = metav1.AddMetaToScheme(scheme)
+ client = &Client{
+ KubeClient: kubeClient,
+ CRDClient: crdClient,
+ DiscoveryClient: discoveryClient,
+ }
}
// SetKubeInterface sets the given kube interface
// Note: To be used for writing test cases only
func SetKubeInterface(i kubernetes.Interface) {
- kubeClient = i
+ client.KubeClient = i
}
-// SetDiscoveryInterface sets the fake discovery interface
+// NewFakeDiscovery creates a fake discovery interface
// Note: To be used for writing test cases only
-func SetDiscoveryInterface(groupsAndMethodsFn fakeServerGroupsAndResourcesMethod, serverVersionInfo *version.Info) {
- discoveryClient = &FakeDiscovery{
- FakeDiscovery: discoveryfake.FakeDiscovery{Fake: &kubeClient.(*kubernetesfake.Clientset).Fake},
+func NewFakeDiscovery(groupsAndMethodsFn fakeServerGroupsAndResourcesMethod, serverVersionInfo *version.Info) *FakeDiscovery {
+ return &FakeDiscovery{
+ FakeDiscovery: discoveryfake.FakeDiscovery{Fake: &client.KubeClient.(*kubernetesfake.Clientset).Fake},
fakeServerGroupsAndResourcesMethod: groupsAndMethodsFn,
versionInfo: serverVersionInfo,
}
diff --git a/pkg/k8s/init.go b/pkg/k8s/init.go
index 17a7983c..f2ed2e8f 100644
--- a/pkg/k8s/init.go
+++ b/pkg/k8s/init.go
@@ -17,6 +17,9 @@
package k8s
import (
+ "fmt"
+ "strconv"
+ "strings"
"sync/atomic"
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
@@ -32,28 +35,80 @@ import (
)
// Init initializes various client interfaces.
-func Init() {
+func Init() error {
if atomic.AddInt32(&initialized, 1) != 1 {
- return
+ return nil
}
-
- var err error
-
- if kubeConfig, err = GetKubeConfig(); err != nil {
+ kubeConfig, err := GetKubeConfig()
+ if err != nil {
klog.Fatalf("unable to get kubernetes configuration; %v", err)
}
-
kubeConfig.WarningHandler = rest.NoWarnings{}
- if kubeClient, err = kubernetes.NewForConfig(kubeConfig); err != nil {
+ client, err = NewClient(kubeConfig)
+ if err != nil {
klog.Fatalf("unable to create new kubernetes client interface; %v", err)
}
+ return nil
+}
+
+// Client represents the kubernetes client set.
+type Client struct {
+ KubeConfig *rest.Config
+ KubeClient kubernetes.Interface
+ APIextensionsClient apiextensions.ApiextensionsV1Interface
+ CRDClient apiextensions.CustomResourceDefinitionInterface
+ DiscoveryClient discovery.DiscoveryInterface
+}
+
+// GetKubeVersion returns the k8s version info
+func (client Client) GetKubeVersion() (major, minor uint, err error) {
+ versionInfo, err := client.DiscoveryClient.ServerVersion()
+ if err != nil {
+ return 0, 0, err
+ }
- if apiextensionsClient, err = apiextensions.NewForConfig(kubeConfig); err != nil {
- klog.Fatalf("unable to create new API extensions client interface; %v", err)
+ var u64 uint64
+ if u64, err = strconv.ParseUint(versionInfo.Major, 10, 64); err != nil {
+ return 0, 0, fmt.Errorf("unable to parse major version %v; %v", versionInfo.Major, err)
}
- crdClient = apiextensionsClient.CustomResourceDefinitions()
+ major = uint(u64)
- if discoveryClient, err = discovery.NewDiscoveryClientForConfig(kubeConfig); err != nil {
- klog.Fatalf("unable to create new discovery client interface; %v", err)
+ minorString := versionInfo.Minor
+ if strings.Contains(versionInfo.GitVersion, "-eks-") {
+ // Do trimming only for EKS.
+ // Refer https://github.com/aws/containers-roadmap/issues/1404
+ i := strings.IndexFunc(minorString, func(r rune) bool { return r < '0' || r > '9' })
+ if i > -1 {
+ minorString = minorString[:i]
+ }
+ }
+ if u64, err = strconv.ParseUint(minorString, 10, 64); err != nil {
+ return 0, 0, fmt.Errorf("unable to parse minor version %v; %v", minor, err)
+ }
+ minor = uint(u64)
+ return major, minor, nil
+}
+
+// NewClient initializes the client with the provided kube config.
+func NewClient(kubeConfig *rest.Config) (*Client, error) {
+ kubeClient, err := kubernetes.NewForConfig(kubeConfig)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new kubernetes client interface; %v", err)
+ }
+ apiextensionsClient, err := apiextensions.NewForConfig(kubeConfig)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new API extensions client interface; %v", err)
+ }
+ crdClient := apiextensionsClient.CustomResourceDefinitions()
+ discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeConfig)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new discovery client interface; %v", err)
}
+ return &Client{
+ KubeConfig: kubeConfig,
+ KubeClient: kubeClient,
+ APIextensionsClient: apiextensionsClient,
+ CRDClient: crdClient,
+ DiscoveryClient: discoveryClient,
+ }, nil
}
diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go
index b33d360b..d93fc874 100644
--- a/pkg/k8s/k8s.go
+++ b/pkg/k8s/k8s.go
@@ -17,12 +17,19 @@
package k8s
import (
+ "context"
+ "fmt"
"os"
"path"
"strings"
+ "time"
+ "github.com/minio/directpv/pkg/consts"
"github.com/minio/directpv/pkg/utils"
"github.com/spf13/viper"
+ corev1 "k8s.io/api/core/v1"
+ storagev1 "k8s.io/api/storage/v1"
+ storagev1beta1 "k8s.io/api/storage/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
@@ -57,8 +64,8 @@ func GetKubeConfig() (*rest.Config, error) {
}
// GetGroupVersionKind gets group/version/kind of given versions.
-func GetGroupVersionKind(group, kind string, versions ...string) (*schema.GroupVersionKind, error) {
- apiGroupResources, err := restmapper.GetAPIGroupResources(discoveryClient)
+func (client Client) GetGroupVersionKind(group, kind string, versions ...string) (*schema.GroupVersionKind, error) {
+ apiGroupResources, err := restmapper.GetAPIGroupResources(client.DiscoveryClient)
if err != nil {
klog.ErrorS(err, "unable to get API group resources")
return nil, err
@@ -83,13 +90,13 @@ func GetGroupVersionKind(group, kind string, versions ...string) (*schema.GroupV
}
// GetClientForNonCoreGroupVersionKind gets client for group/kind of given versions.
-func GetClientForNonCoreGroupVersionKind(group, kind string, versions ...string) (rest.Interface, *schema.GroupVersionKind, error) {
- gvk, err := GetGroupVersionKind(group, kind, versions...)
+func (client Client) GetClientForNonCoreGroupVersionKind(group, kind string, versions ...string) (rest.Interface, *schema.GroupVersionKind, error) {
+ gvk, err := client.GetGroupVersionKind(group, kind, versions...)
if err != nil {
return nil, nil, err
}
- config := *kubeConfig
+ config := *client.KubeConfig
config.GroupVersion = &schema.GroupVersion{
Group: gvk.Group,
Version: gvk.Version,
@@ -100,12 +107,12 @@ func GetClientForNonCoreGroupVersionKind(group, kind string, versions ...string)
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
- client, err := rest.RESTClientFor(&config)
+ restClient, err := rest.RESTClientFor(&config)
if err != nil {
return nil, nil, err
}
- return client, gvk, nil
+ return restClient, gvk, nil
}
// IsCondition checks whether type/status/reason/message in conditions or not.
@@ -190,3 +197,128 @@ func SanitizeResourceName(name string) string {
return string(result)
}
+
+// GetCSINodes fetches the CSI Node list
+func (client Client) GetCSINodes(ctx context.Context) (nodes []string, err error) {
+ storageClient, gvk, err := client.GetClientForNonCoreGroupVersionKind("storage.k8s.io", "CSINode", "v1", "v1beta1", "v1alpha1")
+ if err != nil {
+ return nil, err
+ }
+
+ switch gvk.Version {
+ case "v1apha1":
+ err = fmt.Errorf("unsupported CSINode storage.k8s.io/v1alpha1")
+ case "v1":
+ result := &storagev1.CSINodeList{}
+ if err = storageClient.Get().
+ Resource("csinodes").
+ VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec).
+ Timeout(10 * time.Second).
+ Do(ctx).
+ Into(result); err != nil {
+ err = fmt.Errorf("unable to get csinodes; %w", err)
+ break
+ }
+ for _, csiNode := range result.Items {
+ for _, driver := range csiNode.Spec.Drivers {
+ if driver.Name == consts.Identity {
+ nodes = append(nodes, csiNode.Name)
+ break
+ }
+ }
+ }
+ case "v1beta1":
+ result := &storagev1beta1.CSINodeList{}
+ if err = storageClient.Get().
+ Resource(gvk.Kind).
+ VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec).
+ Timeout(10 * time.Second).
+ Do(ctx).
+ Into(result); err != nil {
+ err = fmt.Errorf("unable to get csinodes; %w", err)
+ break
+ }
+ for _, csiNode := range result.Items {
+ for _, driver := range csiNode.Spec.Drivers {
+ if driver.Name == consts.Identity {
+ nodes = append(nodes, csiNode.Name)
+ break
+ }
+ }
+ }
+ }
+
+ return nodes, err
+}
+
+// ParseNodeSelector parses the provided node selector values
+func ParseNodeSelector(values []string) (map[string]string, error) {
+ nodeSelector := map[string]string{}
+ for _, value := range values {
+ tokens := strings.Split(value, "=")
+ if len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid node selector value %v", value)
+ }
+ if tokens[0] == "" {
+ return nil, fmt.Errorf("invalid key in node selector value %v", value)
+ }
+ nodeSelector[tokens[0]] = tokens[1]
+ }
+ return nodeSelector, nil
+}
+
+// ParseTolerations parses the provided toleration values
+func ParseTolerations(values []string) ([]corev1.Toleration, error) {
+ var tolerations []corev1.Toleration
+ for _, value := range values {
+ var k, v, e string
+ tokens := strings.SplitN(value, "=", 2)
+ switch len(tokens) {
+ case 1:
+ k = tokens[0]
+ tokens = strings.Split(k, ":")
+ switch len(tokens) {
+ case 1:
+ case 2:
+ k, e = tokens[0], tokens[1]
+ default:
+ if len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid toleration %v", value)
+ }
+ }
+ case 2:
+ k, v = tokens[0], tokens[1]
+ default:
+ if len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid toleration %v", value)
+ }
+ }
+ if k == "" {
+ return nil, fmt.Errorf("invalid key in toleration %v", value)
+ }
+ if v != "" {
+ if tokens = strings.Split(v, ":"); len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid value in toleration %v", value)
+ }
+ v, e = tokens[0], tokens[1]
+ }
+ effect := corev1.TaintEffect(e)
+ switch effect {
+ case corev1.TaintEffectNoSchedule, corev1.TaintEffectPreferNoSchedule, corev1.TaintEffectNoExecute:
+ default:
+ return nil, fmt.Errorf("invalid toleration effect in toleration %v", value)
+ }
+ operator := corev1.TolerationOpExists
+ if v != "" {
+ operator = corev1.TolerationOpEqual
+ }
+ tolerations = append(tolerations, corev1.Toleration{
+ Key: k,
+ Operator: operator,
+ Value: v,
+ Effect: effect,
+ })
+ }
+
+ return tolerations, nil
+}
diff --git a/pkg/k8s/k8s_test.go b/pkg/k8s/k8s_test.go
index d0ad8068..9dde4b14 100644
--- a/pkg/k8s/k8s_test.go
+++ b/pkg/k8s/k8s_test.go
@@ -21,8 +21,75 @@ import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/version"
)
+func init() {
+ FakeInit()
+}
+
+var (
+ apiGroups = []*metav1.APIGroup{
+ {
+ Name: "policy",
+ Versions: []metav1.GroupVersionForDiscovery{
+ {
+ GroupVersion: "policy/v1beta1",
+ Version: "v1beta1",
+ },
+ },
+ },
+ {
+ Name: "storage.k8s.io",
+ Versions: []metav1.GroupVersionForDiscovery{
+ {
+ GroupVersion: "storage.k8s.io/v1",
+ Version: "v1",
+ },
+ },
+ },
+ }
+
+ apiResourceList = []*metav1.APIResourceList{
+ {
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: "policy/v1beta1",
+ Kind: "PodSecurityPolicy",
+ },
+ GroupVersion: "policy/v1beta1",
+ APIResources: []metav1.APIResource{
+ {
+ Name: "policy",
+ Group: "policy",
+ Version: "v1beta1",
+ Namespaced: false,
+ Kind: "PodSecurityPolicy",
+ },
+ },
+ },
+ {
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: "storage.k8s.io/v1",
+ Kind: "CSIDriver",
+ },
+ GroupVersion: "storage.k8s.io/v1",
+ APIResources: []metav1.APIResource{
+ {
+ Name: "CSIDriver",
+ Group: "storage.k8s.io",
+ Version: "v1",
+ Namespaced: false,
+ Kind: "CSIDriver",
+ },
+ },
+ },
+ }
+)
+
+func getDiscoveryGroupsAndMethods() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
+ return apiGroups, apiResourceList, nil
+}
+
func TestVolumeStatusTransitions(t *testing.T) {
statusList := []metav1.Condition{
{
@@ -73,3 +140,50 @@ func TestVolumeStatusTransitions(t *testing.T) {
})
}
}
+
+func TestGetKubeVersion(t *testing.T) {
+ testCases := []struct {
+ info version.Info
+ major uint
+ minor uint
+ expectErr bool
+ }{
+ {version.Info{Major: "a", Minor: "0"}, 0, 0, true}, // invalid major
+ {version.Info{Major: "-1", Minor: "0"}, 0, 0, true}, // invalid major
+ {version.Info{Major: "0", Minor: "a"}, 0, 0, true}, // invalid minor
+ {version.Info{Major: "0", Minor: "-1"}, 0, 0, true}, // invalid minor
+ {version.Info{Major: "0", Minor: "-1", GitVersion: "commit-eks-id"}, 0, 0, true}, // invalid minor for eks
+ {version.Info{Major: "0", Minor: "incompat", GitVersion: "commit-eks-"}, 0, 0, true}, // invalid minor for eks
+ {version.Info{Major: "0", Minor: "0"}, 0, 0, false},
+ {version.Info{Major: "1", Minor: "0"}, 1, 0, false},
+ {version.Info{Major: "0", Minor: "1"}, 0, 1, false},
+ {version.Info{Major: "1", Minor: "18"}, 1, 18, false},
+ {version.Info{Major: "1", Minor: "18+", GitVersion: "commit-eks-id"}, 1, 18, false},
+ {version.Info{Major: "1", Minor: "18-", GitVersion: "commit-eks-id"}, 1, 18, false},
+ {version.Info{Major: "1", Minor: "18incompat", GitVersion: "commit-eks-id"}, 1, 18, false},
+ {version.Info{Major: "1", Minor: "18-incompat", GitVersion: "commit-eks-id"}, 1, 18, false},
+ }
+
+ for i, testCase := range testCases {
+ client.DiscoveryClient = NewFakeDiscovery(getDiscoveryGroupsAndMethods, &testCase.info)
+ major, minor, err := client.GetKubeVersion()
+ if testCase.expectErr {
+ if err == nil {
+ t.Fatalf("case %v: expected error, but succeeded", i+1)
+ }
+ continue
+ }
+
+ if err != nil {
+ t.Fatalf("case %v: unexpected error: %v", i+1, err)
+ }
+
+ if major != testCase.major {
+ t.Fatalf("case %v: major: expected: %v, got: %v", i+1, testCase.major, major)
+ }
+
+ if minor != testCase.minor {
+ t.Fatalf("case %v: minor: expected: %v, got: %v", i+1, testCase.minor, minor)
+ }
+ }
+}
diff --git a/pkg/legacy/client/client.go b/pkg/legacy/client/client.go
index 4eb52ece..2629ed0c 100644
--- a/pkg/legacy/client/client.go
+++ b/pkg/legacy/client/client.go
@@ -17,21 +17,46 @@
package client
import (
+ "context"
+ "fmt"
+ "os"
+
"github.com/minio/directpv/pkg/k8s"
directcsi "github.com/minio/directpv/pkg/legacy/apis/direct.csi.min.io/v1beta5"
+ directv1beta5 "github.com/minio/directpv/pkg/legacy/apis/direct.csi.min.io/v1beta5"
typeddirectcsi "github.com/minio/directpv/pkg/legacy/clientset/typed/direct.csi.min.io/v1beta5"
+ "github.com/minio/directpv/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/client-go/restmapper"
- "k8s.io/klog/v2"
+ "k8s.io/client-go/discovery"
)
var (
- initialized int32
- driveClient typeddirectcsi.DirectCSIDriveInterface
- volumeClient typeddirectcsi.DirectCSIVolumeInterface
+ initialized int32
+ client *Client
)
+// Client represents the legacy client
+type Client struct {
+ DriveClient typeddirectcsi.DirectCSIDriveInterface
+ VolumeClient typeddirectcsi.DirectCSIVolumeInterface
+ K8sClient *k8s.Client
+}
+
+// Discovery returns the discovery client
+func (client Client) Discovery() discovery.DiscoveryInterface {
+ return client.K8sClient.DiscoveryClient
+}
+
+// Drive returns the legacy drive client
+func (client Client) Drive() typeddirectcsi.DirectCSIDriveInterface {
+ return client.DriveClient
+}
+
+// Volume returns the volume client
+func (client Client) Volume() typeddirectcsi.DirectCSIVolumeInterface {
+ return client.VolumeClient
+}
+
// DirectCSI group and identity names.
const (
GroupName = "direct.csi.min.io"
@@ -57,38 +82,99 @@ func DirectCSIVolumeTypeMeta() metav1.TypeMeta {
}
}
-// GetGroupKindVersions gets group/version/kind of given versions.
-func GetGroupKindVersions(group, kind string, versions ...string) (*schema.GroupVersionKind, error) {
- apiGroupResources, err := restmapper.GetAPIGroupResources(k8s.DiscoveryClient())
- if err != nil {
- klog.V(3).Infof("could not obtain API group resources: %v", err)
- return nil, err
+// DriveClient gets latest versioned drive interface.
+func DriveClient() typeddirectcsi.DirectCSIDriveInterface {
+ return client.DriveClient
+}
+
+// VolumeClient gets latest versioned volume interface.
+func VolumeClient() typeddirectcsi.DirectCSIVolumeInterface {
+ return client.VolumeClient
+}
+
+// GetClient returns the client
+func GetClient() *Client {
+ return client
+}
+
+// RemoveAllDrives removes legacy drive CRDs.
+func (client Client) RemoveAllDrives(ctx context.Context, backupFile string) (backupCreated bool, err error) {
+ var drives []directv1beta5.DirectCSIDrive
+ for result := range client.ListDrives(ctx) {
+ if result.Err != nil {
+ return false, fmt.Errorf("unable to get legacy drives; %w", result.Err)
+ }
+ drives = append(drives, result.Drive)
}
- restMapper := restmapper.NewDiscoveryRESTMapper(apiGroupResources)
- gk := schema.GroupKind{
- Group: group,
- Kind: kind,
+ if len(drives) == 0 {
+ return false, nil
}
- mapper, err := restMapper.RESTMapping(gk, versions...)
+
+ data, err := utils.ToYAML(directv1beta5.DirectCSIDriveList{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "List",
+ APIVersion: "v1",
+ },
+ Items: drives,
+ })
if err != nil {
- klog.V(3).Infof("could not find valid restmapping: %v", err)
- return nil, err
+ return false, fmt.Errorf("unable to generate legacy drives YAML; %w", err)
}
- gvk := &schema.GroupVersionKind{
- Group: mapper.Resource.Group,
- Version: mapper.Resource.Version,
- Kind: mapper.Resource.Resource,
+ if err = os.WriteFile(backupFile, data, os.ModePerm); err != nil {
+ return false, fmt.Errorf("unable to write legacy drives YAML; %w", err)
}
- return gvk, nil
-}
-// DriveClient gets latest versioned drive interface.
-func DriveClient() typeddirectcsi.DirectCSIDriveInterface {
- return driveClient
+ for _, drive := range drives {
+ drive.Finalizers = []string{}
+ if _, err := client.Drive().Update(ctx, &drive, metav1.UpdateOptions{}); err != nil {
+ return false, fmt.Errorf("unable to update legacy drive %v; %w", drive.Name, err)
+ }
+ if err := client.Drive().Delete(ctx, drive.Name, metav1.DeleteOptions{}); err != nil {
+ return false, fmt.Errorf("unable to remove legacy drive %v; %w", drive.Name, err)
+ }
+ }
+
+ return true, nil
}
-// VolumeClient gets latest versioned volume interface.
-func VolumeClient() typeddirectcsi.DirectCSIVolumeInterface {
- return volumeClient
+// RemoveAllVolumes removes legacy volume CRDs.
+func (client Client) RemoveAllVolumes(ctx context.Context, backupFile string) (backupCreated bool, err error) {
+ var volumes []directv1beta5.DirectCSIVolume
+ for result := range client.ListVolumes(ctx) {
+ if result.Err != nil {
+ return false, fmt.Errorf("unable to get legacy volumes; %w", result.Err)
+ }
+ volumes = append(volumes, result.Volume)
+ }
+ if len(volumes) == 0 {
+ return false, nil
+ }
+
+ data, err := utils.ToYAML(directv1beta5.DirectCSIVolumeList{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "List",
+ APIVersion: "v1",
+ },
+ Items: volumes,
+ })
+ if err != nil {
+ return false, fmt.Errorf("unable to generate legacy volumes YAML; %w", err)
+ }
+
+ if err = os.WriteFile(backupFile, data, os.ModePerm); err != nil {
+ return false, fmt.Errorf("unable to write legacy volumes YAML; %w", err)
+ }
+
+ for _, volume := range volumes {
+ volume.Finalizers = nil
+ if _, err := client.Volume().Update(ctx, &volume, metav1.UpdateOptions{}); err != nil {
+ return false, fmt.Errorf("unable to update legacy volume %v; %w", volume.Name, err)
+ }
+ if err := client.Volume().Delete(ctx, volume.Name, metav1.DeleteOptions{}); err != nil {
+ return false, fmt.Errorf("unable to remove legacy volume %v; %w", volume.Name, err)
+ }
+ }
+
+ return true, nil
}
diff --git a/pkg/legacy/client/fake.go b/pkg/legacy/client/fake.go
index c2f89246..9e949930 100644
--- a/pkg/legacy/client/fake.go
+++ b/pkg/legacy/client/fake.go
@@ -24,20 +24,21 @@ import (
// FakeInit initializes fake clients.
func FakeInit() {
k8s.FakeInit()
-
fakeClientset := legacyclientsetfake.NewSimpleClientset()
- driveClient = fakeClientset.DirectV1beta5().DirectCSIDrives()
- volumeClient = fakeClientset.DirectV1beta5().DirectCSIVolumes()
+ client = &Client{
+ DriveClient: fakeClientset.DirectV1beta5().DirectCSIDrives(),
+ VolumeClient: fakeClientset.DirectV1beta5().DirectCSIVolumes(),
+ }
}
// SetDriveClient sets drive interface from fake clientset.
// Note: To be used for writing test cases only
func SetDriveClient(clientset *legacyclientsetfake.Clientset) {
- driveClient = clientset.DirectV1beta5().DirectCSIDrives()
+ client.DriveClient = clientset.DirectV1beta5().DirectCSIDrives()
}
// SetVolumeClient sets volume interface from fake clientset.
// Note: To be used for writing test cases only
func SetVolumeClient(clientset *legacyclientsetfake.Clientset) {
- volumeClient = clientset.DirectV1beta5().DirectCSIVolumes()
+ client.VolumeClient = clientset.DirectV1beta5().DirectCSIVolumes()
}
diff --git a/pkg/legacy/client/init.go b/pkg/legacy/client/init.go
index acee6ef1..3140b421 100644
--- a/pkg/legacy/client/init.go
+++ b/pkg/legacy/client/init.go
@@ -17,6 +17,7 @@
package client
import (
+ "fmt"
"sync/atomic"
"github.com/minio/directpv/pkg/k8s"
@@ -28,15 +29,29 @@ func Init() {
if atomic.AddInt32(&initialized, 1) != 1 {
return
}
-
- k8s.Init()
-
var err error
- if driveClient, err = DirectCSIDriveInterfaceForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new DirectCSI drive interface; %v", err)
+ if err = k8s.Init(); err != nil {
+ klog.Fatalf("unable to initialize k8s clients; %v", err)
+ }
+ client, err = NewClient(k8s.GetClient())
+ if err != nil {
+ klog.Fatalf("unable to create legacy client; %v", err)
}
+}
- if volumeClient, err = DirectCSIVolumeInterfaceForConfig(k8s.KubeConfig()); err != nil {
- klog.Fatalf("unable to create new DirectCSI volume interface; %v", err)
+// NewClient creates a legacy client
+func NewClient(k8sClient *k8s.Client) (*Client, error) {
+ driveClient, err := DirectCSIDriveInterfaceForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new DirectCSI drive interface; %v", err)
+ }
+ volumeClient, err := DirectCSIVolumeInterfaceForConfig(k8sClient)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create new DirectCSI volume interface; %v", err)
}
+ return &Client{
+ DriveClient: driveClient,
+ VolumeClient: volumeClient,
+ K8sClient: k8sClient,
+ }, nil
}
diff --git a/pkg/legacy/client/interface.go b/pkg/legacy/client/interface.go
index 34fae095..d35ea75e 100644
--- a/pkg/legacy/client/interface.go
+++ b/pkg/legacy/client/interface.go
@@ -42,8 +42,8 @@ import (
)
// GetGroupVersion probes group and version of given resource kind.
-func GetGroupVersion(kind string) (version, group string, err error) {
- gvk, err := GetGroupKindVersions(
+func GetGroupVersion(k8sClient *k8s.Client, kind string) (version, group string, err error) {
+ gvk, err := k8sClient.GetGroupVersionKind(
directcsi.Group,
kind,
directcsi.Version,
@@ -56,7 +56,6 @@ func GetGroupVersion(kind string) (version, group string, err error) {
if err != nil && !meta.IsNoMatchError(err) {
return "", "", err
}
-
version = directcsi.Version
if gvk != nil {
version = gvk.Version
@@ -65,13 +64,12 @@ func GetGroupVersion(kind string) (version, group string, err error) {
if gvk != nil {
group = gvk.Group
}
-
return version, group, nil
}
// GetLatestDirectCSIRESTClient gets REST client of the latest direct-csi.
-func GetLatestDirectCSIRESTClient() rest.Interface {
- directClientset, err := clientset.NewForConfig(k8s.KubeConfig())
+func GetLatestDirectCSIRESTClient(k8sClient *k8s.Client) rest.Interface {
+ directClientset, err := clientset.NewForConfig(k8sClient.KubeConfig)
if err != nil {
panic(err)
}
@@ -100,13 +98,13 @@ type directCSIInterface struct {
groupVersion schema.GroupVersion
}
-func directCSIInterfaceForConfig(config *rest.Config, kind, resource string) (*directCSIInterface, error) {
- version, group, err := GetGroupVersion(kind)
+func directCSIInterfaceForConfig(k8sClient *k8s.Client, kind, resource string) (*directCSIInterface, error) {
+ version, group, err := GetGroupVersion(k8sClient, kind)
if err != nil {
return nil, err
}
- resourceInterface, err := dynamic.NewForConfig(config)
+ resourceInterface, err := dynamic.NewForConfig(k8sClient.KubeConfig)
if err != nil {
return nil, err
}
@@ -284,8 +282,8 @@ type DirectCSIDriveInterface struct {
}
// DirectCSIDriveInterfaceForConfig provides a dynamic client interface for DirectCSIDrives
-func DirectCSIDriveInterfaceForConfig(config *rest.Config) (*DirectCSIDriveInterface, error) {
- inter, err := directCSIInterfaceForConfig(config, "DirectCSIDrive", "directcsidrives")
+func DirectCSIDriveInterfaceForConfig(k8sClient *k8s.Client) (*DirectCSIDriveInterface, error) {
+ inter, err := directCSIInterfaceForConfig(k8sClient, "DirectCSIDrive", "directcsidrives")
if err != nil {
return nil, err
}
@@ -398,8 +396,8 @@ type DirectCSIVolumeInterface struct {
}
// DirectCSIVolumeInterfaceForConfig provides a dynamic client interface for DirectCSIVolumes
-func DirectCSIVolumeInterfaceForConfig(config *rest.Config) (*DirectCSIVolumeInterface, error) {
- inter, err := directCSIInterfaceForConfig(config, "DirectCSIVolume", "directcsivolumes")
+func DirectCSIVolumeInterfaceForConfig(k8sClient *k8s.Client) (*DirectCSIVolumeInterface, error) {
+ inter, err := directCSIInterfaceForConfig(k8sClient, "DirectCSIVolume", "directcsivolumes")
if err != nil {
return nil, err
}
diff --git a/pkg/legacy/client/list.go b/pkg/legacy/client/list.go
index 66b6cced..76fcefaf 100644
--- a/pkg/legacy/client/list.go
+++ b/pkg/legacy/client/list.go
@@ -31,7 +31,7 @@ type ListDriveResult struct {
}
// ListDrives returns channel to loop through drive items.
-func ListDrives(ctx context.Context) <-chan ListDriveResult {
+func (client Client) ListDrives(ctx context.Context) <-chan ListDriveResult {
resultCh := make(chan ListDriveResult)
go func() {
defer close(resultCh)
@@ -47,7 +47,7 @@ func ListDrives(ctx context.Context) <-chan ListDriveResult {
options := metav1.ListOptions{Limit: 1000}
for {
- result, err := DriveClient().List(ctx, options)
+ result, err := client.Drive().List(ctx, options)
if err != nil {
if !apierrors.IsNotFound(err) {
send(ListDriveResult{Err: err})
@@ -79,7 +79,7 @@ type ListVolumeResult struct {
}
// ListVolumes returns channel to loop through volume items.
-func ListVolumes(ctx context.Context) <-chan ListVolumeResult {
+func (client Client) ListVolumes(ctx context.Context) <-chan ListVolumeResult {
resultCh := make(chan ListVolumeResult)
go func() {
defer close(resultCh)
@@ -95,7 +95,7 @@ func ListVolumes(ctx context.Context) <-chan ListVolumeResult {
options := metav1.ListOptions{Limit: 1000}
for {
- result, err := VolumeClient().List(ctx, options)
+ result, err := client.Volume().List(ctx, options)
if err != nil {
if !apierrors.IsNotFound(err) {
send(ListVolumeResult{Err: err})
diff --git a/pkg/metrics/collector.go b/pkg/metrics/collector.go
index d717c45a..2567eabd 100644
--- a/pkg/metrics/collector.go
+++ b/pkg/metrics/collector.go
@@ -24,7 +24,6 @@ import (
"github.com/minio/directpv/pkg/consts"
"github.com/minio/directpv/pkg/sys"
"github.com/minio/directpv/pkg/types"
- "github.com/minio/directpv/pkg/volume"
"github.com/minio/directpv/pkg/xfs"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/klog/v2"
@@ -101,7 +100,7 @@ func (c *metricsCollector) Collect(ch chan<- prometheus.Metric) {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
- resultCh := volume.NewLister().
+ resultCh := client.NewVolumeLister().
NodeSelector([]directpvtypes.LabelValue{directpvtypes.ToLabelValue(string(c.nodeID))}).
List(ctx)
for result := range resultCh {
diff --git a/pkg/metrics/collector_test.go b/pkg/metrics/collector_test.go
index 3dfeecba..b30a4295 100644
--- a/pkg/metrics/collector_test.go
+++ b/pkg/metrics/collector_test.go
@@ -55,6 +55,7 @@ func init() {
volumes[0].Status.TargetPath = "/path/targetpath"
volumes[1].Status.UsedCapacity = 20 * MiB
volumes[1].Status.TargetPath = "/path/targetpath"
+ client.FakeInit()
}
func createFakeMetricsCollector() *metricsCollector {
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 0e12f0b1..94b8ca42 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -25,6 +25,7 @@ import (
"strings"
"github.com/fatih/color"
+ directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"sigs.k8s.io/yaml"
)
@@ -153,3 +154,11 @@ func Eprintf(quiet, asErr bool, format string, a ...any) {
}
fmt.Fprintf(os.Stderr, format, a...)
}
+
+// ToLabelValues converts a string list to label values
+func ToLabelValues(slice []string) (values []directpvtypes.LabelValue) {
+ for _, s := range slice {
+ values = append(values, directpvtypes.ToLabelValue(s))
+ }
+ return
+}
diff --git a/pkg/volume/event_test.go b/pkg/volume/event_test.go
index f11f7d0a..754f10a6 100644
--- a/pkg/volume/event_test.go
+++ b/pkg/volume/event_test.go
@@ -30,6 +30,10 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+func init() {
+ client.FakeInit()
+}
+
const MiB = 1024 * 1024
func createFakeVolumeEventListener(nodeID directpvtypes.NodeID) *volumeEventHandler {