123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937 |
- /*
- Copyright 2015 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 label
- import (
- "context"
- "errors"
- "reflect"
- "sort"
- "testing"
- v1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apiserver/pkg/admission"
- admissiontesting "k8s.io/apiserver/pkg/admission/testing"
- cloudprovider "k8s.io/cloud-provider"
- api "k8s.io/kubernetes/pkg/apis/core"
- persistentvolume "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
- )
- type mockVolumes struct {
- volumeLabels map[string]string
- volumeLabelsError error
- }
- var _ cloudprovider.PVLabeler = &mockVolumes{}
- func (v *mockVolumes) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) {
- return v.volumeLabels, v.volumeLabelsError
- }
- func mockVolumeFailure(err error) *mockVolumes {
- return &mockVolumes{volumeLabelsError: err}
- }
- func mockVolumeLabels(labels map[string]string) *mockVolumes {
- return &mockVolumes{volumeLabels: labels}
- }
- func Test_PVLAdmission(t *testing.T) {
- testcases := []struct {
- name string
- handler *persistentVolumeLabel
- pvlabeler cloudprovider.PVLabeler
- preAdmissionPV *api.PersistentVolume
- postAdmissionPV *api.PersistentVolume
- err error
- }{
- {
- name: "non-cloud PV ignored",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- HostPath: &api.HostPathVolumeSource{
- Path: "/",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- HostPath: &api.HostPathVolumeSource{
- Path: "/",
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "cloud provider error blocks creation of volume",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeFailure(errors.New("invalid volume")),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- err: apierrors.NewForbidden(schema.ParseGroupResource("persistentvolumes"), "awsebs", errors.New("error querying AWS EBS volume 123: invalid volume")),
- },
- {
- name: "cloud provider returns no labels",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{}),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "cloud provider returns nil, nil",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeFailure(nil),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "AWS EBS PV labeled correctly",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "existing labels from dynamic provisioning are not changed",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- v1.LabelZoneFailureDomain: "domain1",
- v1.LabelZoneRegion: "region1",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs", Namespace: "myns",
- Labels: map[string]string{
- v1.LabelZoneFailureDomain: "existingDomain",
- v1.LabelZoneRegion: "existingRegion",
- },
- Annotations: map[string]string{
- persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- v1.LabelZoneFailureDomain: "existingDomain",
- v1.LabelZoneRegion: "existingRegion",
- },
- Annotations: map[string]string{
- persistentvolume.AnnDynamicallyProvisioned: "kubernetes.io/aws-ebs",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: v1.LabelZoneRegion,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"existingRegion"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"existingDomain"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "existing labels from user are changed",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- v1.LabelZoneFailureDomain: "domain1",
- v1.LabelZoneRegion: "region1",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs", Namespace: "myns",
- Labels: map[string]string{
- v1.LabelZoneFailureDomain: "existingDomain",
- v1.LabelZoneRegion: "existingRegion",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- v1.LabelZoneFailureDomain: "domain1",
- v1.LabelZoneRegion: "region1",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: v1.LabelZoneRegion,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"region1"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"domain1"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "GCE PD PV labeled correctly",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{Name: "gcepd", Namespace: "myns"},
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
- PDName: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "gcepd",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
- PDName: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "Azure Disk PV labeled correctly",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "azurepd",
- Namespace: "myns",
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AzureDisk: &api.AzureDiskVolumeSource{
- DiskName: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "azurepd",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AzureDisk: &api.AzureDiskVolumeSource{
- DiskName: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "Cinder Disk PV labeled correctly",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "azurepd",
- Namespace: "myns",
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- Cinder: &api.CinderPersistentVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "azurepd",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- Cinder: &api.CinderPersistentVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "AWS EBS PV overrides user applied labels",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "not1",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "AWS EBS PV conflicting affinity rules left in-tact",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- "c": "3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "c": "3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "c",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"3"},
- },
- },
- },
- },
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- "c": "3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "c",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "AWS EBS PV non-conflicting affinity rules added",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "d": "1",
- "e": "2",
- "f": "3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- "c": "3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: "c",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"3"},
- },
- },
- },
- },
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "awsebs",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- "c": "3",
- "d": "1",
- "e": "2",
- "f": "3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
- VolumeID: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: "c",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"3"},
- },
- {
- Key: "d",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "e",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: "f",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- {
- name: "vSphere PV labeled correctly",
- handler: newPersistentVolumeLabel(),
- pvlabeler: mockVolumeLabels(map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- }),
- preAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "vSpherePV",
- Namespace: "myns",
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
- VolumePath: "123",
- },
- },
- },
- },
- postAdmissionPV: &api.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "vSpherePV",
- Namespace: "myns",
- Labels: map[string]string{
- "a": "1",
- "b": "2",
- v1.LabelZoneFailureDomain: "1__2__3",
- },
- },
- Spec: api.PersistentVolumeSpec{
- PersistentVolumeSource: api.PersistentVolumeSource{
- VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
- VolumePath: "123",
- },
- },
- NodeAffinity: &api.VolumeNodeAffinity{
- Required: &api.NodeSelector{
- NodeSelectorTerms: []api.NodeSelectorTerm{
- {
- MatchExpressions: []api.NodeSelectorRequirement{
- {
- Key: "a",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1"},
- },
- {
- Key: "b",
- Operator: api.NodeSelectorOpIn,
- Values: []string{"2"},
- },
- {
- Key: v1.LabelZoneFailureDomain,
- Operator: api.NodeSelectorOpIn,
- Values: []string{"1", "2", "3"},
- },
- },
- },
- },
- },
- },
- },
- },
- err: nil,
- },
- }
- for _, testcase := range testcases {
- t.Run(testcase.name, func(t *testing.T) {
- setPVLabeler(testcase.handler, testcase.pvlabeler)
- handler := admissiontesting.WithReinvocationTesting(t, admission.NewChainHandler(testcase.handler))
- err := handler.Admit(context.TODO(), admission.NewAttributesRecord(testcase.preAdmissionPV, nil, api.Kind("PersistentVolume").WithVersion("version"), testcase.preAdmissionPV.Namespace, testcase.preAdmissionPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
- if !reflect.DeepEqual(err, testcase.err) {
- t.Logf("expected error: %q", testcase.err)
- t.Logf("actual error: %q", err)
- t.Error("unexpected error when admitting PV")
- }
- // sort node selector match expression by key because they are added out of order in the admission controller
- sortMatchExpressions(testcase.preAdmissionPV)
- if !reflect.DeepEqual(testcase.preAdmissionPV, testcase.postAdmissionPV) {
- t.Logf("expected PV: %+v", testcase.postAdmissionPV)
- t.Logf("actual PV: %+v", testcase.preAdmissionPV)
- t.Error("unexpected PV")
- }
- })
- }
- }
- // setPVLabler applies the given mock pvlabeler to implement PV labeling for all cloud providers.
- // Given we mock out the values of the labels anyways, assigning the same mock labeler for every
- // provider does not reduce test coverage but it does simplify/clean up the tests here because
- // the provider is then decided based on the type of PV (EBS, Cinder, GCEPD, Azure Disk, etc)
- func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabeler) {
- handler.awsPVLabeler = pvlabeler
- handler.gcePVLabeler = pvlabeler
- handler.azurePVLabeler = pvlabeler
- handler.openStackPVLabeler = pvlabeler
- handler.vspherePVLabeler = pvlabeler
- }
- // sortMatchExpressions sorts a PV's node selector match expressions by key name if it is not nil
- func sortMatchExpressions(pv *api.PersistentVolume) {
- if pv.Spec.NodeAffinity == nil ||
- pv.Spec.NodeAffinity.Required == nil ||
- pv.Spec.NodeAffinity.Required.NodeSelectorTerms == nil {
- return
- }
- match := pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions
- sort.Slice(match, func(i, j int) bool {
- return match[i].Key < match[j].Key
- })
- pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = match
- }
|