Merge pull request #49727 from gnufied/expand-pvc-plugin-changes

Automatic merge from submit-queue (batch tested with PRs 49727, 51792)

Implement Controller for growing persistent volumes

This PR implements API and controller plane changes necessary for doing controller side resize.

xref :  https://github.com/kubernetes/community/pull/657 

Also xref https://github.com/kubernetes/features/issues/284 

```
Add alpha support for allowing users to grow persistent volumes. Currently we only support volume types that just require control plane resize (such as glusterfs) and don't need separate file system resize. 
```
pull/6/head
Kubernetes Submit Queue 2017-09-04 03:37:57 -07:00 committed by GitHub
commit cd417b583c
74 changed files with 4342 additions and 1321 deletions

View File

@ -65753,6 +65753,37 @@
}
]
},
"io.k8s.api.core.v1.PersistentVolumeClaimCondition": {
"description": "PersistentVolumeClaimCondition contails details about state of pvc",
"required": [
"type",
"status"
],
"properties": {
"lastProbeTime": {
"description": "Last time we probed the condition.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
},
"lastTransitionTime": {
"description": "Last time the condition transitioned from one status to another.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
},
"message": {
"description": "Human-readable message indicating details about last transition.",
"type": "string"
},
"reason": {
"description": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized.",
"type": "string"
},
"status": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"io.k8s.api.core.v1.PersistentVolumeClaimList": {
"description": "PersistentVolumeClaimList is a list of PersistentVolumeClaim items.",
"required": [
@ -65832,6 +65863,15 @@
"$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity"
}
},
"conditions": {
"description": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimCondition"
},
"x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge"
},
"phase": {
"description": "Phase represents the current phase of PersistentVolumeClaim.",
"type": "string"
@ -70708,6 +70748,10 @@
"provisioner"
],
"properties": {
"allowVolumeExpansion": {
"description": "AllowVolumeExpansion shows whether the storage class allow volume expand",
"type": "boolean"
},
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
@ -70791,6 +70835,10 @@
"provisioner"
],
"properties": {
"allowVolumeExpansion": {
"description": "AllowVolumeExpansion shows whether the storage class allow volume expand",
"type": "boolean"
},
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"

View File

@ -6524,6 +6524,45 @@
"capacity": {
"type": "object",
"description": "Represents the actual resources of the underlying volume."
},
"conditions": {
"type": "array",
"items": {
"$ref": "v1.PersistentVolumeClaimCondition"
},
"description": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'."
}
}
},
"v1.PersistentVolumeClaimCondition": {
"id": "v1.PersistentVolumeClaimCondition",
"description": "PersistentVolumeClaimCondition contails details about state of pvc",
"required": [
"type",
"status"
],
"properties": {
"type": {
"type": "string"
},
"status": {
"type": "string"
},
"lastProbeTime": {
"type": "string",
"description": "Last time we probed the condition."
},
"lastTransitionTime": {
"type": "string",
"description": "Last time the condition transitioned from one status to another."
},
"reason": {
"type": "string",
"description": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized."
},
"message": {
"type": "string",
"description": "Human-readable message indicating details about last transition."
}
}
},

View File

@ -9142,6 +9142,45 @@
"capacity": {
"type": "object",
"description": "Represents the actual resources of the underlying volume."
},
"conditions": {
"type": "array",
"items": {
"$ref": "v1.PersistentVolumeClaimCondition"
},
"description": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'."
}
}
},
"v1.PersistentVolumeClaimCondition": {
"id": "v1.PersistentVolumeClaimCondition",
"description": "PersistentVolumeClaimCondition contails details about state of pvc",
"required": [
"type",
"status"
],
"properties": {
"type": {
"type": "string"
},
"status": {
"type": "string"
},
"lastProbeTime": {
"type": "string",
"description": "Last time we probed the condition."
},
"lastTransitionTime": {
"type": "string",
"description": "Last time the condition transitioned from one status to another."
},
"reason": {
"type": "string",
"description": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized."
},
"message": {
"type": "string",
"description": "Human-readable message indicating details about last transition."
}
}
},

View File

@ -795,6 +795,10 @@
"type": "string"
},
"description": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid."
},
"allowVolumeExpansion": {
"type": "boolean",
"description": "AllowVolumeExpansion shows whether the storage class allow volume expand"
}
}
},

View File

@ -795,6 +795,10 @@
"type": "string"
},
"description": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid."
},
"allowVolumeExpansion": {
"type": "boolean",
"description": "AllowVolumeExpansion shows whether the storage class allow volume expand"
}
}
},

View File

@ -20099,6 +20099,45 @@
"capacity": {
"type": "object",
"description": "Represents the actual resources of the underlying volume."
},
"conditions": {
"type": "array",
"items": {
"$ref": "v1.PersistentVolumeClaimCondition"
},
"description": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'."
}
}
},
"v1.PersistentVolumeClaimCondition": {
"id": "v1.PersistentVolumeClaimCondition",
"description": "PersistentVolumeClaimCondition contails details about state of pvc",
"required": [
"type",
"status"
],
"properties": {
"type": {
"type": "string"
},
"status": {
"type": "string"
},
"lastProbeTime": {
"type": "string",
"description": "Last time we probed the condition."
},
"lastTransitionTime": {
"type": "string",
"description": "Last time the condition transitioned from one status to another."
},
"reason": {
"type": "string",
"description": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized."
},
"message": {
"type": "string",
"description": "Human-readable message indicating details about last transition."
}
}
},

View File

@ -37,6 +37,7 @@ go_library(
"//plugin/pkg/admission/namespace/exists:go_default_library",
"//plugin/pkg/admission/noderestriction:go_default_library",
"//plugin/pkg/admission/persistentvolume/label:go_default_library",
"//plugin/pkg/admission/persistentvolume/resize:go_default_library",
"//plugin/pkg/admission/podnodeselector:go_default_library",
"//plugin/pkg/admission/podpreset:go_default_library",
"//plugin/pkg/admission/podtolerationrestriction:go_default_library",

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/plugin/pkg/admission/namespace/exists"
"k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
"k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label"
"k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize"
"k8s.io/kubernetes/plugin/pkg/admission/podnodeselector"
"k8s.io/kubernetes/plugin/pkg/admission/podpreset"
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
@ -81,4 +82,5 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
serviceaccount.Register(plugins)
setdefault.Register(plugins)
webhook.Register(plugins)
resize.Register(plugins)
}

View File

@ -72,6 +72,7 @@ go_library(
"//pkg/controller/statefulset:go_default_library",
"//pkg/controller/ttl:go_default_library",
"//pkg/controller/volume/attachdetach:go_default_library",
"//pkg/controller/volume/expand:go_default_library",
"//pkg/controller/volume/persistentvolume:go_default_library",
"//pkg/features:go_default_library",
"//pkg/quota/install:go_default_library",

View File

@ -357,6 +357,7 @@ func NewControllerInitializers() map[string]InitFunc {
controllers["route"] = startRouteController
controllers["persistentvolume-binder"] = startPersistentVolumeBinderController
controllers["attachdetach"] = startAttachDetachController
controllers["persistentvolume-expander"] = startVolumeExpandController
return controllers
}

View File

@ -51,6 +51,7 @@ import (
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
"k8s.io/kubernetes/pkg/controller/volume/attachdetach"
"k8s.io/kubernetes/pkg/controller/volume/expand"
persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
"k8s.io/kubernetes/pkg/features"
quotainstall "k8s.io/kubernetes/pkg/quota/install"
@ -189,6 +190,24 @@ func startAttachDetachController(ctx ControllerContext) (bool, error) {
return true, nil
}
func startVolumeExpandController(ctx ControllerContext) (bool, error) {
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
expandController, expandControllerErr := expand.NewExpandController(
ctx.ClientBuilder.ClientOrDie("expand-controller"),
ctx.InformerFactory.Core().V1().PersistentVolumeClaims(),
ctx.InformerFactory.Core().V1().PersistentVolumes(),
ctx.Cloud,
ProbeExpandableVolumePlugins(ctx.Options.VolumeConfiguration))
if expandControllerErr != nil {
return true, fmt.Errorf("Failed to start volume expand controller : %v", expandControllerErr)
}
go expandController.Run(ctx.Stop)
return true, nil
}
return false, nil
}
func startEndpointController(ctx ControllerContext) (bool, error) {
go endpointcontroller.NewEndpointController(
ctx.InformerFactory.Core().V1().Pods(),

View File

@ -88,6 +88,25 @@ func GetDynamicPluginProber(config componentconfig.VolumeConfiguration) volume.D
return flexvolume.GetDynamicPluginProber(config.FlexVolumePluginDir)
}
// ProbeExpandableVolumePlugins returns volume plugins which are expandable
func ProbeExpandableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
return allPlugins
}
// ProbeControllerVolumePlugins collects all persistent volume plugins into an
// easy to use list. Only volume plugins that implement any of
// provisioner/recycler/deleter interface should be returned.

View File

@ -2482,6 +2482,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock">object</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">conditions</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to <em>ResizeStarted</em>.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -2519,6 +2526,75 @@ When an object is created, the system will populate this list with the current s
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</h3>
<div class="paragraph">
<p>PersistentVolumeClaimCondition contails details about state of pvc</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">type</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">status</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastProbeTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time we probed the condition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastTransitionTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time the condition transitioned from one status to another.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Unique, this should be a short, machine understandable string that gives the reason for condition&#8217;s last transition. If it reports "ResizeStarted" that means the underlying persistent volume is being resized.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">message</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Human-readable message indicating details about last transition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_secretvolumesource">v1.SecretVolumeSource</h3>

View File

@ -2808,6 +2808,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock">object</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">conditions</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to <em>ResizeStarted</em>.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -3094,6 +3101,75 @@ When an object is created, the system will populate this list with the current s
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</h3>
<div class="paragraph">
<p>PersistentVolumeClaimCondition contails details about state of pvc</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">type</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">status</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastProbeTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time we probed the condition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastTransitionTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time the condition transitioned from one status to another.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Unique, this should be a short, machine understandable string that gives the reason for condition&#8217;s last transition. If it reports "ResizeStarted" that means the underlying persistent volume is being resized.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">message</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Human-readable message indicating details about last transition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_secretvolumesource">v1.SecretVolumeSource</h3>

View File

@ -989,6 +989,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">allowVolumeExpansion</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">AllowVolumeExpansion shows whether the storage class allow volume expand</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
</tbody>
</table>

View File

@ -951,6 +951,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">allowVolumeExpansion</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">AllowVolumeExpansion shows whether the storage class allow volume expand</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
</tbody>
</table>

View File

@ -1632,6 +1632,54 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_gitrepovolumesource">v1.GitRepoVolumeSource</h3>
<div class="paragraph">
<p>Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">repository</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Repository URL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revision</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Commit hash for the specified revision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">directory</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Target directory name. Must not contain or start with <em>..</em>. If <em>.</em> is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_endpointslist">v1.EndpointsList</h3>
@ -1687,54 +1735,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_gitrepovolumesource">v1.GitRepoVolumeSource</h3>
<div class="paragraph">
<p>Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">repository</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Repository URL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revision</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Commit hash for the specified revision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">directory</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Target directory name. Must not contain or start with <em>..</em>. If <em>.</em> is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_replicationcontrollercondition">v1.ReplicationControllerCondition</h3>
@ -3582,6 +3582,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock">object</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">conditions</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to <em>ResizeStarted</em>.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -3644,6 +3651,75 @@ The resulting set of endpoints can be viewed as:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_persistentvolumeclaimcondition">v1.PersistentVolumeClaimCondition</h3>
<div class="paragraph">
<p>PersistentVolumeClaimCondition contails details about state of pvc</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">type</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">status</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastProbeTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time we probed the condition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">lastTransitionTime</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Last time the condition transitioned from one status to another.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Unique, this should be a short, machine understandable string that gives the reason for condition&#8217;s last transition. If it reports "ResizeStarted" that means the underlying persistent volume is being resized.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">message</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Human-readable message indicating details about last transition.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_secretvolumesource">v1.SecretVolumeSource</h3>

View File

@ -190,6 +190,7 @@ pkg/controller/volume/attachdetach
pkg/controller/volume/attachdetach/statusupdater
pkg/controller/volume/attachdetach/testing
pkg/controller/volume/events
pkg/controller/volume/expand
pkg/controller/volume/persistentvolume
pkg/controller/volume/persistentvolume/options
pkg/credentialprovider

View File

@ -547,6 +547,27 @@ type PersistentVolumeClaimSpec struct {
StorageClassName *string
}
type PersistentVolumeClaimConditionType string
// These are valid conditions of Pvc
const (
// An user trigger resize of pvc has been started
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
)
type PersistentVolumeClaimCondition struct {
Type PersistentVolumeClaimConditionType
Status ConditionStatus
// +optional
LastProbeTime metav1.Time
// +optional
LastTransitionTime metav1.Time
// +optional
Reason string
// +optional
Message string
}
type PersistentVolumeClaimStatus struct {
// Phase represents the current phase of PersistentVolumeClaim
// +optional
@ -557,6 +578,8 @@ type PersistentVolumeClaimStatus struct {
// Represents the actual resources of the underlying volume
// +optional
Capacity ResourceList
// +optional
Conditions []PersistentVolumeClaimCondition
}
type PersistentVolumeAccessMode string

View File

@ -233,6 +233,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_PersistentVolume_To_v1_PersistentVolume,
Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim,
Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim,
Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition,
Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition,
Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList,
Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList,
Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec,
@ -2942,6 +2944,36 @@ func Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.Persi
return autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in, out, s)
}
func autoConvert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *api.PersistentVolumeClaimCondition, s conversion.Scope) error {
out.Type = api.PersistentVolumeClaimConditionType(in.Type)
out.Status = api.ConditionStatus(in.Status)
out.LastProbeTime = in.LastProbeTime
out.LastTransitionTime = in.LastTransitionTime
out.Reason = in.Reason
out.Message = in.Message
return nil
}
// Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition is an autogenerated conversion function.
func Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *api.PersistentVolumeClaimCondition, s conversion.Scope) error {
return autoConvert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in, out, s)
}
func autoConvert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *api.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error {
out.Type = v1.PersistentVolumeClaimConditionType(in.Type)
out.Status = v1.ConditionStatus(in.Status)
out.LastProbeTime = in.LastProbeTime
out.LastTransitionTime = in.LastTransitionTime
out.Reason = in.Reason
out.Message = in.Message
return nil
}
// Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition is an autogenerated conversion function.
func Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *api.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error {
return autoConvert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in, out, s)
}
func autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList, out *api.PersistentVolumeClaimList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
out.Items = *(*[]api.PersistentVolumeClaim)(unsafe.Pointer(&in.Items))
@ -3000,6 +3032,7 @@ func autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStat
out.Phase = api.PersistentVolumeClaimPhase(in.Phase)
out.AccessModes = *(*[]api.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes))
out.Capacity = *(*api.ResourceList)(unsafe.Pointer(&in.Capacity))
out.Conditions = *(*[]api.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions))
return nil
}
@ -3012,6 +3045,7 @@ func autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStat
out.Phase = v1.PersistentVolumeClaimPhase(in.Phase)
out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes))
out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity))
out.Conditions = *(*[]v1.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions))
return nil
}

View File

@ -1588,10 +1588,31 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *api.PersistentVolumeCla
oldPvc.Spec.VolumeName = newPvc.Spec.VolumeName
defer func() { oldPvc.Spec.VolumeName = "" }()
}
// changes to Spec are not allowed, but updates to label/and some annotations are OK.
// no-op updates pass validation.
if !apiequality.Semantic.DeepEqual(newPvc.Spec, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation"))
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
newPVCSpecCopy := newPvc.Spec.DeepCopy()
// lets make sure storage values are same.
if newPvc.Status.Phase == api.ClaimBound && newPVCSpecCopy.Resources.Requests != nil {
newPVCSpecCopy.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"]
}
oldSize := oldPvc.Spec.Resources.Requests["storage"]
newSize := newPvc.Spec.Resources.Requests["storage"]
if !apiequality.Semantic.DeepEqual(*newPVCSpecCopy, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "is immutable after creation except resources.requests for bound claims"))
}
if newSize.Cmp(oldSize) < 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value"))
}
} else {
// changes to Spec are not allowed, but updates to label/and some annotations are OK.
// no-op updates pass validation.
if !apiequality.Semantic.DeepEqual(newPvc.Spec, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation"))
}
}
// storageclass annotation should be immutable after creation
@ -1611,6 +1632,10 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *api.PersistentVol
if len(newPvc.Spec.AccessModes) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), ""))
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) && len(newPvc.Status.Conditions) > 0 {
conditionPath := field.NewPath("status", "conditions")
allErrs = append(allErrs, field.Forbidden(conditionPath, "invalid field"))
}
capPath := field.NewPath("status", "capacity")
for r, qty := range newPvc.Status.Capacity {
allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...)

View File

@ -530,6 +530,17 @@ func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeCla
}
}
func testVolumeClaimWithStatus(
name, namespace string,
spec api.PersistentVolumeClaimSpec,
status api.PersistentVolumeClaimStatus) *api.PersistentVolumeClaim {
return &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: spec,
Status: status,
}
}
func testVolumeClaimStorageClass(name string, namespace string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim {
annotations := map[string]string{
v1.BetaStorageClassAnnotation: annval,
@ -728,7 +739,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}
func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
validClaim := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
@ -738,7 +749,10 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadOnlyMany,
@ -828,50 +842,125 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
},
VolumeName: "volume",
})
validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("15G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("12G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimPending,
})
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *api.PersistentVolumeClaim
newClaim *api.PersistentVolumeClaim
enableResize bool
}{
"valid-update-volumeName-only": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"valid-no-op-update": {
isExpectedFailure: false,
oldClaim: validUpdateClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"invalid-update-change-resources-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimResources,
enableResize: false,
},
"invalid-update-change-access-modes-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimAccessModes,
enableResize: false,
},
"invalid-update-change-storage-class-annotation-after-creation": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidUpdateClaimStorageClass,
enableResize: false,
},
"valid-update-mutable-annotation": {
isExpectedFailure: false,
oldClaim: validClaimAnnotation,
newClaim: validUpdateClaimMutableAnnotation,
enableResize: false,
},
"valid-update-add-annotation": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validAddClaimAnnotation,
enableResize: false,
},
"valid-size-update-resize-disabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: false,
},
"valid-size-update-resize-enabled": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: true,
},
"invalid-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: invalidSizeUpdate,
enableResize: true,
},
"unbound-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: unboundSizeUpdate,
enableResize: true,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
togglePVExpandFeature(scenario.enableResize, t)
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
@ -884,6 +973,23 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
}
}
func togglePVExpandFeature(toggleFlag bool, t *testing.T) {
if toggleFlag {
// Enable alpha feature LocalStorageCapacityIsolation
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true")
if err != nil {
t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err)
return
}
} else {
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false")
if err != nil {
t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err)
return
}
}
}
func TestValidateKeyToPath(t *testing.T) {
testCases := []struct {
kp api.KeyToPath
@ -9232,6 +9338,68 @@ func TestValidateLimitRange(t *testing.T) {
}
func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
})
validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimPending,
Conditions: []api.PersistentVolumeClaimCondition{
{Type: api.PersistentVolumeClaimResizing, Status: api.ConditionTrue},
},
})
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *api.PersistentVolumeClaim
newClaim *api.PersistentVolumeClaim
enableResize bool
}{
"condition-update-with-disabled-feature-gate": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validConditionUpdate,
enableResize: false,
},
"condition-update-with-enabled-feature-gate": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validConditionUpdate,
enableResize: true,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
togglePVExpandFeature(scenario.enableResize, t)
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim)
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 TestValidateResourceQuota(t *testing.T) {
spec := api.ResourceQuotaSpec{
Hard: api.ResourceList{

View File

@ -427,6 +427,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*PersistentVolumeClaim).DeepCopyInto(out.(*PersistentVolumeClaim))
return nil
}, InType: reflect.TypeOf(&PersistentVolumeClaim{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*PersistentVolumeClaimCondition).DeepCopyInto(out.(*PersistentVolumeClaimCondition))
return nil
}, InType: reflect.TypeOf(&PersistentVolumeClaimCondition{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*PersistentVolumeClaimList).DeepCopyInto(out.(*PersistentVolumeClaimList))
return nil
@ -3513,6 +3517,24 @@ func (in *PersistentVolumeClaim) DeepCopyObject() runtime.Object {
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimCondition) DeepCopyInto(out *PersistentVolumeClaimCondition) {
*out = *in
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimCondition.
func (in *PersistentVolumeClaimCondition) DeepCopy() *PersistentVolumeClaimCondition {
if in == nil {
return nil
}
out := new(PersistentVolumeClaimCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimList) DeepCopyInto(out *PersistentVolumeClaimList) {
*out = *in
@ -3602,6 +3624,13 @@ func (in *PersistentVolumeClaimStatus) DeepCopyInto(out *PersistentVolumeClaimSt
(*out)[key] = val.DeepCopy()
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]PersistentVolumeClaimCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

View File

@ -59,6 +59,12 @@ type StorageClass struct {
// PersistentVolumes of this storage class are created with
// +optional
MountOptions []string
// AllowVolumeExpansion shows whether the storage class allow volume expand
// If the field is nil or not set, it would amount to expansion disabled
// for all PVs created from this storageclass.
// +optional
AllowVolumeExpansion *bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -51,6 +51,7 @@ func autoConvert_v1_StorageClass_To_storage_StorageClass(in *v1.StorageClass, ou
out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters))
out.ReclaimPolicy = (*api.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy))
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion))
return nil
}
@ -65,6 +66,7 @@ func autoConvert_storage_StorageClass_To_v1_StorageClass(in *storage.StorageClas
out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters))
out.ReclaimPolicy = (*core_v1.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy))
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion))
return nil
}

View File

@ -51,6 +51,7 @@ func autoConvert_v1beta1_StorageClass_To_storage_StorageClass(in *v1beta1.Storag
out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters))
out.ReclaimPolicy = (*api.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy))
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion))
return nil
}
@ -65,6 +66,7 @@ func autoConvert_storage_StorageClass_To_v1beta1_StorageClass(in *storage.Storag
out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters))
out.ReclaimPolicy = (*v1.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy))
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion))
return nil
}

View File

@ -13,9 +13,11 @@ go_library(
"//pkg/api:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/features:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
@ -27,6 +29,7 @@ go_test(
"//pkg/api:go_default_library",
"//pkg/apis/storage:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)

View File

@ -23,9 +23,11 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api"
apivalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/features"
)
// ValidateStorageClass validates a StorageClass.
@ -34,6 +36,7 @@ func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList {
allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...)
allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...)
allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...)
allErrs = append(allErrs, validateAllowVolumeExpansion(storageClass.AllowVolumeExpansion, field.NewPath("allowVolumeExpansion"))...)
return allErrs
}
@ -108,3 +111,13 @@ func validateReclaimPolicy(reclaimPolicy *api.PersistentVolumeReclaimPolicy, fld
}
return allErrs
}
// validateAllowVolumeExpansion tests that if ExpandPersistentVolumes feature gate is disabled, whether the AllowVolumeExpansion filed
// of storage class is set
func validateAllowVolumeExpansion(allowExpand *bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if allowExpand != nil && !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate ExpandPersistentVolumes"))
}
return allErrs
}

View File

@ -21,6 +21,7 @@ import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/storage"
)
@ -123,3 +124,36 @@ func TestValidateStorageClass(t *testing.T) {
}
}
}
func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) {
deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete")
falseVar := false
testSC := &storage.StorageClass{
// empty parameters
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner",
Parameters: map[string]string{},
ReclaimPolicy: &deleteReclaimPolicy,
AllowVolumeExpansion: &falseVar,
}
// Enable alpha feature ExpandPersistentVolumes
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true")
if err != nil {
t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err)
return
}
if errs := ValidateStorageClass(testSC); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
// Disable alpha feature ExpandPersistentVolumes
err = utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false")
if err != nil {
t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err)
return
}
if errs := ValidateStorageClass(testSC); len(errs) == 0 {
t.Errorf("expected failure, but got no error")
}
}

View File

@ -74,6 +74,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllowVolumeExpansion != nil {
in, out := &in.AllowVolumeExpansion, &out.AllowVolumeExpansion
if *in == nil {
*out = nil
} else {
*out = new(bool)
**out = **in
}
}
return
}

View File

@ -128,6 +128,7 @@ filegroup(
"//pkg/controller/ttl:all-srcs",
"//pkg/controller/volume/attachdetach:all-srcs",
"//pkg/controller/volume/events:all-srcs",
"//pkg/controller/volume/expand:all-srcs",
"//pkg/controller/volume/persistentvolume:all-srcs",
],
tags = ["automanaged"],

View File

@ -0,0 +1,60 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"expand_controller.go",
"pvc_populator.go",
"sync_volume_resize.go",
],
tags = ["automanaged"],
deps = [
"//pkg/cloudprovider:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/volume/expand/cache:go_default_library",
"//pkg/controller/volume/expand/util:go_default_library",
"//pkg/util/goroutinemap/exponentialbackoff:go_default_library",
"//pkg/util/io:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util/operationexecutor:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/controller/volume/expand/cache:all-srcs",
"//pkg/controller/volume/expand/util:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,4 @@
approvers:
- saad-ali
- jsafrane
- gnufied

View File

@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["volume_resize_map.go"],
tags = ["automanaged"],
deps = [
"//pkg/controller/volume/expand/util:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/volume/util/types:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["volume_resize_map_test.go"],
library = ":go_default_library",
deps = [
"//pkg/volume/util/types:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
],
)

View File

@ -0,0 +1,211 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"encoding/json"
"fmt"
"sync"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
commontypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/controller/volume/expand/util"
"k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/volume/util/types"
)
// VolumeResizeMap defines an interface that serves as a cache for holding pending resizing requests
type VolumeResizeMap interface {
// AddPVCUpdate adds pvc for resizing
AddPVCUpdate(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume)
// DeletePVC deletes pvc that is scheduled for resizing
DeletePVC(pvc *v1.PersistentVolumeClaim)
// GetPVCsWithResizeRequest returns all pending pvc resize requests
GetPVCsWithResizeRequest() []*PVCWithResizeRequest
// MarkAsResized marks a pvc as fully resized
MarkAsResized(*PVCWithResizeRequest, resource.Quantity) error
// UpdatePVSize updates just pv size after cloudprovider resizing is successful
UpdatePVSize(*PVCWithResizeRequest, resource.Quantity) error
}
type volumeResizeMap struct {
// map of unique pvc name and resize requests that are pending or inflight
pvcrs map[types.UniquePVCName]*PVCWithResizeRequest
// kube client for making API calls
kubeClient clientset.Interface
// for guarding access to pvcrs map
sync.RWMutex
}
// PVCWithResizeRequest struct defines data structure that stores state needed for
// performing file system resize
type PVCWithResizeRequest struct {
// PVC that needs to be resized
PVC *v1.PersistentVolumeClaim
// persistentvolume
PersistentVolume *v1.PersistentVolume
// Current volume size
CurrentSize resource.Quantity
// Expended volume size
ExpectedSize resource.Quantity
}
// UniquePVCKey returns unique key of the PVC based on its UID
func (pvcr *PVCWithResizeRequest) UniquePVCKey() types.UniquePVCName {
return types.UniquePVCName(pvcr.PVC.UID)
}
// QualifiedName returns namespace and name combination of the PVC
func (pvcr *PVCWithResizeRequest) QualifiedName() string {
return strings.JoinQualifiedName(pvcr.PVC.Namespace, pvcr.PVC.Name)
}
// NewVolumeResizeMap returns new VolumeResizeMap which acts as a cache
// for holding pending resize requests.
func NewVolumeResizeMap(kubeClient clientset.Interface) VolumeResizeMap {
resizeMap := &volumeResizeMap{}
resizeMap.pvcrs = make(map[types.UniquePVCName]*PVCWithResizeRequest)
resizeMap.kubeClient = kubeClient
return resizeMap
}
// AddPVCUpdate adds pvc for resizing
func (resizeMap *volumeResizeMap) AddPVCUpdate(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) {
if pvc.Namespace != pv.Spec.ClaimRef.Namespace || pvc.Name != pv.Spec.ClaimRef.Name {
glog.V(4).Infof("Persistent Volume is not mapped to PVC being updated : %s", util.ClaimToClaimKey(pvc))
return
}
if pvc.Status.Phase != v1.ClaimBound {
return
}
resizeMap.Lock()
defer resizeMap.Unlock()
pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
pvcStatusSize := pvc.Status.Capacity[v1.ResourceStorage]
if pvcStatusSize.Cmp(pvcSize) >= 0 {
return
}
glog.V(4).Infof("Adding pvc %s with Size %s/%s for resizing", util.ClaimToClaimKey(pvc), pvcSize.String(), pvcStatusSize.String())
pvcRequest := &PVCWithResizeRequest{
PVC: pvc,
CurrentSize: pvcStatusSize,
ExpectedSize: pvcSize,
PersistentVolume: pv,
}
resizeMap.pvcrs[types.UniquePVCName(pvc.UID)] = pvcRequest
}
// GetPVCsWithResizeRequest returns all pending pvc resize requests
func (resizeMap *volumeResizeMap) GetPVCsWithResizeRequest() []*PVCWithResizeRequest {
resizeMap.Lock()
defer resizeMap.Unlock()
pvcrs := []*PVCWithResizeRequest{}
for _, pvcr := range resizeMap.pvcrs {
pvcrs = append(pvcrs, pvcr)
}
// Empty out pvcrs map, we will add back failed resize requests later
resizeMap.pvcrs = map[types.UniquePVCName]*PVCWithResizeRequest{}
return pvcrs
}
// DeletePVC removes given pvc object from list of pvcs that needs resizing.
// deleting a pvc in this map doesn't affect operations that are already inflight.
func (resizeMap *volumeResizeMap) DeletePVC(pvc *v1.PersistentVolumeClaim) {
resizeMap.Lock()
defer resizeMap.Unlock()
pvcUniqueName := types.UniquePVCName(pvc.UID)
glog.V(5).Infof("Removing PVC %v from resize map", pvcUniqueName)
delete(resizeMap.pvcrs, pvcUniqueName)
}
// MarkAsResized marks a pvc as fully resized
func (resizeMap *volumeResizeMap) MarkAsResized(pvcr *PVCWithResizeRequest, newSize resource.Quantity) error {
resizeMap.Lock()
defer resizeMap.Unlock()
emptyCondition := []v1.PersistentVolumeClaimCondition{}
err := resizeMap.updatePVCCapacityAndConditions(pvcr, newSize, emptyCondition)
if err != nil {
glog.V(4).Infof("Error updating PV spec capacity for volume %q with : %v", pvcr.QualifiedName(), err)
return err
}
return nil
}
// UpdatePVSize updates just pv size after cloudprovider resizing is successful
func (resizeMap *volumeResizeMap) UpdatePVSize(pvcr *PVCWithResizeRequest, newSize resource.Quantity) error {
resizeMap.Lock()
defer resizeMap.Unlock()
oldPv := pvcr.PersistentVolume
pvClone := oldPv.DeepCopy()
oldData, err := json.Marshal(pvClone)
if err != nil {
return fmt.Errorf("Unexpected error marshaling PV : %q with error %v", pvClone.Name, err)
}
pvClone.Spec.Capacity[v1.ResourceStorage] = newSize
newData, err := json.Marshal(pvClone)
if err != nil {
return fmt.Errorf("Unexpected error marshaling PV : %q with error %v", pvClone.Name, err)
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, pvClone)
if err != nil {
return fmt.Errorf("Error Creating two way merge patch for PV : %q with error %v", pvClone.Name, err)
}
_, updateErr := resizeMap.kubeClient.CoreV1().PersistentVolumes().Patch(pvClone.Name, commontypes.StrategicMergePatchType, patchBytes)
if updateErr != nil {
glog.V(4).Infof("Error updating pv %q with error : %v", pvClone.Name, updateErr)
return updateErr
}
return nil
}
func (resizeMap *volumeResizeMap) updatePVCCapacityAndConditions(pvcr *PVCWithResizeRequest, newSize resource.Quantity, pvcConditions []v1.PersistentVolumeClaimCondition) error {
claimClone := pvcr.PVC.DeepCopy()
claimClone.Status.Capacity[v1.ResourceStorage] = newSize
claimClone.Status.Conditions = pvcConditions
_, updateErr := resizeMap.kubeClient.CoreV1().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(claimClone)
if updateErr != nil {
glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: failed: %v", pvcr.QualifiedName(), updateErr)
return updateErr
}
return nil
}

View File

@ -0,0 +1,88 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/kubernetes/pkg/volume/util/types"
)
func Test_AddValidPvcUpdate(t *testing.T) {
resizeMap := createTestVolumeResizeMap()
claim1 := testVolumeClaim("foo", "ns", v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
v1.ReadOnlyMany,
},
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
},
},
VolumeName: "foo",
})
claimClone := claim1.DeepCopy()
claimClone.Spec.Resources.Requests[v1.ResourceStorage] = resource.MustParse("12G")
pv := getPersistentVolume("foo", resource.MustParse("10G"), claim1)
resizeMap.AddPVCUpdate(claimClone, pv)
pvcr := resizeMap.GetPVCsWithResizeRequest()
if len(pvcr) != 1 {
t.Fatalf("Expected 1 pvc resize request got 0")
}
assert.Equal(t, resource.MustParse("12G"), pvcr[0].ExpectedSize)
assert.Equal(t, 0, len(resizeMap.pvcrs))
}
func createTestVolumeResizeMap() *volumeResizeMap {
fakeClient := &fake.Clientset{}
resizeMap := &volumeResizeMap{}
resizeMap.pvcrs = make(map[types.UniquePVCName]*PVCWithResizeRequest)
resizeMap.kubeClient = fakeClient
return resizeMap
}
func testVolumeClaim(name string, namespace string, spec v1.PersistentVolumeClaimSpec) *v1.PersistentVolumeClaim {
return &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: spec,
Status: v1.PersistentVolumeClaimStatus{
Phase: v1.ClaimBound,
},
}
}
func getPersistentVolume(volumeName string, capacity resource.Quantity, pvc *v1.PersistentVolumeClaim) *v1.PersistentVolume {
return &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: volumeName},
Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): capacity,
},
ClaimRef: &v1.ObjectReference{
Namespace: pvc.Namespace,
Name: pvc.Name,
},
},
}
}

View File

@ -0,0 +1,268 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package expand implements interfaces that attempt to resize a pvc
// by adding pvc to a volume resize map from which PVCs are picked and
// resized
package expand
import (
"fmt"
"net"
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
corelisters "k8s.io/client-go/listers/core/v1"
kcache "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/volume/expand/cache"
"k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
)
const (
// How often resizing loop runs
syncLoopPeriod time.Duration = 30 * time.Second
// How often pvc populator runs
populatorLoopPeriod time.Duration = 2 * time.Minute
)
// ExpandController expands the pvs
type ExpandController interface {
Run(stopCh <-chan struct{})
}
type expandController struct {
// kubeClient is the kube API client used by volumehost to communicate with
// the API server.
kubeClient clientset.Interface
// pvcLister is the shared PVC lister used to fetch and store PVC
// objects from the API server. It is shared with other controllers and
// therefore the PVC objects in its store should be treated as immutable.
pvcLister corelisters.PersistentVolumeClaimLister
pvcsSynced kcache.InformerSynced
pvLister corelisters.PersistentVolumeLister
pvSynced kcache.InformerSynced
// cloud provider used by volume host
cloud cloudprovider.Interface
// volumePluginMgr used to initialize and fetch volume plugins
volumePluginMgr volume.VolumePluginMgr
// recorder is used to record events in the API server
recorder record.EventRecorder
// Volume resize map of volumes that needs resizing
resizeMap cache.VolumeResizeMap
// Worker goroutine to process resize requests from resizeMap
syncResize SyncVolumeResize
// Operation executor
opExecutor operationexecutor.OperationExecutor
// populator for periodically polling all PVCs
pvcPopulator PVCPopulator
}
func NewExpandController(
kubeClient clientset.Interface,
pvcInformer coreinformers.PersistentVolumeClaimInformer,
pvInformer coreinformers.PersistentVolumeInformer,
cloud cloudprovider.Interface,
plugins []volume.VolumePlugin) (ExpandController, error) {
expc := &expandController{
kubeClient: kubeClient,
cloud: cloud,
pvcLister: pvcInformer.Lister(),
pvcsSynced: pvcInformer.Informer().HasSynced,
pvLister: pvInformer.Lister(),
pvSynced: pvInformer.Informer().HasSynced,
}
if err := expc.volumePluginMgr.InitPlugins(plugins, nil, expc); err != nil {
return nil, fmt.Errorf("Could not initialize volume plugins for Expand Controller : %+v", err)
}
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "volume_expand"})
expc.opExecutor = operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
kubeClient,
&expc.volumePluginMgr,
recorder,
false))
expc.resizeMap = cache.NewVolumeResizeMap(expc.kubeClient)
pvcInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{
UpdateFunc: expc.pvcUpdate,
DeleteFunc: expc.deletePVC,
})
expc.syncResize = NewSyncVolumeResize(syncLoopPeriod, expc.opExecutor, expc.resizeMap, kubeClient)
expc.pvcPopulator = NewPVCPopulator(
populatorLoopPeriod,
expc.resizeMap,
expc.pvcLister,
expc.pvLister,
kubeClient)
return expc, nil
}
func (expc *expandController) Run(stopCh <-chan struct{}) {
defer runtime.HandleCrash()
glog.Infof("Starting expand controller")
defer glog.Infof("Shutting down expand controller")
if !controller.WaitForCacheSync("expand", stopCh, expc.pvcsSynced, expc.pvSynced) {
return
}
// Run volume sync work goroutine
go expc.syncResize.Run(stopCh)
// Start the pvc populator loop
go expc.pvcPopulator.Run(stopCh)
<-stopCh
}
func (expc *expandController) deletePVC(obj interface{}) {
pvc, ok := obj.(*v1.PersistentVolumeClaim)
if pvc == nil || !ok {
return
}
expc.resizeMap.DeletePVC(pvc)
}
func (expc *expandController) pvcUpdate(oldObj, newObj interface{}) {
oldPvc, ok := oldObj.(*v1.PersistentVolumeClaim)
if oldPvc == nil || !ok {
return
}
newPVC, ok := newObj.(*v1.PersistentVolumeClaim)
if newPVC == nil || !ok {
return
}
pv, err := getPersistentVolume(newPVC, expc.pvLister)
if err != nil {
glog.V(5).Infof("Error getting Persistent Volume for pvc %q : %v", newPVC.UID, err)
return
}
expc.resizeMap.AddPVCUpdate(newPVC, pv)
}
func getPersistentVolume(pvc *v1.PersistentVolumeClaim, pvLister corelisters.PersistentVolumeLister) (*v1.PersistentVolume, error) {
volumeName := pvc.Spec.VolumeName
pv, err := pvLister.Get(volumeName)
if err != nil {
return nil, fmt.Errorf("failed to find PV %q in PV informer cache with error : %v", volumeName, err)
}
return pv.DeepCopy(), nil
}
// Implementing VolumeHost interface
func (expc *expandController) GetPluginDir(pluginName string) string {
return ""
}
func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
return ""
}
func (expc *expandController) GetPodPluginDir(podUID types.UID, pluginName string) string {
return ""
}
func (expc *expandController) GetKubeClient() clientset.Interface {
return expc.kubeClient
}
func (expc *expandController) NewWrapperMounter(volName string, spec volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return nil, fmt.Errorf("NewWrapperMounter not supported by expand controller's VolumeHost implementation")
}
func (expc *expandController) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
return nil, fmt.Errorf("NewWrapperUnmounter not supported by expand controller's VolumeHost implementation")
}
func (expc *expandController) GetCloudProvider() cloudprovider.Interface {
return expc.cloud
}
func (expc *expandController) GetMounter(pluginName string) mount.Interface {
return nil
}
func (expc *expandController) GetExec(pluginName string) mount.Exec {
return mount.NewOsExec()
}
func (expc *expandController) GetWriter() io.Writer {
return nil
}
func (expc *expandController) GetHostName() string {
return ""
}
func (expc *expandController) GetHostIP() (net.IP, error) {
return nil, fmt.Errorf("GetHostIP not supported by expand controller's VolumeHost implementation")
}
func (expc *expandController) GetNodeAllocatable() (v1.ResourceList, error) {
return v1.ResourceList{}, nil
}
func (expc *expandController) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
return func(_, _ string) (*v1.Secret, error) {
return nil, fmt.Errorf("GetSecret unsupported in expandController")
}
}
func (expc *expandController) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) {
return func(_, _ string) (*v1.ConfigMap, error) {
return nil, fmt.Errorf("GetConfigMap unsupported in expandController")
}
}
func (expc *expandController) GetNodeLabels() (map[string]string, error) {
return nil, fmt.Errorf("GetNodeLabels unsupported in expandController")
}

View File

@ -0,0 +1,85 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package reconciler implements interfaces that attempt to reconcile the
// desired state of the with the actual state of the world by triggering
// actions.
package expand
import (
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/kubernetes/pkg/controller/volume/expand/cache"
)
// PVCPopulator iterates through PVCs and checks if for bound PVCs
// their size doesn't match with Persistent Volume size
type PVCPopulator interface {
Run(stopCh <-chan struct{})
}
type pvcPopulator struct {
loopPeriod time.Duration
resizeMap cache.VolumeResizeMap
pvcLister corelisters.PersistentVolumeClaimLister
pvLister corelisters.PersistentVolumeLister
kubeClient clientset.Interface
}
func NewPVCPopulator(
loopPeriod time.Duration,
resizeMap cache.VolumeResizeMap,
pvcLister corelisters.PersistentVolumeClaimLister,
pvLister corelisters.PersistentVolumeLister,
kubeClient clientset.Interface) PVCPopulator {
populator := &pvcPopulator{
loopPeriod: loopPeriod,
pvcLister: pvcLister,
pvLister: pvLister,
resizeMap: resizeMap,
kubeClient: kubeClient,
}
return populator
}
func (populator *pvcPopulator) Run(stopCh <-chan struct{}) {
wait.Until(populator.Sync, populator.loopPeriod, stopCh)
}
func (populator *pvcPopulator) Sync() {
pvcs, err := populator.pvcLister.List(labels.Everything())
if err != nil {
glog.Errorf("Listing PVCs failed in populator : %v", err)
return
}
for _, pvc := range pvcs {
pv, err := getPersistentVolume(pvc, populator.pvLister)
if err != nil {
glog.V(5).Infof("Error getting persistent volume for pvc %q : %v", pvc.UID, err)
continue
}
populator.resizeMap.AddPVCUpdate(pvc, pv)
}
}

View File

@ -0,0 +1,101 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package expand
import (
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/controller/volume/expand/cache"
"k8s.io/kubernetes/pkg/controller/volume/expand/util"
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
)
type SyncVolumeResize interface {
Run(stopCh <-chan struct{})
}
type syncResize struct {
loopPeriod time.Duration
resizeMap cache.VolumeResizeMap
opsExecutor operationexecutor.OperationExecutor
kubeClient clientset.Interface
}
// NewSyncVolumeResize returns actual volume resize handler
func NewSyncVolumeResize(
loopPeriod time.Duration,
opsExecutor operationexecutor.OperationExecutor,
resizeMap cache.VolumeResizeMap,
kubeClient clientset.Interface) SyncVolumeResize {
rc := &syncResize{
loopPeriod: loopPeriod,
opsExecutor: opsExecutor,
resizeMap: resizeMap,
kubeClient: kubeClient,
}
return rc
}
func (rc *syncResize) Run(stopCh <-chan struct{}) {
wait.Until(rc.Sync, rc.loopPeriod, stopCh)
}
func (rc *syncResize) Sync() {
// Resize PVCs that require resize
for _, pvcWithResizeRequest := range rc.resizeMap.GetPVCsWithResizeRequest() {
uniqueVolumeKey := v1.UniqueVolumeName(pvcWithResizeRequest.UniquePVCKey())
updatedClaim, err := markPVCResizeInProgress(pvcWithResizeRequest, rc.kubeClient)
if err != nil {
glog.V(5).Infof("Error setting PVC %s in progress with error : %v", pvcWithResizeRequest.QualifiedName(), err)
continue
}
if updatedClaim != nil {
pvcWithResizeRequest.PVC = updatedClaim
}
if rc.opsExecutor.IsOperationPending(uniqueVolumeKey, "") {
glog.V(10).Infof("Operation for PVC %v is already pending", pvcWithResizeRequest.QualifiedName())
continue
}
glog.V(5).Infof("Starting opsExecutor.ExpandVolume for volume %s", pvcWithResizeRequest.QualifiedName())
growFuncError := rc.opsExecutor.ExpandVolume(pvcWithResizeRequest, rc.resizeMap)
if growFuncError != nil && !exponentialbackoff.IsExponentialBackoff(growFuncError) {
glog.Errorf("Error growing pvc %s with %v", pvcWithResizeRequest.QualifiedName(), growFuncError)
}
if growFuncError == nil {
glog.V(5).Infof("Started opsExecutor.ExpandVolume for volume %s", pvcWithResizeRequest.QualifiedName())
}
}
}
func markPVCResizeInProgress(pvcWithResizeRequest *cache.PVCWithResizeRequest, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
// Mark PVC as Resize Started
progressCondition := v1.PersistentVolumeClaimCondition{
Type: v1.PersistentVolumeClaimResizing,
Status: v1.ConditionTrue,
LastTransitionTime: metav1.Now(),
}
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
return util.UpdatePVCCondition(pvcWithResizeRequest.PVC, conditions, kubeClient)
}

View File

@ -0,0 +1,26 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["util.go"],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes: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"],
)

View File

@ -0,0 +1,46 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"github.com/golang/glog"
"k8s.io/api/core/v1"
clientset "k8s.io/client-go/kubernetes"
)
// ClaimToClaimKey return namespace/name string for pvc
func ClaimToClaimKey(claim *v1.PersistentVolumeClaim) string {
return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)
}
// UpdatePVCCondition updates pvc with given condition status
func UpdatePVCCondition(pvc *v1.PersistentVolumeClaim,
pvcConditions []v1.PersistentVolumeClaimCondition,
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
claimClone := pvc.DeepCopy()
claimClone.Status.Conditions = pvcConditions
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(claimClone)
if updateErr != nil {
glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: failed: %v", ClaimToClaimKey(pvc), updateErr)
return nil, updateErr
}
return updatedClaim, nil
}

View File

@ -114,6 +114,11 @@ const (
// New local storage types to support local storage capacity isolation
LocalStorageCapacityIsolation utilfeature.Feature = "LocalStorageCapacityIsolation"
// owner: @gnufied
// alpha: v1.8
// Ability to Expand persistent volumes
ExpandPersistentVolumes utilfeature.Feature = "ExpandPersistentVolumes"
// owner: @verb
// alpha: v1.8
//
@ -179,6 +184,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
EnableEquivalenceClassCache: {Default: false, PreRelease: utilfeature.Alpha},
TaintNodesByCondition: {Default: false, PreRelease: utilfeature.Alpha},
MountPropagation: {Default: false, PreRelease: utilfeature.Alpha},
ExpandPersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -45,6 +45,7 @@ const (
FailedAttachVolume = "FailedAttachVolume"
FailedDetachVolume = "FailedDetachVolume"
FailedMountVolume = "FailedMount"
VolumeResizeFailed = "VolumeResizeFailed"
FailedUnMountVolume = "FailedUnMount"
WarnAlreadyMountedVolume = "AlreadyMountedVolume"
SuccessfulDetachVolume = "SuccessfulDetachVolume"

View File

@ -25,6 +25,7 @@ go_library(
"//pkg/api/helper/qos:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/admission/util:go_default_library",
"//pkg/quota:go_default_library",
"//pkg/quota/generic:go_default_library",
@ -36,6 +37,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],

View File

@ -27,11 +27,13 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/helper"
k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
@ -147,6 +149,10 @@ func (p *pvcEvaluator) Handles(a admission.Attributes) bool {
if op == admission.Create {
return true
}
if op == admission.Update && utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
return true
}
updateUninitialized, err := util.IsUpdatingUninitializedObject(a)
if err != nil {
// fail closed, will try to give an evaluation.

View File

@ -16,10 +16,12 @@ go_library(
"//pkg/api:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/validation:go_default_library",
"//pkg/features:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)

View File

@ -21,9 +21,11 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/apis/storage/validation"
"k8s.io/kubernetes/pkg/features"
)
// storageClassStrategy implements behavior for StorageClass objects
@ -42,7 +44,11 @@ func (storageClassStrategy) NamespaceScoped() bool {
// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation.
func (storageClassStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
_ = obj.(*storage.StorageClass)
class := obj.(*storage.StorageClass)
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
class.AllowVolumeExpansion = nil
}
}
func (storageClassStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
@ -60,8 +66,13 @@ func (storageClassStrategy) AllowCreateOnUpdate() bool {
// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a PV
func (storageClassStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
_ = obj.(*storage.StorageClass)
_ = old.(*storage.StorageClass)
newClass := obj.(*storage.StorageClass)
oldClass := old.(*storage.StorageClass)
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
newClass.AllowVolumeExpansion = nil
oldClass.AllowVolumeExpansion = nil
}
}
func (storageClassStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {

View File

@ -127,6 +127,10 @@ func (plugin *glusterfsPlugin) SupportsBulkVolumeVerification() bool {
return false
}
func (plugin *glusterfsPlugin) RequiresFSResize() bool {
return false
}
func (plugin *glusterfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
@ -1046,3 +1050,49 @@ func parseClassParameters(params map[string]string, kubeClient clientset.Interfa
}
return &cfg, nil
}
func (plugin *glusterfsPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) {
pvSpec := spec.PersistentVolume.Spec
glog.V(2).Infof("Request to expand volume: %s ", pvSpec.Glusterfs.Path)
volumeName := pvSpec.Glusterfs.Path
// Fetch the volume for expansion.
volumeID := dstrings.TrimPrefix(volumeName, volPrefix)
//Get details of SC.
class, err := volutil.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume)
if err != nil {
return oldSize, err
}
cfg, err := parseClassParameters(class.Parameters, plugin.host.GetKubeClient())
if err != nil {
return oldSize, err
}
glog.V(4).Infof("Expanding volume %q with configuration %+v", volumeID, cfg)
//Create REST server connection
cli := gcli.NewClient(cfg.url, cfg.user, cfg.secretValue)
if cli == nil {
glog.Errorf("failed to create glusterfs rest client")
return oldSize, fmt.Errorf("failed to create glusterfs rest client, REST server authentication failed")
}
// Find out delta size
expansionSize := (newSize.Value() - oldSize.Value())
expansionSizeGB := int(volume.RoundUpSize(expansionSize, 1024*1024*1024))
// Make volume expansion request
volumeExpandReq := &gapi.VolumeExpandRequest{Size: expansionSizeGB}
// Expand the volume
volumeInfoRes, err := cli.VolumeExpand(volumeID, volumeExpandReq)
if err != nil {
glog.Errorf("error when expanding the volume :%v", err)
return oldSize, err
}
glog.V(2).Infof("volume %s expanded to new size %d successfully", volumeName, volumeInfoRes.Size)
newVolumeSize := resource.MustParse(fmt.Sprintf("%dG", volumeInfoRes.Size))
return newVolumeSize, nil
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@ -201,6 +202,14 @@ type AttachableVolumePlugin interface {
GetDeviceMountRefs(deviceMountPath string) ([]string, error)
}
// ExpandableVolumePlugin is an extended interface of VolumePlugin and is used for volumes that can be
// expanded
type ExpandableVolumePlugin interface {
VolumePlugin
ExpandVolumeDevice(spec *Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error)
RequiresFSResize() bool
}
// VolumeHost is an interface that plugins can use to access the kubelet.
type VolumeHost interface {
// GetPluginDir returns the absolute path to a directory under which
@ -642,6 +651,32 @@ func (pm *VolumePluginMgr) FindAttachablePluginByName(name string) (AttachableVo
return nil, nil
}
// FindExpandablePluginBySpec fetches a persistent volume plugin by spec.
func (pm *VolumePluginMgr) FindExpandablePluginBySpec(spec *Spec) (ExpandableVolumePlugin, error) {
volumePlugin, err := pm.FindPluginBySpec(spec)
if err != nil {
return nil, err
}
if expandableVolumePlugin, ok := volumePlugin.(ExpandableVolumePlugin); ok {
return expandableVolumePlugin, nil
}
return nil, nil
}
// FindExpandablePluginBySpec fetches a persistent volume plugin by name.
func (pm *VolumePluginMgr) FindExpandablePluginByName(name string) (ExpandableVolumePlugin, error) {
volumePlugin, err := pm.FindPluginByName(name)
if err != nil {
return nil, err
}
if expandableVolumePlugin, ok := volumePlugin.(ExpandableVolumePlugin); ok {
return expandableVolumePlugin, nil
}
return nil, nil
}
// NewPersistentVolumeRecyclerPodTemplate creates a template for a recycler
// pod. By default, a recycler pod simply runs "rm -rf" on a volume and tests
// for emptiness. Most attributes of the template will be correct for most

View File

@ -13,6 +13,7 @@ go_library(
"operation_generator.go",
],
deps = [
"//pkg/controller/volume/expand/cache:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubelet/events:go_default_library",
"//pkg/util/mount:go_default_library",
@ -37,6 +38,7 @@ go_test(
srcs = ["operation_executor_test.go"],
library = ":go_default_library",
deps = [
"//pkg/controller/volume/expand/cache:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util/types:go_default_library",

View File

@ -29,6 +29,7 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
@ -119,6 +120,8 @@ type OperationExecutor interface {
// IsOperationPending returns true if an operation for the given volumeName and podName is pending,
// otherwise it returns false
IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
// Expand Volume will grow size available to PVC
ExpandVolume(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) error
}
// NewOperationExecutor returns a new instance of OperationExecutor.
@ -719,6 +722,17 @@ func (oe *operationExecutor) UnmountDevice(
deviceToDetach.VolumeName, "" /* podName */, unmountDeviceFunc, opCompleteFunc)
}
func (oe *operationExecutor) ExpandVolume(pvcWithResizeRequest *expandcache.PVCWithResizeRequest, resizeMap expandcache.VolumeResizeMap) error {
expandFunc, pluginName, err := oe.operationGenerator.GenerateExpandVolumeFunc(pvcWithResizeRequest, resizeMap)
if err != nil {
return err
}
uniqueVolumeKey := v1.UniqueVolumeName(pvcWithResizeRequest.UniquePVCKey())
opCompleteFunc := util.OperationCompleteHook(pluginName, "expand_volume")
return oe.pendingOperations.Run(uniqueVolumeKey, "", expandFunc, opCompleteFunc)
}
func (oe *operationExecutor) VerifyControllerAttachedVolume(
volumeToMount VolumeToMount,
nodeName types.NodeName,

View File

@ -25,6 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
@ -282,6 +283,14 @@ func (fopg *fakeOperationGenerator) GenerateVerifyControllerAttachedVolumeFunc(v
}, "", nil
}
func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvcWithResizeRequest *expandcache.PVCWithResizeRequest,
resizeMap expandcache.VolumeResizeMap) (func() error, string, error) {
return func() error {
startOperationAndBlock(fopg.ch, fopg.quit)
return nil
}, "", nil
}
func (fopg *fakeOperationGenerator) GenerateBulkVolumeVerifyFunc(
pluginNodeVolumes map[types.NodeName][]*volume.Spec,
pluginNane string,

View File

@ -28,6 +28,7 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record"
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
"k8s.io/kubernetes/pkg/features"
kevents "k8s.io/kubernetes/pkg/kubelet/events"
"k8s.io/kubernetes/pkg/util/mount"
@ -100,6 +101,8 @@ type OperationGenerator interface {
map[types.NodeName][]*volume.Spec,
string,
map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (func() error, error)
GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (func() error, string, error)
}
func (og *operationGenerator) GenerateVolumesAreAttachedFunc(
@ -726,6 +729,59 @@ func (og *operationGenerator) verifyVolumeIsSafeToDetach(
return nil
}
func (og *operationGenerator) GenerateExpandVolumeFunc(
pvcWithResizeRequest *expandcache.PVCWithResizeRequest,
resizeMap expandcache.VolumeResizeMap) (func() error, string, error) {
volumeSpec := volume.NewSpecFromPersistentVolume(pvcWithResizeRequest.PersistentVolume, false)
volumePlugin, err := og.volumePluginMgr.FindExpandablePluginBySpec(volumeSpec)
if err != nil {
return nil, "", fmt.Errorf("Error finding plugin for expanding volume: %q with error %v", pvcWithResizeRequest.QualifiedName(), err)
}
expandFunc := func() error {
newSize := pvcWithResizeRequest.ExpectedSize
pvSize := pvcWithResizeRequest.PersistentVolume.Spec.Capacity[v1.ResourceStorage]
if pvSize.Cmp(newSize) < 0 {
updatedSize, expandErr := volumePlugin.ExpandVolumeDevice(
volumeSpec,
pvcWithResizeRequest.ExpectedSize,
pvcWithResizeRequest.CurrentSize)
if expandErr != nil {
glog.Errorf("Error expanding volume %q of plugin %s : %v", pvcWithResizeRequest.QualifiedName(), volumePlugin.GetPluginName(), expandErr)
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, expandErr.Error())
return expandErr
}
newSize = updatedSize
updateErr := resizeMap.UpdatePVSize(pvcWithResizeRequest, newSize)
if updateErr != nil {
glog.V(4).Infof("Error updating PV spec capacity for volume %q with : %v", pvcWithResizeRequest.QualifiedName(), updateErr)
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, updateErr.Error())
return updateErr
}
}
// No Cloudprovider resize needed, lets mark resizing as done
if !volumePlugin.RequiresFSResize() {
glog.V(4).Infof("Controller resizing done for PVC %s", pvcWithResizeRequest.QualifiedName())
err := resizeMap.MarkAsResized(pvcWithResizeRequest, newSize)
if err != nil {
glog.Errorf("Error marking pvc %s as resized : %v", pvcWithResizeRequest.QualifiedName(), err)
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, err.Error())
return err
}
}
return nil
}
return expandFunc, volumePlugin.GetPluginName(), nil
}
func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
mountOptions := volume.MountOptionFromSpec(volumeToMount.VolumeSpec)

View File

@ -21,3 +21,6 @@ import "k8s.io/apimachinery/pkg/types"
// UniquePodName defines the type to key pods off of
type UniquePodName types.UID
// UniquePVCName defines the type to key pvc off
type UniquePVCName types.UID

View File

@ -28,6 +28,7 @@ filegroup(
"//plugin/pkg/admission/namespace/exists:all-srcs",
"//plugin/pkg/admission/noderestriction:all-srcs",
"//plugin/pkg/admission/persistentvolume/label:all-srcs",
"//plugin/pkg/admission/persistentvolume/resize:all-srcs",
"//plugin/pkg/admission/podnodeselector:all-srcs",
"//plugin/pkg/admission/podpreset:all-srcs",
"//plugin/pkg/admission/podtolerationrestriction:all-srcs",

View File

@ -0,0 +1,55 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["admission.go"],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/helper:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/client/listers/storage/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,146 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resize
import (
"fmt"
"io"
"k8s.io/apiserver/pkg/admission"
"k8s.io/kubernetes/pkg/api"
apihelper "k8s.io/kubernetes/pkg/api/helper"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
pvlister "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
storagelisters "k8s.io/kubernetes/pkg/client/listers/storage/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
const (
// PluginName is the name of pvc resize admission plugin
PluginName = "PersistentVolumeClaimResize"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
plugin := newPlugin()
return plugin, nil
})
}
var _ admission.Interface = &persistentVolumeClaimResize{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&persistentVolumeClaimResize{})
type persistentVolumeClaimResize struct {
*admission.Handler
pvLister pvlister.PersistentVolumeLister
scLister storagelisters.StorageClassLister
}
func newPlugin() *persistentVolumeClaimResize {
return &persistentVolumeClaimResize{
Handler: admission.NewHandler(admission.Update),
}
}
func (pvcr *persistentVolumeClaimResize) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
pvcInformer := f.Core().InternalVersion().PersistentVolumes()
pvcr.pvLister = pvcInformer.Lister()
scInformer := f.Storage().InternalVersion().StorageClasses()
pvcr.scLister = scInformer.Lister()
pvcr.SetReadyFunc(func() bool {
return pvcInformer.Informer().HasSynced() && scInformer.Informer().HasSynced()
})
}
// Validate ensures lister is set.
func (pvcr *persistentVolumeClaimResize) Validate() error {
if pvcr.pvLister == nil {
return fmt.Errorf("missing persistent volume lister")
}
if pvcr.scLister == nil {
return fmt.Errorf("missing storageclass lister")
}
return nil
}
func (pvcr *persistentVolumeClaimResize) Admit(a admission.Attributes) error {
if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
return nil
}
if len(a.GetSubresource()) != 0 {
return nil
}
pvc, ok := a.GetObject().(*api.PersistentVolumeClaim)
// if we can't convert then we don't handle this object so just return
if !ok {
return nil
}
oldPvc, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
if !ok {
return nil
}
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
// explicitly allows it
if !pvcr.allowResize(pvc, oldPvc) {
return admission.NewForbidden(a, fmt.Errorf("only dynamically provisioned pvc can be resized and "+
"the storageclass that provisions the pvc must support resize"))
}
// volume plugin must support resize
pv, err := pvcr.pvLister.Get(pvc.Spec.VolumeName)
if err != nil {
return nil
}
if !pvcr.checkVolumePlugin(pv) {
return admission.NewForbidden(a, fmt.Errorf("volume plugin does not support resize"))
}
return nil
}
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
// explicitly allows it.
func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.PersistentVolumeClaim) bool {
pvcStorageClass := apihelper.GetPersistentVolumeClaimClass(pvc)
oldPvcStorageClass := apihelper.GetPersistentVolumeClaimClass(oldPvc)
if pvcStorageClass == "" || oldPvcStorageClass == "" || pvcStorageClass != oldPvcStorageClass {
return false
}
sc, err := pvcr.scLister.Get(pvcStorageClass)
if err != nil {
return false
}
if sc.AllowVolumeExpansion != nil {
return *sc.AllowVolumeExpansion
}
return false
}
// checkVolumePlugin checks whether the volume plugin supports resize
func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool {
if pv.Spec.Glusterfs != nil {
return true
}
return false
}

View File

@ -0,0 +1,265 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resize
import (
"fmt"
"strings"
"testing"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/storage"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/controller"
)
func getResourceList(storage string) api.ResourceList {
res := api.ResourceList{}
if storage != "" {
res[api.ResourceStorage] = resource.MustParse(storage)
}
return res
}
func TestPVCResizeAdmission(t *testing.T) {
goldClassName := "gold"
trueVal := true
falseVar := false
goldClass := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: goldClassName,
},
Provisioner: "kubernetes.io/glusterfs",
AllowVolumeExpansion: &trueVal,
}
silverClassName := "silver"
silverClass := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: silverClassName,
},
Provisioner: "kubernetes.io/glusterfs",
AllowVolumeExpansion: &falseVar,
}
expectNoError := func(err error) bool {
return err == nil
}
expectDynamicallyProvisionedError := func(err error) bool {
return strings.Contains(err.Error(), "only dynamically provisioned pvc can be resized and "+
"the storageclass that provisions the pvc must support resize")
}
expectVolumePluginError := func(err error) bool {
return strings.Contains(err.Error(), "volume plugin does not support resize")
}
tests := []struct {
name string
resource schema.GroupVersionResource
subresource string
oldObj runtime.Object
newObj runtime.Object
checkError func(error) bool
}{
{
name: "pvc-resize, update, no error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume1",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume1",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
},
},
checkError: expectNoError,
},
{
name: "pvc-resize, update, volume plugin error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume2",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume2",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
},
},
checkError: expectVolumePluginError,
},
{
name: "pvc-resize, update, dynamically provisioned error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume3",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume3",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
},
},
checkError: expectDynamicallyProvisionedError,
},
{
name: "pvc-resize, update, dynamically provisioned error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
},
},
checkError: expectDynamicallyProvisionedError,
},
}
ctrl := newPlugin()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetInternalKubeInformerFactory(informerFactory)
err := ctrl.Validate()
if err != nil {
t.Fatalf("neither pv lister nor storageclass lister can be nil")
}
pv1 := &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "volume1"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
Glusterfs: &api.GlusterfsVolumeSource{
EndpointsName: "http://localhost:8080/",
Path: "/heketi",
ReadOnly: false,
},
},
StorageClassName: goldClassName,
},
}
pv2 := &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "volume2"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{},
},
StorageClassName: goldClassName,
},
}
pvs := []*api.PersistentVolume{}
pvs = append(pvs, pv1, pv2)
for _, pv := range pvs {
err := informerFactory.Core().InternalVersion().PersistentVolumes().Informer().GetStore().Add(pv)
if err != nil {
fmt.Println("add pv error: ", err)
}
}
scs := []*storage.StorageClass{}
scs = append(scs, goldClass, silverClass)
for _, sc := range scs {
err := informerFactory.Storage().InternalVersion().StorageClasses().Informer().GetStore().Add(sc)
if err != nil {
fmt.Println("add storageclass error: ", err)
}
}
for _, tc := range tests {
operation := admission.Update
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, nil)
err := ctrl.Admit(attributes)
fmt.Println(tc.name)
fmt.Println(err)
if !tc.checkError(err) {
t.Errorf("%v: unexpected err: %v", tc.name, err)
}
}
}

View File

@ -22,7 +22,9 @@ import (
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/features"
)
const saRolePrefix = "system:controller:"
@ -121,6 +123,23 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) {
eventsRule(),
},
})
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "expand-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update", "patch").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
rbac.NewRule("update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
// glusterfs
rbac.NewRule("get", "list", "watch").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
eventsRule(),
},
})
}
addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "generic-garbage-collector"},
Rules: []rbac.PolicyRule{

File diff suppressed because it is too large Load Diff

View File

@ -2133,6 +2133,31 @@ message PersistentVolumeClaim {
optional PersistentVolumeClaimStatus status = 3;
}
// PersistentVolumeClaimCondition contails details about state of pvc
message PersistentVolumeClaimCondition {
optional string type = 1;
optional string status = 2;
// Last time we probed the condition.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastProbeTime = 3;
// Last time the condition transitioned from one status to another.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4;
// Unique, this should be a short, machine understandable string that gives the reason
// for condition's last transition. If it reports "ResizeStarted" that means the underlying
// persistent volume is being resized.
// +optional
optional string reason = 5;
// Human-readable message indicating details about last transition.
// +optional
optional string message = 6;
}
// PersistentVolumeClaimList is a list of PersistentVolumeClaim items.
message PersistentVolumeClaimList {
// Standard list metadata.
@ -2186,6 +2211,13 @@ message PersistentVolumeClaimStatus {
// Represents the actual resources of the underlying volume.
// +optional
map<string, k8s.io.apimachinery.pkg.api.resource.Quantity> capacity = 3;
// Current Condition of persistent volume claim. If underlying persistent volume is being
// resized then the Condition will be set to 'ResizeStarted'.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
repeated PersistentVolumeClaimCondition conditions = 4;
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.

View File

@ -630,6 +630,34 @@ type PersistentVolumeClaimSpec struct {
StorageClassName *string `json:"storageClassName,omitempty" protobuf:"bytes,5,opt,name=storageClassName"`
}
// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type
type PersistentVolumeClaimConditionType string
const (
// PersistentVolumeClaimResizing - a user trigger resize of pvc has been started
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
)
// PersistentVolumeClaimCondition contails details about state of pvc
type PersistentVolumeClaimCondition struct {
Type PersistentVolumeClaimConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=PersistentVolumeClaimConditionType"`
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
// Last time we probed the condition.
// +optional
LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"`
// Last time the condition transitioned from one status to another.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"`
// Unique, this should be a short, machine understandable string that gives the reason
// for condition's last transition. If it reports "ResizeStarted" that means the underlying
// persistent volume is being resized.
// +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
// Human-readable message indicating details about last transition.
// +optional
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}
// PersistentVolumeClaimStatus is the current status of a persistent volume claim.
type PersistentVolumeClaimStatus struct {
// Phase represents the current phase of PersistentVolumeClaim.
@ -642,6 +670,12 @@ type PersistentVolumeClaimStatus struct {
// Represents the actual resources of the underlying volume.
// +optional
Capacity ResourceList `json:"capacity,omitempty" protobuf:"bytes,3,rep,name=capacity,casttype=ResourceList,castkey=ResourceName"`
// Current Condition of persistent volume claim. If underlying persistent volume is being
// resized then the Condition will be set to 'ResizeStarted'.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []PersistentVolumeClaimCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,4,rep,name=conditions"`
}
type PersistentVolumeAccessMode string

View File

@ -1122,6 +1122,18 @@ func (PersistentVolumeClaim) SwaggerDoc() map[string]string {
return map_PersistentVolumeClaim
}
var map_PersistentVolumeClaimCondition = map[string]string{
"": "PersistentVolumeClaimCondition contails details about state of pvc",
"lastProbeTime": "Last time we probed the condition.",
"lastTransitionTime": "Last time the condition transitioned from one status to another.",
"reason": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized.",
"message": "Human-readable message indicating details about last transition.",
}
func (PersistentVolumeClaimCondition) SwaggerDoc() map[string]string {
return map_PersistentVolumeClaimCondition
}
var map_PersistentVolumeClaimList = map[string]string{
"": "PersistentVolumeClaimList is a list of PersistentVolumeClaim items.",
"metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
@ -1150,6 +1162,7 @@ var map_PersistentVolumeClaimStatus = map[string]string{
"phase": "Phase represents the current phase of PersistentVolumeClaim.",
"accessModes": "AccessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1",
"capacity": "Represents the actual resources of the underlying volume.",
"conditions": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'.",
}
func (PersistentVolumeClaimStatus) SwaggerDoc() map[string]string {

View File

@ -427,6 +427,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*PersistentVolumeClaim).DeepCopyInto(out.(*PersistentVolumeClaim))
return nil
}, InType: reflect.TypeOf(&PersistentVolumeClaim{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*PersistentVolumeClaimCondition).DeepCopyInto(out.(*PersistentVolumeClaimCondition))
return nil
}, InType: reflect.TypeOf(&PersistentVolumeClaimCondition{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*PersistentVolumeClaimList).DeepCopyInto(out.(*PersistentVolumeClaimList))
return nil
@ -3499,6 +3503,24 @@ func (in *PersistentVolumeClaim) DeepCopyObject() runtime.Object {
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimCondition) DeepCopyInto(out *PersistentVolumeClaimCondition) {
*out = *in
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimCondition.
func (in *PersistentVolumeClaimCondition) DeepCopy() *PersistentVolumeClaimCondition {
if in == nil {
return nil
}
out := new(PersistentVolumeClaimCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimList) DeepCopyInto(out *PersistentVolumeClaimList) {
*out = *in
@ -3588,6 +3610,13 @@ func (in *PersistentVolumeClaimStatus) DeepCopyInto(out *PersistentVolumeClaimSt
(*out)[key] = val.DeepCopy()
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]PersistentVolumeClaimCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

View File

@ -136,6 +136,16 @@ func (m *StorageClass) MarshalTo(dAtA []byte) (int, error) {
i += copy(dAtA[i:], s)
}
}
if m.AllowVolumeExpansion != nil {
dAtA[i] = 0x30
i++
if *m.AllowVolumeExpansion {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil
}
@ -229,6 +239,9 @@ func (m *StorageClass) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
}
}
if m.AllowVolumeExpansion != nil {
n += 2
}
return n
}
@ -279,6 +292,7 @@ func (this *StorageClass) String() string {
`Parameters:` + mapStringForParameters + `,`,
`ReclaimPolicy:` + valueToStringGenerated(this.ReclaimPolicy) + `,`,
`MountOptions:` + fmt.Sprintf("%v", this.MountOptions) + `,`,
`AllowVolumeExpansion:` + valueToStringGenerated(this.AllowVolumeExpansion) + `,`,
`}`,
}, "")
return s
@ -565,6 +579,27 @@ func (m *StorageClass) Unmarshal(dAtA []byte) error {
}
m.MountOptions = append(m.MountOptions, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field AllowVolumeExpansion", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.AllowVolumeExpansion = &b
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@ -807,40 +842,43 @@ func init() {
}
var fileDescriptorGenerated = []byte{
// 558 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0x8e, 0x13, 0x22, 0x35, 0x9b, 0x44, 0x44, 0x06, 0x24, 0x2b, 0x07, 0x27, 0x2a, 0x97, 0x08,
0x89, 0xdd, 0xa6, 0x2d, 0xa8, 0x42, 0x82, 0x43, 0x50, 0x25, 0x90, 0xa8, 0x1a, 0x19, 0x89, 0x03,
0xe2, 0xc0, 0xc6, 0x19, 0x9c, 0xc5, 0x3f, 0x6b, 0xed, 0xae, 0x2d, 0xe5, 0xc6, 0x23, 0xf0, 0x3c,
0x3c, 0x41, 0x8e, 0x3d, 0xf6, 0x14, 0x11, 0xf3, 0x06, 0x1c, 0x39, 0x21, 0xdb, 0x21, 0x76, 0x9a,
0x54, 0xf4, 0xb6, 0x33, 0xf3, 0x7d, 0xdf, 0xce, 0xcc, 0x37, 0xe8, 0x95, 0x7b, 0x26, 0x31, 0xe3,
0xc4, 0x8d, 0x26, 0x20, 0x02, 0x50, 0x20, 0x49, 0x0c, 0xc1, 0x94, 0x0b, 0xb2, 0x2e, 0xd0, 0x90,
0x11, 0xa9, 0xb8, 0xa0, 0x0e, 0x90, 0x78, 0x48, 0x1c, 0x08, 0x40, 0x50, 0x05, 0x53, 0x1c, 0x0a,
0xae, 0xb8, 0xfe, 0x28, 0x87, 0x61, 0x1a, 0x32, 0xbc, 0x86, 0xe1, 0x78, 0xd8, 0x7d, 0xea, 0x30,
0x35, 0x8b, 0x26, 0xd8, 0xe6, 0x3e, 0x71, 0xb8, 0xc3, 0x49, 0x86, 0x9e, 0x44, 0x5f, 0xb2, 0x28,
0x0b, 0xb2, 0x57, 0xae, 0xd2, 0x7d, 0xb2, 0xf7, 0xb3, 0x09, 0x28, 0xba, 0xf3, 0x63, 0xf7, 0xb4,
0xc0, 0xfa, 0xd4, 0x9e, 0xb1, 0x00, 0xc4, 0x9c, 0x84, 0xae, 0x93, 0x26, 0x24, 0xf1, 0x41, 0xd1,
0x3d, 0x7d, 0x76, 0xc9, 0x6d, 0x2c, 0x11, 0x05, 0x8a, 0xf9, 0xb0, 0x43, 0x78, 0xfe, 0x3f, 0x82,
0xb4, 0x67, 0xe0, 0xd3, 0x1d, 0xde, 0xc9, 0x6d, 0xbc, 0x48, 0x31, 0x8f, 0xb0, 0x40, 0x49, 0x25,
0x6e, 0x92, 0x0e, 0x7f, 0xd7, 0x50, 0xeb, 0x7d, 0x3e, 0xf7, 0x6b, 0x8f, 0x4a, 0xa9, 0x7f, 0x46,
0x07, 0xe9, 0x24, 0x53, 0xaa, 0xa8, 0xa1, 0xf5, 0xb5, 0x41, 0xf3, 0xf8, 0x08, 0x17, 0x9b, 0xde,
0x08, 0xe3, 0xd0, 0x75, 0xd2, 0x84, 0xc4, 0x29, 0x1a, 0xc7, 0x43, 0x7c, 0x39, 0xf9, 0x0a, 0xb6,
0xba, 0x00, 0x45, 0x47, 0xfa, 0x62, 0xd9, 0xab, 0x24, 0xcb, 0x1e, 0x2a, 0x72, 0xd6, 0x46, 0x55,
0x7f, 0x86, 0x9a, 0xa1, 0xe0, 0x31, 0x93, 0x8c, 0x07, 0x20, 0x8c, 0x6a, 0x5f, 0x1b, 0x34, 0x46,
0x0f, 0xd6, 0x94, 0xe6, 0xb8, 0x28, 0x59, 0x65, 0x9c, 0xee, 0x20, 0x14, 0x52, 0x41, 0x7d, 0x50,
0x20, 0xa4, 0x51, 0xeb, 0xd7, 0x06, 0xcd, 0xe3, 0x13, 0xbc, 0xf7, 0x08, 0x70, 0x79, 0x22, 0x3c,
0xde, 0xb0, 0xce, 0x03, 0x25, 0xe6, 0x45, 0x77, 0x45, 0xc1, 0x2a, 0x49, 0xeb, 0x2e, 0x6a, 0x0b,
0xb0, 0x3d, 0xca, 0xfc, 0x31, 0xf7, 0x98, 0x3d, 0x37, 0xee, 0x65, 0x1d, 0x9e, 0x27, 0xcb, 0x5e,
0xdb, 0x2a, 0x17, 0xfe, 0x2c, 0x7b, 0x47, 0xa5, 0xf3, 0xb1, 0xb9, 0x48, 0x6f, 0x07, 0x8f, 0x41,
0x48, 0x26, 0x15, 0x04, 0xea, 0x03, 0xf7, 0x22, 0x1f, 0xb6, 0x38, 0xd6, 0xb6, 0xb6, 0x7e, 0x8a,
0x5a, 0x3e, 0x8f, 0x02, 0x75, 0x19, 0x2a, 0xc6, 0x03, 0x69, 0xd4, 0xfb, 0xb5, 0x41, 0x63, 0xd4,
0x49, 0x96, 0xbd, 0xd6, 0x45, 0x29, 0x6f, 0x6d, 0xa1, 0xba, 0x2f, 0xd1, 0xfd, 0x1b, 0x53, 0xe9,
0x1d, 0x54, 0x73, 0x61, 0x9e, 0x59, 0xd6, 0xb0, 0xd2, 0xa7, 0xfe, 0x10, 0xd5, 0x63, 0xea, 0x45,
0x90, 0x6f, 0xd8, 0xca, 0x83, 0x17, 0xd5, 0x33, 0xed, 0xf0, 0x87, 0x86, 0x3a, 0xe5, 0x15, 0xbd,
0x63, 0x52, 0xe9, 0x9f, 0x76, 0x8c, 0xc7, 0x77, 0x33, 0x3e, 0x65, 0x67, 0xb6, 0x77, 0xd6, 0x8b,
0x3d, 0xf8, 0x97, 0x29, 0x99, 0xfe, 0x06, 0xd5, 0x99, 0x02, 0x5f, 0x1a, 0xd5, 0xcc, 0xb8, 0xc7,
0x77, 0x30, 0x6e, 0xd4, 0x5e, 0xeb, 0xd5, 0xdf, 0xa6, 0x4c, 0x2b, 0x17, 0x18, 0x0d, 0x16, 0x2b,
0xb3, 0x72, 0xb5, 0x32, 0x2b, 0xd7, 0x2b, 0xb3, 0xf2, 0x2d, 0x31, 0xb5, 0x45, 0x62, 0x6a, 0x57,
0x89, 0xa9, 0x5d, 0x27, 0xa6, 0xf6, 0x33, 0x31, 0xb5, 0xef, 0xbf, 0xcc, 0xca, 0xc7, 0x6a, 0x3c,
0xfc, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xb4, 0xf1, 0xb4, 0x62, 0x04, 0x00, 0x00,
// 593 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x6f, 0xd3, 0x3e,
0x18, 0xc6, 0x9b, 0x76, 0xfd, 0x6b, 0x73, 0x37, 0xfd, 0xab, 0x30, 0xa4, 0xa8, 0x87, 0xb4, 0x1a,
0x97, 0x0a, 0x09, 0x7b, 0xdd, 0x06, 0x9a, 0x90, 0x40, 0xa2, 0x68, 0x12, 0x48, 0x9b, 0x56, 0x05,
0x89, 0x03, 0xe2, 0x80, 0x9b, 0xbd, 0x64, 0x26, 0x89, 0x1d, 0xd9, 0x4e, 0xa0, 0x37, 0x3e, 0x02,
0x9f, 0x87, 0x13, 0xc7, 0x1d, 0x77, 0xdc, 0x29, 0x62, 0xe1, 0x5b, 0x70, 0x42, 0x49, 0xca, 0x92,
0xad, 0x9d, 0xd8, 0xcd, 0x7e, 0xdf, 0xe7, 0xf7, 0xd8, 0x7e, 0xfd, 0xa0, 0xe7, 0xfe, 0xbe, 0xc2,
0x4c, 0x10, 0x3f, 0x9e, 0x82, 0xe4, 0xa0, 0x41, 0x91, 0x04, 0xf8, 0x89, 0x90, 0x64, 0xde, 0xa0,
0x11, 0x23, 0x4a, 0x0b, 0x49, 0x3d, 0x20, 0xc9, 0x88, 0x78, 0xc0, 0x41, 0x52, 0x0d, 0x27, 0x38,
0x92, 0x42, 0x0b, 0xf3, 0x7e, 0x29, 0xc3, 0x34, 0x62, 0x78, 0x2e, 0xc3, 0xc9, 0xa8, 0xf7, 0xc8,
0x63, 0xfa, 0x34, 0x9e, 0x62, 0x57, 0x84, 0xc4, 0x13, 0x9e, 0x20, 0x85, 0x7a, 0x1a, 0x7f, 0x2c,
0x76, 0xc5, 0xa6, 0x58, 0x95, 0x2e, 0xbd, 0x87, 0x4b, 0x0f, 0x9b, 0x82, 0xa6, 0x0b, 0x27, 0xf6,
0xf6, 0x2a, 0x6d, 0x48, 0xdd, 0x53, 0xc6, 0x41, 0xce, 0x48, 0xe4, 0x7b, 0x79, 0x41, 0x91, 0x10,
0x34, 0x5d, 0x72, 0xcf, 0x1e, 0xb9, 0x8d, 0x92, 0x31, 0xd7, 0x2c, 0x84, 0x05, 0xe0, 0xc9, 0xbf,
0x00, 0xe5, 0x9e, 0x42, 0x48, 0x17, 0xb8, 0xdd, 0xdb, 0xb8, 0x58, 0xb3, 0x80, 0x30, 0xae, 0x95,
0x96, 0x37, 0xa1, 0xad, 0x1f, 0x2b, 0x68, 0xfd, 0x4d, 0xf9, 0xee, 0x97, 0x01, 0x55, 0xca, 0xfc,
0x80, 0x56, 0xf3, 0x97, 0x9c, 0x50, 0x4d, 0x2d, 0x63, 0x60, 0x0c, 0x3b, 0x3b, 0xdb, 0xb8, 0x9a,
0xf4, 0x95, 0x31, 0x8e, 0x7c, 0x2f, 0x2f, 0x28, 0x9c, 0xab, 0x71, 0x32, 0xc2, 0xc7, 0xd3, 0x4f,
0xe0, 0xea, 0x23, 0xd0, 0x74, 0x6c, 0x9e, 0xa5, 0xfd, 0x46, 0x96, 0xf6, 0x51, 0x55, 0x73, 0xae,
0x5c, 0xcd, 0xc7, 0xa8, 0x13, 0x49, 0x91, 0x30, 0xc5, 0x04, 0x07, 0x69, 0x35, 0x07, 0xc6, 0x70,
0x6d, 0x7c, 0x6f, 0x8e, 0x74, 0x26, 0x55, 0xcb, 0xa9, 0xeb, 0x4c, 0x0f, 0xa1, 0x88, 0x4a, 0x1a,
0x82, 0x06, 0xa9, 0xac, 0xd6, 0xa0, 0x35, 0xec, 0xec, 0xec, 0xe2, 0xa5, 0x21, 0xc0, 0xf5, 0x17,
0xe1, 0xc9, 0x15, 0x75, 0xc0, 0xb5, 0x9c, 0x55, 0xb7, 0xab, 0x1a, 0x4e, 0xcd, 0xda, 0xf4, 0xd1,
0x86, 0x04, 0x37, 0xa0, 0x2c, 0x9c, 0x88, 0x80, 0xb9, 0x33, 0x6b, 0xa5, 0xb8, 0xe1, 0x41, 0x96,
0xf6, 0x37, 0x9c, 0x7a, 0xe3, 0x77, 0xda, 0xdf, 0xae, 0xc5, 0xc7, 0x15, 0x32, 0xcf, 0x0e, 0x9e,
0x80, 0x54, 0x4c, 0x69, 0xe0, 0xfa, 0xad, 0x08, 0xe2, 0x10, 0xae, 0x31, 0xce, 0x75, 0x6f, 0x73,
0x0f, 0xad, 0x87, 0x22, 0xe6, 0xfa, 0x38, 0xd2, 0x4c, 0x70, 0x65, 0xb5, 0x07, 0xad, 0xe1, 0xda,
0xb8, 0x9b, 0xa5, 0xfd, 0xf5, 0xa3, 0x5a, 0xdd, 0xb9, 0xa6, 0x32, 0x0f, 0xd1, 0x26, 0x0d, 0x02,
0xf1, 0xb9, 0x3c, 0xe0, 0xe0, 0x4b, 0x44, 0x79, 0x3e, 0x25, 0xeb, 0xbf, 0x81, 0x31, 0x5c, 0x1d,
0x5b, 0x59, 0xda, 0xdf, 0x7c, 0xb1, 0xa4, 0xef, 0x2c, 0xa5, 0x7a, 0xcf, 0xd0, 0xff, 0x37, 0x66,
0x64, 0x76, 0x51, 0xcb, 0x87, 0x59, 0x11, 0x80, 0x35, 0x27, 0x5f, 0x9a, 0x9b, 0xa8, 0x9d, 0xd0,
0x20, 0x86, 0xf2, 0xbf, 0x9c, 0x72, 0xf3, 0xb4, 0xb9, 0x6f, 0x6c, 0x7d, 0x37, 0x50, 0xb7, 0x3e,
0xf0, 0x43, 0xa6, 0xb4, 0xf9, 0x7e, 0x21, 0x46, 0xf8, 0x6e, 0x31, 0xca, 0xe9, 0x22, 0x44, 0xdd,
0xf9, 0x37, 0xad, 0xfe, 0xad, 0xd4, 0x22, 0xf4, 0x0a, 0xb5, 0x99, 0x86, 0x50, 0x59, 0xcd, 0x22,
0x06, 0x0f, 0xee, 0x10, 0x83, 0xf1, 0xc6, 0xdc, 0xaf, 0xfd, 0x3a, 0x27, 0x9d, 0xd2, 0x60, 0x3c,
0x3c, 0xbb, 0xb4, 0x1b, 0xe7, 0x97, 0x76, 0xe3, 0xe2, 0xd2, 0x6e, 0x7c, 0xcd, 0x6c, 0xe3, 0x2c,
0xb3, 0x8d, 0xf3, 0xcc, 0x36, 0x2e, 0x32, 0xdb, 0xf8, 0x99, 0xd9, 0xc6, 0xb7, 0x5f, 0x76, 0xe3,
0x5d, 0x33, 0x19, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x57, 0xe7, 0x15, 0xb0, 0x04, 0x00,
0x00,
}

View File

@ -59,6 +59,10 @@ message StorageClass {
// mount of the PVs will simply fail if one is invalid.
// +optional
repeated string mountOptions = 5;
// AllowVolumeExpansion shows whether the storage class allow volume expand
// +optional
optional bool allowVolumeExpansion = 6;
}
// StorageClassList is a collection of storage classes.

View File

@ -55,6 +55,10 @@ type StorageClass struct {
// mount of the PVs will simply fail if one is invalid.
// +optional
MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,5,opt,name=mountOptions"`
// AllowVolumeExpansion shows whether the storage class allow volume expand
// +optional
AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -28,12 +28,13 @@ package v1
// AUTO-GENERATED FUNCTIONS START HERE
var map_StorageClass = map[string]string{
"": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.",
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"provisioner": "Provisioner indicates the type of the provisioner.",
"parameters": "Parameters holds the parameters for the provisioner that should create volumes of this storage class.",
"reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.",
"mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.",
"": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.",
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"provisioner": "Provisioner indicates the type of the provisioner.",
"parameters": "Parameters holds the parameters for the provisioner that should create volumes of this storage class.",
"reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.",
"mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.",
"allowVolumeExpansion": "AllowVolumeExpansion shows whether the storage class allow volume expand",
}
func (StorageClass) SwaggerDoc() map[string]string {

View File

@ -74,6 +74,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllowVolumeExpansion != nil {
in, out := &in.AllowVolumeExpansion, &out.AllowVolumeExpansion
if *in == nil {
*out = nil
} else {
*out = new(bool)
**out = **in
}
}
return
}

View File

@ -136,6 +136,16 @@ func (m *StorageClass) MarshalTo(dAtA []byte) (int, error) {
i += copy(dAtA[i:], s)
}
}
if m.AllowVolumeExpansion != nil {
dAtA[i] = 0x30
i++
if *m.AllowVolumeExpansion {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil
}
@ -229,6 +239,9 @@ func (m *StorageClass) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
}
}
if m.AllowVolumeExpansion != nil {
n += 2
}
return n
}
@ -279,6 +292,7 @@ func (this *StorageClass) String() string {
`Parameters:` + mapStringForParameters + `,`,
`ReclaimPolicy:` + valueToStringGenerated(this.ReclaimPolicy) + `,`,
`MountOptions:` + fmt.Sprintf("%v", this.MountOptions) + `,`,
`AllowVolumeExpansion:` + valueToStringGenerated(this.AllowVolumeExpansion) + `,`,
`}`,
}, "")
return s
@ -565,6 +579,27 @@ func (m *StorageClass) Unmarshal(dAtA []byte) error {
}
m.MountOptions = append(m.MountOptions, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field AllowVolumeExpansion", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.AllowVolumeExpansion = &b
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@ -807,40 +842,42 @@ func init() {
}
var fileDescriptorGenerated = []byte{
// 554 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x3f, 0x8f, 0x12, 0x41,
0x18, 0xc6, 0xd9, 0x43, 0xe2, 0xdd, 0x00, 0x91, 0xac, 0x16, 0x1b, 0x8a, 0x85, 0x50, 0xd1, 0xdc,
0xcc, 0x71, 0x9e, 0x86, 0x98, 0xd8, 0x70, 0xb9, 0xc2, 0x44, 0x72, 0x64, 0x4d, 0x2c, 0x8c, 0x85,
0xc3, 0xf2, 0xba, 0x8c, 0xfb, 0x67, 0x36, 0x33, 0xb3, 0x24, 0x74, 0x7e, 0x04, 0xbf, 0x91, 0x2d,
0xe5, 0x95, 0x57, 0x11, 0x59, 0x3f, 0x84, 0x89, 0x95, 0xd9, 0x3f, 0xb2, 0x0b, 0x48, 0xbc, 0x6e,
0xe6, 0x7d, 0x9f, 0xdf, 0x33, 0x33, 0xef, 0x33, 0xe8, 0xda, 0x1d, 0x4a, 0xcc, 0x38, 0x71, 0xa3,
0x29, 0x88, 0x00, 0x14, 0x48, 0xb2, 0x80, 0x60, 0xc6, 0x05, 0xc9, 0x1b, 0x34, 0x64, 0x44, 0x2a,
0x2e, 0xa8, 0x03, 0x64, 0x31, 0x98, 0x82, 0xa2, 0x03, 0xe2, 0x40, 0x00, 0x82, 0x2a, 0x98, 0xe1,
0x50, 0x70, 0xc5, 0xf5, 0x76, 0xa6, 0xc5, 0x34, 0x64, 0x38, 0xd7, 0xe2, 0x5c, 0xdb, 0x3e, 0x77,
0x98, 0x9a, 0x47, 0x53, 0x6c, 0x73, 0x9f, 0x38, 0xdc, 0xe1, 0x24, 0x45, 0xa6, 0xd1, 0xe7, 0x74,
0x97, 0x6e, 0xd2, 0x55, 0x66, 0xd5, 0xee, 0x95, 0x8e, 0xb5, 0xb9, 0x48, 0xce, 0xdc, 0x3f, 0xae,
0x7d, 0x55, 0x68, 0x7c, 0x6a, 0xcf, 0x59, 0x00, 0x62, 0x49, 0x42, 0xd7, 0x49, 0x0a, 0x92, 0xf8,
0xa0, 0xe8, 0xbf, 0x28, 0x72, 0x8c, 0x12, 0x51, 0xa0, 0x98, 0x0f, 0x07, 0xc0, 0xcb, 0xff, 0x01,
0xd2, 0x9e, 0x83, 0x4f, 0x0f, 0xb8, 0xe7, 0xc7, 0xb8, 0x48, 0x31, 0x8f, 0xb0, 0x40, 0x49, 0x25,
0xf6, 0xa1, 0xde, 0xaf, 0x2a, 0x6a, 0xbc, 0xcb, 0x46, 0x77, 0xed, 0x51, 0x29, 0xf5, 0x4f, 0xe8,
0x34, 0x79, 0xc9, 0x8c, 0x2a, 0x6a, 0x68, 0x5d, 0xad, 0x5f, 0xbf, 0xbc, 0xc0, 0xc5, 0x98, 0xb7,
0xc6, 0x38, 0x74, 0x9d, 0xa4, 0x20, 0x71, 0xa2, 0xc6, 0x8b, 0x01, 0xbe, 0x9d, 0x7e, 0x01, 0x5b,
0x8d, 0x41, 0xd1, 0x91, 0xbe, 0x5a, 0x77, 0x2a, 0xf1, 0xba, 0x83, 0x8a, 0x9a, 0xb5, 0x75, 0xd5,
0x5f, 0xa0, 0x7a, 0x28, 0xf8, 0x82, 0x49, 0xc6, 0x03, 0x10, 0xc6, 0x49, 0x57, 0xeb, 0x9f, 0x8d,
0x9e, 0xe6, 0x48, 0x7d, 0x52, 0xb4, 0xac, 0xb2, 0x4e, 0xf7, 0x10, 0x0a, 0xa9, 0xa0, 0x3e, 0x28,
0x10, 0xd2, 0xa8, 0x76, 0xab, 0xfd, 0xfa, 0xe5, 0x10, 0x1f, 0xff, 0x01, 0xb8, 0xfc, 0x2c, 0x3c,
0xd9, 0xa2, 0x37, 0x81, 0x12, 0xcb, 0xe2, 0x8a, 0x45, 0xc3, 0x2a, 0xf9, 0xeb, 0x2e, 0x6a, 0x0a,
0xb0, 0x3d, 0xca, 0xfc, 0x09, 0xf7, 0x98, 0xbd, 0x34, 0x1e, 0xa5, 0xd7, 0xbc, 0x89, 0xd7, 0x9d,
0xa6, 0x55, 0x6e, 0xfc, 0x5e, 0x77, 0x2e, 0x0e, 0xff, 0x0e, 0x9e, 0x80, 0x90, 0x4c, 0x2a, 0x08,
0xd4, 0x7b, 0xee, 0x45, 0x3e, 0xec, 0x30, 0xd6, 0xae, 0xb7, 0x7e, 0x85, 0x1a, 0x3e, 0x8f, 0x02,
0x75, 0x1b, 0x2a, 0xc6, 0x03, 0x69, 0xd4, 0xba, 0xd5, 0xfe, 0xd9, 0xa8, 0x15, 0xaf, 0x3b, 0x8d,
0x71, 0xa9, 0x6e, 0xed, 0xa8, 0xda, 0xaf, 0xd1, 0x93, 0xbd, 0x57, 0xe9, 0x2d, 0x54, 0x75, 0x61,
0x99, 0xe6, 0x76, 0x66, 0x25, 0x4b, 0xfd, 0x19, 0xaa, 0x2d, 0xa8, 0x17, 0x41, 0x36, 0x66, 0x2b,
0xdb, 0xbc, 0x3a, 0x19, 0x6a, 0xbd, 0xef, 0x1a, 0x6a, 0x95, 0x47, 0xf4, 0x96, 0x49, 0xa5, 0x7f,
0x3c, 0x48, 0x1f, 0x3f, 0x2c, 0xfd, 0x84, 0x4e, 0xb3, 0x6f, 0xe5, 0x83, 0x3d, 0xfd, 0x5b, 0x29,
0x25, 0x3f, 0x46, 0x35, 0xa6, 0xc0, 0x97, 0xc6, 0x49, 0x9a, 0x5e, 0xff, 0xa1, 0xe9, 0x8d, 0x9a,
0xb9, 0x69, 0xed, 0x4d, 0x82, 0x5b, 0x99, 0xcb, 0xe8, 0x7c, 0xb5, 0x31, 0x2b, 0x77, 0x1b, 0xb3,
0x72, 0xbf, 0x31, 0x2b, 0x5f, 0x63, 0x53, 0x5b, 0xc5, 0xa6, 0x76, 0x17, 0x9b, 0xda, 0x7d, 0x6c,
0x6a, 0x3f, 0x62, 0x53, 0xfb, 0xf6, 0xd3, 0xac, 0x7c, 0x78, 0x9c, 0x3b, 0xfe, 0x09, 0x00, 0x00,
0xff, 0xff, 0x2a, 0xfd, 0x7a, 0x0a, 0x73, 0x04, 0x00, 0x00,
// 589 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4f, 0x4f, 0xd4, 0x40,
0x18, 0xc6, 0xb7, 0x2c, 0xab, 0x30, 0x0b, 0x71, 0x53, 0x39, 0x34, 0x7b, 0xe8, 0x6e, 0x38, 0xf5,
0xc2, 0x0c, 0x20, 0x1a, 0x62, 0xe2, 0xc1, 0x12, 0x0e, 0x26, 0x10, 0x36, 0x35, 0xf1, 0x60, 0x3c,
0x38, 0x5b, 0x5e, 0xcb, 0xd8, 0x76, 0xa6, 0x99, 0x99, 0xae, 0xee, 0xcd, 0x8f, 0xe0, 0x37, 0xf2,
0x64, 0xc2, 0x91, 0x23, 0xa7, 0x46, 0xea, 0xb7, 0xf0, 0x64, 0xfa, 0x47, 0x5a, 0x58, 0x88, 0xdc,
0x3a, 0xef, 0xfb, 0xfc, 0x9e, 0xb7, 0xf3, 0xce, 0x83, 0x0e, 0xc2, 0x7d, 0x85, 0x99, 0x20, 0x61,
0x3a, 0x05, 0xc9, 0x41, 0x83, 0x22, 0x33, 0xe0, 0xa7, 0x42, 0x92, 0xba, 0x41, 0x13, 0x46, 0x94,
0x16, 0x92, 0x06, 0x40, 0x66, 0x3b, 0x53, 0xd0, 0x74, 0x87, 0x04, 0xc0, 0x41, 0x52, 0x0d, 0xa7,
0x38, 0x91, 0x42, 0x0b, 0x73, 0x58, 0x69, 0x31, 0x4d, 0x18, 0xae, 0xb5, 0xb8, 0xd6, 0x0e, 0xb7,
0x02, 0xa6, 0xcf, 0xd2, 0x29, 0xf6, 0x45, 0x4c, 0x02, 0x11, 0x08, 0x52, 0x22, 0xd3, 0xf4, 0x53,
0x79, 0x2a, 0x0f, 0xe5, 0x57, 0x65, 0x35, 0xdc, 0x6c, 0x8d, 0xf5, 0x85, 0x2c, 0x66, 0xde, 0x1e,
0x37, 0xdc, 0x6b, 0x34, 0x31, 0xf5, 0xcf, 0x18, 0x07, 0x39, 0x27, 0x49, 0x18, 0x14, 0x05, 0x45,
0x62, 0xd0, 0xf4, 0x2e, 0x8a, 0xdc, 0x47, 0xc9, 0x94, 0x6b, 0x16, 0xc3, 0x02, 0xf0, 0xe2, 0x7f,
0x80, 0xf2, 0xcf, 0x20, 0xa6, 0x0b, 0xdc, 0xb3, 0xfb, 0xb8, 0x54, 0xb3, 0x88, 0x30, 0xae, 0x95,
0x96, 0xb7, 0xa1, 0xcd, 0x9f, 0xcb, 0x68, 0xed, 0x6d, 0xb5, 0xba, 0x83, 0x88, 0x2a, 0x65, 0x7e,
0x44, 0x2b, 0xc5, 0x4d, 0x4e, 0xa9, 0xa6, 0x96, 0x31, 0x36, 0x9c, 0xfe, 0xee, 0x36, 0x6e, 0xd6,
0x7c, 0x6d, 0x8c, 0x93, 0x30, 0x28, 0x0a, 0x0a, 0x17, 0x6a, 0x3c, 0xdb, 0xc1, 0x27, 0xd3, 0xcf,
0xe0, 0xeb, 0x63, 0xd0, 0xd4, 0x35, 0xcf, 0xb3, 0x51, 0x27, 0xcf, 0x46, 0xa8, 0xa9, 0x79, 0xd7,
0xae, 0xe6, 0x73, 0xd4, 0x4f, 0xa4, 0x98, 0x31, 0xc5, 0x04, 0x07, 0x69, 0x2d, 0x8d, 0x0d, 0x67,
0xd5, 0x7d, 0x5a, 0x23, 0xfd, 0x49, 0xd3, 0xf2, 0xda, 0x3a, 0x33, 0x42, 0x28, 0xa1, 0x92, 0xc6,
0xa0, 0x41, 0x2a, 0xab, 0x3b, 0xee, 0x3a, 0xfd, 0xdd, 0x7d, 0x7c, 0x7f, 0x02, 0x70, 0xfb, 0x5a,
0x78, 0x72, 0x8d, 0x1e, 0x72, 0x2d, 0xe7, 0xcd, 0x2f, 0x36, 0x0d, 0xaf, 0xe5, 0x6f, 0x86, 0x68,
0x5d, 0x82, 0x1f, 0x51, 0x16, 0x4f, 0x44, 0xc4, 0xfc, 0xb9, 0xb5, 0x5c, 0xfe, 0xe6, 0x61, 0x9e,
0x8d, 0xd6, 0xbd, 0x76, 0xe3, 0x4f, 0x36, 0xda, 0x5e, 0xcc, 0x0e, 0x9e, 0x80, 0x54, 0x4c, 0x69,
0xe0, 0xfa, 0x9d, 0x88, 0xd2, 0x18, 0x6e, 0x30, 0xde, 0x4d, 0x6f, 0x73, 0x0f, 0xad, 0xc5, 0x22,
0xe5, 0xfa, 0x24, 0xd1, 0x4c, 0x70, 0x65, 0xf5, 0xc6, 0x5d, 0x67, 0xd5, 0x1d, 0xe4, 0xd9, 0x68,
0xed, 0xb8, 0x55, 0xf7, 0x6e, 0xa8, 0xcc, 0x23, 0xb4, 0x41, 0xa3, 0x48, 0x7c, 0xa9, 0x06, 0x1c,
0x7e, 0x4d, 0x28, 0x2f, 0x56, 0x65, 0x3d, 0x1a, 0x1b, 0xce, 0x8a, 0x6b, 0xe5, 0xd9, 0x68, 0xe3,
0xf5, 0x1d, 0x7d, 0xef, 0x4e, 0x6a, 0xf8, 0x0a, 0x3d, 0xb9, 0xb5, 0x23, 0x73, 0x80, 0xba, 0x21,
0xcc, 0xcb, 0x14, 0xac, 0x7a, 0xc5, 0xa7, 0xb9, 0x81, 0x7a, 0x33, 0x1a, 0xa5, 0x50, 0x3d, 0x9a,
0x57, 0x1d, 0x5e, 0x2e, 0xed, 0x1b, 0x9b, 0x3f, 0x0c, 0x34, 0x68, 0x2f, 0xfc, 0x88, 0x29, 0x6d,
0x7e, 0x58, 0xc8, 0x12, 0x7e, 0x58, 0x96, 0x0a, 0xba, 0x4c, 0xd2, 0xa0, 0x7e, 0xa6, 0x95, 0x7f,
0x95, 0x56, 0x8e, 0x8e, 0x51, 0x8f, 0x69, 0x88, 0x95, 0xb5, 0x54, 0x66, 0xc1, 0x79, 0x68, 0x16,
0xdc, 0xf5, 0xda, 0xb4, 0xf7, 0xa6, 0xc0, 0xbd, 0xca, 0xc5, 0xdd, 0x3a, 0xbf, 0xb2, 0x3b, 0x17,
0x57, 0x76, 0xe7, 0xf2, 0xca, 0xee, 0x7c, 0xcb, 0x6d, 0xe3, 0x3c, 0xb7, 0x8d, 0x8b, 0xdc, 0x36,
0x2e, 0x73, 0xdb, 0xf8, 0x95, 0xdb, 0xc6, 0xf7, 0xdf, 0x76, 0xe7, 0xfd, 0xe3, 0xda, 0xf1, 0x6f,
0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xae, 0x44, 0x72, 0xc1, 0x04, 0x00, 0x00,
}

View File

@ -59,6 +59,10 @@ message StorageClass {
// mount of the PVs will simply fail if one is invalid.
// +optional
repeated string mountOptions = 5;
// AllowVolumeExpansion shows whether the storage class allow volume expand
// +optional
optional bool allowVolumeExpansion = 6;
}
// StorageClassList is a collection of storage classes.

View File

@ -55,6 +55,10 @@ type StorageClass struct {
// mount of the PVs will simply fail if one is invalid.
// +optional
MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,5,opt,name=mountOptions"`
// AllowVolumeExpansion shows whether the storage class allow volume expand
// +optional
AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -28,12 +28,13 @@ package v1beta1
// AUTO-GENERATED FUNCTIONS START HERE
var map_StorageClass = map[string]string{
"": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.",
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"provisioner": "Provisioner indicates the type of the provisioner.",
"parameters": "Parameters holds the parameters for the provisioner that should create volumes of this storage class.",
"reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.",
"mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.",
"": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.",
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"provisioner": "Provisioner indicates the type of the provisioner.",
"parameters": "Parameters holds the parameters for the provisioner that should create volumes of this storage class.",
"reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.",
"mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.",
"allowVolumeExpansion": "AllowVolumeExpansion shows whether the storage class allow volume expand",
}
func (StorageClass) SwaggerDoc() map[string]string {

View File

@ -74,6 +74,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllowVolumeExpansion != nil {
in, out := &in.AllowVolumeExpansion, &out.AllowVolumeExpansion
if *in == nil {
*out = nil
} else {
*out = new(bool)
**out = **in
}
}
return
}