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_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_NEW_STORAGE_VERSIONS="storage.k8s.io/v1"
|
||||
KUBE_NEW_STORAGE_VERSIONS="storage.k8s.io/v1beta1,storage.k8s.io/v1"
|
||||
|
||||
### END TEST DEFINITION CUSTOMIZATION ###
|
||||
|
||||
|
|
|
@ -142,6 +142,8 @@ func TestDefaulting(t *testing.T) {
|
|||
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
|
||||
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},
|
||||
{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: "StorageClassList"}: {},
|
||||
{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 isNotIntegerErrorMsg string = `must be an integer`
|
||||
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 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 iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
|
||||
|
||||
var csiDriverNameRexp = regexp.MustCompile(csiDriverNameRexpFmt)
|
||||
|
||||
// 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 {
|
||||
allErrs := field.ErrorList{}
|
||||
|
@ -1446,9 +1442,10 @@ func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorLi
|
|||
allErrs = append(allErrs, field.TooLong(fldPath, driverName, 63))
|
||||
}
|
||||
|
||||
if !csiDriverNameRexp.MatchString(driverName) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, driverName, validation.RegexError(csiDriverNameRexpErrMsg, csiDriverNameRexpFmt, "csi-hostpath")))
|
||||
for _, msg := range validation.IsDNS1123Subdomain(strings.ToLower(driverName)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, driverName, msg))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
|
@ -1775,24 +1775,30 @@ func TestValidateCSIVolumeSource(t *testing.T) {
|
|||
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"},
|
||||
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"},
|
||||
errtype: field.ErrorTypeInvalid,
|
||||
errfield: "driver",
|
||||
},
|
||||
{
|
||||
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",
|
||||
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"},
|
||||
errtype: field.ErrorTypeInvalid,
|
||||
errfield: "driver",
|
||||
},
|
||||
{
|
||||
name: "driver name: invalid length 0",
|
||||
|
@ -1801,10 +1807,8 @@ func TestValidateCSIVolumeSource(t *testing.T) {
|
|||
errfield: "driver",
|
||||
},
|
||||
{
|
||||
name: "driver name: invalid length 1",
|
||||
name: "driver name: ok length 1",
|
||||
csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
|
||||
errtype: field.ErrorTypeInvalid,
|
||||
errfield: "driver",
|
||||
},
|
||||
{
|
||||
name: "driver name: invalid length > 63",
|
||||
|
|
|
@ -34,5 +34,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||
bindingModes := []storage.VolumeBindingMode{storage.VolumeBindingImmediate, storage.VolumeBindingWaitForFirstConsumer}
|
||||
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{},
|
||||
&VolumeAttachment{},
|
||||
&VolumeAttachmentList{},
|
||||
&CSINode{},
|
||||
&CSINodeList{},
|
||||
&CSIDriver{},
|
||||
&CSIDriverList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -215,3 +215,158 @@ const (
|
|||
// binding will occur during Pod scheduing.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
|
@ -36,6 +37,8 @@ const (
|
|||
|
||||
maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
|
||||
maxVolumeErrorMessageSize = 1024
|
||||
|
||||
csiNodeIDMaxLength = 128
|
||||
)
|
||||
|
||||
// ValidateStorageClass validates a StorageClass.
|
||||
|
@ -266,3 +269,133 @@ func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *f
|
|||
|
||||
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/networking: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/schema: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/storage: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"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/apps"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
|
@ -34,6 +35,8 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apis/networking"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
apisstorage "k8s.io/kubernetes/pkg/apis/storage"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// SpecialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
|
||||
|
@ -48,12 +51,23 @@ var SpecialDefaultResourcePrefixes = map[schema.GroupResource]string{
|
|||
}
|
||||
|
||||
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{
|
||||
Serializer: legacyscheme.Codecs,
|
||||
DefaultResourceEncoding: serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Scheme),
|
||||
ResourceEncodingOverrides: []schema.GroupVersionResource{
|
||||
batch.Resource("cronjobs").WithVersion("v1beta1"),
|
||||
},
|
||||
ResourceEncodingOverrides: resources,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ filegroup(
|
|||
"//pkg/registry/scheduling/rest:all-srcs",
|
||||
"//pkg/registry/settings/podpreset: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/storageclass: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 = [
|
||||
"//pkg/api/legacyscheme: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/volumeattachment/storage: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/server: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"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
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"
|
||||
volumeattachmentstore "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage"
|
||||
)
|
||||
|
@ -70,6 +74,18 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
|
|||
volumeAttachmentStorage := volumeattachmentstore.NewStorage(restOptionsGetter)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,12 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||
|
||||
&VolumeAttachment{},
|
||||
&VolumeAttachmentList{},
|
||||
|
||||
&CSIDriver{},
|
||||
&CSIDriverList{},
|
||||
|
||||
&CSINode{},
|
||||
&CSINodeList{},
|
||||
)
|
||||
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
|
|
|
@ -209,3 +209,158 @@ type VolumeError struct {
|
|||
// +optional
|
||||
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",
|
||||
"PodPreset",
|
||||
"AuditSink",
|
||||
"CSINode",
|
||||
"CSIDriver",
|
||||
)
|
||||
|
||||
func TestServerSidePrint(t *testing.T) {
|
||||
|
|
|
@ -55,6 +55,7 @@ go_library(
|
|||
deps = [
|
||||
"//cmd/kube-apiserver/app:go_default_library",
|
||||
"//cmd/kube-apiserver/app/options:go_default_library",
|
||||
"//pkg/features: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/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/util/wait: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/dynamic: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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"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.
|
||||
// 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.
|
||||
func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
||||
return map[schema.GroupVersionResource]StorageData{
|
||||
etcdStorageData := map[schema.GroupVersionResource]StorageData{
|
||||
// k8s.io/kubernetes/pkg/api/v1
|
||||
gvr("", "v1", "configmaps"): {
|
||||
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
|
||||
|
|
|
@ -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})
|
||||
// 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: "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.SetSerializer(
|
||||
|
|
Loading…
Reference in New Issue