Bumping k8s dependencies to 1.13
This commit is contained in:
64
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/BUILD
generated
vendored
Normal file
64
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/BUILD
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["nodeinfomanager.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
|
||||
"//staging/src/k8s.io/csi-api/pkg/apis/csi/v1alpha1:go_default_library",
|
||||
"//vendor/github.com/container-storage-interface/spec/lib/go/csi/v0:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["nodeinfomanager_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/volume/testing:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||
"//staging/src/k8s.io/csi-api/pkg/apis/csi/v1alpha1:go_default_library",
|
||||
"//staging/src/k8s.io/csi-api/pkg/client/clientset/versioned/fake:go_default_library",
|
||||
"//vendor/github.com/container-storage-interface/spec/lib/go/csi/v0:go_default_library",
|
||||
],
|
||||
)
|
543
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go
generated
vendored
Normal file
543
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go
generated
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
Copyright 2018 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 nodeinfomanager includes internal functions used to add/delete labels to
|
||||
// kubernetes nodes for corresponding CSI drivers
|
||||
package nodeinfomanager // import "k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
csipb "github.com/container-storage-interface/spec/lib/go/csi/v0"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/util/retry"
|
||||
csiv1alpha1 "k8s.io/csi-api/pkg/apis/csi/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of node annotation that contains JSON map of driver names to node
|
||||
annotationKeyNodeID = "csi.volume.kubernetes.io/nodeid"
|
||||
)
|
||||
|
||||
var nodeKind = v1.SchemeGroupVersion.WithKind("Node")
|
||||
|
||||
// nodeInfoManager contains necessary common dependencies to update node info on both
|
||||
// the Node and CSINodeInfo objects.
|
||||
type nodeInfoManager struct {
|
||||
nodeName types.NodeName
|
||||
volumeHost volume.VolumeHost
|
||||
}
|
||||
|
||||
// If no updates is needed, the function must return the same Node object as the input.
|
||||
type nodeUpdateFunc func(*v1.Node) (newNode *v1.Node, updated bool, err error)
|
||||
|
||||
// Interface implements an interface for managing labels of a node
|
||||
type Interface interface {
|
||||
// Record in the cluster the given node information from the CSI driver with the given name.
|
||||
// Concurrent calls to AddNodeInfo() is allowed, but they should not be intertwined with calls
|
||||
// to other methods in this interface.
|
||||
AddNodeInfo(driverName string, driverNodeID string, maxVolumeLimit int64, topology *csipb.Topology) error
|
||||
|
||||
// Remove in the cluster node information from the CSI driver with the given name.
|
||||
// Concurrent calls to RemoveNodeInfo() is allowed, but they should not be intertwined with calls
|
||||
// to other methods in this interface.
|
||||
RemoveNodeInfo(driverName string) error
|
||||
}
|
||||
|
||||
// NewNodeInfoManager initializes nodeInfoManager
|
||||
func NewNodeInfoManager(
|
||||
nodeName types.NodeName,
|
||||
volumeHost volume.VolumeHost) Interface {
|
||||
return &nodeInfoManager{
|
||||
nodeName: nodeName,
|
||||
volumeHost: volumeHost,
|
||||
}
|
||||
}
|
||||
|
||||
// AddNodeInfo updates the node ID annotation in the Node object and CSIDrivers field in the
|
||||
// CSINodeInfo object. If the CSINodeInfo object doesn't yet exist, it will be created.
|
||||
// If multiple calls to AddNodeInfo() are made in parallel, some calls might receive Node or
|
||||
// CSINodeInfo update conflicts, which causes the function to retry the corresponding update.
|
||||
func (nim *nodeInfoManager) AddNodeInfo(driverName string, driverNodeID string, maxAttachLimit int64, topology *csipb.Topology) error {
|
||||
if driverNodeID == "" {
|
||||
return fmt.Errorf("error adding CSI driver node info: driverNodeID must not be empty")
|
||||
}
|
||||
|
||||
nodeUpdateFuncs := []nodeUpdateFunc{
|
||||
updateNodeIDInNode(driverName, driverNodeID),
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
|
||||
nodeUpdateFuncs = append(nodeUpdateFuncs, updateTopologyLabels(topology))
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.AttachVolumeLimit) {
|
||||
nodeUpdateFuncs = append(nodeUpdateFuncs, updateMaxAttachLimit(driverName, maxAttachLimit))
|
||||
}
|
||||
|
||||
err := nim.updateNode(nodeUpdateFuncs...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating Node object with CSI driver node info: %v", err)
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
|
||||
err = nim.updateCSINodeInfo(driverName, driverNodeID, topology)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating CSINodeInfo object with CSI driver node info: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNodeInfo removes the node ID annotation from the Node object and CSIDrivers field from the
|
||||
// CSINodeInfo object. If the CSINOdeInfo object contains no CSIDrivers, it will be deleted.
|
||||
// If multiple calls to RemoveNodeInfo() are made in parallel, some calls might receive Node or
|
||||
// CSINodeInfo update conflicts, which causes the function to retry the corresponding update.
|
||||
func (nim *nodeInfoManager) RemoveNodeInfo(driverName string) error {
|
||||
err := nim.removeCSINodeInfo(driverName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing CSI driver node info from CSINodeInfo object %v", err)
|
||||
}
|
||||
|
||||
err = nim.updateNode(
|
||||
removeMaxAttachLimit(driverName),
|
||||
removeNodeIDFromNode(driverName),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing CSI driver node info from Node object %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateNode repeatedly attempts to update the corresponding node object
|
||||
// which is modified by applying the given update functions sequentially.
|
||||
// Because updateFuncs are applied sequentially, later updateFuncs should take into account
|
||||
// the effects of previous updateFuncs to avoid potential conflicts. For example, if multiple
|
||||
// functions update the same field, updates in the last function are persisted.
|
||||
func (nim *nodeInfoManager) updateNode(updateFuncs ...nodeUpdateFunc) error {
|
||||
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
// Retrieve the latest version of Node before attempting update, so that
|
||||
// existing changes are not overwritten. RetryOnConflict uses
|
||||
// exponential backoff to avoid exhausting the apiserver.
|
||||
|
||||
kubeClient := nim.volumeHost.GetKubeClient()
|
||||
if kubeClient == nil {
|
||||
return fmt.Errorf("error getting kube client")
|
||||
}
|
||||
|
||||
nodeClient := kubeClient.CoreV1().Nodes()
|
||||
node, err := nodeClient.Get(string(nim.nodeName), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
needUpdate := false
|
||||
for _, update := range updateFuncs {
|
||||
newNode, updated, err := update(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node = newNode
|
||||
needUpdate = needUpdate || updated
|
||||
}
|
||||
|
||||
if needUpdate {
|
||||
_, updateErr := nodeClient.Update(node)
|
||||
return updateErr // do not wrap error
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if retryErr != nil {
|
||||
return fmt.Errorf("node update failed: %v", retryErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Guarantees the map is non-nil if no error is returned.
|
||||
func buildNodeIDMapFromAnnotation(node *v1.Node) (map[string]string, error) {
|
||||
var previousAnnotationValue string
|
||||
if node.ObjectMeta.Annotations != nil {
|
||||
previousAnnotationValue =
|
||||
node.ObjectMeta.Annotations[annotationKeyNodeID]
|
||||
}
|
||||
|
||||
var existingDriverMap map[string]string
|
||||
if previousAnnotationValue != "" {
|
||||
// Parse previousAnnotationValue as JSON
|
||||
if err := json.Unmarshal([]byte(previousAnnotationValue), &existingDriverMap); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to parse node's %q annotation value (%q) err=%v",
|
||||
annotationKeyNodeID,
|
||||
previousAnnotationValue,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
if existingDriverMap == nil {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
return existingDriverMap, nil
|
||||
}
|
||||
|
||||
// updateNodeIDInNode returns a function that updates a Node object with the given
|
||||
// Node ID information.
|
||||
func updateNodeIDInNode(
|
||||
csiDriverName string,
|
||||
csiDriverNodeID string) nodeUpdateFunc {
|
||||
return func(node *v1.Node) (*v1.Node, bool, error) {
|
||||
existingDriverMap, err := buildNodeIDMapFromAnnotation(node)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if val, ok := existingDriverMap[csiDriverName]; ok {
|
||||
if val == csiDriverNodeID {
|
||||
// Value already exists in node annotation, nothing more to do
|
||||
return node, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add/update annotation value
|
||||
existingDriverMap[csiDriverName] = csiDriverNodeID
|
||||
jsonObj, err := json.Marshal(existingDriverMap)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf(
|
||||
"error while marshalling node ID map updated with driverName=%q, nodeID=%q: %v",
|
||||
csiDriverName,
|
||||
csiDriverNodeID,
|
||||
err)
|
||||
}
|
||||
|
||||
if node.ObjectMeta.Annotations == nil {
|
||||
node.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
node.ObjectMeta.Annotations[annotationKeyNodeID] = string(jsonObj)
|
||||
|
||||
return node, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// removeNodeIDFromNode returns a function that removes node ID information matching the given
|
||||
// driver name from a Node object.
|
||||
func removeNodeIDFromNode(csiDriverName string) nodeUpdateFunc {
|
||||
return func(node *v1.Node) (*v1.Node, bool, error) {
|
||||
var previousAnnotationValue string
|
||||
if node.ObjectMeta.Annotations != nil {
|
||||
previousAnnotationValue =
|
||||
node.ObjectMeta.Annotations[annotationKeyNodeID]
|
||||
}
|
||||
|
||||
if previousAnnotationValue == "" {
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
// Parse previousAnnotationValue as JSON
|
||||
existingDriverMap := map[string]string{}
|
||||
if err := json.Unmarshal([]byte(previousAnnotationValue), &existingDriverMap); err != nil {
|
||||
return nil, false, fmt.Errorf(
|
||||
"failed to parse node's %q annotation value (%q) err=%v",
|
||||
annotationKeyNodeID,
|
||||
previousAnnotationValue,
|
||||
err)
|
||||
}
|
||||
|
||||
if _, ok := existingDriverMap[csiDriverName]; !ok {
|
||||
// Value is already missing in node annotation, nothing more to do
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
// Delete annotation value
|
||||
delete(existingDriverMap, csiDriverName)
|
||||
if len(existingDriverMap) == 0 {
|
||||
delete(node.ObjectMeta.Annotations, annotationKeyNodeID)
|
||||
} else {
|
||||
jsonObj, err := json.Marshal(existingDriverMap)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf(
|
||||
"failed while trying to remove key %q from node %q annotation. Existing data: %v",
|
||||
csiDriverName,
|
||||
annotationKeyNodeID,
|
||||
previousAnnotationValue)
|
||||
}
|
||||
|
||||
node.ObjectMeta.Annotations[annotationKeyNodeID] = string(jsonObj)
|
||||
}
|
||||
|
||||
return node, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateTopologyLabels returns a function that updates labels of a Node object with the given
|
||||
// topology information.
|
||||
func updateTopologyLabels(topology *csipb.Topology) nodeUpdateFunc {
|
||||
return func(node *v1.Node) (*v1.Node, bool, error) {
|
||||
if topology == nil || len(topology.Segments) == 0 {
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
for k, v := range topology.Segments {
|
||||
if curVal, exists := node.Labels[k]; exists && curVal != v {
|
||||
return nil, false, fmt.Errorf("detected topology value collision: driver reported %q:%q but existing label is %q:%q", k, v, k, curVal)
|
||||
}
|
||||
}
|
||||
|
||||
if node.Labels == nil {
|
||||
node.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range topology.Segments {
|
||||
node.Labels[k] = v
|
||||
}
|
||||
return node, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (nim *nodeInfoManager) updateCSINodeInfo(
|
||||
driverName string,
|
||||
driverNodeID string,
|
||||
topology *csipb.Topology) error {
|
||||
|
||||
csiKubeClient := nim.volumeHost.GetCSIClient()
|
||||
if csiKubeClient == nil {
|
||||
return fmt.Errorf("error getting CSI client")
|
||||
}
|
||||
|
||||
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
nodeInfo, err := csiKubeClient.CsiV1alpha1().CSINodeInfos().Get(string(nim.nodeName), metav1.GetOptions{})
|
||||
if nodeInfo == nil || errors.IsNotFound(err) {
|
||||
return nim.createNodeInfoObject(driverName, driverNodeID, topology)
|
||||
}
|
||||
if err != nil {
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
return nim.updateNodeInfoObject(nodeInfo, driverName, driverNodeID, topology)
|
||||
})
|
||||
if retryErr != nil {
|
||||
return fmt.Errorf("CSINodeInfo update failed: %v", retryErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nim *nodeInfoManager) createNodeInfoObject(
|
||||
driverName string,
|
||||
driverNodeID string,
|
||||
topology *csipb.Topology) error {
|
||||
|
||||
kubeClient := nim.volumeHost.GetKubeClient()
|
||||
if kubeClient == nil {
|
||||
return fmt.Errorf("error getting kube client")
|
||||
}
|
||||
|
||||
csiKubeClient := nim.volumeHost.GetCSIClient()
|
||||
if csiKubeClient == nil {
|
||||
return fmt.Errorf("error getting CSI client")
|
||||
}
|
||||
|
||||
topologyKeys := []string{} // must be an empty slice instead of nil to satisfy CRD OpenAPI Schema validation
|
||||
if topology != nil {
|
||||
for k := range topology.Segments {
|
||||
topologyKeys = append(topologyKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
node, err := kubeClient.CoreV1().Nodes().Get(string(nim.nodeName), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
nodeInfo := &csiv1alpha1.CSINodeInfo{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: string(nim.nodeName),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: nodeKind.Version,
|
||||
Kind: nodeKind.Kind,
|
||||
Name: node.Name,
|
||||
UID: node.UID,
|
||||
},
|
||||
},
|
||||
},
|
||||
CSIDrivers: []csiv1alpha1.CSIDriverInfo{
|
||||
{
|
||||
Driver: driverName,
|
||||
NodeID: driverNodeID,
|
||||
TopologyKeys: topologyKeys,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = csiKubeClient.CsiV1alpha1().CSINodeInfos().Create(nodeInfo)
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
func (nim *nodeInfoManager) updateNodeInfoObject(
|
||||
nodeInfo *csiv1alpha1.CSINodeInfo,
|
||||
driverName string,
|
||||
driverNodeID string,
|
||||
topology *csipb.Topology) error {
|
||||
|
||||
csiKubeClient := nim.volumeHost.GetCSIClient()
|
||||
if csiKubeClient == nil {
|
||||
return fmt.Errorf("error getting CSI client")
|
||||
}
|
||||
|
||||
topologyKeys := make(sets.String)
|
||||
if topology != nil {
|
||||
for k := range topology.Segments {
|
||||
topologyKeys.Insert(k)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone driver list, omitting the driver that matches the given driverName,
|
||||
// unless the driver is identical to information provided, in which case no update is necessary.
|
||||
var newDriverInfos []csiv1alpha1.CSIDriverInfo
|
||||
for _, driverInfo := range nodeInfo.CSIDrivers {
|
||||
if driverInfo.Driver == driverName {
|
||||
prevTopologyKeys := sets.NewString(driverInfo.TopologyKeys...)
|
||||
if driverInfo.NodeID == driverNodeID && prevTopologyKeys.Equal(topologyKeys) {
|
||||
// No update needed
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Omit driverInfo matching given driverName
|
||||
newDriverInfos = append(newDriverInfos, driverInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Append new driver
|
||||
driverInfo := csiv1alpha1.CSIDriverInfo{
|
||||
Driver: driverName,
|
||||
NodeID: driverNodeID,
|
||||
TopologyKeys: topologyKeys.List(),
|
||||
}
|
||||
newDriverInfos = append(newDriverInfos, driverInfo)
|
||||
nodeInfo.CSIDrivers = newDriverInfos
|
||||
|
||||
_, err := csiKubeClient.CsiV1alpha1().CSINodeInfos().Update(nodeInfo)
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
func (nim *nodeInfoManager) removeCSINodeInfo(csiDriverName string) error {
|
||||
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
|
||||
csiKubeClient := nim.volumeHost.GetCSIClient()
|
||||
if csiKubeClient == nil {
|
||||
return fmt.Errorf("error getting CSI client")
|
||||
}
|
||||
|
||||
nodeInfoClient := csiKubeClient.CsiV1alpha1().CSINodeInfos()
|
||||
nodeInfo, err := nodeInfoClient.Get(string(nim.nodeName), metav1.GetOptions{})
|
||||
if nodeInfo == nil || errors.IsNotFound(err) {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err // do not wrap error
|
||||
}
|
||||
|
||||
// Remove matching driver from driver list
|
||||
var newDriverInfos []csiv1alpha1.CSIDriverInfo
|
||||
for _, driverInfo := range nodeInfo.CSIDrivers {
|
||||
if driverInfo.Driver != csiDriverName {
|
||||
newDriverInfos = append(newDriverInfos, driverInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if len(newDriverInfos) == len(nodeInfo.CSIDrivers) {
|
||||
// No changes, don't update
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(newDriverInfos) == 0 {
|
||||
// No drivers left, delete CSINodeInfo object
|
||||
return nodeInfoClient.Delete(string(nim.nodeName), &metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// TODO (verult) make sure CSINodeInfo has validation logic to prevent duplicate driver names
|
||||
_, updateErr := nodeInfoClient.Update(nodeInfo)
|
||||
return updateErr // do not wrap error
|
||||
|
||||
})
|
||||
if retryErr != nil {
|
||||
return fmt.Errorf("CSINodeInfo update failed: %v", retryErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMaxAttachLimit(driverName string, maxLimit int64) nodeUpdateFunc {
|
||||
return func(node *v1.Node) (*v1.Node, bool, error) {
|
||||
if maxLimit <= 0 {
|
||||
glog.V(4).Infof("skipping adding attach limit for %s", driverName)
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
if node.Status.Capacity == nil {
|
||||
node.Status.Capacity = v1.ResourceList{}
|
||||
}
|
||||
if node.Status.Allocatable == nil {
|
||||
node.Status.Allocatable = v1.ResourceList{}
|
||||
}
|
||||
limitKeyName := util.GetCSIAttachLimitKey(driverName)
|
||||
node.Status.Capacity[v1.ResourceName(limitKeyName)] = *resource.NewQuantity(maxLimit, resource.DecimalSI)
|
||||
node.Status.Allocatable[v1.ResourceName(limitKeyName)] = *resource.NewQuantity(maxLimit, resource.DecimalSI)
|
||||
|
||||
return node, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func removeMaxAttachLimit(driverName string) nodeUpdateFunc {
|
||||
return func(node *v1.Node) (*v1.Node, bool, error) {
|
||||
limitKey := v1.ResourceName(util.GetCSIAttachLimitKey(driverName))
|
||||
|
||||
capacityExists := false
|
||||
if node.Status.Capacity != nil {
|
||||
_, capacityExists = node.Status.Capacity[limitKey]
|
||||
}
|
||||
|
||||
allocatableExists := false
|
||||
if node.Status.Allocatable != nil {
|
||||
_, allocatableExists = node.Status.Allocatable[limitKey]
|
||||
}
|
||||
|
||||
if !capacityExists && !allocatableExists {
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
delete(node.Status.Capacity, limitKey)
|
||||
if len(node.Status.Capacity) == 0 {
|
||||
node.Status.Capacity = nil
|
||||
}
|
||||
|
||||
delete(node.Status.Allocatable, limitKey)
|
||||
if len(node.Status.Allocatable) == 0 {
|
||||
node.Status.Allocatable = nil
|
||||
}
|
||||
|
||||
return node, true, nil
|
||||
}
|
||||
}
|
809
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go
generated
vendored
Normal file
809
vendor/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go
generated
vendored
Normal file
@@ -0,0 +1,809 @@
|
||||
/*
|
||||
Copyright 2018 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 nodeinfomanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/container-storage-interface/spec/lib/go/csi/v0"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
csiv1alpha1 "k8s.io/csi-api/pkg/apis/csi/v1alpha1"
|
||||
csifake "k8s.io/csi-api/pkg/client/clientset/versioned/fake"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
type testcase struct {
|
||||
name string
|
||||
driverName string
|
||||
existingNode *v1.Node
|
||||
existingNodeInfo *csiv1alpha1.CSINodeInfo
|
||||
inputNodeID string
|
||||
inputTopology *csi.Topology
|
||||
inputVolumeLimit int64
|
||||
expectedNodeIDMap map[string]string
|
||||
expectedTopologyMap map[string]sets.String
|
||||
expectedLabels map[string]string
|
||||
expectNoNodeInfo bool
|
||||
expectedVolumeLimit int64
|
||||
expectFail bool
|
||||
}
|
||||
|
||||
type nodeIDMap map[string]string
|
||||
type topologyKeyMap map[string][]string
|
||||
type labelMap map[string]string
|
||||
|
||||
// TestAddNodeInfo tests AddNodeInfo with various existing Node and/or CSINodeInfo objects.
|
||||
// The node IDs in all test cases below are the same between the Node annotation and CSINodeInfo.
|
||||
func TestAddNodeInfo(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "empty node",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": sets.NewString("com.example.csi/zone"),
|
||||
},
|
||||
expectedLabels: map[string]string{"com.example.csi/zone": "zoneA"},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"com.example.csi/driver1": {"com.example.csi/zone"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": sets.NewString("com.example.csi/zone"),
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver, but without topology info",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil, /* topologyKeys */
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": sets.NewString("com.example.csi/zone"),
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from different driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
labelMap{
|
||||
"net.example.storage/rack": "rack1",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"net.example.storage/other-driver": {"net.example.storage/rack"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
},
|
||||
},
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": sets.NewString("com.example.csi/zone"),
|
||||
"net.example.storage/other-driver": sets.NewString("net.example.storage/rack"),
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
"net.example.storage/rack": "rack1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver, but different node ID and topology values; labels should conflict",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"com.example.csi/driver1": {"com.example.csi/zone"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/zone": "other-zone",
|
||||
},
|
||||
},
|
||||
expectFail: true,
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver, but different node ID and topology keys; new labels should be added",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"com.example.csi/driver1": {"com.example.csi/zone"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/other-node",
|
||||
inputTopology: &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
"com.example.csi/rack": "rack1",
|
||||
},
|
||||
},
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/other-node",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": sets.NewString("com.example.csi/rack"),
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
"com.example.csi/rack": "rack1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil topology, empty node",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: nil,
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": nil,
|
||||
},
|
||||
expectedLabels: nil,
|
||||
},
|
||||
{
|
||||
name: "nil topology, pre-existing node info from the same driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"com.example.csi/driver1": {"com.example.csi/zone"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: nil,
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": nil,
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"com.example.csi/zone": "zoneA", // old labels are not removed
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil topology, pre-existing node info from different driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
labelMap{
|
||||
"net.example.storage/rack": "rack1",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"net.example.storage/other-driver": {"net.example.storage/rack"},
|
||||
},
|
||||
),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
inputTopology: nil,
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"net.example.storage/other-driver": sets.NewString("net.example.storage/rack"),
|
||||
"com.example.csi/driver1": nil,
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"net.example.storage/rack": "rack1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty node ID",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "",
|
||||
expectFail: true,
|
||||
},
|
||||
{
|
||||
name: "new node with valid max limit",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
|
||||
inputVolumeLimit: 10,
|
||||
inputTopology: nil,
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectedVolumeLimit: 10,
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": nil,
|
||||
},
|
||||
expectedLabels: nil,
|
||||
},
|
||||
{
|
||||
name: "node with existing valid max limit",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nil, /*nodeIDs*/
|
||||
nil, /*labels*/
|
||||
map[v1.ResourceName]resource.Quantity{
|
||||
v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
|
||||
v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
|
||||
}),
|
||||
inputVolumeLimit: 20,
|
||||
inputTopology: nil,
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectedVolumeLimit: 20,
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"com.example.csi/driver1": nil,
|
||||
},
|
||||
expectedLabels: nil,
|
||||
},
|
||||
}
|
||||
|
||||
test(t, true /* addNodeInfo */, true /* csiNodeInfoEnabled */, testcases)
|
||||
}
|
||||
|
||||
// TestAddNodeInfo_CSINodeInfoDisabled tests AddNodeInfo with various existing Node annotations
|
||||
// and CSINodeInfo feature gate disabled.
|
||||
func TestAddNodeInfo_CSINodeInfoDisabled(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "empty node",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from different driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test(t, true /* addNodeInfo */, false /* csiNodeInfoEnabled */, testcases)
|
||||
}
|
||||
|
||||
// TestRemoveNodeInfo tests RemoveNodeInfo with various existing Node and/or CSINodeInfo objects.
|
||||
func TestRemoveNodeInfo(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "empty node and no CSINodeInfo",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: nil,
|
||||
expectedLabels: nil,
|
||||
expectNoNodeInfo: true,
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"com.example.csi/zone": "zoneA",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"com.example.csi/driver1": {"com.example.csi/zone"},
|
||||
},
|
||||
),
|
||||
expectedNodeIDMap: nil,
|
||||
expectedLabels: map[string]string{"com.example.csi/zone": "zoneA"},
|
||||
expectNoNodeInfo: true,
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from different driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
labelMap{
|
||||
"net.example.storage/zone": "zoneA",
|
||||
}, nil /*capacity*/),
|
||||
existingNodeInfo: generateNodeInfo(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
topologyKeyMap{
|
||||
"net.example.storage/other-driver": {"net.example.storage/zone"},
|
||||
},
|
||||
),
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
expectedTopologyMap: map[string]sets.String{
|
||||
"net.example.storage/other-driver": sets.NewString("net.example.storage/zone"),
|
||||
},
|
||||
expectedLabels: map[string]string{"net.example.storage/zone": "zoneA"},
|
||||
},
|
||||
{
|
||||
name: "pre-existing info about the same driver in node, but no CSINodeInfo",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: nil,
|
||||
expectedLabels: nil,
|
||||
expectNoNodeInfo: true,
|
||||
},
|
||||
{
|
||||
name: "pre-existing info about a different driver in node, but no CSINodeInfo",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
expectedLabels: nil,
|
||||
expectNoNodeInfo: true,
|
||||
},
|
||||
{
|
||||
name: "new node with valid max limit",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nil, /*nodeIDs*/
|
||||
nil, /*labels*/
|
||||
map[v1.ResourceName]resource.Quantity{
|
||||
v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
|
||||
v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
|
||||
},
|
||||
),
|
||||
inputTopology: nil,
|
||||
inputNodeID: "com.example.csi/csi-node1",
|
||||
expectNoNodeInfo: true,
|
||||
expectedVolumeLimit: 0,
|
||||
},
|
||||
}
|
||||
|
||||
test(t, false /* addNodeInfo */, true /* csiNodeInfoEnabled */, testcases)
|
||||
}
|
||||
|
||||
// TestRemoveNodeInfo tests RemoveNodeInfo with various existing Node objects and CSINodeInfo
|
||||
// feature disabled.
|
||||
func TestRemoveNodeInfo_CSINodeInfoDisabled(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "empty node",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: nil,
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from the same driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: nil,
|
||||
},
|
||||
{
|
||||
name: "pre-existing node info from different driver",
|
||||
driverName: "com.example.csi/driver1",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
expectedNodeIDMap: map[string]string{
|
||||
"net.example.storage/other-driver": "net.example.storage/csi-node1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test(t, false /* addNodeInfo */, false /* csiNodeInfoEnabled */, testcases)
|
||||
}
|
||||
|
||||
func TestAddNodeInfoExistingAnnotation(t *testing.T) {
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeInfo, true)()
|
||||
|
||||
driverName := "com.example.csi/driver1"
|
||||
nodeID := "com.example.csi/some-node"
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
existingNode *v1.Node
|
||||
}{
|
||||
{
|
||||
name: "pre-existing info about the same driver in node, but no CSINodeInfo",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"com.example.csi/driver1": "com.example.csi/csi-node1",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
},
|
||||
{
|
||||
name: "pre-existing info about a different driver in node, but no CSINodeInfo",
|
||||
existingNode: generateNode(
|
||||
nodeIDMap{
|
||||
"net.example.storage/other-driver": "net.example.storage/test-node",
|
||||
},
|
||||
nil /* labels */, nil /*capacity*/),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Logf("test case: %q", tc.name)
|
||||
|
||||
// Arrange
|
||||
nodeName := tc.existingNode.Name
|
||||
client := fake.NewSimpleClientset(tc.existingNode)
|
||||
csiClient := csifake.NewSimpleClientset()
|
||||
|
||||
tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
|
||||
if err != nil {
|
||||
t.Fatalf("can't create temp dir: %v", err)
|
||||
}
|
||||
host := volumetest.NewFakeVolumeHostWithCSINodeName(
|
||||
tmpDir,
|
||||
client,
|
||||
csiClient,
|
||||
nil,
|
||||
nodeName,
|
||||
)
|
||||
|
||||
nim := NewNodeInfoManager(types.NodeName(nodeName), host)
|
||||
|
||||
// Act
|
||||
err = nim.AddNodeInfo(driverName, nodeID, 0 /* maxVolumeLimit */, nil) // TODO test maxVolumeLimit
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from AddNodeInfo call but got: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Assert
|
||||
nodeInfo, err := csiClient.Csi().CSINodeInfos().Get(nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("error getting CSINodeInfo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(nodeInfo.CSIDrivers) != 1 {
|
||||
t.Errorf("expected 1 CSIDriverInfo entry but got: %d", len(nodeInfo.CSIDrivers))
|
||||
continue
|
||||
}
|
||||
|
||||
driver := nodeInfo.CSIDrivers[0]
|
||||
if driver.Driver != driverName || driver.NodeID != nodeID {
|
||||
t.Errorf("expected Driver to be %q and NodeID to be %q, but got: %q:%q", driverName, nodeID, driver.Driver, driver.NodeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func test(t *testing.T, addNodeInfo bool, csiNodeInfoEnabled bool, testcases []testcase) {
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeInfo, csiNodeInfoEnabled)()
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AttachVolumeLimit, true)()
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Logf("test case: %q", tc.name)
|
||||
|
||||
//// Arrange
|
||||
nodeName := tc.existingNode.Name
|
||||
client := fake.NewSimpleClientset(tc.existingNode)
|
||||
var csiClient *csifake.Clientset
|
||||
if tc.existingNodeInfo == nil {
|
||||
csiClient = csifake.NewSimpleClientset()
|
||||
} else {
|
||||
csiClient = csifake.NewSimpleClientset(tc.existingNodeInfo)
|
||||
}
|
||||
|
||||
tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
|
||||
if err != nil {
|
||||
t.Fatalf("can't create temp dir: %v", err)
|
||||
}
|
||||
host := volumetest.NewFakeVolumeHostWithCSINodeName(
|
||||
tmpDir,
|
||||
client,
|
||||
csiClient,
|
||||
nil,
|
||||
nodeName,
|
||||
)
|
||||
nim := NewNodeInfoManager(types.NodeName(nodeName), host)
|
||||
|
||||
//// Act
|
||||
if addNodeInfo {
|
||||
err = nim.AddNodeInfo(tc.driverName, tc.inputNodeID, tc.inputVolumeLimit, tc.inputTopology)
|
||||
} else {
|
||||
err = nim.RemoveNodeInfo(tc.driverName)
|
||||
}
|
||||
|
||||
//// Assert
|
||||
if tc.expectFail {
|
||||
if err == nil {
|
||||
t.Errorf("expected an error from AddNodeInfo call but got none")
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
t.Errorf("expected no error from AddNodeInfo call but got: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
/* Node Validation */
|
||||
node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("error getting node: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We are testing max volume limits
|
||||
attachLimit := getVolumeLimit(node, tc.driverName)
|
||||
if attachLimit != tc.expectedVolumeLimit {
|
||||
t.Errorf("expected volume limit to be %d got %d", tc.expectedVolumeLimit, attachLimit)
|
||||
continue
|
||||
}
|
||||
|
||||
// Node ID annotation
|
||||
annNodeID, ok := node.Annotations[annotationKeyNodeID]
|
||||
if ok {
|
||||
if tc.expectedNodeIDMap == nil {
|
||||
t.Errorf("expected annotation %q to not exist, but got: %q", annotationKeyNodeID, annNodeID)
|
||||
} else {
|
||||
var actualNodeIDs map[string]string
|
||||
err = json.Unmarshal([]byte(annNodeID), &actualNodeIDs)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error when parsing annotation %q, but got error: %v", annotationKeyNodeID, err)
|
||||
}
|
||||
|
||||
if !helper.Semantic.DeepEqual(actualNodeIDs, tc.expectedNodeIDMap) {
|
||||
t.Errorf("expected annotation %v; got: %v", tc.expectedNodeIDMap, actualNodeIDs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if tc.expectedNodeIDMap != nil {
|
||||
t.Errorf("expected annotation %q, but got none", annotationKeyNodeID)
|
||||
}
|
||||
}
|
||||
|
||||
if csiNodeInfoEnabled {
|
||||
// Topology labels
|
||||
if !helper.Semantic.DeepEqual(node.Labels, tc.expectedLabels) {
|
||||
t.Errorf("expected topology labels to be %v; got: %v", tc.expectedLabels, node.Labels)
|
||||
}
|
||||
|
||||
/* CSINodeInfo validation */
|
||||
nodeInfo, err := csiClient.Csi().CSINodeInfos().Get(nodeName, metav1.GetOptions{})
|
||||
if tc.expectNoNodeInfo && errors.IsNotFound(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
t.Errorf("error getting CSINodeInfo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract node IDs and topology keys
|
||||
actualNodeIDs := make(map[string]string)
|
||||
actualTopologyKeys := make(map[string]sets.String)
|
||||
for _, driver := range nodeInfo.CSIDrivers {
|
||||
actualNodeIDs[driver.Driver] = driver.NodeID
|
||||
actualTopologyKeys[driver.Driver] = sets.NewString(driver.TopologyKeys...)
|
||||
}
|
||||
|
||||
// Node IDs
|
||||
if !helper.Semantic.DeepEqual(actualNodeIDs, tc.expectedNodeIDMap) {
|
||||
t.Errorf("expected node IDs %v from CSINodeInfo; got: %v", tc.expectedNodeIDMap, actualNodeIDs)
|
||||
}
|
||||
|
||||
// Topology keys
|
||||
if !helper.Semantic.DeepEqual(actualTopologyKeys, tc.expectedTopologyMap) {
|
||||
t.Errorf("expected topology keys %v from CSINodeInfo; got: %v", tc.expectedTopologyMap, actualTopologyKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getVolumeLimit(node *v1.Node, driverName string) int64 {
|
||||
volumeLimits := map[v1.ResourceName]int64{}
|
||||
nodeAllocatables := node.Status.Allocatable
|
||||
for k, v := range nodeAllocatables {
|
||||
if v1helper.IsAttachableVolumeResourceName(k) {
|
||||
volumeLimits[k] = v.Value()
|
||||
}
|
||||
}
|
||||
attachKey := v1.ResourceName(util.GetCSIAttachLimitKey(driverName))
|
||||
attachLimit := volumeLimits[attachKey]
|
||||
return attachLimit
|
||||
}
|
||||
|
||||
func generateNode(nodeIDs, labels map[string]string, capacity map[v1.ResourceName]resource.Quantity) *v1.Node {
|
||||
var annotations map[string]string
|
||||
if len(nodeIDs) > 0 {
|
||||
b, _ := json.Marshal(nodeIDs)
|
||||
annotations = map[string]string{annotationKeyNodeID: string(b)}
|
||||
}
|
||||
node := &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
Annotations: annotations,
|
||||
Labels: labels,
|
||||
},
|
||||
}
|
||||
|
||||
if len(capacity) > 0 {
|
||||
node.Status.Capacity = v1.ResourceList(capacity)
|
||||
node.Status.Allocatable = v1.ResourceList(capacity)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func generateNodeInfo(nodeIDs map[string]string, topologyKeys map[string][]string) *csiv1alpha1.CSINodeInfo {
|
||||
var drivers []csiv1alpha1.CSIDriverInfo
|
||||
for k, nodeID := range nodeIDs {
|
||||
d := csiv1alpha1.CSIDriverInfo{
|
||||
Driver: k,
|
||||
NodeID: nodeID,
|
||||
}
|
||||
if top, exists := topologyKeys[k]; exists {
|
||||
d.TopologyKeys = top
|
||||
}
|
||||
drivers = append(drivers, d)
|
||||
}
|
||||
return &csiv1alpha1.CSINodeInfo{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
CSIDrivers: drivers,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user