/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package csi import ( "context" "errors" "fmt" "io" "net" "time" csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" api "k8s.io/api/core/v1" utilversion "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog" "k8s.io/kubernetes/pkg/features" csipbv0 "k8s.io/kubernetes/pkg/volume/csi/csiv0" ) type csiClient interface { NodeGetInfo(ctx context.Context) ( nodeID string, maxVolumePerNode int64, accessibleTopology map[string]string, err error) NodePublishVolume( ctx context.Context, volumeid string, readOnly bool, stagingTargetPath string, targetPath string, accessMode api.PersistentVolumeAccessMode, publishContext map[string]string, volumeContext map[string]string, secrets map[string]string, fsType string, mountOptions []string, ) error NodeUnpublishVolume( ctx context.Context, volID string, targetPath string, ) error NodeStageVolume(ctx context.Context, volID string, publishVolumeInfo map[string]string, stagingTargetPath string, fsType string, accessMode api.PersistentVolumeAccessMode, secrets map[string]string, volumeContext map[string]string, ) error NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error NodeSupportsStageUnstage(ctx context.Context) (bool, error) } // Strongly typed address type csiAddr string // Strongly typed driver name type csiDriverName string // csiClient encapsulates all csi-plugin methods type csiDriverClient struct { driverName csiDriverName addr csiAddr nodeV1ClientCreator nodeV1ClientCreator nodeV0ClientCreator nodeV0ClientCreator } var _ csiClient = &csiDriverClient{} type nodeV1ClientCreator func(addr csiAddr) ( nodeClient csipbv1.NodeClient, closer io.Closer, err error, ) type nodeV0ClientCreator func(addr csiAddr) ( nodeClient csipbv0.NodeClient, closer io.Closer, err error, ) // newV1NodeClient creates a new NodeClient with the internally used gRPC // connection set up. It also returns a closer which must to be called to close // the gRPC connection when the NodeClient is not used anymore. // This is the default implementation for the nodeV1ClientCreator, used in // newCsiDriverClient. func newV1NodeClient(addr csiAddr) (nodeClient csipbv1.NodeClient, closer io.Closer, err error) { var conn *grpc.ClientConn conn, err = newGrpcConn(addr) if err != nil { return nil, nil, err } nodeClient = csipbv1.NewNodeClient(conn) return nodeClient, conn, nil } // newV0NodeClient creates a new NodeClient with the internally used gRPC // connection set up. It also returns a closer which must to be called to close // the gRPC connection when the NodeClient is not used anymore. // This is the default implementation for the nodeV1ClientCreator, used in // newCsiDriverClient. func newV0NodeClient(addr csiAddr) (nodeClient csipbv0.NodeClient, closer io.Closer, err error) { var conn *grpc.ClientConn conn, err = newGrpcConn(addr) if err != nil { return nil, nil, err } nodeClient = csipbv0.NewNodeClient(conn) return nodeClient, conn, nil } func newCsiDriverClient(driverName csiDriverName) (*csiDriverClient, error) { if driverName == "" { return nil, fmt.Errorf("driver name is empty") } addr := fmt.Sprintf(csiAddrTemplate, driverName) requiresV0Client := true if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPluginsWatcher) { var existingDriver csiDriver driverExists := false func() { csiDrivers.RLock() defer csiDrivers.RUnlock() existingDriver, driverExists = csiDrivers.driversMap[string(driverName)] }() if !driverExists { return nil, fmt.Errorf("driver name %s not found in the list of registered CSI drivers", driverName) } addr = existingDriver.driverEndpoint requiresV0Client = versionRequiresV0Client(existingDriver.highestSupportedVersion) } nodeV1ClientCreator := newV1NodeClient nodeV0ClientCreator := newV0NodeClient if requiresV0Client { nodeV1ClientCreator = nil } else { nodeV0ClientCreator = nil } return &csiDriverClient{ driverName: driverName, addr: csiAddr(addr), nodeV1ClientCreator: nodeV1ClientCreator, nodeV0ClientCreator: nodeV0ClientCreator, }, nil } func (c *csiDriverClient) NodeGetInfo(ctx context.Context) ( nodeID string, maxVolumePerNode int64, accessibleTopology map[string]string, err error) { klog.V(4).Info(log("calling NodeGetInfo rpc")) if c.nodeV1ClientCreator != nil { return c.nodeGetInfoV1(ctx) } else if c.nodeV0ClientCreator != nil { return c.nodeGetInfoV0(ctx) } err = fmt.Errorf("failed to call NodeGetInfo. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") return nodeID, maxVolumePerNode, accessibleTopology, err } func (c *csiDriverClient) nodeGetInfoV1(ctx context.Context) ( nodeID string, maxVolumePerNode int64, accessibleTopology map[string]string, err error) { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return "", 0, nil, err } defer closer.Close() res, err := nodeClient.NodeGetInfo(ctx, &csipbv1.NodeGetInfoRequest{}) if err != nil { return "", 0, nil, err } topology := res.GetAccessibleTopology() if topology != nil { accessibleTopology = topology.Segments } return res.GetNodeId(), res.GetMaxVolumesPerNode(), accessibleTopology, nil } func (c *csiDriverClient) nodeGetInfoV0(ctx context.Context) ( nodeID string, maxVolumePerNode int64, accessibleTopology map[string]string, err error) { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return "", 0, nil, err } defer closer.Close() res, err := nodeClient.NodeGetInfo(ctx, &csipbv0.NodeGetInfoRequest{}) if err != nil { return "", 0, nil, err } topology := res.GetAccessibleTopology() if topology != nil { accessibleTopology = topology.Segments } return res.GetNodeId(), res.GetMaxVolumesPerNode(), accessibleTopology, nil } func (c *csiDriverClient) NodePublishVolume( ctx context.Context, volID string, readOnly bool, stagingTargetPath string, targetPath string, accessMode api.PersistentVolumeAccessMode, publishContext map[string]string, volumeContext map[string]string, secrets map[string]string, fsType string, mountOptions []string, ) error { klog.V(4).Info(log("calling NodePublishVolume rpc [volid=%s,target_path=%s]", volID, targetPath)) if volID == "" { return errors.New("missing volume id") } if targetPath == "" { return errors.New("missing target path") } if c.nodeV1ClientCreator != nil { return c.nodePublishVolumeV1( ctx, volID, readOnly, stagingTargetPath, targetPath, accessMode, publishContext, volumeContext, secrets, fsType, mountOptions, ) } else if c.nodeV0ClientCreator != nil { return c.nodePublishVolumeV0( ctx, volID, readOnly, stagingTargetPath, targetPath, accessMode, publishContext, volumeContext, secrets, fsType, mountOptions, ) } return fmt.Errorf("failed to call NodePublishVolume. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") } func (c *csiDriverClient) nodePublishVolumeV1( ctx context.Context, volID string, readOnly bool, stagingTargetPath string, targetPath string, accessMode api.PersistentVolumeAccessMode, publishContext map[string]string, volumeContext map[string]string, secrets map[string]string, fsType string, mountOptions []string, ) error { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv1.NodePublishVolumeRequest{ VolumeId: volID, TargetPath: targetPath, Readonly: readOnly, PublishContext: publishContext, VolumeContext: volumeContext, Secrets: secrets, VolumeCapability: &csipbv1.VolumeCapability{ AccessMode: &csipbv1.VolumeCapability_AccessMode{ Mode: asCSIAccessModeV1(accessMode), }, }, } if stagingTargetPath != "" { req.StagingTargetPath = stagingTargetPath } if fsType == fsTypeBlockName { req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Block{ Block: &csipbv1.VolumeCapability_BlockVolume{}, } } else { req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ Mount: &csipbv1.VolumeCapability_MountVolume{ FsType: fsType, MountFlags: mountOptions, }, } } _, err = nodeClient.NodePublishVolume(ctx, req) return err } func (c *csiDriverClient) nodePublishVolumeV0( ctx context.Context, volID string, readOnly bool, stagingTargetPath string, targetPath string, accessMode api.PersistentVolumeAccessMode, publishContext map[string]string, volumeContext map[string]string, secrets map[string]string, fsType string, mountOptions []string, ) error { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv0.NodePublishVolumeRequest{ VolumeId: volID, TargetPath: targetPath, Readonly: readOnly, PublishInfo: publishContext, VolumeAttributes: volumeContext, NodePublishSecrets: secrets, VolumeCapability: &csipbv0.VolumeCapability{ AccessMode: &csipbv0.VolumeCapability_AccessMode{ Mode: asCSIAccessModeV0(accessMode), }, }, } if stagingTargetPath != "" { req.StagingTargetPath = stagingTargetPath } if fsType == fsTypeBlockName { req.VolumeCapability.AccessType = &csipbv0.VolumeCapability_Block{ Block: &csipbv0.VolumeCapability_BlockVolume{}, } } else { req.VolumeCapability.AccessType = &csipbv0.VolumeCapability_Mount{ Mount: &csipbv0.VolumeCapability_MountVolume{ FsType: fsType, MountFlags: mountOptions, }, } } _, err = nodeClient.NodePublishVolume(ctx, req) return err } func (c *csiDriverClient) NodeUnpublishVolume(ctx context.Context, volID string, targetPath string) error { klog.V(4).Info(log("calling NodeUnpublishVolume rpc: [volid=%s, target_path=%s", volID, targetPath)) if volID == "" { return errors.New("missing volume id") } if targetPath == "" { return errors.New("missing target path") } if c.nodeV1ClientCreator != nil { return c.nodeUnpublishVolumeV1(ctx, volID, targetPath) } else if c.nodeV0ClientCreator != nil { return c.nodeUnpublishVolumeV0(ctx, volID, targetPath) } return fmt.Errorf("failed to call NodeUnpublishVolume. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") } func (c *csiDriverClient) nodeUnpublishVolumeV1(ctx context.Context, volID string, targetPath string) error { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv1.NodeUnpublishVolumeRequest{ VolumeId: volID, TargetPath: targetPath, } _, err = nodeClient.NodeUnpublishVolume(ctx, req) return err } func (c *csiDriverClient) nodeUnpublishVolumeV0(ctx context.Context, volID string, targetPath string) error { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv0.NodeUnpublishVolumeRequest{ VolumeId: volID, TargetPath: targetPath, } _, err = nodeClient.NodeUnpublishVolume(ctx, req) return err } func (c *csiDriverClient) NodeStageVolume(ctx context.Context, volID string, publishContext map[string]string, stagingTargetPath string, fsType string, accessMode api.PersistentVolumeAccessMode, secrets map[string]string, volumeContext map[string]string, ) error { klog.V(4).Info(log("calling NodeStageVolume rpc [volid=%s,staging_target_path=%s]", volID, stagingTargetPath)) if volID == "" { return errors.New("missing volume id") } if stagingTargetPath == "" { return errors.New("missing staging target path") } if c.nodeV1ClientCreator != nil { return c.nodeStageVolumeV1(ctx, volID, publishContext, stagingTargetPath, fsType, accessMode, secrets, volumeContext) } else if c.nodeV0ClientCreator != nil { return c.nodeStageVolumeV0(ctx, volID, publishContext, stagingTargetPath, fsType, accessMode, secrets, volumeContext) } return fmt.Errorf("failed to call NodeStageVolume. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") } func (c *csiDriverClient) nodeStageVolumeV1( ctx context.Context, volID string, publishContext map[string]string, stagingTargetPath string, fsType string, accessMode api.PersistentVolumeAccessMode, secrets map[string]string, volumeContext map[string]string, ) error { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv1.NodeStageVolumeRequest{ VolumeId: volID, PublishContext: publishContext, StagingTargetPath: stagingTargetPath, VolumeCapability: &csipbv1.VolumeCapability{ AccessMode: &csipbv1.VolumeCapability_AccessMode{ Mode: asCSIAccessModeV1(accessMode), }, }, Secrets: secrets, VolumeContext: volumeContext, } if fsType == fsTypeBlockName { req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Block{ Block: &csipbv1.VolumeCapability_BlockVolume{}, } } else { req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ Mount: &csipbv1.VolumeCapability_MountVolume{ FsType: fsType, }, } } _, err = nodeClient.NodeStageVolume(ctx, req) return err } func (c *csiDriverClient) nodeStageVolumeV0( ctx context.Context, volID string, publishContext map[string]string, stagingTargetPath string, fsType string, accessMode api.PersistentVolumeAccessMode, secrets map[string]string, volumeContext map[string]string, ) error { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv0.NodeStageVolumeRequest{ VolumeId: volID, PublishInfo: publishContext, StagingTargetPath: stagingTargetPath, VolumeCapability: &csipbv0.VolumeCapability{ AccessMode: &csipbv0.VolumeCapability_AccessMode{ Mode: asCSIAccessModeV0(accessMode), }, }, NodeStageSecrets: secrets, VolumeAttributes: volumeContext, } if fsType == fsTypeBlockName { req.VolumeCapability.AccessType = &csipbv0.VolumeCapability_Block{ Block: &csipbv0.VolumeCapability_BlockVolume{}, } } else { req.VolumeCapability.AccessType = &csipbv0.VolumeCapability_Mount{ Mount: &csipbv0.VolumeCapability_MountVolume{ FsType: fsType, }, } } _, err = nodeClient.NodeStageVolume(ctx, req) return err } func (c *csiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error { klog.V(4).Info(log("calling NodeUnstageVolume rpc [volid=%s,staging_target_path=%s]", volID, stagingTargetPath)) if volID == "" { return errors.New("missing volume id") } if stagingTargetPath == "" { return errors.New("missing staging target path") } if c.nodeV1ClientCreator != nil { return c.nodeUnstageVolumeV1(ctx, volID, stagingTargetPath) } else if c.nodeV0ClientCreator != nil { return c.nodeUnstageVolumeV0(ctx, volID, stagingTargetPath) } return fmt.Errorf("failed to call NodeUnstageVolume. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") } func (c *csiDriverClient) nodeUnstageVolumeV1(ctx context.Context, volID, stagingTargetPath string) error { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv1.NodeUnstageVolumeRequest{ VolumeId: volID, StagingTargetPath: stagingTargetPath, } _, err = nodeClient.NodeUnstageVolume(ctx, req) return err } func (c *csiDriverClient) nodeUnstageVolumeV0(ctx context.Context, volID, stagingTargetPath string) error { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return err } defer closer.Close() req := &csipbv0.NodeUnstageVolumeRequest{ VolumeId: volID, StagingTargetPath: stagingTargetPath, } _, err = nodeClient.NodeUnstageVolume(ctx, req) return err } func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) { klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage")) if c.nodeV1ClientCreator != nil { return c.nodeSupportsStageUnstageV1(ctx) } else if c.nodeV0ClientCreator != nil { return c.nodeSupportsStageUnstageV0(ctx) } return false, fmt.Errorf("failed to call NodeSupportsStageUnstage. Both nodeV1ClientCreator and nodeV0ClientCreator are nil") } func (c *csiDriverClient) nodeSupportsStageUnstageV1(ctx context.Context) (bool, error) { nodeClient, closer, err := c.nodeV1ClientCreator(c.addr) if err != nil { return false, err } defer closer.Close() req := &csipbv1.NodeGetCapabilitiesRequest{} resp, err := nodeClient.NodeGetCapabilities(ctx, req) if err != nil { return false, err } capabilities := resp.GetCapabilities() stageUnstageSet := false if capabilities == nil { return false, nil } for _, capability := range capabilities { if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME { stageUnstageSet = true } } return stageUnstageSet, nil } func (c *csiDriverClient) nodeSupportsStageUnstageV0(ctx context.Context) (bool, error) { nodeClient, closer, err := c.nodeV0ClientCreator(c.addr) if err != nil { return false, err } defer closer.Close() req := &csipbv0.NodeGetCapabilitiesRequest{} resp, err := nodeClient.NodeGetCapabilities(ctx, req) if err != nil { return false, err } capabilities := resp.GetCapabilities() stageUnstageSet := false if capabilities == nil { return false, nil } for _, capability := range capabilities { if capability.GetRpc().GetType() == csipbv0.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME { stageUnstageSet = true } } return stageUnstageSet, nil } func asCSIAccessModeV1(am api.PersistentVolumeAccessMode) csipbv1.VolumeCapability_AccessMode_Mode { switch am { case api.ReadWriteOnce: return csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER case api.ReadOnlyMany: return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY case api.ReadWriteMany: return csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER } return csipbv1.VolumeCapability_AccessMode_UNKNOWN } func asCSIAccessModeV0(am api.PersistentVolumeAccessMode) csipbv0.VolumeCapability_AccessMode_Mode { switch am { case api.ReadWriteOnce: return csipbv0.VolumeCapability_AccessMode_SINGLE_NODE_WRITER case api.ReadOnlyMany: return csipbv0.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY case api.ReadWriteMany: return csipbv0.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER } return csipbv0.VolumeCapability_AccessMode_UNKNOWN } func newGrpcConn(addr csiAddr) (*grpc.ClientConn, error) { network := "unix" klog.V(4).Infof(log("creating new gRPC connection for [%s://%s]", network, addr)) return grpc.Dial( string(addr), grpc.WithInsecure(), grpc.WithDialer(func(target string, timeout time.Duration) (net.Conn, error) { return net.Dial(network, target) }), ) } func versionRequiresV0Client(version *utilversion.Version) bool { if version != nil && version.Major() == 0 { return true } return false }