mirror of https://github.com/k3s-io/k3s
Merge pull request #59391 from msau42/topology-beta
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Move volume scheduling and local storage to beta **What this PR does / why we need it**: * Move the feature gates and APIs for volume scheduling and local storage to beta * Update tests to use the beta fields @kubernetes/sig-storage-pr-reviews **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #59390 **Special notes for your reviewer**: **Release note**: ```release-note ACTION REQUIRED: VolumeScheduling and LocalPersistentVolume features are beta and enabled by default. The PersistentVolume NodeAffinity alpha annotation is deprecated and will be removed in a future release. ```pull/6/head
commit
6ba46963f8
|
@ -78602,6 +78602,10 @@
|
|||
"description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs",
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource"
|
||||
},
|
||||
"nodeAffinity": {
|
||||
"description": "NodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.",
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.VolumeNodeAffinity"
|
||||
},
|
||||
"persistentVolumeReclaimPolicy": {
|
||||
"description": "What happens to a persistent volume when released from its claim. Valid options are Retain (default) and Recycle. Recycling must be supported by the volume plugin underlying this persistent volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming",
|
||||
"type": "string"
|
||||
|
@ -80580,6 +80584,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.api.core.v1.VolumeNodeAffinity": {
|
||||
"description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
|
||||
"properties": {
|
||||
"required": {
|
||||
"description": "Required specifies hard node constraints that must be met.",
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector"
|
||||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.api.core.v1.VolumeProjection": {
|
||||
"description": "Projection that may be projected along with other supported volume types",
|
||||
"properties": {
|
||||
|
|
|
@ -20682,6 +20682,10 @@
|
|||
"volumeMode": {
|
||||
"$ref": "v1.PersistentVolumeMode",
|
||||
"description": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future."
|
||||
},
|
||||
"nodeAffinity": {
|
||||
"$ref": "v1.VolumeNodeAffinity",
|
||||
"description": "NodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -21331,6 +21335,73 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.VolumeNodeAffinity": {
|
||||
"id": "v1.VolumeNodeAffinity",
|
||||
"description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
|
||||
"properties": {
|
||||
"required": {
|
||||
"$ref": "v1.NodeSelector",
|
||||
"description": "Required specifies hard node constraints that must be met."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelector": {
|
||||
"id": "v1.NodeSelector",
|
||||
"description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.",
|
||||
"required": [
|
||||
"nodeSelectorTerms"
|
||||
],
|
||||
"properties": {
|
||||
"nodeSelectorTerms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "v1.NodeSelectorTerm"
|
||||
},
|
||||
"description": "Required. A list of node selector terms. The terms are ORed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelectorTerm": {
|
||||
"id": "v1.NodeSelectorTerm",
|
||||
"description": "A null or empty node selector term matches no objects.",
|
||||
"required": [
|
||||
"matchExpressions"
|
||||
],
|
||||
"properties": {
|
||||
"matchExpressions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "v1.NodeSelectorRequirement"
|
||||
},
|
||||
"description": "Required. A list of node selector requirements. The requirements are ANDed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelectorRequirement": {
|
||||
"id": "v1.NodeSelectorRequirement",
|
||||
"description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
|
||||
"required": [
|
||||
"key",
|
||||
"operator"
|
||||
],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The label key that the selector applies to."
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.PersistentVolumeStatus": {
|
||||
"id": "v1.PersistentVolumeStatus",
|
||||
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
|
||||
|
@ -22869,63 +22940,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelector": {
|
||||
"id": "v1.NodeSelector",
|
||||
"description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.",
|
||||
"required": [
|
||||
"nodeSelectorTerms"
|
||||
],
|
||||
"properties": {
|
||||
"nodeSelectorTerms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "v1.NodeSelectorTerm"
|
||||
},
|
||||
"description": "Required. A list of node selector terms. The terms are ORed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelectorTerm": {
|
||||
"id": "v1.NodeSelectorTerm",
|
||||
"description": "A null or empty node selector term matches no objects.",
|
||||
"required": [
|
||||
"matchExpressions"
|
||||
],
|
||||
"properties": {
|
||||
"matchExpressions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "v1.NodeSelectorRequirement"
|
||||
},
|
||||
"description": "Required. A list of node selector requirements. The requirements are ANDed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.NodeSelectorRequirement": {
|
||||
"id": "v1.NodeSelectorRequirement",
|
||||
"description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
|
||||
"required": [
|
||||
"key",
|
||||
"operator"
|
||||
],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The label key that the selector applies to."
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.PreferredSchedulingTerm": {
|
||||
"id": "v1.PreferredSchedulingTerm",
|
||||
"description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).",
|
||||
|
|
|
@ -861,54 +861,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_persistentvolumestatus">v1.PersistentVolumeStatus</h3>
|
||||
<div class="paragraph">
|
||||
<p>PersistentVolumeStatus is the current status of a persistent volume.</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">phase</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Phase indicates if a volume is available, bound to a claim, or released by a claim. More info: <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase">https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase</a></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">A human-readable message indicating details about why the volume is in this state.</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">Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.</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_configmaplist">v1.ConfigMapList</h3>
|
||||
|
@ -964,6 +916,54 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_persistentvolumestatus">v1.PersistentVolumeStatus</h3>
|
||||
<div class="paragraph">
|
||||
<p>PersistentVolumeStatus is the current status of a persistent volume.</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">phase</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Phase indicates if a volume is available, bound to a claim, or released by a claim. More info: <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase">https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase</a></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">A human-readable message indicating details about why the volume is in this state.</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">Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.</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_gitrepovolumesource">v1.GitRepoVolumeSource</h3>
|
||||
|
@ -4211,6 +4211,13 @@ Examples:<br>
|
|||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_persistentvolumemode">v1.PersistentVolumeMode</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">nodeAffinity</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">NodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.</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_volumenodeaffinity">v1.VolumeNodeAffinity</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -5541,6 +5548,40 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_nodeselector">v1.NodeSelector</h3>
|
||||
<div class="paragraph">
|
||||
<p>A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.</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">nodeSelectorTerms</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required. A list of node selector terms. The terms are ORed.</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"><a href="#_v1_nodeselectorterm">v1.NodeSelectorTerm</a> array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_persistentvolumelist">v1.PersistentVolumeList</h3>
|
||||
|
@ -5596,40 +5637,6 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_nodeselector">v1.NodeSelector</h3>
|
||||
<div class="paragraph">
|
||||
<p>A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.</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">nodeSelectorTerms</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required. A list of node selector terms. The terms are ORed.</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"><a href="#_v1_nodeselectorterm">v1.NodeSelectorTerm</a> array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_patch">v1.Patch</h3>
|
||||
|
@ -6432,40 +6439,6 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_localvolumesource">v1.LocalVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Local represents directly-attached storage with node affinity</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">path</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device</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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_nodeselectorterm">v1.NodeSelectorTerm</h3>
|
||||
|
@ -6500,6 +6473,40 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_localvolumesource">v1.LocalVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Local represents directly-attached storage with node affinity</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">path</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device</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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_selinuxoptions">v1.SELinuxOptions</h3>
|
||||
|
@ -11111,6 +11118,40 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_volumenodeaffinity">v1.VolumeNodeAffinity</h3>
|
||||
<div class="paragraph">
|
||||
<p>VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.</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">required</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required specifies hard node constraints that must be met.</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_nodeselector">v1.NodeSelector</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_scaleiopersistentvolumesource">v1.ScaleIOPersistentVolumeSource</h3>
|
||||
|
|
|
@ -467,6 +467,16 @@ type PersistentVolumeSpec struct {
|
|||
// This is an alpha feature and may change in the future.
|
||||
// +optional
|
||||
VolumeMode *PersistentVolumeMode
|
||||
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
// This field influences the scheduling of pods that use this volume.
|
||||
// +optional
|
||||
NodeAffinity *VolumeNodeAffinity
|
||||
}
|
||||
|
||||
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
type VolumeNodeAffinity struct {
|
||||
// Required specifies hard node constraints that must be met.
|
||||
Required *NodeSelector
|
||||
}
|
||||
|
||||
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes
|
||||
|
|
|
@ -406,6 +406,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
|
|||
Convert_core_VolumeDevice_To_v1_VolumeDevice,
|
||||
Convert_v1_VolumeMount_To_core_VolumeMount,
|
||||
Convert_core_VolumeMount_To_v1_VolumeMount,
|
||||
Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity,
|
||||
Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity,
|
||||
Convert_v1_VolumeProjection_To_core_VolumeProjection,
|
||||
Convert_core_VolumeProjection_To_v1_VolumeProjection,
|
||||
Convert_v1_VolumeSource_To_core_VolumeSource,
|
||||
|
@ -3346,6 +3348,7 @@ func autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.Per
|
|||
out.StorageClassName = in.StorageClassName
|
||||
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
|
||||
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.NodeAffinity = (*core.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -3365,6 +3368,7 @@ func autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.P
|
|||
out.StorageClassName = in.StorageClassName
|
||||
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
|
||||
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.NodeAffinity = (*v1.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -5508,6 +5512,26 @@ func Convert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v1.Vo
|
|||
return autoConvert_core_VolumeMount_To_v1_VolumeMount(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in *v1.VolumeNodeAffinity, out *core.VolumeNodeAffinity, s conversion.Scope) error {
|
||||
out.Required = (*core.NodeSelector)(unsafe.Pointer(in.Required))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity is an autogenerated conversion function.
|
||||
func Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in *v1.VolumeNodeAffinity, out *core.VolumeNodeAffinity, s conversion.Scope) error {
|
||||
return autoConvert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in *core.VolumeNodeAffinity, out *v1.VolumeNodeAffinity, s conversion.Scope) error {
|
||||
out.Required = (*v1.NodeSelector)(unsafe.Pointer(in.Required))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity is an autogenerated conversion function.
|
||||
func Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in *core.VolumeNodeAffinity, out *v1.VolumeNodeAffinity, s conversion.Scope) error {
|
||||
return autoConvert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_VolumeProjection_To_core_VolumeProjection(in *v1.VolumeProjection, out *core.VolumeProjection, s conversion.Scope) error {
|
||||
out.Secret = (*core.SecretProjection)(unsafe.Pointer(in.Secret))
|
||||
out.DownwardAPI = (*core.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI))
|
||||
|
|
|
@ -1383,6 +1383,9 @@ func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path)
|
|||
return allErrs
|
||||
}
|
||||
|
||||
if !path.IsAbs(ls.Path) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, ls.Path, "must be an absolute path"))
|
||||
}
|
||||
allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
@ -1497,6 +1500,15 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
|
|||
nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations"))
|
||||
allErrs = append(allErrs, errs...)
|
||||
|
||||
volumeNodeAffinitySpecified, errs := validateVolumeNodeAffinity(pv.Spec.NodeAffinity, specPath.Child("nodeAffinity"))
|
||||
allErrs = append(allErrs, errs...)
|
||||
|
||||
if nodeAffinitySpecified && volumeNodeAffinitySpecified {
|
||||
allErrs = append(allErrs, field.Forbidden(specPath.Child("nodeAffinity"), "may not specify both alpha nodeAffinity annotation and nodeAffinity field"))
|
||||
}
|
||||
|
||||
nodeAffinitySpecified = nodeAffinitySpecified || volumeNodeAffinitySpecified
|
||||
|
||||
numVolumes := 0
|
||||
if pv.Spec.HostPath != nil {
|
||||
if numVolumes > 0 {
|
||||
|
@ -1725,6 +1737,13 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
|
|||
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
// Allow setting NodeAffinity if oldPv NodeAffinity was not set
|
||||
if oldPv.Spec.NodeAffinity != nil {
|
||||
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...)
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -4936,7 +4955,7 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
|
|||
return false, allErrs
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate"))
|
||||
}
|
||||
|
||||
|
@ -4952,6 +4971,30 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
|
|||
return policySpecified, allErrs
|
||||
}
|
||||
|
||||
// validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data
|
||||
// returns:
|
||||
// - true if volumeNodeAffinity is set
|
||||
// - errorList if there are validation errors
|
||||
func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if nodeAffinity == nil {
|
||||
return false, allErrs
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "Volume node affinity is disabled by feature-gate"))
|
||||
}
|
||||
|
||||
if nodeAffinity.Required != nil {
|
||||
allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, fldPath.Child("required"))...)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints"))
|
||||
}
|
||||
|
||||
return true, allErrs
|
||||
}
|
||||
|
||||
// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR
|
||||
func ValidateCIDR(cidr string) (*net.IPNet, error) {
|
||||
_, net, err := net.ParseCIDR(cidr)
|
||||
|
|
|
@ -63,7 +63,7 @@ func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *
|
|||
}
|
||||
}
|
||||
|
||||
func testVolumeWithNodeAffinity(t *testing.T, name string, namespace string, affinity *core.NodeAffinity, spec core.PersistentVolumeSpec) *core.PersistentVolume {
|
||||
func testVolumeWithAlphaNodeAffinity(t *testing.T, name string, namespace string, affinity *core.NodeAffinity, spec core.PersistentVolumeSpec) *core.PersistentVolume {
|
||||
objMeta := metav1.ObjectMeta{Name: name}
|
||||
if namespace != "" {
|
||||
objMeta.Namespace = namespace
|
||||
|
@ -372,42 +372,6 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||
VolumeMode: &validMode,
|
||||
}),
|
||||
},
|
||||
// LocalVolume alpha feature disabled
|
||||
// TODO: remove when no longer alpha
|
||||
"alpha disabled valid local volume": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
t,
|
||||
"valid-local-volume",
|
||||
"",
|
||||
&core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "test-label-key",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"test-label-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
},
|
||||
"bad-hostpath-volume-backsteps": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("foo", "", core.PersistentVolumeSpec{
|
||||
|
@ -424,20 +388,31 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||
StorageClassName: "backstep-hostpath",
|
||||
}),
|
||||
},
|
||||
"bad-local-volume-backsteps": {
|
||||
"volume-node-affinity": {
|
||||
isExpectedFailure: false,
|
||||
volume: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
},
|
||||
"volume-empty-node-affinity": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("foo", "", core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo/..",
|
||||
volume: testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}),
|
||||
},
|
||||
"volume-bad-node-affinity": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
&core.VolumeNodeAffinity{
|
||||
Required: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"test-label-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
StorageClassName: "backstep-local",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -514,14 +489,30 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
|
||||
return core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: path,
|
||||
},
|
||||
},
|
||||
NodeAffinity: affinity,
|
||||
StorageClassName: "test-storage-class",
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLocalVolumes(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
volume *core.PersistentVolume
|
||||
}{
|
||||
"valid local volume": {
|
||||
"alpha valid local volume": {
|
||||
isExpectedFailure: false,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
volume: testVolumeWithAlphaNodeAffinity(
|
||||
t,
|
||||
"valid-local-volume",
|
||||
"",
|
||||
|
@ -540,60 +531,27 @@ func TestValidateLocalVolumes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"invalid local volume nil annotations": {
|
||||
"alpha invalid local volume nil annotations": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume(
|
||||
"invalid-local-volume-nil-annotations",
|
||||
"",
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"invalid local volume empty affinity": {
|
||||
"alpha invalid local volume empty affinity": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
volume: testVolumeWithAlphaNodeAffinity(
|
||||
t,
|
||||
"invalid-local-volume-empty-affinity",
|
||||
"",
|
||||
&core.NodeAffinity{},
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"invalid local volume preferred affinity": {
|
||||
"alpha invalid local volume preferred affinity": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
volume: testVolumeWithAlphaNodeAffinity(
|
||||
t,
|
||||
"invalid-local-volume-preferred-affinity",
|
||||
"",
|
||||
|
@ -626,24 +584,13 @@ func TestValidateLocalVolumes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"invalid local volume empty path": {
|
||||
"alpha and beta local volume": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithNodeAffinity(
|
||||
volume: testVolumeWithAlphaNodeAffinity(
|
||||
t,
|
||||
"invalid-local-volume-empty-path",
|
||||
"invalid-alpha-beta-local-volume",
|
||||
"",
|
||||
&core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
|
@ -660,24 +607,35 @@ func TestValidateLocalVolumes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
Local: &core.LocalVolumeSource{},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
}),
|
||||
testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
"valid local volume": {
|
||||
isExpectedFailure: false,
|
||||
volume: testVolume("valid-local-volume", "",
|
||||
testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
"invalid local volume no node affinity": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("invalid-local-volume-no-node-affinity", "",
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"invalid local volume empty path": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("invalid-local-volume-empty-path", "",
|
||||
testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
"invalid-local-volume-backsteps": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("foo", "",
|
||||
testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
"invalid-local-volume-relative-path": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("foo", "",
|
||||
testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
}
|
||||
|
||||
err := utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=true")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to enable feature gate for LocalPersistentVolumes: %v", err)
|
||||
return
|
||||
}
|
||||
for name, scenario := range scenarios {
|
||||
errs := ValidatePersistentVolume(scenario.volume)
|
||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||
|
@ -689,6 +647,145 @@ func TestValidateLocalVolumes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateLocalVolumesDisabled(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
volume *core.PersistentVolume
|
||||
}{
|
||||
"alpha disabled valid local volume": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolumeWithAlphaNodeAffinity(
|
||||
t,
|
||||
"valid-local-volume",
|
||||
"",
|
||||
&core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "test-label-key",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"test-label-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testLocalVolume("/foo", nil)),
|
||||
},
|
||||
"feature disabled valid local volume": {
|
||||
isExpectedFailure: true,
|
||||
volume: testVolume("valid-local-volume", "",
|
||||
testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
|
||||
},
|
||||
}
|
||||
|
||||
utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=false")
|
||||
for name, scenario := range scenarios {
|
||||
errs := ValidatePersistentVolume(scenario.volume)
|
||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected success for scenario: %s", name)
|
||||
}
|
||||
if len(errs) > 0 && !scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
|
||||
}
|
||||
}
|
||||
utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=true")
|
||||
|
||||
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
for name, scenario := range scenarios {
|
||||
errs := ValidatePersistentVolume(scenario.volume)
|
||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected success for scenario: %s", name)
|
||||
}
|
||||
if len(errs) > 0 && !scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
|
||||
}
|
||||
}
|
||||
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
}
|
||||
|
||||
func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
|
||||
return testVolume("test-affinity-volume", "",
|
||||
core.PersistentVolumeSpec{
|
||||
Capacity: core.ResourceList{
|
||||
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
|
||||
PDName: "foo",
|
||||
},
|
||||
},
|
||||
StorageClassName: "test-storage-class",
|
||||
NodeAffinity: affinity,
|
||||
})
|
||||
}
|
||||
|
||||
func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity {
|
||||
return &core.VolumeNodeAffinity{
|
||||
Required: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: key,
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{value},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
oldPV *core.PersistentVolume
|
||||
newPV *core.PersistentVolume
|
||||
}{
|
||||
"nil-nothing-changed": {
|
||||
isExpectedFailure: false,
|
||||
oldPV: testVolumeWithNodeAffinity(nil),
|
||||
newPV: testVolumeWithNodeAffinity(nil),
|
||||
},
|
||||
"affinity-nothing-changed": {
|
||||
isExpectedFailure: false,
|
||||
oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
},
|
||||
"affinity-changed": {
|
||||
isExpectedFailure: true,
|
||||
oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")),
|
||||
},
|
||||
"nil-to-obj": {
|
||||
isExpectedFailure: false,
|
||||
oldPV: testVolumeWithNodeAffinity(nil),
|
||||
newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
},
|
||||
"obj-to-nil": {
|
||||
isExpectedFailure: true,
|
||||
oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
newPV: testVolumeWithNodeAffinity(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
|
||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected success for scenario: %s", name)
|
||||
}
|
||||
if len(errs) > 0 && !scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
|
||||
return &core.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
|
|
|
@ -3369,6 +3369,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) {
|
|||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.NodeAffinity != nil {
|
||||
in, out := &in.NodeAffinity, &out.NodeAffinity
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(VolumeNodeAffinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5570,6 +5579,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeNodeAffinity) DeepCopyInto(out *VolumeNodeAffinity) {
|
||||
*out = *in
|
||||
if in.Required != nil {
|
||||
in, out := &in.Required, &out.Required
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(NodeSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeNodeAffinity.
|
||||
func (in *VolumeNodeAffinity) DeepCopy() *VolumeNodeAffinity {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(VolumeNodeAffinity)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) {
|
||||
*out = *in
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
storageapi "k8s.io/kubernetes/pkg/apis/storage"
|
||||
)
|
||||
|
||||
// Funcs returns the fuzzer functions for the storage api group.
|
||||
|
@ -31,6 +32,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||
c.FuzzNoCustom(obj) // fuzz self without calling this function again
|
||||
reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimDelete, api.PersistentVolumeReclaimRetain}
|
||||
obj.ReclaimPolicy = &reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))]
|
||||
bindingModes := []storageapi.VolumeBindingMode{storageapi.VolumeBindingImmediate, storageapi.VolumeBindingWaitForFirstConsumer}
|
||||
obj.VolumeBindingMode = &bindingModes[c.Rand.Intn(len(bindingModes))]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ func TestDropAlphaFields(t *testing.T) {
|
|||
bindingMode := storage.VolumeBindingWaitForFirstConsumer
|
||||
|
||||
// Test that field gets dropped when feature gate is not set
|
||||
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
|
||||
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
class := &storage.StorageClass{
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
|
|||
class := &storagev1.StorageClass{}
|
||||
|
||||
// When feature gate is disabled, field should not be defaulted
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
|
||||
if output.VolumeBindingMode != nil {
|
||||
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
|
||||
|
@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
|
|||
|
||||
class = &storagev1.StorageClass{}
|
||||
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
// When feature gate is enabled, field should be defaulted
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
// When feature gate is enabled, field should be defaulted
|
||||
defaultMode := storagev1.VolumeBindingImmediate
|
||||
output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
|
||||
outMode := output.VolumeBindingMode
|
||||
|
|
|
@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
|
|||
class := &storagev1beta1.StorageClass{}
|
||||
|
||||
// When feature gate is disabled, field should not be defaulted
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
|
||||
if output.VolumeBindingMode != nil {
|
||||
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
|
||||
|
@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
|
|||
|
||||
class = &storagev1beta1.StorageClass{}
|
||||
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
// When feature gate is enabled, field should be defaulted
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
// When feature gate is enabled, field should be defaulted
|
||||
defaultMode := storagev1beta1.VolumeBindingImmediate
|
||||
output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
|
||||
outMode := output.VolumeBindingMode
|
||||
|
|
|
@ -42,16 +42,18 @@ func TestValidateStorageClass(t *testing.T) {
|
|||
successCases := []storage.StorageClass{
|
||||
{
|
||||
// empty parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
Parameters: map[string]string{},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
Parameters: map[string]string{},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// nil parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// some parameters
|
||||
|
@ -62,13 +64,15 @@ func TestValidateStorageClass(t *testing.T) {
|
|||
"foo-parameter": "free-form-string",
|
||||
"foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// retain reclaimPolicy
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &retainReclaimPolicy,
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &retainReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -144,6 +148,7 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) {
|
|||
Parameters: map[string]string{},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
AllowVolumeExpansion: &falseVar,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
}
|
||||
|
||||
// Enable alpha feature ExpandPersistentVolumes
|
||||
|
@ -462,6 +467,10 @@ func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) {
|
|||
"invalid mode": makeClassWithBinding(&invalidMode),
|
||||
}
|
||||
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
for testName, storageClass := range errorCases {
|
||||
if errs := ValidateStorageClass(storageClass); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %v", testName)
|
||||
|
|
|
@ -78,7 +78,6 @@ go_test(
|
|||
deps = [
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -29,7 +27,6 @@ import (
|
|||
"k8s.io/client-go/kubernetes/scheme"
|
||||
ref "k8s.io/client-go/tools/reference"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -680,9 +677,8 @@ func createTestVolumes() []*v1.PersistentVolume {
|
|||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "affinity-pv",
|
||||
Name: "affinity001",
|
||||
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
|
||||
UID: "affinity-pv",
|
||||
Name: "affinity001",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
|
@ -696,13 +692,13 @@ func createTestVolumes() []*v1.PersistentVolume {
|
|||
v1.ReadOnlyMany,
|
||||
},
|
||||
StorageClassName: classWait,
|
||||
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "affinity-pv2",
|
||||
Name: "affinity002",
|
||||
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
|
||||
UID: "affinity-pv2",
|
||||
Name: "affinity002",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
|
@ -716,13 +712,13 @@ func createTestVolumes() []*v1.PersistentVolume {
|
|||
v1.ReadOnlyMany,
|
||||
},
|
||||
StorageClassName: classWait,
|
||||
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "affinity-prebound",
|
||||
Name: "affinity003",
|
||||
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
|
||||
UID: "affinity-prebound",
|
||||
Name: "affinity003",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
|
@ -737,13 +733,13 @@ func createTestVolumes() []*v1.PersistentVolume {
|
|||
},
|
||||
StorageClassName: classWait,
|
||||
ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"},
|
||||
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "affinity-pv3",
|
||||
Name: "affinity003",
|
||||
Annotations: getAnnotationWithNodeAffinity("key1", "value3"),
|
||||
UID: "affinity-pv3",
|
||||
Name: "affinity003",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
|
@ -757,6 +753,7 @@ func createTestVolumes() []*v1.PersistentVolume {
|
|||
v1.ReadOnlyMany,
|
||||
},
|
||||
StorageClassName: classWait,
|
||||
NodeAffinity: getVolumeNodeAffinity("key1", "value3"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -776,9 +773,9 @@ func testVolume(name, size string) *v1.PersistentVolume {
|
|||
}
|
||||
}
|
||||
|
||||
func getAnnotationWithNodeAffinity(key string, value string) map[string]string {
|
||||
affinity := &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
func getVolumeNodeAffinity(key string, value string) *v1.VolumeNodeAffinity {
|
||||
return &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
|
@ -792,14 +789,6 @@ func getAnnotationWithNodeAffinity(key string, value string) map[string]string {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
annotations := map[string]string{}
|
||||
err := helper.StorageNodeAffinityToAlphaAnnotation(annotations, affinity)
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to get node affinity annotation: %v", err)
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
||||
func createVolumeModeBlockTestVolume() *v1.PersistentVolume {
|
||||
|
|
|
@ -331,7 +331,7 @@ func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentV
|
|||
},
|
||||
}
|
||||
if node != "" {
|
||||
pv.Annotations = getAnnotationWithNodeAffinity("key1", node)
|
||||
pv.Spec.NodeAffinity = getVolumeNodeAffinity("key1", node)
|
||||
}
|
||||
|
||||
if boundToPVC != nil {
|
||||
|
|
|
@ -263,7 +263,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
|||
TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
RotateKubeletServerCertificate: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta},
|
||||
PersistentLocalVolumes: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
PersistentLocalVolumes: {Default: true, PreRelease: utilfeature.Beta},
|
||||
LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
HugePages: {Default: true, PreRelease: utilfeature.Beta},
|
||||
DebugContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
@ -276,7 +276,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
|||
CPUManager: {Default: true, PreRelease: utilfeature.Beta},
|
||||
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
VolumeScheduling: {Default: true, PreRelease: utilfeature.Beta},
|
||||
CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta},
|
||||
CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
|
|
@ -45,6 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
|||
|
||||
func validNewStorageClass(name string) *storageapi.StorageClass {
|
||||
deleteReclaimPolicy := api.PersistentVolumeReclaimDelete
|
||||
bindingMode := storageapi.VolumeBindingImmediate
|
||||
return &storageapi.StorageClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
|
@ -53,7 +54,8 @@ func validNewStorageClass(name string) *storageapi.StorageClass {
|
|||
Parameters: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,7 +207,8 @@ func TestScheduler(t *testing.T) {
|
|||
NextPod: func() *v1.Pod {
|
||||
return item.sendPod
|
||||
},
|
||||
Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}),
|
||||
Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}),
|
||||
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -555,6 +556,7 @@ func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache.
|
|||
Recorder: &record.FakeRecorder{},
|
||||
PodConditionUpdater: fakePodConditionUpdater{},
|
||||
PodPreemptor: fakePodPreemptor{},
|
||||
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -604,6 +606,7 @@ func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, sc
|
|||
PodConditionUpdater: fakePodConditionUpdater{},
|
||||
PodPreemptor: fakePodPreemptor{},
|
||||
StopEverything: stop,
|
||||
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -237,6 +237,13 @@ func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume)
|
|||
// CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
|
||||
// This ensures that we don't mount a volume that doesn't belong to this node
|
||||
func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
|
||||
if err := checkAlphaNodeAffinity(pv, nodeLabels); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkVolumeNodeAffinity(pv, nodeLabels)
|
||||
}
|
||||
|
||||
func checkAlphaNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
|
||||
affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting storage node affinity: %v", err)
|
||||
|
@ -261,6 +268,27 @@ func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) er
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkVolumeNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
|
||||
if pv.Spec.NodeAffinity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if pv.Spec.NodeAffinity.Required != nil {
|
||||
terms := pv.Spec.NodeAffinity.Required.NodeSelectorTerms
|
||||
glog.V(10).Infof("Match for Required node selector terms %+v", terms)
|
||||
for _, term := range terms {
|
||||
selector, err := v1helper.NodeSelectorRequirementsAsSelector(term.MatchExpressions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse MatchExpressions: %v", err)
|
||||
}
|
||||
if !selector.Matches(labels.Set(nodeLabels)) {
|
||||
return fmt.Errorf("NodeSelectorTerm %+v does not match node labels", term.MatchExpressions)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPodFromFile will read, decode, and return a Pod from a file.
|
||||
func LoadPodFromFile(filePath string) (*v1.Pod, error) {
|
||||
if filePath == "" {
|
||||
|
|
|
@ -37,7 +37,7 @@ var nodeLabels map[string]string = map[string]string{
|
|||
"test-key2": "test-value2",
|
||||
}
|
||||
|
||||
func TestCheckNodeAffinity(t *testing.T) {
|
||||
func TestCheckAlphaNodeAffinity(t *testing.T) {
|
||||
type affinityTest struct {
|
||||
name string
|
||||
expectSuccess bool
|
||||
|
@ -48,12 +48,12 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||
{
|
||||
name: "valid-no-constraints",
|
||||
expectSuccess: true,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{}),
|
||||
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{}),
|
||||
},
|
||||
{
|
||||
name: "valid-constraints",
|
||||
expectSuccess: true,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
||||
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
|
@ -77,7 +77,7 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||
{
|
||||
name: "invalid-key",
|
||||
expectSuccess: false,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
||||
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
|
@ -101,7 +101,7 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||
{
|
||||
name: "invalid-values",
|
||||
expectSuccess: false,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
||||
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
|
@ -136,7 +136,111 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume {
|
||||
func TestCheckVolumeNodeAffinity(t *testing.T) {
|
||||
type affinityTest struct {
|
||||
name string
|
||||
expectSuccess bool
|
||||
pv *v1.PersistentVolume
|
||||
}
|
||||
|
||||
cases := []affinityTest{
|
||||
{
|
||||
name: "valid-nil",
|
||||
expectSuccess: true,
|
||||
pv: testVolumeWithNodeAffinity(t, nil),
|
||||
},
|
||||
{
|
||||
name: "valid-no-constraints",
|
||||
expectSuccess: true,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{}),
|
||||
},
|
||||
{
|
||||
name: "valid-constraints",
|
||||
expectSuccess: true,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "test-key1",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value1", "test-value3"},
|
||||
},
|
||||
{
|
||||
Key: "test-key2",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value0", "test-value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "invalid-key",
|
||||
expectSuccess: false,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "test-key1",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value1", "test-value3"},
|
||||
},
|
||||
{
|
||||
Key: "test-key3",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value0", "test-value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "invalid-values",
|
||||
expectSuccess: false,
|
||||
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "test-key1",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value3", "test-value4"},
|
||||
},
|
||||
{
|
||||
Key: "test-key2",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"test-value0", "test-value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
err := CheckNodeAffinity(c.pv, nodeLabels)
|
||||
|
||||
if err != nil && c.expectSuccess {
|
||||
t.Errorf("CheckTopology %v returned error: %v", c.name, err)
|
||||
}
|
||||
if err == nil && !c.expectSuccess {
|
||||
t.Errorf("CheckTopology %v returned success, expected error", c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testVolumeWithAlphaNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume {
|
||||
objMeta := metav1.ObjectMeta{Name: "test-constraints"}
|
||||
objMeta.Annotations = map[string]string{}
|
||||
err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity)
|
||||
|
@ -149,6 +253,16 @@ func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.Per
|
|||
}
|
||||
}
|
||||
|
||||
func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.VolumeNodeAffinity) *v1.PersistentVolume {
|
||||
objMeta := metav1.ObjectMeta{Name: "test-constraints"}
|
||||
return &v1.PersistentVolume{
|
||||
ObjectMeta: objMeta,
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
NodeAffinity: affinity,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPodFromFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
@ -27,8 +27,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
|
||||
Read = []string{"get", "list", "watch"}
|
||||
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
|
||||
Read = []string{"get", "list", "watch"}
|
||||
ReadUpdate = []string{"get", "list", "watch", "update", "patch"}
|
||||
|
||||
Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}
|
||||
Annotation = map[string]string{rbac.AutoUpdateAnnotationKey: "true"}
|
||||
|
@ -483,15 +484,13 @@ func ClusterRoles() []rbac.ClusterRole {
|
|||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
// Find the scheduler role
|
||||
for i, role := range roles {
|
||||
if role.Name == "system:kube-scheduler" {
|
||||
pvRule := rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie()
|
||||
scRule := rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie()
|
||||
roles[i].Rules = append(role.Rules, pvRule, scRule)
|
||||
break
|
||||
}
|
||||
}
|
||||
roles = append(roles, rbac.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "system:volume-scheduler"},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbac.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
|
||||
rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
addClusterRoleLabel(roles)
|
||||
|
@ -520,6 +519,10 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding {
|
|||
},
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
rolebindings = append(rolebindings, rbac.NewClusterBinding("system:volume-scheduler").Users(user.KubeScheduler).BindingOrDie())
|
||||
}
|
||||
|
||||
addClusterRoleBindingLabel(rolebindings)
|
||||
|
||||
return rolebindings
|
||||
|
|
|
@ -156,5 +156,22 @@ items:
|
|||
- apiGroup: rbac.authorization.k8s.io
|
||||
kind: User
|
||||
name: system:kube-proxy
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
name: system:volume-scheduler
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:volume-scheduler
|
||||
subjects:
|
||||
- apiGroup: rbac.authorization.k8s.io
|
||||
kind: User
|
||||
name: system:kube-scheduler
|
||||
kind: List
|
||||
metadata: {}
|
||||
|
|
|
@ -1171,6 +1171,34 @@ items:
|
|||
- create
|
||||
- patch
|
||||
- update
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
annotations:
|
||||
rbac.authorization.kubernetes.io/autoupdate: "true"
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
name: system:volume-scheduler
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- storage.k8s.io
|
||||
resources:
|
||||
- storageclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- aggregationRule:
|
||||
clusterRoleSelectors:
|
||||
- matchLabels:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2578,6 +2578,11 @@ message PersistentVolumeSpec {
|
|||
// This is an alpha feature and may change in the future.
|
||||
// +optional
|
||||
optional string volumeMode = 8;
|
||||
|
||||
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
// This field influences the scheduling of pods that use this volume.
|
||||
// +optional
|
||||
optional VolumeNodeAffinity nodeAffinity = 9;
|
||||
}
|
||||
|
||||
// PersistentVolumeStatus is the current status of a persistent volume.
|
||||
|
@ -4457,6 +4462,12 @@ message VolumeMount {
|
|||
optional string mountPropagation = 5;
|
||||
}
|
||||
|
||||
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
message VolumeNodeAffinity {
|
||||
// Required specifies hard node constraints that must be met.
|
||||
optional NodeSelector required = 1;
|
||||
}
|
||||
|
||||
// Projection that may be projected along with other supported volume types
|
||||
message VolumeProjection {
|
||||
// information about the secret data to project
|
||||
|
|
|
@ -530,6 +530,16 @@ type PersistentVolumeSpec struct {
|
|||
// This is an alpha feature and may change in the future.
|
||||
// +optional
|
||||
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"`
|
||||
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
// This field influences the scheduling of pods that use this volume.
|
||||
// +optional
|
||||
NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,9,opt,name=nodeAffinity"`
|
||||
}
|
||||
|
||||
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
type VolumeNodeAffinity struct {
|
||||
// Required specifies hard node constraints that must be met.
|
||||
Required *NodeSelector `json:"required,omitempty" protobuf:"bytes,1,opt,name=required"`
|
||||
}
|
||||
|
||||
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes.
|
||||
|
|
|
@ -1292,6 +1292,7 @@ var map_PersistentVolumeSpec = map[string]string{
|
|||
"storageClassName": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.",
|
||||
"mountOptions": "A list of mount options, e.g. [\"ro\", \"soft\"]. Not validated - mount will simply fail if one is invalid. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options",
|
||||
"volumeMode": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future.",
|
||||
"nodeAffinity": "NodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.",
|
||||
}
|
||||
|
||||
func (PersistentVolumeSpec) SwaggerDoc() map[string]string {
|
||||
|
@ -2176,6 +2177,15 @@ func (VolumeMount) SwaggerDoc() map[string]string {
|
|||
return map_VolumeMount
|
||||
}
|
||||
|
||||
var map_VolumeNodeAffinity = map[string]string{
|
||||
"": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
|
||||
"required": "Required specifies hard node constraints that must be met.",
|
||||
}
|
||||
|
||||
func (VolumeNodeAffinity) SwaggerDoc() map[string]string {
|
||||
return map_VolumeNodeAffinity
|
||||
}
|
||||
|
||||
var map_VolumeProjection = map[string]string{
|
||||
"": "Projection that may be projected along with other supported volume types",
|
||||
"secret": "information about the secret data to project",
|
||||
|
|
|
@ -3355,6 +3355,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) {
|
|||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.NodeAffinity != nil {
|
||||
in, out := &in.NodeAffinity, &out.NodeAffinity
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(VolumeNodeAffinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5572,6 +5581,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeNodeAffinity) DeepCopyInto(out *VolumeNodeAffinity) {
|
||||
*out = *in
|
||||
if in.Required != nil {
|
||||
in, out := &in.Required, &out.Required
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(NodeSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeNodeAffinity.
|
||||
func (in *VolumeNodeAffinity) DeepCopy() *VolumeNodeAffinity {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(VolumeNodeAffinity)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) {
|
||||
*out = *in
|
||||
|
|
|
@ -50,7 +50,6 @@ go_library(
|
|||
"//pkg/apis/apps:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/conditions:go_default_library",
|
||||
|
|
|
@ -36,7 +36,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
|
@ -81,7 +80,7 @@ type PersistentVolumeConfig struct {
|
|||
NamePrefix string
|
||||
Labels labels.Set
|
||||
StorageClassName string
|
||||
NodeAffinity *v1.NodeAffinity
|
||||
NodeAffinity *v1.VolumeNodeAffinity
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object.
|
||||
|
@ -603,13 +602,9 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume
|
|||
},
|
||||
ClaimRef: claimRef,
|
||||
StorageClassName: pvConfig.StorageClassName,
|
||||
NodeAffinity: pvConfig.NodeAffinity,
|
||||
},
|
||||
}
|
||||
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, pvConfig.NodeAffinity)
|
||||
if err != nil {
|
||||
Logf("Setting storage node affinity failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
return pv
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ var (
|
|||
Level: "s0:c0,c1"}
|
||||
)
|
||||
|
||||
var _ = utils.SIGDescribe("PersistentVolumes-local [Feature:LocalPersistentVolumes]", func() {
|
||||
var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
|
||||
f := framework.NewDefaultFramework("persistent-local-volumes-test")
|
||||
|
||||
var (
|
||||
|
@ -680,8 +680,8 @@ func makeLocalPVConfig(config *localTestConfig, volume *localTestVolume) framewo
|
|||
},
|
||||
NamePrefix: "local-pv",
|
||||
StorageClassName: config.scName,
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
|
|
|
@ -27,7 +27,6 @@ go_test(
|
|||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/apis/componentconfig:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/controller/nodelifecycle:go_default_library",
|
||||
|
|
|
@ -39,7 +39,6 @@ import (
|
|||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
|
||||
"k8s.io/kubernetes/pkg/scheduler"
|
||||
"k8s.io/kubernetes/pkg/scheduler/factory"
|
||||
|
@ -253,31 +252,26 @@ func makeHostBoundPV(t *testing.T, name, scName, pvcName, ns string, node string
|
|||
Path: "/tmp/" + node + "/test-path",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if pvcName != "" {
|
||||
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
|
||||
}
|
||||
|
||||
testNodeAffinity := &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
Key: affinityLabelKey,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{node},
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: affinityLabelKey,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{node},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
|
||||
if err != nil {
|
||||
t.Fatalf("Setting storage node affinity failed: %v", err)
|
||||
|
||||
if pvcName != "" {
|
||||
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
|
||||
}
|
||||
|
||||
return pv
|
||||
|
|
|
@ -29,7 +29,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
)
|
||||
|
||||
type testConfig struct {
|
||||
|
@ -252,6 +251,21 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume
|
|||
Path: "/test-path",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: labelKey,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{labelValue},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -259,25 +273,6 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume
|
|||
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
|
||||
}
|
||||
|
||||
testNodeAffinity := &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: labelKey,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{labelValue},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
|
||||
if err != nil {
|
||||
t.Fatalf("Setting storage node affinity failed: %v", err)
|
||||
}
|
||||
return pv
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue