This is a complete revamp of the original API. Some of the key differences: - refocused on structured parameters and allocating devices - support for constraints across devices - support for allocating "all" or a fixed amount of similar devices in a single request - no class for ResourceClaims, instead individual device requests are associated with a mandatory DeviceClass For the sake of simplicity, optional basic types (ints, strings) where the null value is the default are represented as values in the API types. This makes Go code simpler because it doesn't have to check for nil (consumers) and values can be set directly (producers). The effect is that in protobuf, these fields always get encoded because `opt` only has an effect for pointers. The roundtrip test data for v1.29.0 and v1.30.0 changes because of the new "request" field. This is considered acceptable because the entire `claims` field in the pod spec is still alpha. The implementation is complete enough to bring up the apiserver. Adapting other components follows.
631 lines
35 KiB
Go
631 lines
35 KiB
Go
/*
|
|
Copyright 2018 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 etcd
|
|
|
|
import (
|
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/kubernetes/test/utils/image"
|
|
)
|
|
|
|
// GetEtcdStorageData returns etcd data for all persisted objects.
|
|
// It is exported so that it can be reused across multiple tests.
|
|
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
|
func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|
return GetEtcdStorageDataForNamespace("etcdstoragepathtestnamespace")
|
|
}
|
|
|
|
// GetEtcdStorageDataForNamespace returns etcd data for all persisted objects.
|
|
// It is exported so that it can be reused across multiple tests.
|
|
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
|
// Namespaced objects keys are computed for the specified namespace.
|
|
func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionResource]StorageData {
|
|
image := image.GetE2EImage(image.BusyBox)
|
|
etcdStorageData := map[schema.GroupVersionResource]StorageData{
|
|
// k8s.io/kubernetes/pkg/api/v1
|
|
gvr("", "v1", "configmaps"): {
|
|
Stub: `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
|
|
ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
|
|
},
|
|
gvr("", "v1", "services"): {
|
|
Stub: `{"metadata": {"name": "service1"}, "spec": {"type": "LoadBalancer", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
|
|
ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
|
|
},
|
|
gvr("", "v1", "podtemplates"): {
|
|
Stub: `{"metadata": {"name": "pt1name"}, "template": {"metadata": {"labels": {"pt": "01"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container9"}]}}}`,
|
|
ExpectedEtcdPath: "/registry/podtemplates/" + namespace + "/pt1name",
|
|
},
|
|
gvr("", "v1", "pods"): {
|
|
Stub: `{"metadata": {"name": "pod1"}, "spec": {"containers": [{"image": "` + image + `", "name": "container7", "resources": {"limits": {"cpu": "1M"}, "requests": {"cpu": "1M"}}}]}}`,
|
|
ExpectedEtcdPath: "/registry/pods/" + namespace + "/pod1",
|
|
},
|
|
gvr("", "v1", "endpoints"): {
|
|
Stub: `{"metadata": {"name": "ep1name"}, "subsets": [{"addresses": [{"hostname": "bar-001", "ip": "192.168.3.1"}], "ports": [{"port": 8000}]}]}`,
|
|
ExpectedEtcdPath: "/registry/services/endpoints/" + namespace + "/ep1name",
|
|
},
|
|
gvr("", "v1", "resourcequotas"): {
|
|
Stub: `{"metadata": {"name": "rq1name"}, "spec": {"hard": {"cpu": "5M"}}}`,
|
|
ExpectedEtcdPath: "/registry/resourcequotas/" + namespace + "/rq1name",
|
|
},
|
|
gvr("", "v1", "limitranges"): {
|
|
Stub: `{"metadata": {"name": "lr1name"}, "spec": {"limits": [{"type": "Pod"}]}}`,
|
|
ExpectedEtcdPath: "/registry/limitranges/" + namespace + "/lr1name",
|
|
},
|
|
gvr("", "v1", "namespaces"): {
|
|
Stub: `{"metadata": {"name": "namespace1"}, "spec": {"finalizers": ["kubernetes"]}}`,
|
|
ExpectedEtcdPath: "/registry/namespaces/namespace1",
|
|
},
|
|
gvr("", "v1", "nodes"): {
|
|
Stub: `{"metadata": {"name": "node1"}, "spec": {"unschedulable": true}}`,
|
|
ExpectedEtcdPath: "/registry/minions/node1",
|
|
},
|
|
gvr("", "v1", "persistentvolumes"): {
|
|
Stub: `{"metadata": {"name": "pv1name"}, "spec": {"accessModes": ["ReadWriteOnce"], "capacity": {"storage": "3M"}, "hostPath": {"path": "/tmp/test/"}}}`,
|
|
ExpectedEtcdPath: "/registry/persistentvolumes/pv1name",
|
|
},
|
|
gvr("", "v1", "events"): {
|
|
Stub: `{"involvedObject": {"namespace": "` + namespace + `"}, "message": "some data here", "metadata": {"name": "event1"}}`,
|
|
ExpectedEtcdPath: "/registry/events/" + namespace + "/event1",
|
|
},
|
|
gvr("", "v1", "persistentvolumeclaims"): {
|
|
Stub: `{"metadata": {"name": "pvc1"}, "spec": {"accessModes": ["ReadWriteOnce"], "resources": {"limits": {"storage": "1M"}, "requests": {"storage": "2M"}}, "selector": {"matchLabels": {"pvc": "stuff"}}}}`,
|
|
ExpectedEtcdPath: "/registry/persistentvolumeclaims/" + namespace + "/pvc1",
|
|
},
|
|
gvr("", "v1", "serviceaccounts"): {
|
|
Stub: `{"metadata": {"name": "sa1name"}, "secrets": [{"name": "secret00"}]}`,
|
|
ExpectedEtcdPath: "/registry/serviceaccounts/" + namespace + "/sa1name",
|
|
},
|
|
gvr("", "v1", "secrets"): {
|
|
Stub: `{"data": {"key": "ZGF0YSBmaWxl"}, "metadata": {"name": "secret1"}}`,
|
|
ExpectedEtcdPath: "/registry/secrets/" + namespace + "/secret1",
|
|
},
|
|
gvr("", "v1", "replicationcontrollers"): {
|
|
Stub: `{"metadata": {"name": "rc1"}, "spec": {"selector": {"new": "stuff"}, "template": {"metadata": {"labels": {"new": "stuff"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container8"}]}}}}`,
|
|
ExpectedEtcdPath: "/registry/controllers/" + namespace + "/rc1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/apps/v1
|
|
gvr("apps", "v1", "daemonsets"): {
|
|
Stub: `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
|
|
ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds6",
|
|
},
|
|
gvr("apps", "v1", "deployments"): {
|
|
Stub: `{"metadata": {"name": "deployment4"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
|
|
ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment4",
|
|
},
|
|
gvr("apps", "v1", "statefulsets"): {
|
|
Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
|
|
ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
|
|
},
|
|
gvr("apps", "v1", "replicasets"): {
|
|
Stub: `{"metadata": {"name": "rs3"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container4"}]}}}}`,
|
|
ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs3",
|
|
},
|
|
gvr("apps", "v1", "controllerrevisions"): {
|
|
Stub: `{"metadata":{"name":"crs3"},"data":{"name":"abc","namespace":"default","creationTimestamp":null,"Spec":{"Replicas":0,"Selector":{"matchLabels":{"foo":"bar"}},"Template":{"creationTimestamp":null,"labels":{"foo":"bar"},"Spec":{"Volumes":null,"InitContainers":null,"Containers":null,"RestartPolicy":"Always","TerminationGracePeriodSeconds":null,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"","AutomountServiceAccountToken":null,"NodeName":"","SecurityContext":null,"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"","Tolerations":null,"HostAliases":null}},"VolumeClaimTemplates":null,"ServiceName":""},"Status":{"ObservedGeneration":null,"Replicas":0}},"revision":0}`,
|
|
ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/autoscaling/v1
|
|
gvr("autoscaling", "v1", "horizontalpodautoscalers"): {
|
|
Stub: `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
|
|
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa2",
|
|
ExpectedGVK: gvkP("autoscaling", "v2", "HorizontalPodAutoscaler"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/autoscaling/v2
|
|
gvr("autoscaling", "v2", "horizontalpodautoscalers"): {
|
|
Stub: `{"metadata": {"name": "hpa4"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
|
|
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa4",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/batch/v1
|
|
gvr("batch", "v1", "jobs"): {
|
|
Stub: `{"metadata": {"name": "job1"}, "spec": {"manualSelector": true, "selector": {"matchLabels": {"controller-uid": "uid1"}}, "template": {"metadata": {"labels": {"controller-uid": "uid1"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container1"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}`,
|
|
ExpectedEtcdPath: "/registry/jobs/" + namespace + "/job1",
|
|
},
|
|
gvr("batch", "v1", "cronjobs"): {
|
|
Stub: `{"metadata": {"name": "cjv1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
|
|
ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/certificates/v1
|
|
gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): {
|
|
Stub: `{"metadata": {"name": "csr2"}, "spec": {"signerName":"example.com/signer", "usages":["any"], "request": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQnlqQ0NBVE1DQVFBd2dZa3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saApNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1NXNWpNUjh3CkhRWURWUVFMRXhaSmJtWnZjbTFoZEdsdmJpQlVaV05vYm05c2IyZDVNUmN3RlFZRFZRUURFdzUzZDNjdVoyOXYKWjJ4bExtTnZiVENCbnpBTkJna3Foa2lHOXcwQkFRRUZBQU9CalFBd2dZa0NnWUVBcFp0WUpDSEo0VnBWWEhmVgpJbHN0UVRsTzRxQzAzaGpYK1prUHl2ZFlkMVE0K3FiQWVUd1htQ1VLWUhUaFZSZDVhWFNxbFB6eUlCd2llTVpyCldGbFJRZGRaMUl6WEFsVlJEV3dBbzYwS2VjcWVBWG5uVUsrNWZYb1RJL1VnV3NocmU4dEoreC9UTUhhUUtSL0oKY0lXUGhxYVFoc0p1elpidkFkR0E4MEJMeGRNQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUlobAo0UHZGcStlN2lwQVJnSTVaTStHWng2bXBDejQ0RFRvMEprd2ZSRGYrQnRyc2FDMHE2OGVUZjJYaFlPc3E0ZmtIClEwdUEwYVZvZzNmNWlKeENhM0hwNWd4YkpRNnpWNmtKMFRFc3VhYU9oRWtvOXNkcENvUE9uUkJtMmkvWFJEMkQKNmlOaDhmOHowU2hHc0ZxakRnRkh5RjNvK2xVeWorVUM2SDFRVzdibgotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0="}}`,
|
|
ExpectedEtcdPath: "/registry/certificatesigningrequests/csr2",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/certificates/v1alpha1
|
|
gvr("certificates.k8s.io", "v1alpha1", "clustertrustbundles"): {
|
|
Stub: `{"metadata": {"name": "example.com:signer:abc"}, "spec": {"signerName":"example.com/signer", "trustBundle": "-----BEGIN CERTIFICATE-----\nMIIBBDCBt6ADAgECAgEAMAUGAytlcDAQMQ4wDAYDVQQDEwVyb290MTAiGA8wMDAx\nMDEwMTAwMDAwMFoYDzAwMDEwMTAxMDAwMDAwWjAQMQ4wDAYDVQQDEwVyb290MTAq\nMAUGAytlcAMhAF2MoFeGa97gK2NGT1h6p1/a1GlMXAXbcjI/OShyIobPozIwMDAP\nBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTWDdK2CNQiHqRjPaAWYPPtIykQgjAF\nBgMrZXADQQCtom9WGl7m2SAa4tXM9Soo/mbInBsRhn187BMoqTAHInHchKup5/3y\nl1tYJSZZsEXnXrCvw2qLCBNif6+2YYgE\n-----END CERTIFICATE-----\n"}}`,
|
|
ExpectedEtcdPath: "/registry/clustertrustbundles/example.com:signer:abc",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/coordination/v1
|
|
gvr("coordination.k8s.io", "v1", "leases"): {
|
|
Stub: `{"metadata": {"name": "leasev1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
|
|
ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/discovery/v1
|
|
gvr("discovery.k8s.io", "v1", "endpointslices"): {
|
|
Stub: `{"metadata": {"name": "slicev1"}, "addressType": "IPv4", "protocol": "TCP", "ports": [], "endpoints": []}`,
|
|
ExpectedEtcdPath: "/registry/endpointslices/" + namespace + "/slicev1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/events/v1
|
|
gvr("events.k8s.io", "v1", "events"): {
|
|
Stub: `{"metadata": {"name": "event3"}, "regarding": {"namespace": "` + namespace + `"}, "note": "some data here", "eventTime": "2017-08-09T15:04:05.000000Z", "reportingInstance": "node-xyz", "reportingController": "k8s.io/my-controller", "action": "DidNothing", "reason": "Laziness", "type": "Normal"}`,
|
|
ExpectedEtcdPath: "/registry/events/" + namespace + "/event3",
|
|
ExpectedGVK: gvkP("", "v1", "Event"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/networking/v1
|
|
gvr("networking.k8s.io", "v1", "ingresses"): {
|
|
Stub: `{"metadata": {"name": "ingress3"}, "spec": {"defaultBackend": {"service":{"name":"service", "port":{"number": 5000}}}}}`,
|
|
ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress3",
|
|
},
|
|
gvr("networking.k8s.io", "v1", "ingressclasses"): {
|
|
Stub: `{"metadata": {"name": "ingressclass3"}, "spec": {"controller": "example.com/controller"}}`,
|
|
ExpectedEtcdPath: "/registry/ingressclasses/ingressclass3",
|
|
},
|
|
gvr("networking.k8s.io", "v1", "networkpolicies"): {
|
|
Stub: `{"metadata": {"name": "np2"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
|
|
ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np2",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/networking/v1beta1
|
|
gvr("networking.k8s.io", "v1beta1", "ipaddresses"): {
|
|
Stub: `{"metadata": {"name": "192.168.1.3"}, "spec": {"parentRef": {"resource": "services","name": "test", "namespace": "ns"}}}`,
|
|
ExpectedEtcdPath: "/registry/ipaddresses/192.168.1.3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/networking/v1beta1
|
|
gvr("networking.k8s.io", "v1beta1", "servicecidrs"): {
|
|
Stub: `{"metadata": {"name": "range-b1"}, "spec": {"cidrs": ["192.168.0.0/16","fd00:1::/120"]}}`,
|
|
ExpectedEtcdPath: "/registry/servicecidrs/range-b1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/policy/v1
|
|
gvr("policy", "v1", "poddisruptionbudgets"): {
|
|
Stub: `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
|
ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdbv1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/storagemigration/v1alpha1
|
|
gvr("storagemigration.k8s.io", "v1alpha1", "storageversionmigrations"): {
|
|
Stub: `{"metadata": {"name": "test-migration"}, "spec":{"resource": {"group": "test-group", "resource": "test-resource", "version": "test-version"}}}`,
|
|
ExpectedEtcdPath: "/registry/storageversionmigrations/test-migration",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
|
|
gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "flowschemas"): {
|
|
Stub: `{"metadata": {"name": "fs-2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
|
|
ExpectedEtcdPath: "/registry/flowschemas/fs-2",
|
|
ExpectedGVK: gvkP("flowcontrol.apiserver.k8s.io", "v1", "FlowSchema"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
|
|
gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "prioritylevelconfigurations"): {
|
|
Stub: `{"metadata": {"name": "conf4"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
|
|
ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf4",
|
|
ExpectedGVK: gvkP("flowcontrol.apiserver.k8s.io", "v1", "PriorityLevelConfiguration"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
|
|
gvr("flowcontrol.apiserver.k8s.io", "v1", "flowschemas"): {
|
|
Stub: `{"metadata": {"name": "fs-3"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
|
|
ExpectedEtcdPath: "/registry/flowschemas/fs-3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
|
|
gvr("flowcontrol.apiserver.k8s.io", "v1", "prioritylevelconfigurations"): {
|
|
Stub: `{"metadata": {"name": "conf5"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
|
|
ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf5",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/storage/v1
|
|
gvr("storage.k8s.io", "v1", "volumeattachments"): {
|
|
Stub: `{"metadata": {"name": "va3"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv3"}}}`,
|
|
ExpectedEtcdPath: "/registry/volumeattachments/va3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/storage/v1alpha1
|
|
gvr("storage.k8s.io", "v1alpha1", "volumeattributesclasses"): {
|
|
Stub: `{"metadata": {"name": "vac1"}, "driverName": "example.com/driver", "parameters": {"foo": "bar"}}`,
|
|
ExpectedEtcdPath: "/registry/volumeattributesclasses/vac1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/storage/v1
|
|
gvr("storage.k8s.io", "v1", "storageclasses"): {
|
|
Stub: `{"metadata": {"name": "sc2"}, "provisioner": "aws"}`,
|
|
ExpectedEtcdPath: "/registry/storageclasses/sc2",
|
|
},
|
|
gvr("storage.k8s.io", "v1", "csistoragecapacities"): {
|
|
Stub: `{"metadata": {"name": "csc-12345-3"}, "storageClassName": "sc1"}`,
|
|
ExpectedEtcdPath: "/registry/csistoragecapacities/" + namespace + "/csc-12345-3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/rbac/v1
|
|
gvr("rbac.authorization.k8s.io", "v1", "roles"): {
|
|
Stub: `{"metadata": {"name": "role3"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
|
|
ExpectedEtcdPath: "/registry/roles/" + namespace + "/role3",
|
|
},
|
|
gvr("rbac.authorization.k8s.io", "v1", "clusterroles"): {
|
|
Stub: `{"metadata": {"name": "crole3"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
|
|
ExpectedEtcdPath: "/registry/clusterroles/crole3",
|
|
},
|
|
gvr("rbac.authorization.k8s.io", "v1", "rolebindings"): {
|
|
Stub: `{"metadata": {"name": "roleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
|
ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb3",
|
|
},
|
|
gvr("rbac.authorization.k8s.io", "v1", "clusterrolebindings"): {
|
|
Stub: `{"metadata": {"name": "croleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
|
ExpectedEtcdPath: "/registry/clusterrolebindings/croleb3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/admissionregistration/v1
|
|
gvr("admissionregistration.k8s.io", "v1", "validatingwebhookconfigurations"): {
|
|
Stub: `{"metadata":{"name":"hook2","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"ns","name":"n"},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore","sideEffects":"None","admissionReviewVersions":["v1beta1"]}]}`,
|
|
ExpectedEtcdPath: "/registry/validatingwebhookconfigurations/hook2",
|
|
},
|
|
gvr("admissionregistration.k8s.io", "v1", "mutatingwebhookconfigurations"): {
|
|
Stub: `{"metadata":{"name":"hook2","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"ns","name":"n"},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore","sideEffects":"None","admissionReviewVersions":["v1beta1"]}]}`,
|
|
ExpectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook2",
|
|
},
|
|
gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies"): {
|
|
Stub: `{"metadata":{"name":"vap1","creationTimestamp":null},"spec":{"paramKind":{"apiVersion":"test.example.com/v1","kind":"Example"},"matchConstraints":{"resourceRules": [{"resourceNames": ["fakeName"], "apiGroups":["apps"],"apiVersions":["v1"],"operations":["CREATE", "UPDATE"], "resources":["deployments"]}]},"validations":[{"expression":"object.spec.replicas <= params.maxReplicas","message":"Too many replicas"}]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1",
|
|
},
|
|
gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicybindings"): {
|
|
Stub: `{"metadata":{"name":"pb1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com","parameterNotFoundAction":"Deny"},"validationActions":["Deny"]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1
|
|
gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): {
|
|
Stub: `{"metadata":{"name":"vap1b1","creationTimestamp":null},"spec":{"paramKind":{"apiVersion":"test.example.com/v1","kind":"Example"},"matchConstraints":{"resourceRules": [{"resourceNames": ["fakeName"], "apiGroups":["apps"],"apiVersions":["v1"],"operations":["CREATE", "UPDATE"], "resources":["deployments"]}]},"validations":[{"expression":"object.spec.replicas <= params.maxReplicas","message":"Too many replicas"}]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1b1",
|
|
ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1", "ValidatingAdmissionPolicy"),
|
|
},
|
|
gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicybindings"): {
|
|
Stub: `{"metadata":{"name":"pb1b1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com","parameterNotFoundAction":"Deny"},"validationActions":["Deny"]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1b1",
|
|
ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1", "ValidatingAdmissionPolicyBinding"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1
|
|
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): {
|
|
Stub: `{"metadata":{"name":"vap1a1","creationTimestamp":null},"spec":{"paramKind":{"apiVersion":"test.example.com/v1","kind":"Example"},"matchConstraints":{"resourceRules": [{"resourceNames": ["fakeName"], "apiGroups":["apps"],"apiVersions":["v1"],"operations":["CREATE", "UPDATE"], "resources":["deployments"]}]},"validations":[{"expression":"object.spec.replicas <= params.maxReplicas","message":"Too many replicas"}]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1a1",
|
|
ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1", "ValidatingAdmissionPolicy"),
|
|
},
|
|
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicybindings"): {
|
|
Stub: `{"metadata":{"name":"pb1a1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com"},"validationActions":["Deny"]}}`,
|
|
ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1a1",
|
|
ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1", "ValidatingAdmissionPolicyBinding"),
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/scheduling/v1
|
|
gvr("scheduling.k8s.io", "v1", "priorityclasses"): {
|
|
Stub: `{"metadata":{"name":"pc3"},"Value":1000}`,
|
|
ExpectedEtcdPath: "/registry/priorityclasses/pc3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kube-aggregator/pkg/apis/apiregistration/v1
|
|
// depends on aggregator using the same ungrouped RESTOptionsGetter as the kube apiserver, not SimpleRestOptionsFactory in aggregator.go
|
|
gvr("apiregistration.k8s.io", "v1", "apiservices"): {
|
|
Stub: `{"metadata": {"name": "as2.foo.com"}, "spec": {"group": "foo.com", "version": "as2", "groupPriorityMinimum":100, "versionPriority":10}}`,
|
|
ExpectedEtcdPath: "/registry/apiregistration.k8s.io/apiservices/as2.foo.com",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
|
|
gvr("apiextensions.k8s.io", "v1", "customresourcedefinitions"): {
|
|
Stub: `{"metadata": {"name": "openshiftwebconsoleconfigs.webconsole2.operator.openshift.io"},"spec": {` +
|
|
`"scope": "Cluster","group": "webconsole2.operator.openshift.io",` +
|
|
`"versions": [{"name":"v1alpha1","storage":true,"served":true,"schema":{"openAPIV3Schema":{"type":"object"}}}],` +
|
|
`"names": {"kind": "OpenShiftWebConsoleConfig","plural": "openshiftwebconsoleconfigs","singular": "openshiftwebconsoleconfig"}}}`,
|
|
ExpectedEtcdPath: "/registry/apiextensions.k8s.io/customresourcedefinitions/openshiftwebconsoleconfigs.webconsole2.operator.openshift.io",
|
|
ExpectedGVK: gvkP("apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition"),
|
|
},
|
|
gvr("cr.bar.com", "v1", "foos"): {
|
|
Stub: `{"kind": "Foo", "apiVersion": "cr.bar.com/v1", "metadata": {"name": "cr1foo"}, "color": "blue"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
|
ExpectedEtcdPath: "/registry/cr.bar.com/foos/" + namespace + "/cr1foo",
|
|
},
|
|
gvr("custom.fancy.com", "v2", "pants"): {
|
|
Stub: `{"kind": "Pant", "apiVersion": "custom.fancy.com/v2", "metadata": {"name": "cr2pant"}, "isFancy": true}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
|
ExpectedEtcdPath: "/registry/custom.fancy.com/pants/cr2pant",
|
|
},
|
|
gvr("awesome.bears.com", "v1", "pandas"): {
|
|
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "spec":{"replicas": 100}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
|
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr3panda",
|
|
},
|
|
gvr("awesome.bears.com", "v3", "pandas"): {
|
|
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "spec":{"replicas": 300}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
|
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
|
|
ExpectedGVK: gvkP("awesome.bears.com", "v1", "Panda"),
|
|
},
|
|
gvr("random.numbers.com", "v1", "integers"): {
|
|
Stub: `{"kind": "Integer", "apiVersion": "random.numbers.com/v1", "metadata": {"name": "fortytwo"}, "value": 42, "garbage": "oiujnasdf"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
|
ExpectedEtcdPath: "/registry/random.numbers.com/integers/fortytwo",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/node/v1
|
|
gvr("node.k8s.io", "v1", "runtimeclasses"): {
|
|
Stub: `{"metadata": {"name": "rc3"}, "handler": "h3"}`,
|
|
ExpectedEtcdPath: "/registry/runtimeclasses/rc3",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/kubernetes/pkg/apis/resource/v1alpha3
|
|
gvr("resource.k8s.io", "v1alpha3", "deviceclasses"): {
|
|
Stub: `{"metadata": {"name": "class1name"}}`,
|
|
ExpectedEtcdPath: "/registry/deviceclasses/class1name",
|
|
},
|
|
gvr("resource.k8s.io", "v1alpha3", "resourceclaims"): {
|
|
Stub: `{"metadata": {"name": "claim1name"}, "spec": {"devices": {"requests": [{"name": "req-0", "deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}]}}}`,
|
|
ExpectedEtcdPath: "/registry/resourceclaims/" + namespace + "/claim1name",
|
|
},
|
|
gvr("resource.k8s.io", "v1alpha3", "resourceclaimtemplates"): {
|
|
Stub: `{"metadata": {"name": "claimtemplate1name"}, "spec": {"spec": {"devices": {"requests": [{"name": "req-0", "deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}]}}}}`,
|
|
ExpectedEtcdPath: "/registry/resourceclaimtemplates/" + namespace + "/claimtemplate1name",
|
|
},
|
|
gvr("resource.k8s.io", "v1alpha3", "podschedulingcontexts"): {
|
|
Stub: `{"metadata": {"name": "pod1name"}, "spec": {"selectedNode": "node1name", "potentialNodes": ["node1name", "node2name"]}}`,
|
|
ExpectedEtcdPath: "/registry/podschedulingcontexts/" + namespace + "/pod1name",
|
|
},
|
|
gvr("resource.k8s.io", "v1alpha3", "resourceslices"): {
|
|
Stub: `{"metadata": {"name": "node1slice"}, "spec": {"nodeName": "worker1", "driver": "dra.example.com", "pool": {"name": "worker1", "resourceSliceCount": 1}}}`,
|
|
ExpectedEtcdPath: "/registry/resourceslices/node1slice",
|
|
},
|
|
// --
|
|
|
|
// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
|
|
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
|
|
Stub: `{"metadata":{"name":"sv1.test"},"spec":{}}`,
|
|
ExpectedEtcdPath: "/registry/storageversions/sv1.test",
|
|
},
|
|
// --
|
|
|
|
}
|
|
|
|
// add csinodes
|
|
// k8s.io/kubernetes/pkg/apis/storage/v1
|
|
etcdStorageData[gvr("storage.k8s.io", "v1", "csinodes")] = StorageData{
|
|
Stub: `{"metadata": {"name": "csini2"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}}`,
|
|
ExpectedEtcdPath: "/registry/csinodes/csini2",
|
|
}
|
|
|
|
// add csidrivers
|
|
// k8s.io/kubernetes/pkg/apis/storage/v1
|
|
etcdStorageData[gvr("storage.k8s.io", "v1", "csidrivers")] = StorageData{
|
|
Stub: `{"metadata": {"name": "csid2"}, "spec": {"attachRequired": true, "podInfoOnMount": true}}`,
|
|
ExpectedEtcdPath: "/registry/csidrivers/csid2",
|
|
}
|
|
|
|
return etcdStorageData
|
|
}
|
|
|
|
// StorageData contains information required to create an object and verify its storage in etcd
|
|
// It must be paired with a specific resource
|
|
type StorageData struct {
|
|
Stub string // Valid JSON stub to use during create
|
|
Prerequisites []Prerequisite // Optional, ordered list of JSON objects to create before stub
|
|
ExpectedEtcdPath string // Expected location of object in etcd, do not use any variables, constants, etc to derive this value - always supply the full raw string
|
|
ExpectedGVK *schema.GroupVersionKind // The GVK that we expect this object to be stored as - leave this nil to use the default
|
|
}
|
|
|
|
// Prerequisite contains information required to create a resource (but not verify it)
|
|
type Prerequisite struct {
|
|
GvrData schema.GroupVersionResource
|
|
Stub string
|
|
}
|
|
|
|
// GetCustomResourceDefinitionData returns the resource definitions that back the custom resources
|
|
// included in GetEtcdStorageData. They should be created using CreateTestCRDs before running any tests.
|
|
// We can switch this to v1 CRDs based on transitive call site analysis.
|
|
// Call sites:
|
|
// 1. TestDedupOwnerReferences - beta doesn't matter
|
|
// 2. TestWebhookAdmissionWithWatchCache/TestWebhookAdmissionWithoutWatchCache - beta doesn't matter
|
|
// 3. TestApplyStatus - the version fields don't matter. Pruning isn't checked, just ownership.
|
|
// 4. TestDryRun - versions and pruning don't matter
|
|
// 5. TestStorageVersionBootstrap - versions and pruning don't matter.
|
|
// 6. TestEtcdStoragePath - beta doesn't matter
|
|
// 7. TestCrossGroupStorage - beta doesn't matter
|
|
// 8. TestOverlappingCustomResourceCustomResourceDefinition - beta doesn't matter
|
|
// 9. TestOverlappingCustomResourceAPIService - beta doesn't matter
|
|
func GetCustomResourceDefinitionData() []*apiextensionsv1.CustomResourceDefinition {
|
|
return []*apiextensionsv1.CustomResourceDefinition{
|
|
// namespaced
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foos.cr.bar.com",
|
|
},
|
|
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
|
Group: "cr.bar.com",
|
|
Scope: apiextensionsv1.NamespaceScoped,
|
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
|
Plural: "foos",
|
|
Kind: "Foo",
|
|
},
|
|
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
|
{
|
|
Name: "v1",
|
|
Served: true,
|
|
Storage: true,
|
|
Schema: fixtures.AllowAllSchema(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// cluster scoped
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pants.custom.fancy.com",
|
|
},
|
|
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
|
Group: "custom.fancy.com",
|
|
Scope: apiextensionsv1.ClusterScoped,
|
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
|
Plural: "pants",
|
|
Kind: "Pant",
|
|
},
|
|
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
|
{
|
|
Name: "v2",
|
|
Served: true,
|
|
Storage: true,
|
|
Schema: fixtures.AllowAllSchema(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// cluster scoped with legacy version field and pruning.
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "integers.random.numbers.com",
|
|
},
|
|
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
|
Group: "random.numbers.com",
|
|
Scope: apiextensionsv1.ClusterScoped,
|
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
|
Plural: "integers",
|
|
Kind: "Integer",
|
|
},
|
|
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
|
{
|
|
Name: "v1",
|
|
Served: true,
|
|
Storage: true,
|
|
Schema: &apiextensionsv1.CustomResourceValidation{
|
|
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
|
|
Type: "object",
|
|
Properties: map[string]apiextensionsv1.JSONSchemaProps{
|
|
"value": {
|
|
Type: "number",
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// cluster scoped with versions field
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pandas.awesome.bears.com",
|
|
},
|
|
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
|
Group: "awesome.bears.com",
|
|
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
|
{
|
|
Name: "v1",
|
|
Served: true,
|
|
Storage: true,
|
|
Schema: fixtures.AllowAllSchema(),
|
|
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
|
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
|
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
|
|
SpecReplicasPath: ".spec.replicas",
|
|
StatusReplicasPath: ".status.replicas",
|
|
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "v2",
|
|
Served: false,
|
|
Storage: false,
|
|
Schema: fixtures.AllowAllSchema(),
|
|
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
|
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
|
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
|
|
SpecReplicasPath: ".spec.replicas",
|
|
StatusReplicasPath: ".status.replicas",
|
|
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "v3",
|
|
Served: true,
|
|
Storage: false,
|
|
Schema: fixtures.AllowAllSchema(),
|
|
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
|
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
|
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
|
|
SpecReplicasPath: ".spec.replicas",
|
|
StatusReplicasPath: ".status.replicas",
|
|
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Scope: apiextensionsv1.ClusterScoped,
|
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
|
Plural: "pandas",
|
|
Kind: "Panda",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func gvr(g, v, r string) schema.GroupVersionResource {
|
|
return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
|
|
}
|
|
|
|
func gvkP(g, v, k string) *schema.GroupVersionKind {
|
|
return &schema.GroupVersionKind{Group: g, Version: v, Kind: k}
|
|
}
|
|
|
|
func gvk(g, v, k string) schema.GroupVersionKind {
|
|
return schema.GroupVersionKind{Group: g, Version: v, Kind: k}
|
|
}
|