k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/etcd/data.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package etcd
    18  
    19  import (
    20  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    21  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/runtime/schema"
    24  
    25  	"k8s.io/kubernetes/test/utils/image"
    26  )
    27  
    28  // GetEtcdStorageData returns etcd data for all persisted objects.
    29  // It is exported so that it can be reused across multiple tests.
    30  // It returns a new map on every invocation to prevent different tests from mutating shared state.
    31  func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
    32  	return GetEtcdStorageDataForNamespace("etcdstoragepathtestnamespace")
    33  }
    34  
    35  // GetEtcdStorageDataForNamespace returns etcd data for all persisted objects.
    36  // It is exported so that it can be reused across multiple tests.
    37  // It returns a new map on every invocation to prevent different tests from mutating shared state.
    38  // Namespaced objects keys are computed for the specified namespace.
    39  func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionResource]StorageData {
    40  	image := image.GetE2EImage(image.BusyBox)
    41  	etcdStorageData := map[schema.GroupVersionResource]StorageData{
    42  		// k8s.io/kubernetes/pkg/api/v1
    43  		gvr("", "v1", "configmaps"): {
    44  			Stub:             `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
    45  			ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
    46  		},
    47  		gvr("", "v1", "services"): {
    48  			Stub:             `{"metadata": {"name": "service1"}, "spec": {"type": "LoadBalancer", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
    49  			ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
    50  		},
    51  		gvr("", "v1", "podtemplates"): {
    52  			Stub:             `{"metadata": {"name": "pt1name"}, "template": {"metadata": {"labels": {"pt": "01"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container9"}]}}}`,
    53  			ExpectedEtcdPath: "/registry/podtemplates/" + namespace + "/pt1name",
    54  		},
    55  		gvr("", "v1", "pods"): {
    56  			Stub:             `{"metadata": {"name": "pod1"}, "spec": {"containers": [{"image": "` + image + `", "name": "container7", "resources": {"limits": {"cpu": "1M"}, "requests": {"cpu": "1M"}}}]}}`,
    57  			ExpectedEtcdPath: "/registry/pods/" + namespace + "/pod1",
    58  		},
    59  		gvr("", "v1", "endpoints"): {
    60  			Stub:             `{"metadata": {"name": "ep1name"}, "subsets": [{"addresses": [{"hostname": "bar-001", "ip": "192.168.3.1"}], "ports": [{"port": 8000}]}]}`,
    61  			ExpectedEtcdPath: "/registry/services/endpoints/" + namespace + "/ep1name",
    62  		},
    63  		gvr("", "v1", "resourcequotas"): {
    64  			Stub:             `{"metadata": {"name": "rq1name"}, "spec": {"hard": {"cpu": "5M"}}}`,
    65  			ExpectedEtcdPath: "/registry/resourcequotas/" + namespace + "/rq1name",
    66  		},
    67  		gvr("", "v1", "limitranges"): {
    68  			Stub:             `{"metadata": {"name": "lr1name"}, "spec": {"limits": [{"type": "Pod"}]}}`,
    69  			ExpectedEtcdPath: "/registry/limitranges/" + namespace + "/lr1name",
    70  		},
    71  		gvr("", "v1", "namespaces"): {
    72  			Stub:             `{"metadata": {"name": "namespace1"}, "spec": {"finalizers": ["kubernetes"]}}`,
    73  			ExpectedEtcdPath: "/registry/namespaces/namespace1",
    74  		},
    75  		gvr("", "v1", "nodes"): {
    76  			Stub:             `{"metadata": {"name": "node1"}, "spec": {"unschedulable": true}}`,
    77  			ExpectedEtcdPath: "/registry/minions/node1",
    78  		},
    79  		gvr("", "v1", "persistentvolumes"): {
    80  			Stub:             `{"metadata": {"name": "pv1name"}, "spec": {"accessModes": ["ReadWriteOnce"], "capacity": {"storage": "3M"}, "hostPath": {"path": "/tmp/test/"}}}`,
    81  			ExpectedEtcdPath: "/registry/persistentvolumes/pv1name",
    82  		},
    83  		gvr("", "v1", "events"): {
    84  			Stub:             `{"involvedObject": {"namespace": "` + namespace + `"}, "message": "some data here", "metadata": {"name": "event1"}}`,
    85  			ExpectedEtcdPath: "/registry/events/" + namespace + "/event1",
    86  		},
    87  		gvr("", "v1", "persistentvolumeclaims"): {
    88  			Stub:             `{"metadata": {"name": "pvc1"}, "spec": {"accessModes": ["ReadWriteOnce"], "resources": {"limits": {"storage": "1M"}, "requests": {"storage": "2M"}}, "selector": {"matchLabels": {"pvc": "stuff"}}}}`,
    89  			ExpectedEtcdPath: "/registry/persistentvolumeclaims/" + namespace + "/pvc1",
    90  		},
    91  		gvr("", "v1", "serviceaccounts"): {
    92  			Stub:             `{"metadata": {"name": "sa1name"}, "secrets": [{"name": "secret00"}]}`,
    93  			ExpectedEtcdPath: "/registry/serviceaccounts/" + namespace + "/sa1name",
    94  		},
    95  		gvr("", "v1", "secrets"): {
    96  			Stub:             `{"data": {"key": "ZGF0YSBmaWxl"}, "metadata": {"name": "secret1"}}`,
    97  			ExpectedEtcdPath: "/registry/secrets/" + namespace + "/secret1",
    98  		},
    99  		gvr("", "v1", "replicationcontrollers"): {
   100  			Stub:             `{"metadata": {"name": "rc1"}, "spec": {"selector": {"new": "stuff"}, "template": {"metadata": {"labels": {"new": "stuff"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container8"}]}}}}`,
   101  			ExpectedEtcdPath: "/registry/controllers/" + namespace + "/rc1",
   102  		},
   103  		// --
   104  
   105  		// k8s.io/kubernetes/pkg/apis/apps/v1
   106  		gvr("apps", "v1", "daemonsets"): {
   107  			Stub:             `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
   108  			ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds6",
   109  		},
   110  		gvr("apps", "v1", "deployments"): {
   111  			Stub:             `{"metadata": {"name": "deployment4"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
   112  			ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment4",
   113  		},
   114  		gvr("apps", "v1", "statefulsets"): {
   115  			Stub:             `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
   116  			ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
   117  		},
   118  		gvr("apps", "v1", "replicasets"): {
   119  			Stub:             `{"metadata": {"name": "rs3"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container4"}]}}}}`,
   120  			ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs3",
   121  		},
   122  		gvr("apps", "v1", "controllerrevisions"): {
   123  			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}`,
   124  			ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs3",
   125  		},
   126  		// --
   127  
   128  		// k8s.io/kubernetes/pkg/apis/autoscaling/v1
   129  		gvr("autoscaling", "v1", "horizontalpodautoscalers"): {
   130  			Stub:             `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
   131  			ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa2",
   132  			ExpectedGVK:      gvkP("autoscaling", "v2", "HorizontalPodAutoscaler"),
   133  		},
   134  		// --
   135  
   136  		// k8s.io/kubernetes/pkg/apis/autoscaling/v2
   137  		gvr("autoscaling", "v2", "horizontalpodautoscalers"): {
   138  			Stub:             `{"metadata": {"name": "hpa4"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
   139  			ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa4",
   140  		},
   141  		// --
   142  
   143  		// k8s.io/kubernetes/pkg/apis/batch/v1
   144  		gvr("batch", "v1", "jobs"): {
   145  			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"}}}}`,
   146  			ExpectedEtcdPath: "/registry/jobs/" + namespace + "/job1",
   147  		},
   148  		gvr("batch", "v1", "cronjobs"): {
   149  			Stub:             `{"metadata": {"name": "cjv1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
   150  			ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv1",
   151  		},
   152  		// --
   153  
   154  		// k8s.io/kubernetes/pkg/apis/certificates/v1
   155  		gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): {
   156  			Stub:             `{"metadata": {"name": "csr2"}, "spec": {"signerName":"example.com/signer", "usages":["any"], "request": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQnlqQ0NBVE1DQVFBd2dZa3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saApNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1NXNWpNUjh3CkhRWURWUVFMRXhaSmJtWnZjbTFoZEdsdmJpQlVaV05vYm05c2IyZDVNUmN3RlFZRFZRUURFdzUzZDNjdVoyOXYKWjJ4bExtTnZiVENCbnpBTkJna3Foa2lHOXcwQkFRRUZBQU9CalFBd2dZa0NnWUVBcFp0WUpDSEo0VnBWWEhmVgpJbHN0UVRsTzRxQzAzaGpYK1prUHl2ZFlkMVE0K3FiQWVUd1htQ1VLWUhUaFZSZDVhWFNxbFB6eUlCd2llTVpyCldGbFJRZGRaMUl6WEFsVlJEV3dBbzYwS2VjcWVBWG5uVUsrNWZYb1RJL1VnV3NocmU4dEoreC9UTUhhUUtSL0oKY0lXUGhxYVFoc0p1elpidkFkR0E4MEJMeGRNQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUlobAo0UHZGcStlN2lwQVJnSTVaTStHWng2bXBDejQ0RFRvMEprd2ZSRGYrQnRyc2FDMHE2OGVUZjJYaFlPc3E0ZmtIClEwdUEwYVZvZzNmNWlKeENhM0hwNWd4YkpRNnpWNmtKMFRFc3VhYU9oRWtvOXNkcENvUE9uUkJtMmkvWFJEMkQKNmlOaDhmOHowU2hHc0ZxakRnRkh5RjNvK2xVeWorVUM2SDFRVzdibgotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0="}}`,
   157  			ExpectedEtcdPath: "/registry/certificatesigningrequests/csr2",
   158  		},
   159  		// --
   160  
   161  		// k8s.io/kubernetes/pkg/apis/certificates/v1alpha1
   162  		gvr("certificates.k8s.io", "v1alpha1", "clustertrustbundles"): {
   163  			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"}}`,
   164  			ExpectedEtcdPath: "/registry/clustertrustbundles/example.com:signer:abc",
   165  		},
   166  		// --
   167  
   168  		// k8s.io/kubernetes/pkg/apis/coordination/v1
   169  		gvr("coordination.k8s.io", "v1", "leases"): {
   170  			Stub:             `{"metadata": {"name": "leasev1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
   171  			ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1",
   172  		},
   173  		// --
   174  
   175  		// k8s.io/kubernetes/pkg/apis/discovery/v1
   176  		gvr("discovery.k8s.io", "v1", "endpointslices"): {
   177  			Stub:             `{"metadata": {"name": "slicev1"}, "addressType": "IPv4", "protocol": "TCP", "ports": [], "endpoints": []}`,
   178  			ExpectedEtcdPath: "/registry/endpointslices/" + namespace + "/slicev1",
   179  		},
   180  		// --
   181  
   182  		// k8s.io/kubernetes/pkg/apis/events/v1
   183  		gvr("events.k8s.io", "v1", "events"): {
   184  			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"}`,
   185  			ExpectedEtcdPath: "/registry/events/" + namespace + "/event3",
   186  			ExpectedGVK:      gvkP("", "v1", "Event"),
   187  		},
   188  		// --
   189  
   190  		// k8s.io/kubernetes/pkg/apis/networking/v1
   191  		gvr("networking.k8s.io", "v1", "ingresses"): {
   192  			Stub:             `{"metadata": {"name": "ingress3"}, "spec": {"defaultBackend": {"service":{"name":"service", "port":{"number": 5000}}}}}`,
   193  			ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress3",
   194  		},
   195  		gvr("networking.k8s.io", "v1", "ingressclasses"): {
   196  			Stub:             `{"metadata": {"name": "ingressclass3"}, "spec": {"controller": "example.com/controller"}}`,
   197  			ExpectedEtcdPath: "/registry/ingressclasses/ingressclass3",
   198  		},
   199  		gvr("networking.k8s.io", "v1", "networkpolicies"): {
   200  			Stub:             `{"metadata": {"name": "np2"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
   201  			ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np2",
   202  		},
   203  		// --
   204  
   205  		// k8s.io/kubernetes/pkg/apis/networking/v1alpha1
   206  		gvr("networking.k8s.io", "v1alpha1", "ipaddresses"): {
   207  			Stub:             `{"metadata": {"name": "192.168.1.2"}, "spec": {"parentRef": {"resource": "services","name": "test", "namespace": "ns"}}}`,
   208  			ExpectedEtcdPath: "/registry/ipaddresses/192.168.1.2",
   209  		},
   210  		// --
   211  
   212  		// k8s.io/kubernetes/pkg/apis/networking/v1alpha1
   213  		gvr("networking.k8s.io", "v1alpha1", "servicecidrs"): {
   214  			Stub:             `{"metadata": {"name": "range1"}, "spec": {"cidrs": ["192.168.0.0/16","fd00:1::/120"]}}`,
   215  			ExpectedEtcdPath: "/registry/servicecidrs/range1",
   216  		},
   217  		// --
   218  
   219  		// k8s.io/kubernetes/pkg/apis/policy/v1
   220  		gvr("policy", "v1", "poddisruptionbudgets"): {
   221  			Stub:             `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
   222  			ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdbv1",
   223  		},
   224  		// --
   225  
   226  		// k8s.io/kubernetes/pkg/apis/storagemigration/v1alpha1
   227  		gvr("storagemigration.k8s.io", "v1alpha1", "storageversionmigrations"): {
   228  			Stub:             `{"metadata": {"name": "test-migration"}, "spec":{"resource": {"group": "test-group", "resource": "test-resource", "version": "test-version"}}}`,
   229  			ExpectedEtcdPath: "/registry/storageversionmigrations/test-migration",
   230  		},
   231  		// --
   232  
   233  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta1
   234  		gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "flowschemas"): {
   235  			Stub:             `{"metadata": {"name": "va2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   236  			ExpectedEtcdPath: "/registry/flowschemas/va2",
   237  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "FlowSchema"),
   238  		},
   239  		// --
   240  
   241  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta1
   242  		gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "prioritylevelconfigurations"): {
   243  			Stub:             `{"metadata": {"name": "conf2"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   244  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf2",
   245  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "PriorityLevelConfiguration"),
   246  		},
   247  		// --
   248  
   249  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta2
   250  		gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "flowschemas"): {
   251  			Stub:             `{"metadata": {"name": "fs-1"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   252  			ExpectedEtcdPath: "/registry/flowschemas/fs-1",
   253  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "FlowSchema"),
   254  		},
   255  		// --
   256  
   257  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta2
   258  		gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "prioritylevelconfigurations"): {
   259  			Stub:             `{"metadata": {"name": "conf3"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   260  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf3",
   261  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "PriorityLevelConfiguration"),
   262  		},
   263  		// --
   264  
   265  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
   266  		gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "flowschemas"): {
   267  			Stub:             `{"metadata": {"name": "fs-2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   268  			ExpectedEtcdPath: "/registry/flowschemas/fs-2",
   269  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "FlowSchema"),
   270  		},
   271  		// --
   272  
   273  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
   274  		gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "prioritylevelconfigurations"): {
   275  			Stub:             `{"metadata": {"name": "conf4"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   276  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf4",
   277  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1", "PriorityLevelConfiguration"),
   278  		},
   279  		// --
   280  
   281  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
   282  		gvr("flowcontrol.apiserver.k8s.io", "v1", "flowschemas"): {
   283  			Stub:             `{"metadata": {"name": "fs-3"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   284  			ExpectedEtcdPath: "/registry/flowschemas/fs-3",
   285  		},
   286  		// --
   287  
   288  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
   289  		gvr("flowcontrol.apiserver.k8s.io", "v1", "prioritylevelconfigurations"): {
   290  			Stub:             `{"metadata": {"name": "conf5"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   291  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf5",
   292  		},
   293  		// --
   294  
   295  		// k8s.io/kubernetes/pkg/apis/storage/v1
   296  		gvr("storage.k8s.io", "v1", "volumeattachments"): {
   297  			Stub:             `{"metadata": {"name": "va3"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv3"}}}`,
   298  			ExpectedEtcdPath: "/registry/volumeattachments/va3",
   299  		},
   300  		// --
   301  
   302  		// k8s.io/kubernetes/pkg/apis/storage/v1alpha1
   303  		gvr("storage.k8s.io", "v1alpha1", "volumeattributesclasses"): {
   304  			Stub:             `{"metadata": {"name": "vac1"}, "driverName": "example.com/driver", "parameters": {"foo": "bar"}}`,
   305  			ExpectedEtcdPath: "/registry/volumeattributesclasses/vac1",
   306  		},
   307  		// --
   308  
   309  		// k8s.io/kubernetes/pkg/apis/storage/v1
   310  		gvr("storage.k8s.io", "v1", "storageclasses"): {
   311  			Stub:             `{"metadata": {"name": "sc2"}, "provisioner": "aws"}`,
   312  			ExpectedEtcdPath: "/registry/storageclasses/sc2",
   313  		},
   314  		gvr("storage.k8s.io", "v1", "csistoragecapacities"): {
   315  			Stub:             `{"metadata": {"name": "csc-12345-3"}, "storageClassName": "sc1"}`,
   316  			ExpectedEtcdPath: "/registry/csistoragecapacities/" + namespace + "/csc-12345-3",
   317  		},
   318  		// --
   319  
   320  		// k8s.io/kubernetes/pkg/apis/rbac/v1
   321  		gvr("rbac.authorization.k8s.io", "v1", "roles"): {
   322  			Stub:             `{"metadata": {"name": "role3"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
   323  			ExpectedEtcdPath: "/registry/roles/" + namespace + "/role3",
   324  		},
   325  		gvr("rbac.authorization.k8s.io", "v1", "clusterroles"): {
   326  			Stub:             `{"metadata": {"name": "crole3"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
   327  			ExpectedEtcdPath: "/registry/clusterroles/crole3",
   328  		},
   329  		gvr("rbac.authorization.k8s.io", "v1", "rolebindings"): {
   330  			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"}]}`,
   331  			ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb3",
   332  		},
   333  		gvr("rbac.authorization.k8s.io", "v1", "clusterrolebindings"): {
   334  			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"}]}`,
   335  			ExpectedEtcdPath: "/registry/clusterrolebindings/croleb3",
   336  		},
   337  		// --
   338  
   339  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1
   340  		gvr("admissionregistration.k8s.io", "v1", "validatingwebhookconfigurations"): {
   341  			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"]}]}`,
   342  			ExpectedEtcdPath: "/registry/validatingwebhookconfigurations/hook2",
   343  		},
   344  		gvr("admissionregistration.k8s.io", "v1", "mutatingwebhookconfigurations"): {
   345  			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"]}]}`,
   346  			ExpectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook2",
   347  		},
   348  		gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies"): {
   349  			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"}]}}`,
   350  			ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1",
   351  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicy"),
   352  		},
   353  		gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicybindings"): {
   354  			Stub:             `{"metadata":{"name":"pb1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com","parameterNotFoundAction":"Deny"},"validationActions":["Deny"]}}`,
   355  			ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1",
   356  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicyBinding"),
   357  		},
   358  		// --
   359  
   360  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1
   361  		gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): {
   362  			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"}]}}`,
   363  			ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1b1",
   364  		},
   365  		gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicybindings"): {
   366  			Stub:             `{"metadata":{"name":"pb1b1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com","parameterNotFoundAction":"Deny"},"validationActions":["Deny"]}}`,
   367  			ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1b1",
   368  		},
   369  		// --
   370  
   371  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1
   372  		gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): {
   373  			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"}]}}`,
   374  			ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1a1",
   375  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicy"),
   376  		},
   377  		gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicybindings"): {
   378  			Stub:             `{"metadata":{"name":"pb1a1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com"},"validationActions":["Deny"]}}`,
   379  			ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1a1",
   380  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicyBinding"),
   381  		},
   382  		// --
   383  
   384  		// k8s.io/kubernetes/pkg/apis/scheduling/v1
   385  		gvr("scheduling.k8s.io", "v1", "priorityclasses"): {
   386  			Stub:             `{"metadata":{"name":"pc3"},"Value":1000}`,
   387  			ExpectedEtcdPath: "/registry/priorityclasses/pc3",
   388  		},
   389  		// --
   390  
   391  		// k8s.io/kube-aggregator/pkg/apis/apiregistration/v1
   392  		// depends on aggregator using the same ungrouped RESTOptionsGetter as the kube apiserver, not SimpleRestOptionsFactory in aggregator.go
   393  		gvr("apiregistration.k8s.io", "v1", "apiservices"): {
   394  			Stub:             `{"metadata": {"name": "as2.foo.com"}, "spec": {"group": "foo.com", "version": "as2", "groupPriorityMinimum":100, "versionPriority":10}}`,
   395  			ExpectedEtcdPath: "/registry/apiregistration.k8s.io/apiservices/as2.foo.com",
   396  		},
   397  		// --
   398  
   399  		// k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
   400  		gvr("apiextensions.k8s.io", "v1", "customresourcedefinitions"): {
   401  			Stub: `{"metadata": {"name": "openshiftwebconsoleconfigs.webconsole2.operator.openshift.io"},"spec": {` +
   402  				`"scope": "Cluster","group": "webconsole2.operator.openshift.io",` +
   403  				`"versions": [{"name":"v1alpha1","storage":true,"served":true,"schema":{"openAPIV3Schema":{"type":"object"}}}],` +
   404  				`"names": {"kind": "OpenShiftWebConsoleConfig","plural": "openshiftwebconsoleconfigs","singular": "openshiftwebconsoleconfig"}}}`,
   405  			ExpectedEtcdPath: "/registry/apiextensions.k8s.io/customresourcedefinitions/openshiftwebconsoleconfigs.webconsole2.operator.openshift.io",
   406  			ExpectedGVK:      gvkP("apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition"),
   407  		},
   408  		gvr("cr.bar.com", "v1", "foos"): {
   409  			Stub:             `{"kind": "Foo", "apiVersion": "cr.bar.com/v1", "metadata": {"name": "cr1foo"}, "color": "blue"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   410  			ExpectedEtcdPath: "/registry/cr.bar.com/foos/" + namespace + "/cr1foo",
   411  		},
   412  		gvr("custom.fancy.com", "v2", "pants"): {
   413  			Stub:             `{"kind": "Pant", "apiVersion": "custom.fancy.com/v2", "metadata": {"name": "cr2pant"}, "isFancy": true}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   414  			ExpectedEtcdPath: "/registry/custom.fancy.com/pants/cr2pant",
   415  		},
   416  		gvr("awesome.bears.com", "v1", "pandas"): {
   417  			Stub:             `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "spec":{"replicas": 100}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   418  			ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr3panda",
   419  		},
   420  		gvr("awesome.bears.com", "v3", "pandas"): {
   421  			Stub:             `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "spec":{"replicas": 300}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   422  			ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
   423  			ExpectedGVK:      gvkP("awesome.bears.com", "v1", "Panda"),
   424  		},
   425  		gvr("random.numbers.com", "v1", "integers"): {
   426  			Stub:             `{"kind": "Integer", "apiVersion": "random.numbers.com/v1", "metadata": {"name": "fortytwo"}, "value": 42, "garbage": "oiujnasdf"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   427  			ExpectedEtcdPath: "/registry/random.numbers.com/integers/fortytwo",
   428  		},
   429  		// --
   430  
   431  		// k8s.io/kubernetes/pkg/apis/node/v1
   432  		gvr("node.k8s.io", "v1", "runtimeclasses"): {
   433  			Stub:             `{"metadata": {"name": "rc3"}, "handler": "h3"}`,
   434  			ExpectedEtcdPath: "/registry/runtimeclasses/rc3",
   435  		},
   436  		// --
   437  
   438  		// k8s.io/kubernetes/pkg/apis/resource/v1alpha2
   439  		gvr("resource.k8s.io", "v1alpha2", "resourceclasses"): {
   440  			Stub:             `{"metadata": {"name": "class1name"}, "driverName": "example.com"}`,
   441  			ExpectedEtcdPath: "/registry/resourceclasses/class1name",
   442  		},
   443  		gvr("resource.k8s.io", "v1alpha2", "resourceclaims"): {
   444  			Stub:             `{"metadata": {"name": "claim1name"}, "spec": {"resourceClassName": "class1name", "allocationMode": "WaitForFirstConsumer"}}`,
   445  			ExpectedEtcdPath: "/registry/resourceclaims/" + namespace + "/claim1name",
   446  		},
   447  		gvr("resource.k8s.io", "v1alpha2", "resourceclaimtemplates"): {
   448  			Stub:             `{"metadata": {"name": "claimtemplate1name"}, "spec": {"spec": {"resourceClassName": "class1name", "allocationMode": "WaitForFirstConsumer"}}}`,
   449  			ExpectedEtcdPath: "/registry/resourceclaimtemplates/" + namespace + "/claimtemplate1name",
   450  		},
   451  		gvr("resource.k8s.io", "v1alpha2", "podschedulingcontexts"): {
   452  			Stub:             `{"metadata": {"name": "pod1name"}, "spec": {"selectedNode": "node1name", "potentialNodes": ["node1name", "node2name"]}}`,
   453  			ExpectedEtcdPath: "/registry/podschedulingcontexts/" + namespace + "/pod1name",
   454  		},
   455  		gvr("resource.k8s.io", "v1alpha2", "resourceclassparameters"): {
   456  			Stub:             `{"metadata": {"name": "class1parameters"}}`,
   457  			ExpectedEtcdPath: "/registry/resourceclassparameters/" + namespace + "/class1parameters",
   458  		},
   459  		gvr("resource.k8s.io", "v1alpha2", "resourceclaimparameters"): {
   460  			Stub:             `{"metadata": {"name": "claim1parameters"}}`,
   461  			ExpectedEtcdPath: "/registry/resourceclaimparameters/" + namespace + "/claim1parameters",
   462  		},
   463  		gvr("resource.k8s.io", "v1alpha2", "resourceslices"): {
   464  			Stub:             `{"metadata": {"name": "node1slice"}, "nodeName": "worker1", "driverName": "dra.example.com", "namedResources": {}}`,
   465  			ExpectedEtcdPath: "/registry/resourceslices/node1slice",
   466  		},
   467  		// --
   468  
   469  		// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
   470  		gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
   471  			Stub:             `{"metadata":{"name":"sv1.test"},"spec":{}}`,
   472  			ExpectedEtcdPath: "/registry/storageversions/sv1.test",
   473  		},
   474  		// --
   475  
   476  	}
   477  
   478  	// add csinodes
   479  	// k8s.io/kubernetes/pkg/apis/storage/v1
   480  	etcdStorageData[gvr("storage.k8s.io", "v1", "csinodes")] = StorageData{
   481  		Stub:             `{"metadata": {"name": "csini2"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}}`,
   482  		ExpectedEtcdPath: "/registry/csinodes/csini2",
   483  	}
   484  
   485  	// add csidrivers
   486  	// k8s.io/kubernetes/pkg/apis/storage/v1
   487  	etcdStorageData[gvr("storage.k8s.io", "v1", "csidrivers")] = StorageData{
   488  		Stub:             `{"metadata": {"name": "csid2"}, "spec": {"attachRequired": true, "podInfoOnMount": true}}`,
   489  		ExpectedEtcdPath: "/registry/csidrivers/csid2",
   490  	}
   491  
   492  	return etcdStorageData
   493  }
   494  
   495  // StorageData contains information required to create an object and verify its storage in etcd
   496  // It must be paired with a specific resource
   497  type StorageData struct {
   498  	Stub             string                   // Valid JSON stub to use during create
   499  	Prerequisites    []Prerequisite           // Optional, ordered list of JSON objects to create before stub
   500  	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
   501  	ExpectedGVK      *schema.GroupVersionKind // The GVK that we expect this object to be stored as - leave this nil to use the default
   502  }
   503  
   504  // Prerequisite contains information required to create a resource (but not verify it)
   505  type Prerequisite struct {
   506  	GvrData schema.GroupVersionResource
   507  	Stub    string
   508  }
   509  
   510  // GetCustomResourceDefinitionData returns the resource definitions that back the custom resources
   511  // included in GetEtcdStorageData.  They should be created using CreateTestCRDs before running any tests.
   512  // We can switch this to v1 CRDs based on transitive call site analysis.
   513  // Call sites:
   514  // 1. TestDedupOwnerReferences - beta doesn't matter
   515  // 2. TestWebhookAdmissionWithWatchCache/TestWebhookAdmissionWithoutWatchCache - beta doesn't matter
   516  // 3. TestApplyStatus - the version fields don't matter.  Pruning isn't checked, just ownership.
   517  // 4. TestDryRun - versions and pruning don't matter
   518  // 5. TestStorageVersionBootstrap - versions and pruning don't matter.
   519  // 6. TestEtcdStoragePath - beta doesn't matter
   520  // 7. TestCrossGroupStorage - beta doesn't matter
   521  // 8. TestOverlappingCustomResourceCustomResourceDefinition - beta doesn't matter
   522  // 9. TestOverlappingCustomResourceAPIService - beta doesn't matter
   523  func GetCustomResourceDefinitionData() []*apiextensionsv1.CustomResourceDefinition {
   524  	return []*apiextensionsv1.CustomResourceDefinition{
   525  		// namespaced
   526  		{
   527  			ObjectMeta: metav1.ObjectMeta{
   528  				Name: "foos.cr.bar.com",
   529  			},
   530  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   531  				Group: "cr.bar.com",
   532  				Scope: apiextensionsv1.NamespaceScoped,
   533  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   534  					Plural: "foos",
   535  					Kind:   "Foo",
   536  				},
   537  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   538  					{
   539  						Name:    "v1",
   540  						Served:  true,
   541  						Storage: true,
   542  						Schema:  fixtures.AllowAllSchema(),
   543  					},
   544  				},
   545  			},
   546  		},
   547  		// cluster scoped
   548  		{
   549  			ObjectMeta: metav1.ObjectMeta{
   550  				Name: "pants.custom.fancy.com",
   551  			},
   552  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   553  				Group: "custom.fancy.com",
   554  				Scope: apiextensionsv1.ClusterScoped,
   555  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   556  					Plural: "pants",
   557  					Kind:   "Pant",
   558  				},
   559  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   560  					{
   561  						Name:    "v2",
   562  						Served:  true,
   563  						Storage: true,
   564  						Schema:  fixtures.AllowAllSchema(),
   565  					},
   566  				},
   567  			},
   568  		},
   569  		// cluster scoped with legacy version field and pruning.
   570  		{
   571  			ObjectMeta: metav1.ObjectMeta{
   572  				Name: "integers.random.numbers.com",
   573  			},
   574  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   575  				Group: "random.numbers.com",
   576  				Scope: apiextensionsv1.ClusterScoped,
   577  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   578  					Plural: "integers",
   579  					Kind:   "Integer",
   580  				},
   581  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   582  					{
   583  						Name:    "v1",
   584  						Served:  true,
   585  						Storage: true,
   586  						Schema: &apiextensionsv1.CustomResourceValidation{
   587  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   588  								Type: "object",
   589  								Properties: map[string]apiextensionsv1.JSONSchemaProps{
   590  									"value": {
   591  										Type: "number",
   592  									},
   593  								},
   594  							}},
   595  					},
   596  				},
   597  			},
   598  		},
   599  		// cluster scoped with versions field
   600  		{
   601  			ObjectMeta: metav1.ObjectMeta{
   602  				Name: "pandas.awesome.bears.com",
   603  			},
   604  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   605  				Group: "awesome.bears.com",
   606  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   607  					{
   608  						Name:    "v1",
   609  						Served:  true,
   610  						Storage: true,
   611  						Schema:  fixtures.AllowAllSchema(),
   612  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   613  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   614  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   615  								SpecReplicasPath:   ".spec.replicas",
   616  								StatusReplicasPath: ".status.replicas",
   617  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   618  							},
   619  						},
   620  					},
   621  					{
   622  						Name:    "v2",
   623  						Served:  false,
   624  						Storage: false,
   625  						Schema:  fixtures.AllowAllSchema(),
   626  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   627  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   628  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   629  								SpecReplicasPath:   ".spec.replicas",
   630  								StatusReplicasPath: ".status.replicas",
   631  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   632  							},
   633  						},
   634  					},
   635  					{
   636  						Name:    "v3",
   637  						Served:  true,
   638  						Storage: false,
   639  						Schema:  fixtures.AllowAllSchema(),
   640  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   641  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   642  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   643  								SpecReplicasPath:   ".spec.replicas",
   644  								StatusReplicasPath: ".status.replicas",
   645  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   646  							},
   647  						},
   648  					},
   649  				},
   650  				Scope: apiextensionsv1.ClusterScoped,
   651  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   652  					Plural: "pandas",
   653  					Kind:   "Panda",
   654  				},
   655  			},
   656  		},
   657  	}
   658  }
   659  
   660  func gvr(g, v, r string) schema.GroupVersionResource {
   661  	return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
   662  }
   663  
   664  func gvkP(g, v, k string) *schema.GroupVersionKind {
   665  	return &schema.GroupVersionKind{Group: g, Version: v, Kind: k}
   666  }
   667  
   668  func gvk(g, v, k string) schema.GroupVersionKind {
   669  	return schema.GroupVersionKind{Group: g, Version: v, Kind: k}
   670  }