Commit f6b3d083 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #44640 from msau42/local-pv-api

Automatic merge from submit-queue LocalStorage api **What this PR does / why we need it**: API changes to support persistent local volumes, as described [here](https://github.com/kubernetes/community/pull/306) **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes # Part of #43640 **Special notes for your reviewer**: There were a few items I was concerned about. Will add review comments in those places. **Release note**: NONE Note will be added in subsequent PR with the volume plugin changes
parents 12fbd825 936269a2
...@@ -44496,6 +44496,18 @@ ...@@ -44496,6 +44496,18 @@
} }
} }
}, },
"io.k8s.kubernetes.pkg.api.v1.LocalVolumeSource": {
"description": "Local represents directly-attached storage with node affinity",
"required": [
"path"
],
"properties": {
"path": {
"description": "The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device",
"type": "string"
}
}
},
"io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource": { "io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource": {
"description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", "description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.",
"required": [ "required": [
...@@ -45273,6 +45285,10 @@ ...@@ -45273,6 +45285,10 @@
"description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin.", "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource" "$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource"
}, },
"local": {
"description": "Local represents directly-attached storage with node affinity",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.LocalVolumeSource"
},
"nfs": { "nfs": {
"description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource" "$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource"
...@@ -18966,6 +18966,10 @@ ...@@ -18966,6 +18966,10 @@
"$ref": "v1.ScaleIOVolumeSource", "$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes." "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
}, },
"local": {
"$ref": "v1.LocalVolumeSource",
"description": "Local represents directly-attached storage with node affinity"
},
"accessModes": { "accessModes": {
"type": "array", "type": "array",
"items": { "items": {
...@@ -19536,6 +19540,19 @@ ...@@ -19536,6 +19540,19 @@
} }
} }
}, },
"v1.LocalVolumeSource": {
"id": "v1.LocalVolumeSource",
"description": "Local represents directly-attached storage with node affinity",
"required": [
"path"
],
"properties": {
"path": {
"type": "string",
"description": "The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device"
}
}
},
"v1.PersistentVolumeStatus": { "v1.PersistentVolumeStatus": {
"id": "v1.PersistentVolumeStatus", "id": "v1.PersistentVolumeStatus",
"description": "PersistentVolumeStatus is the current status of a persistent volume.", "description": "PersistentVolumeStatus is the current status of a persistent volume.",
......
...@@ -594,3 +594,29 @@ func PersistentVolumeClaimHasClass(claim *api.PersistentVolumeClaim) bool { ...@@ -594,3 +594,29 @@ func PersistentVolumeClaimHasClass(claim *api.PersistentVolumeClaim) bool {
return false return false
} }
// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations
// and converts it to the NodeAffinity type in api.
// TODO: update when storage node affinity graduates to beta
func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*api.NodeAffinity, error) {
if len(annotations) > 0 && annotations[api.AlphaStorageNodeAffinityAnnotation] != "" {
var affinity api.NodeAffinity
err := json.Unmarshal([]byte(annotations[api.AlphaStorageNodeAffinityAnnotation]), &affinity)
if err != nil {
return nil, err
}
return &affinity, nil
}
return nil, nil
}
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
// TODO: update when storage node affinity graduates to beta
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
json, err := json.Marshal(*affinity)
if err != nil {
return err
}
annotations[api.AlphaStorageNodeAffinityAnnotation] = string(json)
return nil
}
...@@ -266,3 +266,90 @@ func TestSysctlsFromPodAnnotation(t *testing.T) { ...@@ -266,3 +266,90 @@ func TestSysctlsFromPodAnnotation(t *testing.T) {
} }
} }
} }
// TODO: remove when alpha support for topology constraints is removed
func TestGetNodeAffinityFromAnnotations(t *testing.T) {
testCases := []struct {
annotations map[string]string
expectErr bool
}{
{
annotations: nil,
expectErr: false,
},
{
annotations: map[string]string{},
expectErr: false,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}`,
},
expectErr: false,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `[{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}]`,
},
expectErr: true,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms":
"matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
}
}`,
},
expectErr: true,
},
}
for i, tc := range testCases {
_, err := GetStorageNodeAffinityFromAnnotation(tc.annotations)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
}
}
...@@ -381,6 +381,9 @@ type PersistentVolumeSource struct { ...@@ -381,6 +381,9 @@ type PersistentVolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
ScaleIO *ScaleIOVolumeSource ScaleIO *ScaleIOVolumeSource
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource
} }
type PersistentVolumeClaimVolumeSource struct { type PersistentVolumeClaimVolumeSource struct {
...@@ -399,6 +402,10 @@ const ( ...@@ -399,6 +402,10 @@ const (
// MountOptionAnnotation defines mount option annotation used in PVs // MountOptionAnnotation defines mount option annotation used in PVs
MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options"
// AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume.
// Value is a string of the json representation of type NodeAffinity
AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity"
) )
// +genclient=true // +genclient=true
...@@ -1223,6 +1230,14 @@ type KeyToPath struct { ...@@ -1223,6 +1230,14 @@ type KeyToPath struct {
Mode *int32 Mode *int32
} }
// Local represents directly-attached storage with node affinity
type LocalVolumeSource struct {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
Path string
}
// ContainerPort represents a network port in a single container // ContainerPort represents a network port in a single container
type ContainerPort struct { type ContainerPort struct {
// Optional: If specified, this must be an IANA_SVC_NAME Each named port // Optional: If specified, this must be an IANA_SVC_NAME Each named port
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -1440,6 +1440,14 @@ message LocalObjectReference { ...@@ -1440,6 +1440,14 @@ message LocalObjectReference {
optional string name = 1; optional string name = 1;
} }
// Local represents directly-attached storage with node affinity
message LocalVolumeSource {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
optional string path = 1;
}
// Represents an NFS mount that lasts the lifetime of a pod. // Represents an NFS mount that lasts the lifetime of a pod.
// NFS volumes do not support ownership management or SELinux relabeling. // NFS volumes do not support ownership management or SELinux relabeling.
message NFSVolumeSource { message NFSVolumeSource {
...@@ -2202,6 +2210,10 @@ message PersistentVolumeSource { ...@@ -2202,6 +2210,10 @@ message PersistentVolumeSource {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
optional ScaleIOVolumeSource scaleIO = 19; optional ScaleIOVolumeSource scaleIO = 19;
// Local represents directly-attached storage with node affinity
// +optional
optional LocalVolumeSource local = 20;
} }
// PersistentVolumeSpec is the specification of a persistent volume. // PersistentVolumeSpec is the specification of a persistent volume.
......
...@@ -498,3 +498,29 @@ func PersistentVolumeClaimHasClass(claim *v1.PersistentVolumeClaim) bool { ...@@ -498,3 +498,29 @@ func PersistentVolumeClaimHasClass(claim *v1.PersistentVolumeClaim) bool {
return false return false
} }
// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations
// and converts it to the NodeAffinity type in api.
// TODO: update when storage node affinity graduates to beta
func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*v1.NodeAffinity, error) {
if len(annotations) > 0 && annotations[v1.AlphaStorageNodeAffinityAnnotation] != "" {
var affinity v1.NodeAffinity
err := json.Unmarshal([]byte(annotations[v1.AlphaStorageNodeAffinityAnnotation]), &affinity)
if err != nil {
return nil, err
}
return &affinity, nil
}
return nil, nil
}
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
// TODO: update when storage node affinity graduates to beta
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *v1.NodeAffinity) error {
json, err := json.Marshal(*affinity)
if err != nil {
return err
}
annotations[v1.AlphaStorageNodeAffinityAnnotation] = string(json)
return nil
}
...@@ -499,3 +499,90 @@ func TestGetAffinityFromPodAnnotations(t *testing.T) { ...@@ -499,3 +499,90 @@ func TestGetAffinityFromPodAnnotations(t *testing.T) {
} }
} }
} }
// TODO: remove when alpha support for topology constraints is removed
func TestGetNodeAffinityFromAnnotations(t *testing.T) {
testCases := []struct {
annotations map[string]string
expectErr bool
}{
{
annotations: nil,
expectErr: false,
},
{
annotations: map[string]string{},
expectErr: false,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}`,
},
expectErr: false,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `[{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}]`,
},
expectErr: true,
},
{
annotations: map[string]string{
v1.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms":
"matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
}
}`,
},
expectErr: true,
},
}
for i, tc := range testCases {
_, err := GetStorageNodeAffinityFromAnnotation(tc.annotations)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
}
}
...@@ -439,6 +439,9 @@ type PersistentVolumeSource struct { ...@@ -439,6 +439,9 @@ type PersistentVolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"`
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"`
} }
const ( const (
...@@ -448,6 +451,10 @@ const ( ...@@ -448,6 +451,10 @@ const (
// MountOptionAnnotation defines mount option annotation used in PVs // MountOptionAnnotation defines mount option annotation used in PVs
MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options"
// AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume.
// Value is a string of the json representation of type NodeAffinity
AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity"
) )
// +genclient=true // +genclient=true
...@@ -1310,6 +1317,14 @@ type KeyToPath struct { ...@@ -1310,6 +1317,14 @@ type KeyToPath struct {
Mode *int32 `json:"mode,omitempty" protobuf:"varint,3,opt,name=mode"` Mode *int32 `json:"mode,omitempty" protobuf:"varint,3,opt,name=mode"`
} }
// Local represents directly-attached storage with node affinity
type LocalVolumeSource struct {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
Path string `json:"path" protobuf:"bytes,1,opt,name=path"`
}
// ContainerPort represents a network port in a single container. // ContainerPort represents a network port in a single container.
type ContainerPort struct { type ContainerPort struct {
// If specified, this must be an IANA_SVC_NAME and unique within the pod. Each // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
......
...@@ -794,6 +794,15 @@ func (LocalObjectReference) SwaggerDoc() map[string]string { ...@@ -794,6 +794,15 @@ func (LocalObjectReference) SwaggerDoc() map[string]string {
return map_LocalObjectReference return map_LocalObjectReference
} }
var map_LocalVolumeSource = map[string]string{
"": "Local represents directly-attached storage with node affinity",
"path": "The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device",
}
func (LocalVolumeSource) SwaggerDoc() map[string]string {
return map_LocalVolumeSource
}
var map_NFSVolumeSource = map[string]string{ var map_NFSVolumeSource = map[string]string{
"": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", "": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.",
"server": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "server": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs",
...@@ -1150,6 +1159,7 @@ var map_PersistentVolumeSource = map[string]string{ ...@@ -1150,6 +1159,7 @@ var map_PersistentVolumeSource = map[string]string{
"photonPersistentDisk": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine", "photonPersistentDisk": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine",
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine", "portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", "scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"local": "Local represents directly-attached storage with node affinity",
} }
func (PersistentVolumeSource) SwaggerDoc() map[string]string { func (PersistentVolumeSource) SwaggerDoc() map[string]string {
......
...@@ -173,6 +173,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { ...@@ -173,6 +173,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus, Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus,
Convert_v1_LocalObjectReference_To_api_LocalObjectReference, Convert_v1_LocalObjectReference_To_api_LocalObjectReference,
Convert_api_LocalObjectReference_To_v1_LocalObjectReference, Convert_api_LocalObjectReference_To_v1_LocalObjectReference,
Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource,
Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource,
Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource, Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource,
Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource, Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource,
Convert_v1_Namespace_To_api_Namespace, Convert_v1_Namespace_To_api_Namespace,
...@@ -2166,6 +2168,26 @@ func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalOb ...@@ -2166,6 +2168,26 @@ func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalOb
return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s)
} }
func autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
return nil
}
// Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource is an autogenerated conversion function.
func Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error {
return autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in, out, s)
}
func autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
return nil
}
// Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource is an autogenerated conversion function.
func Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *LocalVolumeSource, s conversion.Scope) error {
return autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in, out, s)
}
func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error {
out.Server = in.Server out.Server = in.Server
out.Path = in.Path out.Path = in.Path
...@@ -3002,6 +3024,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per ...@@ -3002,6 +3024,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per
out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk))
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*api.LocalVolumeSource)(unsafe.Pointer(in.Local))
return nil return nil
} }
...@@ -3030,6 +3053,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api ...@@ -3030,6 +3053,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api
out.PhotonPersistentDisk = (*PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) out.PhotonPersistentDisk = (*PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk))
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*LocalVolumeSource)(unsafe.Pointer(in.Local))
return nil return nil
} }
......
...@@ -104,6 +104,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -104,6 +104,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalVolumeSource, InType: reflect.TypeOf(&LocalVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Namespace, InType: reflect.TypeOf(&Namespace{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Namespace, InType: reflect.TypeOf(&Namespace{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})},
...@@ -1495,6 +1496,16 @@ func DeepCopy_v1_LocalObjectReference(in interface{}, out interface{}, c *conver ...@@ -1495,6 +1496,16 @@ func DeepCopy_v1_LocalObjectReference(in interface{}, out interface{}, c *conver
} }
} }
// DeepCopy_v1_LocalVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_LocalVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*LocalVolumeSource)
out := out.(*LocalVolumeSource)
*out = *in
return nil
}
}
// DeepCopy_v1_NFSVolumeSource is an autogenerated deepcopy function. // DeepCopy_v1_NFSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error { func DeepCopy_v1_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{ {
...@@ -2165,6 +2176,11 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv ...@@ -2165,6 +2176,11 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv
return err return err
} }
} }
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -1101,6 +1101,14 @@ func validateScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, fldPath *field.Pa ...@@ -1101,6 +1101,14 @@ func validateScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, fldPath *field.Pa
return allErrs return allErrs
} }
func validateLocalVolumeSource(ls *api.LocalVolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if ls.Path == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
}
return allErrs
}
// ValidatePersistentVolumeName checks that a name is appropriate for a // ValidatePersistentVolumeName checks that a name is appropriate for a
// PersistentVolumeName object. // PersistentVolumeName object.
var ValidatePersistentVolumeName = NameIsDNSSubdomain var ValidatePersistentVolumeName = NameIsDNSSubdomain
...@@ -1110,7 +1118,8 @@ var supportedAccessModes = sets.NewString(string(api.ReadWriteOnce), string(api. ...@@ -1110,7 +1118,8 @@ var supportedAccessModes = sets.NewString(string(api.ReadWriteOnce), string(api.
var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRecycle), string(api.PersistentVolumeReclaimRetain)) var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRecycle), string(api.PersistentVolumeReclaimRetain))
func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, field.NewPath("metadata")) metaPath := field.NewPath("metadata")
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
specPath := field.NewPath("spec") specPath := field.NewPath("spec")
if len(pv.Spec.AccessModes) == 0 { if len(pv.Spec.AccessModes) == 0 {
...@@ -1139,6 +1148,9 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { ...@@ -1139,6 +1148,9 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
} }
} }
nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations"))
allErrs = append(allErrs, errs...)
numVolumes := 0 numVolumes := 0
if pv.Spec.HostPath != nil { if pv.Spec.HostPath != nil {
if numVolumes > 0 { if numVolumes > 0 {
...@@ -1290,6 +1302,22 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { ...@@ -1290,6 +1302,22 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
allErrs = append(allErrs, validateScaleIOVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...) allErrs = append(allErrs, validateScaleIOVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...)
} }
} }
if pv.Spec.Local != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "may not specify more than 1 volume type"))
} else {
numVolumes++
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "Local volumes are disabled by feature-gate"))
}
allErrs = append(allErrs, validateLocalVolumeSource(pv.Spec.Local, specPath.Child("local"))...)
// NodeAffinity is required
if !nodeAffinitySpecified {
allErrs = append(allErrs, field.Required(metaPath.Child("annotations"), "Local volume requires node affinity"))
}
}
}
if numVolumes == 0 { if numVolumes == 0 {
allErrs = append(allErrs, field.Required(specPath, "must specify a volume type")) allErrs = append(allErrs, field.Required(specPath, "must specify a volume type"))
...@@ -4044,3 +4072,32 @@ func sysctlIntersection(a []api.Sysctl, b []api.Sysctl) []string { ...@@ -4044,3 +4072,32 @@ func sysctlIntersection(a []api.Sysctl, b []api.Sysctl) []string {
} }
return result return result
} }
// validateStorageNodeAffinityAnnotation tests that the serialized TopologyConstraints in PersistentVolume.Annotations has valid data
func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPath *field.Path) (bool, field.ErrorList) {
allErrs := field.ErrorList{}
na, err := helper.GetStorageNodeAffinityFromAnnotation(annotations)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, api.AlphaStorageNodeAffinityAnnotation, err.Error()))
return false, allErrs
}
if na == nil {
return false, allErrs
}
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate"))
}
policySpecified := false
if na.RequiredDuringSchedulingIgnoredDuringExecution != nil {
allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
policySpecified = true
}
if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("preferredDuringSchedulingIgnoredDuringExection"), "Storage node affinity does not support preferredDuringSchedulingIgnoredDuringExecution"))
}
return policySpecified, allErrs
}
...@@ -58,6 +58,24 @@ func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *a ...@@ -58,6 +58,24 @@ func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *a
} }
} }
func testVolumeWithNodeAffinity(t *testing.T, name string, namespace string, affinity *api.NodeAffinity, spec api.PersistentVolumeSpec) *api.PersistentVolume {
objMeta := metav1.ObjectMeta{Name: name}
if namespace != "" {
objMeta.Namespace = namespace
}
objMeta.Annotations = map[string]string{}
err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity)
if err != nil {
t.Fatalf("Failed to get node affinity annotation: %v", err)
}
return &api.PersistentVolume{
ObjectMeta: objMeta,
Spec: spec,
}
}
func TestValidatePersistentVolumes(t *testing.T) { func TestValidatePersistentVolumes(t *testing.T) {
scenarios := map[string]struct { scenarios := map[string]struct {
isExpectedFailure bool isExpectedFailure bool
...@@ -213,6 +231,42 @@ func TestValidatePersistentVolumes(t *testing.T) { ...@@ -213,6 +231,42 @@ func TestValidatePersistentVolumes(t *testing.T) {
StorageClassName: "-invalid-", StorageClassName: "-invalid-",
}), }),
}, },
// LocalVolume alpha feature disabled
// TODO: remove when no longer alpha
"alpha disabled valid local volume": {
isExpectedFailure: true,
volume: testVolumeWithNodeAffinity(
t,
"valid-local-volume",
"",
&api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "test-label-key",
Operator: api.NodeSelectorOpIn,
Values: []string{"test-label-value"},
},
},
},
},
},
},
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{
Path: "/foo",
},
},
StorageClassName: "test-storage-class",
}),
},
} }
for name, scenario := range scenarios { for name, scenario := range scenarios {
...@@ -227,6 +281,181 @@ func TestValidatePersistentVolumes(t *testing.T) { ...@@ -227,6 +281,181 @@ func TestValidatePersistentVolumes(t *testing.T) {
} }
func TestValidateLocalVolumes(t *testing.T) {
scenarios := map[string]struct {
isExpectedFailure bool
volume *api.PersistentVolume
}{
"valid local volume": {
isExpectedFailure: false,
volume: testVolumeWithNodeAffinity(
t,
"valid-local-volume",
"",
&api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "test-label-key",
Operator: api.NodeSelectorOpIn,
Values: []string{"test-label-value"},
},
},
},
},
},
},
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{
Path: "/foo",
},
},
StorageClassName: "test-storage-class",
}),
},
"invalid local volume nil annotations": {
isExpectedFailure: true,
volume: testVolume(
"invalid-local-volume-nil-annotations",
"",
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{
Path: "/foo",
},
},
StorageClassName: "test-storage-class",
}),
},
"invalid local volume empty affinity": {
isExpectedFailure: true,
volume: testVolumeWithNodeAffinity(
t,
"invalid-local-volume-empty-affinity",
"",
&api.NodeAffinity{},
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{
Path: "/foo",
},
},
StorageClassName: "test-storage-class",
}),
},
"invalid local volume preferred affinity": {
isExpectedFailure: true,
volume: testVolumeWithNodeAffinity(
t,
"invalid-local-volume-preferred-affinity",
"",
&api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "test-label-key",
Operator: api.NodeSelectorOpIn,
Values: []string{"test-label-value"},
},
},
},
},
},
PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{
{
Weight: 10,
Preference: api.NodeSelectorTerm{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "test-label-key",
Operator: api.NodeSelectorOpIn,
Values: []string{"test-label-value"},
},
},
},
},
},
},
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{
Path: "/foo",
},
},
StorageClassName: "test-storage-class",
}),
},
"invalid local volume empty path": {
isExpectedFailure: true,
volume: testVolumeWithNodeAffinity(
t,
"invalid-local-volume-empty-path",
"",
&api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "test-label-key",
Operator: api.NodeSelectorOpIn,
Values: []string{"test-label-value"},
},
},
},
},
},
},
api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{},
},
StorageClassName: "test-storage-class",
}),
},
}
err := utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=true")
if err != nil {
t.Errorf("Failed to enable feature gate for LocalPersistentVolumes: %v", err)
return
}
for name, scenario := range scenarios {
errs := ValidatePersistentVolume(scenario.volume)
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name)
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
}
}
}
func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim {
return &api.PersistentVolumeClaim{ return &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
......
...@@ -106,6 +106,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -106,6 +106,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalVolumeSource, InType: reflect.TypeOf(&LocalVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Namespace, InType: reflect.TypeOf(&Namespace{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Namespace, InType: reflect.TypeOf(&Namespace{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})},
...@@ -1513,6 +1514,16 @@ func DeepCopy_api_LocalObjectReference(in interface{}, out interface{}, c *conve ...@@ -1513,6 +1514,16 @@ func DeepCopy_api_LocalObjectReference(in interface{}, out interface{}, c *conve
} }
} }
// DeepCopy_api_LocalVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_LocalVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*LocalVolumeSource)
out := out.(*LocalVolumeSource)
*out = *in
return nil
}
}
// DeepCopy_api_NFSVolumeSource is an autogenerated deepcopy function. // DeepCopy_api_NFSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error { func DeepCopy_api_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{ {
...@@ -2183,6 +2194,11 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con ...@@ -2183,6 +2194,11 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con
return err return err
} }
} }
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -88,6 +88,12 @@ const ( ...@@ -88,6 +88,12 @@ const (
// Changes the logic behind evicting Pods from not ready Nodes // Changes the logic behind evicting Pods from not ready Nodes
// to take advantage of NoExecute Taints and Tolerations. // to take advantage of NoExecute Taints and Tolerations.
TaintBasedEvictions utilfeature.Feature = "TaintBasedEvictions" TaintBasedEvictions utilfeature.Feature = "TaintBasedEvictions"
// owner: @msau
// alpha: v1.7
//
// A new volume type that supports local disks on a node.
PersistentLocalVolumes utilfeature.Feature = "PersistentLocalVolumes"
) )
func init() { func init() {
...@@ -107,6 +113,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -107,6 +113,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
AffinityInAnnotations: {Default: false, PreRelease: utilfeature.Alpha}, AffinityInAnnotations: {Default: false, PreRelease: utilfeature.Alpha},
Accelerators: {Default: false, PreRelease: utilfeature.Alpha}, Accelerators: {Default: false, PreRelease: utilfeature.Alpha},
TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha}, TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha},
PersistentLocalVolumes: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed // inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side: // unintentionally on either side:
......
...@@ -912,6 +912,12 @@ func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) { ...@@ -912,6 +912,12 @@ func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) {
sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly) sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly)
} }
func printLocalVolumeSource(ls *api.LocalVolumeSource, w PrefixWriter) {
w.Write(LEVEL_2, "Type:\tLocalVolume (a persistent volume backed by local storage on a node)\n"+
" Path:\t%v\n",
ls.Path)
}
type PersistentVolumeDescriber struct { type PersistentVolumeDescriber struct {
clientset.Interface clientset.Interface
} }
...@@ -981,6 +987,8 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( ...@@ -981,6 +987,8 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) (
printPortworxVolumeSource(pv.Spec.PortworxVolume, w) printPortworxVolumeSource(pv.Spec.PortworxVolume, w)
case pv.Spec.ScaleIO != nil: case pv.Spec.ScaleIO != nil:
printScaleIOVolumeSource(pv.Spec.ScaleIO, w) printScaleIOVolumeSource(pv.Spec.ScaleIO, w)
case pv.Spec.Local != nil:
printLocalVolumeSource(pv.Spec.Local, w)
} }
if events != nil { if events != nil {
......
...@@ -594,3 +594,29 @@ func PersistentVolumeClaimHasClass(claim *api.PersistentVolumeClaim) bool { ...@@ -594,3 +594,29 @@ func PersistentVolumeClaimHasClass(claim *api.PersistentVolumeClaim) bool {
return false return false
} }
// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations
// and converts it to the NodeAffinity type in api.
// TODO: update when storage node affinity graduates to beta
func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*api.NodeAffinity, error) {
if len(annotations) > 0 && annotations[api.AlphaStorageNodeAffinityAnnotation] != "" {
var affinity api.NodeAffinity
err := json.Unmarshal([]byte(annotations[api.AlphaStorageNodeAffinityAnnotation]), &affinity)
if err != nil {
return nil, err
}
return &affinity, nil
}
return nil, nil
}
// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes
// TODO: update when storage node affinity graduates to beta
func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error {
json, err := json.Marshal(*affinity)
if err != nil {
return err
}
annotations[api.AlphaStorageNodeAffinityAnnotation] = string(json)
return nil
}
...@@ -266,3 +266,90 @@ func TestSysctlsFromPodAnnotation(t *testing.T) { ...@@ -266,3 +266,90 @@ func TestSysctlsFromPodAnnotation(t *testing.T) {
} }
} }
} }
// TODO: remove when alpha support for topology constraints is removed
func TestGetNodeAffinityFromAnnotations(t *testing.T) {
testCases := []struct {
annotations map[string]string
expectErr bool
}{
{
annotations: nil,
expectErr: false,
},
{
annotations: map[string]string{},
expectErr: false,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}`,
},
expectErr: false,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `[{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{ "matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
]}
}]`,
},
expectErr: true,
},
{
annotations: map[string]string{
api.AlphaStorageNodeAffinityAnnotation: `{
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms":
"matchExpressions": [
{ "key": "test-key1",
"operator": "In",
"values": ["test-value1", "test-value2"]
},
{ "key": "test-key2",
"operator": "In",
"values": ["test-value1", "test-value2"]
}
]}
}
}`,
},
expectErr: true,
},
}
for i, tc := range testCases {
_, err := GetStorageNodeAffinityFromAnnotation(tc.annotations)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
}
}
...@@ -381,6 +381,9 @@ type PersistentVolumeSource struct { ...@@ -381,6 +381,9 @@ type PersistentVolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
ScaleIO *ScaleIOVolumeSource ScaleIO *ScaleIOVolumeSource
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource
} }
type PersistentVolumeClaimVolumeSource struct { type PersistentVolumeClaimVolumeSource struct {
...@@ -399,6 +402,10 @@ const ( ...@@ -399,6 +402,10 @@ const (
// MountOptionAnnotation defines mount option annotation used in PVs // MountOptionAnnotation defines mount option annotation used in PVs
MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options"
// AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume.
// Value is a string of the json representation of type NodeAffinity
AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity"
) )
// +genclient=true // +genclient=true
...@@ -1223,6 +1230,14 @@ type KeyToPath struct { ...@@ -1223,6 +1230,14 @@ type KeyToPath struct {
Mode *int32 Mode *int32
} }
// Local represents directly-attached storage with node affinity
type LocalVolumeSource struct {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
Path string
}
// ContainerPort represents a network port in a single container // ContainerPort represents a network port in a single container
type ContainerPort struct { type ContainerPort struct {
// Optional: If specified, this must be an IANA_SVC_NAME Each named port // Optional: If specified, this must be an IANA_SVC_NAME Each named port
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -1440,6 +1440,14 @@ message LocalObjectReference { ...@@ -1440,6 +1440,14 @@ message LocalObjectReference {
optional string name = 1; optional string name = 1;
} }
// Local represents directly-attached storage with node affinity
message LocalVolumeSource {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
optional string path = 1;
}
// Represents an NFS mount that lasts the lifetime of a pod. // Represents an NFS mount that lasts the lifetime of a pod.
// NFS volumes do not support ownership management or SELinux relabeling. // NFS volumes do not support ownership management or SELinux relabeling.
message NFSVolumeSource { message NFSVolumeSource {
...@@ -2202,6 +2210,10 @@ message PersistentVolumeSource { ...@@ -2202,6 +2210,10 @@ message PersistentVolumeSource {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
optional ScaleIOVolumeSource scaleIO = 19; optional ScaleIOVolumeSource scaleIO = 19;
// Local represents directly-attached storage with node affinity
// +optional
optional LocalVolumeSource local = 20;
} }
// PersistentVolumeSpec is the specification of a persistent volume. // PersistentVolumeSpec is the specification of a persistent volume.
......
...@@ -439,6 +439,9 @@ type PersistentVolumeSource struct { ...@@ -439,6 +439,9 @@ type PersistentVolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional // +optional
ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"`
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"`
} }
const ( const (
...@@ -448,6 +451,10 @@ const ( ...@@ -448,6 +451,10 @@ const (
// MountOptionAnnotation defines mount option annotation used in PVs // MountOptionAnnotation defines mount option annotation used in PVs
MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options"
// AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume.
// Value is a string of the json representation of type NodeAffinity
AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity"
) )
// +genclient=true // +genclient=true
...@@ -1310,6 +1317,14 @@ type KeyToPath struct { ...@@ -1310,6 +1317,14 @@ type KeyToPath struct {
Mode *int32 `json:"mode,omitempty" protobuf:"varint,3,opt,name=mode"` Mode *int32 `json:"mode,omitempty" protobuf:"varint,3,opt,name=mode"`
} }
// Local represents directly-attached storage with node affinity
type LocalVolumeSource struct {
// The full path to the volume on the node
// For alpha, this path must be a directory
// Once block as a source is supported, then this path can point to a block device
Path string `json:"path" protobuf:"bytes,1,opt,name=path"`
}
// ContainerPort represents a network port in a single container. // ContainerPort represents a network port in a single container.
type ContainerPort struct { type ContainerPort struct {
// If specified, this must be an IANA_SVC_NAME and unique within the pod. Each // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
......
...@@ -794,6 +794,15 @@ func (LocalObjectReference) SwaggerDoc() map[string]string { ...@@ -794,6 +794,15 @@ func (LocalObjectReference) SwaggerDoc() map[string]string {
return map_LocalObjectReference return map_LocalObjectReference
} }
var map_LocalVolumeSource = map[string]string{
"": "Local represents directly-attached storage with node affinity",
"path": "The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device",
}
func (LocalVolumeSource) SwaggerDoc() map[string]string {
return map_LocalVolumeSource
}
var map_NFSVolumeSource = map[string]string{ var map_NFSVolumeSource = map[string]string{
"": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", "": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.",
"server": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "server": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs",
...@@ -1150,6 +1159,7 @@ var map_PersistentVolumeSource = map[string]string{ ...@@ -1150,6 +1159,7 @@ var map_PersistentVolumeSource = map[string]string{
"photonPersistentDisk": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine", "photonPersistentDisk": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine",
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine", "portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", "scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"local": "Local represents directly-attached storage with node affinity",
} }
func (PersistentVolumeSource) SwaggerDoc() map[string]string { func (PersistentVolumeSource) SwaggerDoc() map[string]string {
......
...@@ -173,6 +173,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { ...@@ -173,6 +173,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus, Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus,
Convert_v1_LocalObjectReference_To_api_LocalObjectReference, Convert_v1_LocalObjectReference_To_api_LocalObjectReference,
Convert_api_LocalObjectReference_To_v1_LocalObjectReference, Convert_api_LocalObjectReference_To_v1_LocalObjectReference,
Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource,
Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource,
Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource, Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource,
Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource, Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource,
Convert_v1_Namespace_To_api_Namespace, Convert_v1_Namespace_To_api_Namespace,
...@@ -2166,6 +2168,26 @@ func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalOb ...@@ -2166,6 +2168,26 @@ func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalOb
return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s)
} }
func autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
return nil
}
// Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource is an autogenerated conversion function.
func Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error {
return autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in, out, s)
}
func autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
return nil
}
// Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource is an autogenerated conversion function.
func Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *LocalVolumeSource, s conversion.Scope) error {
return autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in, out, s)
}
func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error {
out.Server = in.Server out.Server = in.Server
out.Path = in.Path out.Path = in.Path
...@@ -3002,6 +3024,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per ...@@ -3002,6 +3024,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per
out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk))
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*api.LocalVolumeSource)(unsafe.Pointer(in.Local))
return nil return nil
} }
...@@ -3030,6 +3053,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api ...@@ -3030,6 +3053,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api
out.PhotonPersistentDisk = (*PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) out.PhotonPersistentDisk = (*PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk))
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*LocalVolumeSource)(unsafe.Pointer(in.Local))
return nil return nil
} }
......
...@@ -104,6 +104,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -104,6 +104,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_LocalVolumeSource, InType: reflect.TypeOf(&LocalVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Namespace, InType: reflect.TypeOf(&Namespace{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Namespace, InType: reflect.TypeOf(&Namespace{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})},
...@@ -1495,6 +1496,16 @@ func DeepCopy_v1_LocalObjectReference(in interface{}, out interface{}, c *conver ...@@ -1495,6 +1496,16 @@ func DeepCopy_v1_LocalObjectReference(in interface{}, out interface{}, c *conver
} }
} }
// DeepCopy_v1_LocalVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_LocalVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*LocalVolumeSource)
out := out.(*LocalVolumeSource)
*out = *in
return nil
}
}
// DeepCopy_v1_NFSVolumeSource is an autogenerated deepcopy function. // DeepCopy_v1_NFSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error { func DeepCopy_v1_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{ {
...@@ -2165,6 +2176,11 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv ...@@ -2165,6 +2176,11 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv
return err return err
} }
} }
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -106,6 +106,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -106,6 +106,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerIngress, InType: reflect.TypeOf(&LoadBalancerIngress{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LoadBalancerStatus, InType: reflect.TypeOf(&LoadBalancerStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalObjectReference, InType: reflect.TypeOf(&LocalObjectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_LocalVolumeSource, InType: reflect.TypeOf(&LocalVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NFSVolumeSource, InType: reflect.TypeOf(&NFSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Namespace, InType: reflect.TypeOf(&Namespace{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Namespace, InType: reflect.TypeOf(&Namespace{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})}, conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_NamespaceList, InType: reflect.TypeOf(&NamespaceList{})},
...@@ -1513,6 +1514,16 @@ func DeepCopy_api_LocalObjectReference(in interface{}, out interface{}, c *conve ...@@ -1513,6 +1514,16 @@ func DeepCopy_api_LocalObjectReference(in interface{}, out interface{}, c *conve
} }
} }
// DeepCopy_api_LocalVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_LocalVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*LocalVolumeSource)
out := out.(*LocalVolumeSource)
*out = *in
return nil
}
}
// DeepCopy_api_NFSVolumeSource is an autogenerated deepcopy function. // DeepCopy_api_NFSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error { func DeepCopy_api_NFSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{ {
...@@ -2183,6 +2194,11 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con ...@@ -2183,6 +2194,11 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con
return err return err
} }
} }
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
}
return nil return nil
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment