mirror of https://github.com/k3s-io/k3s
Make CSINodeInfo and CSIDriver Core APIs
This PR is the first step to transition CSINodeInfo and CSIDriver CRD's to in-tree APIs. It adds them to the existing API group “storage.k8s.io” as core storage APIs.pull/564/head
parent
e1b79abfec
commit
bb45b8ee34
|
@ -107,9 +107,9 @@ test/e2e/testing-manifests/rbd-storage-class.yaml,storageclasses,,slow,v1beta1,v
|
||||||
)
|
)
|
||||||
|
|
||||||
KUBE_OLD_API_VERSION="networking.k8s.io/v1,storage.k8s.io/v1beta1,extensions/v1beta1"
|
KUBE_OLD_API_VERSION="networking.k8s.io/v1,storage.k8s.io/v1beta1,extensions/v1beta1"
|
||||||
KUBE_NEW_API_VERSION="networking.k8s.io/v1,storage.k8s.io/v1,extensions/v1beta1,policy/v1beta1"
|
KUBE_NEW_API_VERSION="networking.k8s.io/v1,storage.k8s.io/v1beta1,storage.k8s.io/v1,extensions/v1beta1,policy/v1beta1"
|
||||||
KUBE_OLD_STORAGE_VERSIONS="storage.k8s.io/v1beta1"
|
KUBE_OLD_STORAGE_VERSIONS="storage.k8s.io/v1beta1"
|
||||||
KUBE_NEW_STORAGE_VERSIONS="storage.k8s.io/v1"
|
KUBE_NEW_STORAGE_VERSIONS="storage.k8s.io/v1beta1,storage.k8s.io/v1"
|
||||||
|
|
||||||
### END TEST DEFINITION CUSTOMIZATION ###
|
### END TEST DEFINITION CUSTOMIZATION ###
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,8 @@ func TestDefaulting(t *testing.T) {
|
||||||
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
|
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
|
||||||
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},
|
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},
|
||||||
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {},
|
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {},
|
||||||
|
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "CSIDriver"}: {},
|
||||||
|
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "CSIDriverList"}: {},
|
||||||
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {},
|
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {},
|
||||||
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {},
|
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {},
|
||||||
{Group: "authentication.k8s.io", Version: "v1", Kind: "TokenRequest"}: {},
|
{Group: "authentication.k8s.io", Version: "v1", Kind: "TokenRequest"}: {},
|
||||||
|
|
|
@ -61,8 +61,6 @@ const isInvalidQuotaResource string = `must be a standard resource for quota`
|
||||||
const fieldImmutableErrorMsg string = apimachineryvalidation.FieldImmutableErrorMsg
|
const fieldImmutableErrorMsg string = apimachineryvalidation.FieldImmutableErrorMsg
|
||||||
const isNotIntegerErrorMsg string = `must be an integer`
|
const isNotIntegerErrorMsg string = `must be an integer`
|
||||||
const isNotPositiveErrorMsg string = `must be greater than zero`
|
const isNotPositiveErrorMsg string = `must be greater than zero`
|
||||||
const csiDriverNameRexpErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
|
||||||
const csiDriverNameRexpFmt string = `^[a-zA-Z0-9][-a-zA-Z0-9_.]{0,61}[a-zA-Z-0-9]$`
|
|
||||||
|
|
||||||
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
||||||
var fileModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
|
var fileModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
|
||||||
|
@ -74,8 +72,6 @@ var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.
|
||||||
var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`)
|
var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`)
|
||||||
var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
|
var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
|
||||||
|
|
||||||
var csiDriverNameRexp = regexp.MustCompile(csiDriverNameRexpFmt)
|
|
||||||
|
|
||||||
// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue
|
// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue
|
||||||
func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
@ -1446,9 +1442,10 @@ func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorLi
|
||||||
allErrs = append(allErrs, field.TooLong(fldPath, driverName, 63))
|
allErrs = append(allErrs, field.TooLong(fldPath, driverName, 63))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !csiDriverNameRexp.MatchString(driverName) {
|
for _, msg := range validation.IsDNS1123Subdomain(strings.ToLower(driverName)) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, driverName, validation.RegexError(csiDriverNameRexpErrMsg, csiDriverNameRexpFmt, "csi-hostpath")))
|
allErrs = append(allErrs, field.Invalid(fldPath, driverName, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1775,24 +1775,30 @@ func TestValidateCSIVolumeSource(t *testing.T) {
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: ok underscore only",
|
name: "driver name: invalid underscore",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
|
||||||
|
errtype: field.ErrorTypeInvalid,
|
||||||
|
errfield: "driver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: ok dot underscores",
|
name: "driver name: invalid dot underscores",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
|
||||||
|
errtype: field.ErrorTypeInvalid,
|
||||||
|
errfield: "driver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: ok beginnin with number",
|
name: "driver name: ok beginnin with number",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: ok ending with number",
|
name: "driver name: ok ending with number",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex2", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: ok dot dash underscores",
|
name: "driver name: invalid dot dash underscores",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
|
||||||
|
errtype: field.ErrorTypeInvalid,
|
||||||
|
errfield: "driver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: invalid length 0",
|
name: "driver name: invalid length 0",
|
||||||
|
@ -1801,10 +1807,8 @@ func TestValidateCSIVolumeSource(t *testing.T) {
|
||||||
errfield: "driver",
|
errfield: "driver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: invalid length 1",
|
name: "driver name: ok length 1",
|
||||||
csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
|
||||||
errtype: field.ErrorTypeInvalid,
|
|
||||||
errfield: "driver",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "driver name: invalid length > 63",
|
name: "driver name: invalid length > 63",
|
||||||
|
|
|
@ -34,5 +34,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
||||||
bindingModes := []storage.VolumeBindingMode{storage.VolumeBindingImmediate, storage.VolumeBindingWaitForFirstConsumer}
|
bindingModes := []storage.VolumeBindingMode{storage.VolumeBindingImmediate, storage.VolumeBindingWaitForFirstConsumer}
|
||||||
obj.VolumeBindingMode = &bindingModes[c.Rand.Intn(len(bindingModes))]
|
obj.VolumeBindingMode = &bindingModes[c.Rand.Intn(len(bindingModes))]
|
||||||
},
|
},
|
||||||
|
func(obj *storage.CSIDriver, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(obj) // fuzz self without calling this function again
|
||||||
|
|
||||||
|
// match defaulting
|
||||||
|
if obj.Spec.AttachRequired == nil {
|
||||||
|
obj.Spec.AttachRequired = new(bool)
|
||||||
|
*(obj.Spec.AttachRequired) = true
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
&StorageClassList{},
|
&StorageClassList{},
|
||||||
&VolumeAttachment{},
|
&VolumeAttachment{},
|
||||||
&VolumeAttachmentList{},
|
&VolumeAttachmentList{},
|
||||||
|
&CSINode{},
|
||||||
|
&CSINodeList{},
|
||||||
|
&CSIDriver{},
|
||||||
|
&CSIDriverList{},
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,3 +215,158 @@ const (
|
||||||
// binding will occur during Pod scheduing.
|
// binding will occur during Pod scheduing.
|
||||||
VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer"
|
VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSIDriver captures information about a Container Storage Interface (CSI)
|
||||||
|
// volume driver deployed on the cluster.
|
||||||
|
// CSI drivers do not need to create the CSIDriver object directly. Instead they may use the
|
||||||
|
// cluster-driver-registrar sidecar container. When deployed with a CSI driver it automatically
|
||||||
|
// creates a CSIDriver object representing the driver.
|
||||||
|
// Kubernetes attach detach controller uses this object to determine whether attach is required.
|
||||||
|
// Kubelet uses this object to determine whether pod information needs to be passed on mount.
|
||||||
|
// CSIDriver objects are non-namespaced.
|
||||||
|
type CSIDriver struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// Standard object metadata.
|
||||||
|
// metadata.Name indicates the name of the CSI driver that this object
|
||||||
|
// refers to; it MUST be the same name returned by the CSI GetPluginName()
|
||||||
|
// call for that driver.
|
||||||
|
// The driver name must be 63 characters or less, beginning and ending with
|
||||||
|
// an alphanumeric character ([a-z0-9A-Z]) with dashes (-), dots (.), and
|
||||||
|
// alphanumerics between.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// Specification of the CSI Driver.
|
||||||
|
Spec CSIDriverSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSIDriverList is a collection of CSIDriver objects.
|
||||||
|
type CSIDriverList struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// Standard list metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta
|
||||||
|
|
||||||
|
// items is the list of CSIDriver
|
||||||
|
Items []CSIDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSIDriverSpec is the specification of a CSIDriver.
|
||||||
|
type CSIDriverSpec struct {
|
||||||
|
// attachRequired indicates this CSI volume driver requires an attach
|
||||||
|
// operation (because it implements the CSI ControllerPublishVolume()
|
||||||
|
// method), and that the Kubernetes attach detach controller should call
|
||||||
|
// the attach volume interface which checks the volumeattachment status
|
||||||
|
// and waits until the volume is attached before proceeding to mounting.
|
||||||
|
// The CSI external-attacher coordinates with CSI volume driver and updates
|
||||||
|
// the volumeattachment status when the attach operation is complete.
|
||||||
|
// If the CSIDriverRegistry feature gate is enabled and the value is
|
||||||
|
// specified to false, the attach operation will be skipped.
|
||||||
|
// Otherwise the attach operation will be called.
|
||||||
|
// +optional
|
||||||
|
AttachRequired *bool
|
||||||
|
|
||||||
|
// If set to true, podInfoOnMount indicates this CSI volume driver
|
||||||
|
// requires additional pod information (like podName, podUID, etc.) during
|
||||||
|
// mount operations.
|
||||||
|
// If not set or set to false, pod information will not be passed on mount.
|
||||||
|
// The CSI driver specifies podInfoOnMount as part of driver deployment.
|
||||||
|
// If true, Kubelet will pass pod information as VolumeContext in the CSI
|
||||||
|
// NodePublishVolume() calls.
|
||||||
|
// The CSI driver is responsible for parsing and validating the information
|
||||||
|
// passed in as VolumeContext.
|
||||||
|
// The following VolumeConext will be passed if podInfoOnMount is set to true:
|
||||||
|
// "csi.storage.k8s.io/pod.name": pod.Name
|
||||||
|
// "csi.storage.k8s.io/pod.namespace": pod.Namespace
|
||||||
|
// "csi.storage.k8s.io/pod.uid": string(pod.UID)
|
||||||
|
// +optional
|
||||||
|
PodInfoOnMount *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSINode holds information about all CSI drivers installed on a node.
|
||||||
|
// CSI drivers do not need to create the CSINode object directly. As long as
|
||||||
|
// they use the node-driver-registrar sidecar container, the kubelet will
|
||||||
|
// automatically populate the CSINode object for the CSI driver as part of
|
||||||
|
// kubelet plugin registration.
|
||||||
|
// CSINode has the same name as a node. If the object is missing, it means either
|
||||||
|
// there are no CSI Drivers available on the node, or the Kubelet version is low
|
||||||
|
// enough that it doesn't create this object.
|
||||||
|
// CSINode has an OwnerReference that points to the corresponding node object.
|
||||||
|
type CSINode struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// metadata.name must be the Kubernetes node name.
|
||||||
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// spec is the specification of CSINode
|
||||||
|
Spec CSINodeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSINodeSpec holds information about the specification of all CSI drivers installed on a node
|
||||||
|
type CSINodeSpec struct {
|
||||||
|
// drivers is a list of information of all CSI Drivers existing on a node.
|
||||||
|
// It can be empty on initialization.
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
Drivers []CSINodeDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSINodeDriver holds information about the specification of one CSI driver installed on a node
|
||||||
|
type CSINodeDriver struct {
|
||||||
|
// This is the name of the CSI driver that this object refers to.
|
||||||
|
// This MUST be the same name returned by the CSI GetPluginName() call for
|
||||||
|
// that driver.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// nodeID of the node from the driver point of view.
|
||||||
|
// This field enables Kubernetes to communicate with storage systems that do
|
||||||
|
// not share the same nomenclature for nodes. For example, Kubernetes may
|
||||||
|
// refer to a given node as "node1", but the storage system may refer to
|
||||||
|
// the same node as "nodeA". When Kubernetes issues a command to the storage
|
||||||
|
// system to attach a volume to a specific node, it can use this field to
|
||||||
|
// refer to the node name using the ID that the storage system will
|
||||||
|
// understand, e.g. "nodeA" instead of "node1". This field is required.
|
||||||
|
NodeID string
|
||||||
|
|
||||||
|
// topologyKeys is the list of keys supported by the driver.
|
||||||
|
// When a driver is initialized on a cluster, it provides a set of topology
|
||||||
|
// keys that it understands (e.g. "company.com/zone", "company.com/region").
|
||||||
|
// When a driver is initialized on a node, it provides the same topology keys
|
||||||
|
// along with values. Kubelet will expose these topology keys as labels
|
||||||
|
// on its own node object.
|
||||||
|
// When Kubernetes does topology aware provisioning, it can use this list to
|
||||||
|
// determine which labels it should retrieve from the node object and pass
|
||||||
|
// back to the driver.
|
||||||
|
// It is possible for different nodes to use different topology keys.
|
||||||
|
// This can be empty if driver does not support topology.
|
||||||
|
// +optional
|
||||||
|
TopologyKeys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSINodeList is a collection of CSINode objects.
|
||||||
|
type CSINodeList struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// Standard list metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta
|
||||||
|
|
||||||
|
// items is the list of CSINode
|
||||||
|
Items []CSINode
|
||||||
|
}
|
||||||
|
|
|
@ -37,3 +37,10 @@ func SetDefaults_StorageClass(obj *storagev1beta1.StorageClass) {
|
||||||
*obj.VolumeBindingMode = storagev1beta1.VolumeBindingImmediate
|
*obj.VolumeBindingMode = storagev1beta1.VolumeBindingImmediate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetDefaults_CSIDriver(obj *storagev1beta1.CSIDriver) {
|
||||||
|
if obj.Spec.AttachRequired == nil {
|
||||||
|
obj.Spec.AttachRequired = new(bool)
|
||||||
|
*(obj.Spec.AttachRequired) = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,3 +60,17 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
|
||||||
t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: %+v", defaultMode, outMode)
|
t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: %+v", defaultMode, outMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetDefaultAttachRequired(t *testing.T) {
|
||||||
|
driver := &storagev1beta1.CSIDriver{}
|
||||||
|
|
||||||
|
// field should be defaulted
|
||||||
|
defaultMode := true
|
||||||
|
output := roundTrip(t, runtime.Object(driver)).(*storagev1beta1.CSIDriver)
|
||||||
|
outMode := output.Spec.AttachRequired
|
||||||
|
if outMode == nil {
|
||||||
|
t.Errorf("Expected AttachRequired to be defaulted to: %+v, got: nil", defaultMode)
|
||||||
|
} else if *outMode != defaultMode {
|
||||||
|
t.Errorf("Expected AttachRequired to be defaulted to: %+v, got: %+v", defaultMode, outMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -36,6 +37,8 @@ const (
|
||||||
|
|
||||||
maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
|
maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
|
||||||
maxVolumeErrorMessageSize = 1024
|
maxVolumeErrorMessageSize = 1024
|
||||||
|
|
||||||
|
csiNodeIDMaxLength = 128
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateStorageClass validates a StorageClass.
|
// ValidateStorageClass validates a StorageClass.
|
||||||
|
@ -266,3 +269,133 @@ func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *f
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateCSINode validates a CSINode.
|
||||||
|
func ValidateCSINode(csiNode *storage.CSINode) field.ErrorList {
|
||||||
|
allErrs := apivalidation.ValidateObjectMeta(&csiNode.ObjectMeta, false, apivalidation.ValidateNodeName, field.NewPath("metadata"))
|
||||||
|
allErrs = append(allErrs, validateCSINodeSpec(&csiNode.Spec, field.NewPath("spec"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSINodeUpdate validates a CSINode.
|
||||||
|
func ValidateCSINodeUpdate(new, old *storage.CSINode) field.ErrorList {
|
||||||
|
allErrs := ValidateCSINode(new)
|
||||||
|
|
||||||
|
// Validate modifying fields inside an existing CSINodeDriver entry is not allowed
|
||||||
|
for _, oldDriver := range old.Spec.Drivers {
|
||||||
|
for _, newDriver := range new.Spec.Drivers {
|
||||||
|
if oldDriver.Name == newDriver.Name {
|
||||||
|
if !apiequality.Semantic.DeepEqual(oldDriver, newDriver) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("CSINodeDriver"), newDriver, "field is immutable"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSINodeSpec tests that the specified CSINodeSpec has valid data.
|
||||||
|
func validateCSINodeSpec(
|
||||||
|
spec *storage.CSINodeSpec, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
allErrs = append(allErrs, validateCSINodeDrivers(spec.Drivers, fldPath.Child("drivers"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSINodeDrivers tests that the specified CSINodeDrivers have valid data.
|
||||||
|
func validateCSINodeDrivers(drivers []storage.CSINodeDriver, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
driverNamesInSpecs := make(sets.String)
|
||||||
|
for i, driver := range drivers {
|
||||||
|
idxPath := fldPath.Index(i)
|
||||||
|
allErrs = append(allErrs, validateCSINodeDriver(driver, driverNamesInSpecs, idxPath)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCSINodeDriverNodeID tests if Name in CSINodeDriver is a valid node id.
|
||||||
|
func validateCSINodeDriverNodeID(nodeID string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
// nodeID is always required
|
||||||
|
if len(nodeID) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath, nodeID))
|
||||||
|
}
|
||||||
|
if len(nodeID) > csiNodeIDMaxLength {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, nodeID, fmt.Sprintf("nodeID must be %d characters or less", csiNodeIDMaxLength)))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCSINodeDriver tests if CSINodeDriver has valid entries
|
||||||
|
func validateCSINodeDriver(driver storage.CSINodeDriver, driverNamesInSpecs sets.String, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(driver.Name, fldPath.Child("name"))...)
|
||||||
|
allErrs = append(allErrs, validateCSINodeDriverNodeID(driver.NodeID, fldPath.Child("nodeID"))...)
|
||||||
|
|
||||||
|
// check for duplicate entries for the same driver in specs
|
||||||
|
if driverNamesInSpecs.Has(driver.Name) {
|
||||||
|
allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), driver.Name))
|
||||||
|
}
|
||||||
|
driverNamesInSpecs.Insert(driver.Name)
|
||||||
|
topoKeys := make(sets.String)
|
||||||
|
for _, key := range driver.TopologyKeys {
|
||||||
|
if len(key) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath, key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if topoKeys.Has(key) {
|
||||||
|
allErrs = append(allErrs, field.Duplicate(fldPath, key))
|
||||||
|
}
|
||||||
|
topoKeys.Insert(key)
|
||||||
|
|
||||||
|
for _, msg := range validation.IsQualifiedName(key) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, driver.TopologyKeys, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSIDriver validates a CSIDriver.
|
||||||
|
func ValidateCSIDriver(csiDriver *storage.CSIDriver) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(csiDriver.Name, field.NewPath("name"))...)
|
||||||
|
|
||||||
|
allErrs = append(allErrs, validateCSIDriverSpec(&csiDriver.Spec, field.NewPath("spec"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSIDriverUpdate validates a CSIDriver.
|
||||||
|
func ValidateCSIDriverUpdate(new, old *storage.CSIDriver) field.ErrorList {
|
||||||
|
allErrs := ValidateCSIDriver(new)
|
||||||
|
|
||||||
|
// Spec is read-only
|
||||||
|
// If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate
|
||||||
|
if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCSIDriverSpec tests that the specified CSIDriverSpec
|
||||||
|
// has valid data.
|
||||||
|
func validateCSIDriverSpec(
|
||||||
|
spec *storage.CSIDriverSpec, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAttachRequired tests if attachRequired is set for CSIDriver.
|
||||||
|
func validateAttachRequired(attachRequired *bool, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if attachRequired == nil {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
|
@ -870,3 +870,612 @@ func TestValidateAllowedTopologies(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCSINodeValidation(t *testing.T) {
|
||||||
|
driverName := "driver-name"
|
||||||
|
driverName2 := "1io.kubernetes-storage-2-csi-driver3"
|
||||||
|
longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
|
||||||
|
nodeID := "nodeA"
|
||||||
|
successCases := []storage.CSINode{
|
||||||
|
{
|
||||||
|
// driver name: dot only
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dash only
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io-kubernetes-storage-csi-driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: numbers
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "1io-kubernetes-storage-2-csi-driver3",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dot, dash
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo4"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage-csi-driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dot, dash, and numbers
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo5"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: driverName2,
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Driver name length 1
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "a",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// multiple drivers with different node IDs, topology keys
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo6"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1", "key2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "driverB",
|
||||||
|
NodeID: "nodeA",
|
||||||
|
TopologyKeys: []string{"keyA", "keyB"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// multiple drivers with same node IDs, topology keys
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "driver2",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// topology key names with -, _, and dot .
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo8"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"zone_1", "zone.2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "driver2",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"zone-3", "zone.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// topology prefix with - and dot.
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"company-com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// No topology keys
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo10"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: driverName,
|
||||||
|
NodeID: nodeID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiNode := range successCases {
|
||||||
|
if errs := ValidateCSINode(&csiNode); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorCases := []storage.CSINode{
|
||||||
|
{
|
||||||
|
// Empty driver name
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Invalid start char in driver name
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "_io.kubernetes.storage.csi.driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Invalid end char in driver name
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo4"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver/",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Invalid separators in driver name
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo5"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io/kubernetes/storage/csi~driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: underscore only
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo6"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io_kubernetes_storage_csi_driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Driver name length > 63
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: longName,
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// No driver name
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo8"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Empty individual topology key
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: driverName,
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// duplicate drivers in driver specs
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo10"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1", "key2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "nodeX",
|
||||||
|
TopologyKeys: []string{"keyA", "keyB"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// single driver with duplicate topology keys in driver specs
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo11"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1", "key1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// multiple drivers with one set of duplicate topology keys in driver specs
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo12"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "driver1",
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"key1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "driver2",
|
||||||
|
NodeID: "nodeX",
|
||||||
|
TopologyKeys: []string{"keyA", "keyA"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Empty NodeID
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo13"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: driverName,
|
||||||
|
NodeID: "",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// topology prefix should be lower case
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo14"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: driverName,
|
||||||
|
NodeID: "node1",
|
||||||
|
TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiNode := range errorCases {
|
||||||
|
if errs := ValidateCSINode(&csiNode); len(errs) == 0 {
|
||||||
|
t.Errorf("Expected failure for test: %v", csiNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSINodeUpdateValidation(t *testing.T) {
|
||||||
|
//driverName := "driver-name"
|
||||||
|
//driverName2 := "1io.kubernetes-storage-2-csi-driver3"
|
||||||
|
//longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
|
||||||
|
nodeID := "nodeA"
|
||||||
|
|
||||||
|
old := storage.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-2",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
successCases := []storage.CSINode{
|
||||||
|
{
|
||||||
|
// no change
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-2",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// remove a driver
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// add a driver
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-2",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-3",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// remove a driver and add a driver
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.new-driver",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiNode := range successCases {
|
||||||
|
if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success: %+v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCases := []storage.CSINode{
|
||||||
|
{
|
||||||
|
// invalid change node id
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: "nodeB",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-2",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid change topology keys
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-1",
|
||||||
|
NodeID: "nodeB",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "io.kubernetes.storage.csi.driver-2",
|
||||||
|
NodeID: nodeID,
|
||||||
|
TopologyKeys: []string{"company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiNode := range errorCases {
|
||||||
|
if errs := ValidateCSINodeUpdate(&csiNode, &old); len(errs) == 0 {
|
||||||
|
t.Errorf("Expected failure for test: %+v", csiNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSIDriverValidation(t *testing.T) {
|
||||||
|
driverName := "test-driver"
|
||||||
|
longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
|
||||||
|
invalidName := "-invalid-@#$%^&*()-"
|
||||||
|
attachRequired := true
|
||||||
|
attachNotRequired := false
|
||||||
|
podInfoOnMount := true
|
||||||
|
notPodInfoOnMount := false
|
||||||
|
successCases := []storage.CSIDriver{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: driverName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dot only
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dash only
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: numbers
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// driver name: dot and dash
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: driverName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: driverName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: driverName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachNotRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiDriver := range successCases {
|
||||||
|
if errs := ValidateCSIDriver(&csiDriver); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorCases := []storage.CSIDriver{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: invalidName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: longName},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachNotRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, csiDriver := range errorCases {
|
||||||
|
if errs := ValidateCSIDriver(&csiDriver); len(errs) == 0 {
|
||||||
|
t.Errorf("Expected failure for test: %v", csiDriver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ go_library(
|
||||||
"//pkg/apis/extensions:go_default_library",
|
"//pkg/apis/extensions:go_default_library",
|
||||||
"//pkg/apis/networking:go_default_library",
|
"//pkg/apis/networking:go_default_library",
|
||||||
"//pkg/apis/policy:go_default_library",
|
"//pkg/apis/policy:go_default_library",
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server/options:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server/options:go_default_library",
|
||||||
|
@ -28,6 +30,7 @@ go_library(
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server/resourceconfig:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server/resourceconfig:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/server/resourceconfig"
|
"k8s.io/apiserver/pkg/server/resourceconfig"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
"k8s.io/kubernetes/pkg/apis/batch"
|
"k8s.io/kubernetes/pkg/apis/batch"
|
||||||
|
@ -34,6 +35,8 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking"
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
"k8s.io/kubernetes/pkg/apis/policy"
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
|
apisstorage "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SpecialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
|
// SpecialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
|
||||||
|
@ -48,12 +51,23 @@ var SpecialDefaultResourcePrefixes = map[schema.GroupResource]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStorageFactoryConfig() *StorageFactoryConfig {
|
func NewStorageFactoryConfig() *StorageFactoryConfig {
|
||||||
|
|
||||||
|
resources := []schema.GroupVersionResource{
|
||||||
|
batch.Resource("cronjobs").WithVersion("v1beta1"),
|
||||||
|
}
|
||||||
|
// add csinodes if CSINodeInfo feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
|
||||||
|
resources = append(resources, apisstorage.Resource("csinodes").WithVersion("v1beta1"))
|
||||||
|
}
|
||||||
|
// add csidrivers if CSIDriverRegistry feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
|
||||||
|
resources = append(resources, apisstorage.Resource("csidrivers").WithVersion("v1beta1"))
|
||||||
|
}
|
||||||
|
|
||||||
return &StorageFactoryConfig{
|
return &StorageFactoryConfig{
|
||||||
Serializer: legacyscheme.Codecs,
|
Serializer: legacyscheme.Codecs,
|
||||||
DefaultResourceEncoding: serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Scheme),
|
DefaultResourceEncoding: serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Scheme),
|
||||||
ResourceEncodingOverrides: []schema.GroupVersionResource{
|
ResourceEncodingOverrides: resources,
|
||||||
batch.Resource("cronjobs").WithVersion("v1beta1"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,8 @@ filegroup(
|
||||||
"//pkg/registry/scheduling/rest:all-srcs",
|
"//pkg/registry/scheduling/rest:all-srcs",
|
||||||
"//pkg/registry/settings/podpreset:all-srcs",
|
"//pkg/registry/settings/podpreset:all-srcs",
|
||||||
"//pkg/registry/settings/rest:all-srcs",
|
"//pkg/registry/settings/rest:all-srcs",
|
||||||
|
"//pkg/registry/storage/csidriver:all-srcs",
|
||||||
|
"//pkg/registry/storage/csinode:all-srcs",
|
||||||
"//pkg/registry/storage/rest:all-srcs",
|
"//pkg/registry/storage/rest:all-srcs",
|
||||||
"//pkg/registry/storage/storageclass:all-srcs",
|
"//pkg/registry/storage/storageclass:all-srcs",
|
||||||
"//pkg/registry/storage/volumeattachment:all-srcs",
|
"//pkg/registry/storage/volumeattachment:all-srcs",
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"strategy.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/registry/storage/csidriver",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/legacyscheme:go_default_library",
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/apis/storage/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//pkg/registry/storage/csidriver/storage:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["strategy_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csidriver provides Registry interface and its REST
|
||||||
|
// implementation for storing csidriver api objects.
|
||||||
|
package csidriver
|
|
@ -0,0 +1,48 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["storage.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/registry/storage/csidriver/storage",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/registry/storage/csidriver:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry: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 = ["storage_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/testapi:go_default_library",
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/registry/registrytest:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/storage/v1beta1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/storage/csidriver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CSIDriverStorage includes storage for CSIDrivers and all subresources
|
||||||
|
type CSIDriverStorage struct {
|
||||||
|
CSIDriver *REST
|
||||||
|
}
|
||||||
|
|
||||||
|
// REST object that will work for CSIDrivers
|
||||||
|
type REST struct {
|
||||||
|
*genericregistry.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage returns a RESTStorage object that will work against CSIDrivers
|
||||||
|
func NewStorage(optsGetter generic.RESTOptionsGetter) *CSIDriverStorage {
|
||||||
|
store := &genericregistry.Store{
|
||||||
|
NewFunc: func() runtime.Object { return &storageapi.CSIDriver{} },
|
||||||
|
NewListFunc: func() runtime.Object { return &storageapi.CSIDriverList{} },
|
||||||
|
DefaultQualifiedResource: storageapi.Resource("csidrivers"),
|
||||||
|
|
||||||
|
CreateStrategy: csidriver.Strategy,
|
||||||
|
UpdateStrategy: csidriver.Strategy,
|
||||||
|
DeleteStrategy: csidriver.Strategy,
|
||||||
|
ReturnDeletedObject: true,
|
||||||
|
}
|
||||||
|
options := &generic.StoreOptions{RESTOptions: optsGetter}
|
||||||
|
if err := store.CompleteWithOptions(options); err != nil {
|
||||||
|
panic(err) // TODO: Propagate error up
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CSIDriverStorage{
|
||||||
|
CSIDriver: &REST{store},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
storageapiv1beta1 "k8s.io/api/storage/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||||
|
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
||||||
|
etcdStorage, server := registrytest.NewEtcdStorage(t, storageapi.GroupName)
|
||||||
|
restOptions := generic.RESTOptions{
|
||||||
|
StorageConfig: etcdStorage,
|
||||||
|
Decorator: generic.UndecoratedStorage,
|
||||||
|
DeleteCollectionWorkers: 1,
|
||||||
|
ResourcePrefix: "csidrivers",
|
||||||
|
}
|
||||||
|
csiDriverStorage := NewStorage(restOptions)
|
||||||
|
return csiDriverStorage.CSIDriver, server
|
||||||
|
}
|
||||||
|
|
||||||
|
func validNewCSIDriver(name string) *storageapi.CSIDriver {
|
||||||
|
attachRequired := true
|
||||||
|
podInfoOnMount := true
|
||||||
|
return &storageapi.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Spec: storageapi.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
csiDriver := validNewCSIDriver("foo")
|
||||||
|
csiDriver.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"}
|
||||||
|
attachNotRequired := false
|
||||||
|
notPodInfoOnMount := false
|
||||||
|
test.TestCreate(
|
||||||
|
// valid
|
||||||
|
csiDriver,
|
||||||
|
// invalid
|
||||||
|
&storageapi.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "*BadName!"},
|
||||||
|
Spec: storageapi.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachNotRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
notPodInfoOnMount := false
|
||||||
|
|
||||||
|
test.TestUpdate(
|
||||||
|
// valid
|
||||||
|
validNewCSIDriver("foo"),
|
||||||
|
//invalid update
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*storageapi.CSIDriver)
|
||||||
|
object.Spec.PodInfoOnMount = ¬PodInfoOnMount
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject()
|
||||||
|
test.TestDelete(validNewCSIDriver("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestGet(validNewCSIDriver("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestList(validNewCSIDriver("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatch(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestWatch(
|
||||||
|
validNewCSIDriver("foo"),
|
||||||
|
// matching labels
|
||||||
|
[]labels.Set{},
|
||||||
|
// not matching labels
|
||||||
|
[]labels.Set{
|
||||||
|
{"foo": "bar"},
|
||||||
|
},
|
||||||
|
// matching fields
|
||||||
|
[]fields.Set{
|
||||||
|
{"metadata.name": "foo"},
|
||||||
|
},
|
||||||
|
// not matching fields
|
||||||
|
[]fields.Set{
|
||||||
|
{"metadata.name": "bar"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csidriver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// csiDriverStrategy implements behavior for CSIDriver objects
|
||||||
|
type csiDriverStrategy struct {
|
||||||
|
runtime.ObjectTyper
|
||||||
|
names.NameGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy is the default logic that applies when creating and updating
|
||||||
|
// CSIDriver objects via the REST API.
|
||||||
|
var Strategy = csiDriverStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
||||||
|
|
||||||
|
func (csiDriverStrategy) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation.
|
||||||
|
func (csiDriverStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiDriverStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
|
csiDriver := obj.(*storage.CSIDriver)
|
||||||
|
|
||||||
|
errs := validation.ValidateCSIDriver(csiDriver)
|
||||||
|
errs = append(errs, validation.ValidateCSIDriver(csiDriver)...)
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonicalize normalizes the object after validation.
|
||||||
|
func (csiDriverStrategy) Canonicalize(obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiDriverStrategy) AllowCreateOnUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a CSIDriver
|
||||||
|
func (csiDriverStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiDriverStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
newCSIDriverObj := obj.(*storage.CSIDriver)
|
||||||
|
oldCSIDriverObj := old.(*storage.CSIDriver)
|
||||||
|
errorList := validation.ValidateCSIDriver(newCSIDriverObj)
|
||||||
|
return append(errorList, validation.ValidateCSIDriverUpdate(newCSIDriverObj, oldCSIDriverObj)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiDriverStrategy) AllowUnconditionalUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csidriver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getValidCSIDriver(name string) *storage.CSIDriver {
|
||||||
|
attachRequired := true
|
||||||
|
podInfoOnMount := true
|
||||||
|
return &storage.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSIDriverStrategy(t *testing.T) {
|
||||||
|
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
||||||
|
APIGroup: "storage.k8s.io",
|
||||||
|
APIVersion: "v1beta1",
|
||||||
|
Resource: "csidrivers",
|
||||||
|
})
|
||||||
|
if Strategy.NamespaceScoped() {
|
||||||
|
t.Errorf("CSIDriver must not be namespace scoped")
|
||||||
|
}
|
||||||
|
if Strategy.AllowCreateOnUpdate() {
|
||||||
|
t.Errorf("CSIDriver should not allow create on update")
|
||||||
|
}
|
||||||
|
|
||||||
|
csiDriver := getValidCSIDriver("valid-csidriver")
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(ctx, csiDriver)
|
||||||
|
|
||||||
|
errs := Strategy.Validate(ctx, csiDriver)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("unexpected error validating %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update of spec is disallowed
|
||||||
|
newCSIDriver := csiDriver.DeepCopy()
|
||||||
|
attachNotRequired := false
|
||||||
|
newCSIDriver.Spec.AttachRequired = &attachNotRequired
|
||||||
|
|
||||||
|
Strategy.PrepareForUpdate(ctx, newCSIDriver, csiDriver)
|
||||||
|
|
||||||
|
errs = Strategy.ValidateUpdate(ctx, newCSIDriver, csiDriver)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("Expected a validation error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSIDriverValidation(t *testing.T) {
|
||||||
|
attachRequired := true
|
||||||
|
notAttachRequired := false
|
||||||
|
podInfoOnMount := true
|
||||||
|
notPodInfoOnMount := false
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
csiDriver *storage.CSIDriver
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid csidriver",
|
||||||
|
getValidCSIDriver("foo"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"true PodInfoOnMount and AttachRequired",
|
||||||
|
&storage.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"false PodInfoOnMount and AttachRequired",
|
||||||
|
&storage.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: ¬AttachRequired,
|
||||||
|
PodInfoOnMount: ¬PodInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid driver name",
|
||||||
|
&storage.CSIDriver{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "*foo#",
|
||||||
|
},
|
||||||
|
Spec: storage.CSIDriverSpec{
|
||||||
|
AttachRequired: &attachRequired,
|
||||||
|
PodInfoOnMount: &podInfoOnMount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
testValidation := func(csiDriver *storage.CSIDriver, apiVersion string) field.ErrorList {
|
||||||
|
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
||||||
|
APIGroup: "storage.k8s.io",
|
||||||
|
APIVersion: "v1beta1",
|
||||||
|
Resource: "csidrivers",
|
||||||
|
})
|
||||||
|
return Strategy.Validate(ctx, csiDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
betaErr := testValidation(test.csiDriver, "v1beta1")
|
||||||
|
if len(betaErr) > 0 && !test.expectError {
|
||||||
|
t.Errorf("Validation of v1beta1 object failed: %+v", betaErr)
|
||||||
|
}
|
||||||
|
if len(betaErr) == 0 && test.expectError {
|
||||||
|
t.Errorf("Validation of v1beta1 object unexpectedly succeeded")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"strategy.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/registry/storage/csinode",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/legacyscheme:go_default_library",
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/apis/storage/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//pkg/registry/storage/csinode/storage:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["strategy_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csinode provides Registry interface and its REST
|
||||||
|
// implementation for storing csinode api objects.
|
||||||
|
package csinode
|
|
@ -0,0 +1,48 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["storage.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/registry/storage/csinode/storage",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/registry/storage/csinode:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry: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 = ["storage_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/testapi:go_default_library",
|
||||||
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/registry/registrytest:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/storage/v1beta1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/storage/csinode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CSINodeStorage includes storage for CSINodes and all subresources
|
||||||
|
type CSINodeStorage struct {
|
||||||
|
CSINode *REST
|
||||||
|
}
|
||||||
|
|
||||||
|
// REST object that will work for CSINodes
|
||||||
|
type REST struct {
|
||||||
|
*genericregistry.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage returns a RESTStorage object that will work against CSINodes
|
||||||
|
func NewStorage(optsGetter generic.RESTOptionsGetter) *CSINodeStorage {
|
||||||
|
store := &genericregistry.Store{
|
||||||
|
NewFunc: func() runtime.Object { return &storageapi.CSINode{} },
|
||||||
|
NewListFunc: func() runtime.Object { return &storageapi.CSINodeList{} },
|
||||||
|
DefaultQualifiedResource: storageapi.Resource("csinodes"),
|
||||||
|
|
||||||
|
CreateStrategy: csinode.Strategy,
|
||||||
|
UpdateStrategy: csinode.Strategy,
|
||||||
|
DeleteStrategy: csinode.Strategy,
|
||||||
|
ReturnDeletedObject: true,
|
||||||
|
}
|
||||||
|
options := &generic.StoreOptions{RESTOptions: optsGetter}
|
||||||
|
if err := store.CompleteWithOptions(options); err != nil {
|
||||||
|
panic(err) // TODO: Propagate error up
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CSINodeStorage{
|
||||||
|
CSINode: &REST{store},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
storageapiv1beta1 "k8s.io/api/storage/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||||
|
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
||||||
|
etcdStorage, server := registrytest.NewEtcdStorage(t, storageapi.GroupName)
|
||||||
|
restOptions := generic.RESTOptions{
|
||||||
|
StorageConfig: etcdStorage,
|
||||||
|
Decorator: generic.UndecoratedStorage,
|
||||||
|
DeleteCollectionWorkers: 1,
|
||||||
|
ResourcePrefix: "csinodes",
|
||||||
|
}
|
||||||
|
csiNodeStorage := NewStorage(restOptions)
|
||||||
|
return csiNodeStorage.CSINode, server
|
||||||
|
}
|
||||||
|
|
||||||
|
func validNewCSINode(name string) *storageapi.CSINode {
|
||||||
|
return &storageapi.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Spec: storageapi.CSINodeSpec{
|
||||||
|
Drivers: []storageapi.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "valid-driver-name",
|
||||||
|
NodeID: "valid-node",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
csiNode := validNewCSINode("foo")
|
||||||
|
csiNode.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"}
|
||||||
|
test.TestCreate(
|
||||||
|
// valid
|
||||||
|
csiNode,
|
||||||
|
// invalid
|
||||||
|
&storageapi.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "*BadName!"},
|
||||||
|
Spec: storageapi.CSINodeSpec{
|
||||||
|
Drivers: []storageapi.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "invalid-name-!@#$%^&*()",
|
||||||
|
NodeID: "invalid-node-!@#$%^&*()",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
|
||||||
|
test.TestUpdate(
|
||||||
|
// valid
|
||||||
|
validNewCSINode("foo"),
|
||||||
|
// we allow status field to be set in v1beta1
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*storageapi.CSINode)
|
||||||
|
//object.Status = *getCSINodeStatus()
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
//invalid update
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*storageapi.CSINode)
|
||||||
|
object.Spec.Drivers[0].Name = "invalid-name-!@#$%^&*()"
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject()
|
||||||
|
test.TestDelete(validNewCSINode("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestGet(validNewCSINode("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestList(validNewCSINode("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatch(t *testing.T) {
|
||||||
|
if *testapi.Storage.GroupVersion() != storageapiv1beta1.SchemeGroupVersion {
|
||||||
|
// skip the test for all versions exception v1beta1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||||
|
test.TestWatch(
|
||||||
|
validNewCSINode("foo"),
|
||||||
|
// matching labels
|
||||||
|
[]labels.Set{},
|
||||||
|
// not matching labels
|
||||||
|
[]labels.Set{
|
||||||
|
{"foo": "bar"},
|
||||||
|
},
|
||||||
|
// matching fields
|
||||||
|
[]fields.Set{
|
||||||
|
{"metadata.name": "foo"},
|
||||||
|
},
|
||||||
|
// not matching fields
|
||||||
|
[]fields.Set{
|
||||||
|
{"metadata.name": "bar"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csinode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// csiNodeStrategy implements behavior for CSINode objects
|
||||||
|
type csiNodeStrategy struct {
|
||||||
|
runtime.ObjectTyper
|
||||||
|
names.NameGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy is the default logic that applies when creating and updating
|
||||||
|
// CSINode objects via the REST API.
|
||||||
|
var Strategy = csiNodeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
||||||
|
|
||||||
|
func (csiNodeStrategy) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation.
|
||||||
|
func (csiNodeStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiNodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
|
csiNode := obj.(*storage.CSINode)
|
||||||
|
|
||||||
|
errs := validation.ValidateCSINode(csiNode)
|
||||||
|
errs = append(errs, validation.ValidateCSINode(csiNode)...)
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonicalize normalizes the object after validation.
|
||||||
|
func (csiNodeStrategy) Canonicalize(obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiNodeStrategy) AllowCreateOnUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a CSINode
|
||||||
|
func (csiNodeStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiNodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
newCSINodeObj := obj.(*storage.CSINode)
|
||||||
|
oldCSINodeObj := old.(*storage.CSINode)
|
||||||
|
errorList := validation.ValidateCSINode(newCSINodeObj)
|
||||||
|
return append(errorList, validation.ValidateCSINodeUpdate(newCSINodeObj, oldCSINodeObj)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiNodeStrategy) AllowUnconditionalUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 csinode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getValidCSINode(name string) *storage.CSINode {
|
||||||
|
return &storage.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "valid-driver-name",
|
||||||
|
NodeID: "valid-node",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSINodeStrategy(t *testing.T) {
|
||||||
|
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
||||||
|
APIGroup: "storage.k8s.io",
|
||||||
|
APIVersion: "v1beta1",
|
||||||
|
Resource: "csinodes",
|
||||||
|
})
|
||||||
|
if Strategy.NamespaceScoped() {
|
||||||
|
t.Errorf("CSINode must not be namespace scoped")
|
||||||
|
}
|
||||||
|
if Strategy.AllowCreateOnUpdate() {
|
||||||
|
t.Errorf("CSINode should not allow create on update")
|
||||||
|
}
|
||||||
|
|
||||||
|
csiNode := getValidCSINode("valid-csinode")
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(ctx, csiNode)
|
||||||
|
|
||||||
|
errs := Strategy.Validate(ctx, csiNode)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("unexpected error validating %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update of spec is allowed
|
||||||
|
newCSINode := csiNode.DeepCopy()
|
||||||
|
newCSINode.Spec.Drivers[0].NodeID = "valid-node-2"
|
||||||
|
|
||||||
|
Strategy.PrepareForUpdate(ctx, newCSINode, csiNode)
|
||||||
|
|
||||||
|
errs = Strategy.ValidateUpdate(ctx, newCSINode, csiNode)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("expected validation error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSINodeValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
csiNode *storage.CSINode
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid csinode",
|
||||||
|
getValidCSINode("foo"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid driver name",
|
||||||
|
&storage.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "$csi-driver@",
|
||||||
|
NodeID: "valid-node",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty node id",
|
||||||
|
&storage.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "valid-driver-name",
|
||||||
|
NodeID: "",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid topology keys",
|
||||||
|
&storage.CSINode{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Spec: storage.CSINodeSpec{
|
||||||
|
Drivers: []storage.CSINodeDriver{
|
||||||
|
{
|
||||||
|
Name: "valid-driver-name",
|
||||||
|
NodeID: "valid-node",
|
||||||
|
TopologyKeys: []string{"company.com/zone1", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
testValidation := func(csiNode *storage.CSINode, apiVersion string) field.ErrorList {
|
||||||
|
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
|
||||||
|
APIGroup: "storage.k8s.io",
|
||||||
|
APIVersion: "v1beta1",
|
||||||
|
Resource: "csinodes",
|
||||||
|
})
|
||||||
|
return Strategy.Validate(ctx, csiNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
betaErr := testValidation(test.csiNode, "v1beta1")
|
||||||
|
if len(betaErr) > 0 && !test.expectError {
|
||||||
|
t.Errorf("Validation of v1beta1 object failed: %+v", betaErr)
|
||||||
|
}
|
||||||
|
if len(betaErr) == 0 && test.expectError {
|
||||||
|
t.Errorf("Validation of v1beta1 object unexpectedly succeeded")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ go_library(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api/legacyscheme:go_default_library",
|
"//pkg/api/legacyscheme:go_default_library",
|
||||||
"//pkg/apis/storage:go_default_library",
|
"//pkg/apis/storage:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
|
"//pkg/registry/storage/csidriver/storage:go_default_library",
|
||||||
|
"//pkg/registry/storage/csinode/storage:go_default_library",
|
||||||
"//pkg/registry/storage/storageclass/storage:go_default_library",
|
"//pkg/registry/storage/storageclass/storage:go_default_library",
|
||||||
"//pkg/registry/storage/volumeattachment/storage:go_default_library",
|
"//pkg/registry/storage/volumeattachment/storage:go_default_library",
|
||||||
"//staging/src/k8s.io/api/storage/v1:go_default_library",
|
"//staging/src/k8s.io/api/storage/v1:go_default_library",
|
||||||
|
@ -21,6 +24,7 @@ go_library(
|
||||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,12 @@ import (
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
csidriverstore "k8s.io/kubernetes/pkg/registry/storage/csidriver/storage"
|
||||||
|
csinodestore "k8s.io/kubernetes/pkg/registry/storage/csinode/storage"
|
||||||
storageclassstore "k8s.io/kubernetes/pkg/registry/storage/storageclass/storage"
|
storageclassstore "k8s.io/kubernetes/pkg/registry/storage/storageclass/storage"
|
||||||
volumeattachmentstore "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage"
|
volumeattachmentstore "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage"
|
||||||
)
|
)
|
||||||
|
@ -70,6 +74,18 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
|
||||||
volumeAttachmentStorage := volumeattachmentstore.NewStorage(restOptionsGetter)
|
volumeAttachmentStorage := volumeattachmentstore.NewStorage(restOptionsGetter)
|
||||||
storage["volumeattachments"] = volumeAttachmentStorage.VolumeAttachment
|
storage["volumeattachments"] = volumeAttachmentStorage.VolumeAttachment
|
||||||
|
|
||||||
|
// register csinodes if CSINodeInfo feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
|
||||||
|
csiNodeStorage := csinodestore.NewStorage(restOptionsGetter)
|
||||||
|
storage["csinodes"] = csiNodeStorage.CSINode
|
||||||
|
}
|
||||||
|
|
||||||
|
// register csidrivers if CSIDriverRegistry feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
|
||||||
|
csiDriverStorage := csidriverstore.NewStorage(restOptionsGetter)
|
||||||
|
storage["csidrivers"] = csiDriverStorage.CSIDriver
|
||||||
|
}
|
||||||
|
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,12 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
|
||||||
&VolumeAttachment{},
|
&VolumeAttachment{},
|
||||||
&VolumeAttachmentList{},
|
&VolumeAttachmentList{},
|
||||||
|
|
||||||
|
&CSIDriver{},
|
||||||
|
&CSIDriverList{},
|
||||||
|
|
||||||
|
&CSINode{},
|
||||||
|
&CSINodeList{},
|
||||||
)
|
)
|
||||||
|
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
|
|
@ -209,3 +209,158 @@ type VolumeError struct {
|
||||||
// +optional
|
// +optional
|
||||||
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
|
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSIDriver captures information about a Container Storage Interface (CSI)
|
||||||
|
// volume driver deployed on the cluster.
|
||||||
|
// CSI drivers do not need to create the CSIDriver object directly. Instead they may use the
|
||||||
|
// cluster-driver-registrar sidecar container. When deployed with a CSI driver it automatically
|
||||||
|
// creates a CSIDriver object representing the driver.
|
||||||
|
// Kubernetes attach detach controller uses this object to determine whether attach is required.
|
||||||
|
// Kubelet uses this object to determine whether pod information needs to be passed on mount.
|
||||||
|
// CSIDriver objects are non-namespaced.
|
||||||
|
type CSIDriver struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Standard object metadata.
|
||||||
|
// metadata.Name indicates the name of the CSI driver that this object
|
||||||
|
// refers to; it MUST be the same name returned by the CSI GetPluginName()
|
||||||
|
// call for that driver.
|
||||||
|
// The driver name must be 63 characters or less, beginning and ending with
|
||||||
|
// an alphanumeric character ([a-z0-9A-Z]) with dashes (-), dots (.), and
|
||||||
|
// alphanumerics between.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// Specification of the CSI Driver.
|
||||||
|
Spec CSIDriverSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSIDriverList is a collection of CSIDriver objects.
|
||||||
|
type CSIDriverList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Standard list metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// items is the list of CSIDriver
|
||||||
|
Items []CSIDriver `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSIDriverSpec is the specification of a CSIDriver.
|
||||||
|
type CSIDriverSpec struct {
|
||||||
|
// attachRequired indicates this CSI volume driver requires an attach
|
||||||
|
// operation (because it implements the CSI ControllerPublishVolume()
|
||||||
|
// method), and that the Kubernetes attach detach controller should call
|
||||||
|
// the attach volume interface which checks the volumeattachment status
|
||||||
|
// and waits until the volume is attached before proceeding to mounting.
|
||||||
|
// The CSI external-attacher coordinates with CSI volume driver and updates
|
||||||
|
// the volumeattachment status when the attach operation is complete.
|
||||||
|
// If the CSIDriverRegistry feature gate is enabled and the value is
|
||||||
|
// specified to false, the attach operation will be skipped.
|
||||||
|
// Otherwise the attach operation will be called.
|
||||||
|
// +optional
|
||||||
|
AttachRequired *bool `json:"attachRequired,omitempty" protobuf:"varint,1,opt,name=attachRequired"`
|
||||||
|
|
||||||
|
// If set to true, podInfoOnMount indicates this CSI volume driver
|
||||||
|
// requires additional pod information (like podName, podUID, etc.) during
|
||||||
|
// mount operations.
|
||||||
|
// If not set or set to false, pod information will not be passed on mount.
|
||||||
|
// The CSI driver specifies podInfoOnMount as part of driver deployment.
|
||||||
|
// If true, Kubelet will pass pod information as VolumeContext in the CSI
|
||||||
|
// NodePublishVolume() calls.
|
||||||
|
// The CSI driver is responsible for parsing and validating the information
|
||||||
|
// passed in as VolumeContext.
|
||||||
|
// The following VolumeConext will be passed if podInfoOnMount is set to true:
|
||||||
|
// "csi.storage.k8s.io/pod.name": pod.Name
|
||||||
|
// "csi.storage.k8s.io/pod.namespace": pod.Namespace
|
||||||
|
// "csi.storage.k8s.io/pod.uid": string(pod.UID)
|
||||||
|
// +optional
|
||||||
|
PodInfoOnMount *bool `json:"podInfoOnMount,omitempty" protobuf:"bytes,2,opt,name=podInfoOnMount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSINode holds information about all CSI drivers installed on a node.
|
||||||
|
// CSI drivers do not need to create the CSINode object directly. As long as
|
||||||
|
// they use the node-driver-registrar sidecar container, the kubelet will
|
||||||
|
// automatically populate the CSINode object for the CSI driver as part of
|
||||||
|
// kubelet plugin registration.
|
||||||
|
// CSINode has the same name as a node. If the object is missing, it means either
|
||||||
|
// there are no CSI Drivers available on the node, or the Kubelet version is low
|
||||||
|
// enough that it doesn't create this object.
|
||||||
|
// CSINode has an OwnerReference that points to the corresponding node object.
|
||||||
|
type CSINode struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// metadata.name must be the Kubernetes node name.
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// spec is the specification of CSINode
|
||||||
|
Spec CSINodeSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSINodeSpec holds information about the specification of all CSI drivers installed on a node
|
||||||
|
type CSINodeSpec struct {
|
||||||
|
// drivers is a list of information of all CSI Drivers existing on a node.
|
||||||
|
// It can be empty on initialization.
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
Drivers []CSINodeDriver `json:"drivers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,1,rep,name=drivers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSINodeDriver holds information about the specification of one CSI driver installed on a node
|
||||||
|
type CSINodeDriver struct {
|
||||||
|
// This is the name of the CSI driver that this object refers to.
|
||||||
|
// This MUST be the same name returned by the CSI GetPluginName() call for
|
||||||
|
// that driver.
|
||||||
|
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
|
|
||||||
|
// nodeID of the node from the driver point of view.
|
||||||
|
// This field enables Kubernetes to communicate with storage systems that do
|
||||||
|
// not share the same nomenclature for nodes. For example, Kubernetes may
|
||||||
|
// refer to a given node as "node1", but the storage system may refer to
|
||||||
|
// the same node as "nodeA". When Kubernetes issues a command to the storage
|
||||||
|
// system to attach a volume to a specific node, it can use this field to
|
||||||
|
// refer to the node name using the ID that the storage system will
|
||||||
|
// understand, e.g. "nodeA" instead of "node1". This field is required.
|
||||||
|
NodeID string `json:"nodeID" protobuf:"bytes,2,opt,name=nodeID"`
|
||||||
|
|
||||||
|
// topologyKeys is the list of keys supported by the driver.
|
||||||
|
// When a driver is initialized on a cluster, it provides a set of topology
|
||||||
|
// keys that it understands (e.g. "company.com/zone", "company.com/region").
|
||||||
|
// When a driver is initialized on a node, it provides the same topology keys
|
||||||
|
// along with values. Kubelet will expose these topology keys as labels
|
||||||
|
// on its own node object.
|
||||||
|
// When Kubernetes does topology aware provisioning, it can use this list to
|
||||||
|
// determine which labels it should retrieve from the node object and pass
|
||||||
|
// back to the driver.
|
||||||
|
// It is possible for different nodes to use different topology keys.
|
||||||
|
// This can be empty if driver does not support topology.
|
||||||
|
// +optional
|
||||||
|
TopologyKeys []string `json:"topologyKeys" protobuf:"bytes,3,rep,name=topologyKeys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CSINodeList is a collection of CSINode objects.
|
||||||
|
type CSINodeList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Standard list metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// items is the list of CSINode
|
||||||
|
Items []CSINode `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
|
}
|
||||||
|
|
|
@ -133,6 +133,8 @@ var missingHanlders = sets.NewString(
|
||||||
"PriorityClass",
|
"PriorityClass",
|
||||||
"PodPreset",
|
"PodPreset",
|
||||||
"AuditSink",
|
"AuditSink",
|
||||||
|
"CSINode",
|
||||||
|
"CSIDriver",
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerSidePrint(t *testing.T) {
|
func TestServerSidePrint(t *testing.T) {
|
||||||
|
|
|
@ -55,6 +55,7 @@ go_library(
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kube-apiserver/app:go_default_library",
|
"//cmd/kube-apiserver/app:go_default_library",
|
||||||
"//cmd/kube-apiserver/app/options:go_default_library",
|
"//cmd/kube-apiserver/app/options:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/master:go_default_library",
|
"//pkg/master:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
||||||
|
@ -65,6 +66,7 @@ go_library(
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/server/options:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/server/options:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/discovery/cached/memory:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery/cached/memory:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
|
|
@ -20,13 +20,15 @@ import (
|
||||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetEtcdStorageData returns etcd data for all persisted objects.
|
// GetEtcdStorageData returns etcd data for all persisted objects.
|
||||||
// It is exported so that it can be reused across multiple tests.
|
// It is exported so that it can be reused across multiple tests.
|
||||||
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
||||||
func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
||||||
return map[schema.GroupVersionResource]StorageData{
|
etcdStorageData := map[schema.GroupVersionResource]StorageData{
|
||||||
// k8s.io/kubernetes/pkg/api/v1
|
// k8s.io/kubernetes/pkg/api/v1
|
||||||
gvr("", "v1", "configmaps"): {
|
gvr("", "v1", "configmaps"): {
|
||||||
Stub: `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
|
Stub: `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
|
||||||
|
@ -484,6 +486,26 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
||||||
},
|
},
|
||||||
// --
|
// --
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// k8s.io/kubernetes/pkg/apis/storage/v1beta1
|
||||||
|
// add csinodes if CSINodeInfo feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
|
||||||
|
etcdStorageData[gvr("storage.k8s.io", "v1beta1", "csinodes")] = StorageData{
|
||||||
|
Stub: `{"metadata": {"name": "csini1"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}`,
|
||||||
|
ExpectedEtcdPath: "/registry/csinodes/csini1",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// k8s.io/kubernetes/pkg/apis/storage/v1beta1
|
||||||
|
// add csidrivers if CSIDriverRegistry feature gate is enabled
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
|
||||||
|
etcdStorageData[gvr("storage.k8s.io", "v1beta1", "csidrivers")] = StorageData{
|
||||||
|
Stub: `{"metadata": {"name": "csid1"}, "spec": {"attachRequired": true, "podInfoOnMount": true}}`,
|
||||||
|
ExpectedEtcdPath: "/registry/csidrivers/csid1",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return etcdStorageData
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageData contains information required to create an object and verify its storage in etcd
|
// StorageData contains information required to create an object and verify its storage in etcd
|
||||||
|
|
|
@ -267,6 +267,8 @@ func NewMasterConfig() *master.Config {
|
||||||
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: batch.GroupName, Resource: "cronjobs"}, schema.GroupVersion{Group: batch.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
|
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: batch.GroupName, Resource: "cronjobs"}, schema.GroupVersion{Group: batch.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
|
||||||
// we also need to set both for the storage group and for volumeattachments, separately
|
// we also need to set both for the storage group and for volumeattachments, separately
|
||||||
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: storage.GroupName, Resource: "volumeattachments"}, schema.GroupVersion{Group: storage.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
|
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: storage.GroupName, Resource: "volumeattachments"}, schema.GroupVersion{Group: storage.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
|
||||||
|
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: storage.GroupName, Resource: "csinodes"}, schema.GroupVersion{Group: storage.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
|
||||||
|
resourceEncoding.SetResourceEncoding(schema.GroupResource{Group: storage.GroupName, Resource: "csidrivers"}, schema.GroupVersion{Group: storage.GroupName, Version: "v1beta1"}, schema.GroupVersion{Group: storage.GroupName, Version: runtime.APIVersionInternal})
|
||||||
|
|
||||||
storageFactory := serverstorage.NewDefaultStorageFactory(etcdOptions.StorageConfig, runtime.ContentTypeJSON, ns, resourceEncoding, master.DefaultAPIResourceConfigSource(), nil)
|
storageFactory := serverstorage.NewDefaultStorageFactory(etcdOptions.StorageConfig, runtime.ContentTypeJSON, ns, resourceEncoding, master.DefaultAPIResourceConfigSource(), nil)
|
||||||
storageFactory.SetSerializer(
|
storageFactory.SetSerializer(
|
||||||
|
|
Loading…
Reference in New Issue