Skip to content

Commit

Permalink
Add volume commands (#645)
Browse files Browse the repository at this point in the history
add volume commands

Signed-off-by: Bala.FA <bala@minio.io>

Signed-off-by: Bala.FA <bala@minio.io>
  • Loading branch information
balamurugana committed Sep 27, 2022
1 parent 0cda5cb commit d6e1faa
Show file tree
Hide file tree
Showing 12 changed files with 774 additions and 14 deletions.
11 changes: 8 additions & 3 deletions cmd/kubectl-directpv/main.go
Expand Up @@ -50,12 +50,17 @@ var (
jsonOutput = false
yamlOutput = false
noHeaders = false
allFlag = false
)

var (
drives, nodes, driveGlobs, nodeGlobs []string
driveSelectorValues, nodeSelectorValues []types.LabelValue
printer func(interface{}) error
driveArgs []string
nodeArgs []string

driveSelectors []types.LabelValue
nodeSelectors []types.LabelValue

printer func(interface{}) error
)

var mainCmd = &cobra.Command{
Expand Down
151 changes: 151 additions & 0 deletions cmd/kubectl-directpv/utils.go
Expand Up @@ -17,17 +17,27 @@
package main

import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"time"

"github.com/dustin/go-humanize"
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"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/minio/directpv/pkg/volume"
"github.com/mitchellh/go-homedir"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
)

const dot = "•"
Expand Down Expand Up @@ -138,3 +148,144 @@ func openAuditFile(auditFile string) (*utils.SafeFile, error) {
}
return utils.NewSafeFile(path.Join(defaultAuditDir, fmt.Sprintf("%v.%v", auditFile, time.Now().UnixNano())))
}

func printableString(s string) string {
if s == "" {
return "-"
}
return s
}

func printableBytes(value int64) string {
if value == 0 {
return "-"
}

return humanize.IBytes(uint64(value))
}

func getLabelValue(obj metav1.Object, key string) string {
if labels := obj.GetLabels(); labels != nil {
return labels[key]
}
return ""
}

func matchVolumeStatus(volume types.Volume, statusList []string) bool {
return k8s.MatchTrueConditions(
volume.Status.Conditions,
[]string{string(directpvtypes.VolumeConditionTypePublished), string(directpvtypes.VolumeConditionTypeStaged)},
statusList,
)
}

func getFilteredVolumeList(ctx context.Context, filterFunc func(types.Volume) bool) ([]types.Volume, error) {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()

resultCh, err := volume.ListVolumes(
ctx,
nodeSelectors,
driveSelectors,
podNameSelectors,
podNSSelectors,
k8s.MaxThreadCount,
)
if err != nil {
return nil, err
}

filteredVolumes := []types.Volume{}
for result := range resultCh {
if result.Err != nil {
return nil, result.Err
}
if matchVolumeStatus(result.Volume, volumeStatusSelectors) && filterFunc(result.Volume) {
filteredVolumes = append(filteredVolumes, result.Volume)
}
}

return filteredVolumes, nil
}

func getVolumesByNames(ctx context.Context, names []string) <-chan volume.ListVolumeResult {
resultCh := make(chan volume.ListVolumeResult)
go func() {
defer close(resultCh)
for _, name := range names {
volumeName := strings.TrimSpace(name)
vol, err := client.VolumeClient().Get(ctx, volumeName, metav1.GetOptions{})
switch {
case err == nil:
resultCh <- volume.ListVolumeResult{Volume: *vol}
case apierrors.IsNotFound(err):
klog.V(5).Infof("No volume found by name %v", volumeName)
default:
klog.ErrorS(err, "unable to get volume", "volumeName", volumeName)
return
}
}
}()
return resultCh
}

func processFilteredVolumes(
ctx context.Context,
names []string,
matchFunc func(*types.Volume) bool,
applyFunc func(*types.Volume) error,
processFunc func(context.Context, *types.Volume) error,
auditFile string,
) error {
var resultCh <-chan volume.ListVolumeResult
var err error

if applyFunc == nil || processFunc == nil {
klog.Fatalf("Either applyFunc or processFunc must be provided. This should not happen.")
}

ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()

if len(names) == 0 {
resultCh, err = volume.ListVolumes(ctx,
nodeSelectors,
driveSelectors,
podNameSelectors,
podNSSelectors,
k8s.MaxThreadCount)
if err != nil {
return err
}
} else {
resultCh = getVolumesByNames(ctx, names)
}

file, err := openAuditFile(auditFile)
if err != nil {
klog.ErrorS(err, "unable to open audit file", "auditFile", auditFile)
}

defer func() {
if file != nil {
if err := file.Close(); err != nil {
klog.ErrorS(err, "unable to close audit file")
}
}
}()

return volume.ProcessVolumes(
ctx,
resultCh,
func(volume *types.Volume) bool {
if matchVolumeStatus(*volume, volumeStatusSelectors) {
return matchFunc == nil || matchFunc(volume)
}
return false
},
applyFunc,
processFunc,
file,
dryRun,
)
}
151 changes: 151 additions & 0 deletions cmd/kubectl-directpv/volumes.go
@@ -0,0 +1,151 @@
// 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 <http://www.gnu.org/licenses/>.

package main

import (
"errors"
"fmt"
"regexp"
"strings"

directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
"github.com/minio/directpv/pkg/ellipsis"
"github.com/minio/directpv/pkg/types"
"github.com/minio/directpv/pkg/utils"
"github.com/spf13/cobra"
)

var (
volumeStatusArgs []string
podNameArgs []string
podNSArgs []string

volumeStatusSelectors []string
podNameSelectors []types.LabelValue
podNSSelectors []types.LabelValue
)

var volumesCmd = &cobra.Command{
Use: "volumes",
Short: "Manage DirectPV Volumes",
Aliases: []string{
"volume",
"vol",
},
}

func init() {
volumesCmd.AddCommand(listVolumesCmd)
volumesCmd.AddCommand(purgeVolumesCmd)
}

var (
globRegexp = regexp.MustCompile(`(^|[^\\])[\*\?\[]`)
errGlobPatternUnsupported = errors.New("glob patterns are unsupported")
)

func getSelectorValues(selectors []string) (values []types.LabelValue, err error) {
for _, selector := range selectors {
if globRegexp.MatchString(selector) {
return nil, errGlobPatternUnsupported
}

result, err := ellipsis.Expand(selector)
if err != nil {
return nil, err
}

for _, value := range result {
values = append(values, types.NewLabelValue(value))
}
}

return values, nil
}

func getDriveSelectors() ([]types.LabelValue, error) {
var values []string
for i := range driveArgs {
if utils.TrimDevPrefix(driveArgs[i]) == "" {
return nil, fmt.Errorf("empty device name %v", driveArgs[i])
}
values = append(values, utils.TrimDevPrefix(driveArgs[i]))
}
return getSelectorValues(values)
}

func getNodeSelectors() ([]types.LabelValue, error) {
for i := range nodeArgs {
if utils.TrimDevPrefix(nodeArgs[i]) == "" {
return nil, fmt.Errorf("empty node name %v", nodeArgs[i])
}
}
return getSelectorValues(nodeArgs)
}

func getPodNameSelectors() ([]types.LabelValue, error) {
for i := range podNameArgs {
if utils.TrimDevPrefix(podNameArgs[i]) == "" {
return nil, fmt.Errorf("empty pod name %v", podNameArgs[i])
}
}
return getSelectorValues(podNameArgs)
}

func getPodNamespaceSelectors() ([]types.LabelValue, error) {
for i := range podNSArgs {
if utils.TrimDevPrefix(podNSArgs[i]) == "" {
return nil, fmt.Errorf("empty pod namespace %v", podNSArgs[i])
}
}
return getSelectorValues(podNSArgs)
}

func getVolumeStatusSelectors() ([]string, error) {
for _, status := range volumeStatusArgs {
switch directpvtypes.VolumeConditionType(strings.Title(status)) {
case directpvtypes.VolumeConditionTypePublished:
case directpvtypes.VolumeConditionTypeStaged:
case directpvtypes.VolumeConditionTypeReady:
default:
return nil, fmt.Errorf("unknown volume condition type %v", status)
}
}
return volumeStatusArgs, nil
}

func validateVolumeSelectors() (err error) {
if driveSelectors, err = getDriveSelectors(); err != nil {
return err
}

if nodeSelectors, err = getNodeSelectors(); err != nil {
return err
}

if volumeStatusSelectors, err = getVolumeStatusSelectors(); err != nil {
return err
}

if podNameSelectors, err = getPodNameSelectors(); err != nil {
return err
}

podNSSelectors, err = getPodNamespaceSelectors()

return err
}

0 comments on commit d6e1faa

Please sign in to comment.