From 8b208efb73c5397867af26b9609bd4973f29c0ba Mon Sep 17 00:00:00 2001 From: micw523 Date: Fri, 9 Nov 2018 22:23:49 -0600 Subject: [PATCH] Enhance List handling, add yaml/json tests --- kubernetes/e2e_test/test_utils.py | 84 +++++++++++- kubernetes/e2e_test/test_yaml/list.yaml | 29 +++++ .../test_yaml/multi-resource-svclist.json | 60 +++++++++ .../test_yaml/multi-resource-yaml.yaml | 33 +++++ .../test_yaml/yaml-conflict-first.yaml | 13 ++ .../test_yaml/yaml-conflict-multi.yaml | 33 +++++ kubernetes/utils/create_from_yaml.py | 122 +++++++++++++----- 7 files changed, 338 insertions(+), 36 deletions(-) create mode 100644 kubernetes/e2e_test/test_yaml/list.yaml create mode 100644 kubernetes/e2e_test/test_yaml/multi-resource-svclist.json create mode 100644 kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml create mode 100644 kubernetes/e2e_test/test_yaml/yaml-conflict-first.yaml create mode 100644 kubernetes/e2e_test/test_yaml/yaml-conflict-multi.yaml diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index b43a77c432..a426b73ae7 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -103,7 +103,7 @@ def test_deployment_in_namespace(self): body={}) resp = core_api.delete_namespace(name="dep", body={}) - def test_api_service(self): + def test_api_service_with_conflict(self): k8s_client = client.api_client.ApiClient(configuration=self.config) k8s_api = utils.create_from_yaml(k8s_client, "kubernetes/e2e_test/test_yaml/api-service.yaml") @@ -112,5 +112,85 @@ def test_api_service(self): svc = k8s_api.read_api_service( name="v1alpha1.wardle.k8s.io") self.assertIsNotNone(svc) + svc_conflict = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/api-service.yaml") + self.assertEqual([], svc_conflict) resp = k8s_api.delete_api_service( - name="v1alpha1.wardle.k8s.io", body={}) \ No newline at end of file + name="v1alpha1.wardle.k8s.io", body={}) + + def test_list(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/list.yaml") + svc_api = k8s_api[0] + self.assertEqual("v1", svc_api.get_api_resources().group_version) + svc = svc_api.read_namespaced_service(name="list-service-test", + namespace="default") + self.assertIsNotNone(svc) + ext_api = k8s_api[1] + self.assertEqual("extensions/v1beta1", + ext_api.get_api_resources().group_version) + dep = ext_api.read_namespaced_deployment(name="list-deployment-test", + namespace="default") + self.assertIsNotNone(dep) + ext_api.delete_namespaced_deployment(name="list-deployment-test", + namespace="default", body={}) + svc_api.delete_namespaced_service(name="list-service-test", + namespace="default", body={}) + + def test_multi_resource(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + k8s_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml") + svc_api = k8s_api[0] + self.assertEqual("v1", svc_api.get_api_resources().group_version) + svc = svc_api.read_namespaced_service(name="mock", + namespace="default") + self.assertIsNotNone(svc) + ctr_api = k8s_api[1] + self.assertEqual("v1", ctr_api.get_api_resources().group_version) + ctr = ctr_api.read_namespaced_replication_controller( + name="mock", namespace="default") + self.assertIsNotNone(ctr) + ctr_api.delete_namespaced_replication_controller(name="mock", + namespace="default", body={}) + svc_api.delete_namespaced_service(name="mock", + namespace="default", body={}) + + def test_multi_resource_with_conflict(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + svc_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/yaml-conflict-first.yaml") + self.assertEqual("v1", svc_api.get_api_resources().group_version) + svc = svc_api.read_namespaced_service(name="mock-2", + namespace="default") + self.assertIsNotNone(svc) + ctr_api = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/yaml-conflict-multi.yaml") + self.assertEqual("v1", ctr_api.get_api_resources().group_version) + ctr = ctr_api.read_namespaced_replication_controller( + name="mock-2", namespace="default") + self.assertIsNotNone(ctr) + ctr_api.delete_namespaced_replication_controller(name="mock-2", + namespace="default", body={}) + svc_api.delete_namespaced_service(name="mock-2", + namespace="default", body={}) + + def test_svc_list_with_conflict_no_kind(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + svc_apis = utils.create_from_yaml(k8s_client, + "kubernetes/e2e_test/test_yaml/multi-resource-svclist.json") + svc_api_0 = svc_apis[0] + self.assertEqual("v1", svc_api_0.get_api_resources().group_version) + svc_0 = svc_api_0.read_namespaced_service(name="mock-3", + namespace="default") + self.assertIsNotNone(svc_0) + svc_api_1 = svc_apis[1] + self.assertEqual("v1", svc_api_1.get_api_resources().group_version) + svc_1 = svc_api_1.read_namespaced_service(name="mock-4", + namespace="default") + self.assertIsNotNone(svc_1) + svc_api_0.delete_namespaced_service(name="mock-3", + namespace="default", body={}) + svc_api_1.delete_namespaced_service(name="mock-4", + namespace="default", body={}) \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/list.yaml b/kubernetes/e2e_test/test_yaml/list.yaml new file mode 100644 index 0000000000..3416ec429d --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/list.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: Service + metadata: + name: list-service-test + spec: + ports: + - protocol: TCP + port: 80 + selector: + app: list-deployment-test +- apiVersion: extensions/v1beta1 + kind: Deployment + metadata: + name: list-deployment-test + labels: + app: list-deployment-test + spec: + replicas: 1 + template: + metadata: + labels: + app: list-deployment-test + spec: + containers: + - name: nginx + image: nginx \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/multi-resource-svclist.json b/kubernetes/e2e_test/test_yaml/multi-resource-svclist.json new file mode 100644 index 0000000000..46aa90dc87 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/multi-resource-svclist.json @@ -0,0 +1,60 @@ +{ + "kind":"ServiceList", + "apiVersion":"v1", + "items":[ + { + "metadata":{ + "name":"mock-3", + "labels":{ + "app":"mock-3" + } + }, + "spec":{ + "ports": [{ + "protocol": "TCP", + "port": 99, + "targetPort": 9949 + }], + "selector":{ + "app":"mock-3" + } + } + }, + { + "metadata":{ + "name":"mock-3", + "labels":{ + "app":"mock-3" + } + }, + "spec":{ + "ports": [{ + "protocol": "TCP", + "port": 99, + "targetPort": 9949 + }], + "selector":{ + "app":"mock-3" + } + } + }, + { + "metadata":{ + "name":"mock-4", + "labels":{ + "app":"mock-4" + } + }, + "spec":{ + "ports": [{ + "protocol": "TCP", + "port": 99, + "targetPort": 9949 + }], + "selector":{ + "app":"mock-4" + } + } + } + ] +} diff --git a/kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml b/kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml new file mode 100644 index 0000000000..7052b575af --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: mock + labels: + app: mock +spec: + ports: + - port: 99 + protocol: TCP + targetPort: 9949 + selector: + app: mock +--- +apiVersion: v1 +kind: ReplicationController +metadata: + name: mock +spec: + replicas: 1 + selector: + app: mock + template: + metadata: + labels: + app: mock + spec: + containers: + - name: mock-container + image: k8s.gcr.io/pause:2.0 + ports: + - containerPort: 9949 + protocol: TCP \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/yaml-conflict-first.yaml b/kubernetes/e2e_test/test_yaml/yaml-conflict-first.yaml new file mode 100644 index 0000000000..73e9376369 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/yaml-conflict-first.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: mock-2 + labels: + app: mock-2 +spec: + ports: + - port: 99 + protocol: TCP + targetPort: 9949 + selector: + app: mock-2 \ No newline at end of file diff --git a/kubernetes/e2e_test/test_yaml/yaml-conflict-multi.yaml b/kubernetes/e2e_test/test_yaml/yaml-conflict-multi.yaml new file mode 100644 index 0000000000..6a5cf16c29 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/yaml-conflict-multi.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: mock-2 + labels: + app: mock-2 +spec: + ports: + - port: 99 + protocol: TCP + targetPort: 9949 + selector: + app: mock-2 +--- +apiVersion: v1 +kind: ReplicationController +metadata: + name: mock-2 +spec: + replicas: 1 + selector: + app: mock-2 + template: + metadata: + labels: + app: mock-2 + spec: + containers: + - name: mock-container + image: k8s.gcr.io/pause:2.0 + ports: + - containerPort: 9949 + protocol: TCP \ No newline at end of file diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 4a69a8ad51..cf4b7b6fb3 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -23,51 +23,105 @@ from kubernetes import client -def create_from_yaml(k8s_client, yaml_file, verbose=False, **kwargs): +def create_from_yaml( + k8s_client, + yaml_file, + verbose=False, + output_list=False, + **kwargs): """ Perform an action from a yaml file. Pass True for verbose to print confirmation information. Input: yaml_file: string. Contains the path to yaml file. k8s_cline: an ApiClient object, initialized with the client args. + verbose: If True, print confirmation from the create action. Default is False. + output_list: compatibility option with v8.0.0. Default is False. + Function returns a single api object when there is only one when set False. + Does not affect when multiple objects are generated. - Available parameters for performing the subsequent action: + Returns: + An k8s api object or list of apis objects created from YAML. + When a single object is generated, return type is dependent on output_list. + + Available parameters for creating : :param async_req bool :param bool include_uninitialized: If true, partially initialized resources are included in the response. :param str pretty: If 'true', then the output is pretty printed. :param str dry_run: When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed """ + k8s_api_all = [] with open(path.abspath(yaml_file)) as f: - yml_object = yaml.load(f) - # TODO: case of yaml file containing multiple objects - group, _, version = yml_object["apiVersion"].partition("/") - if version == "": - version = group - group = "core" - # Take care for the case e.g. api_type is "apiextensions.k8s.io" - # Only replace the last instance - group = "".join(group.rsplit(".k8s.io", 1)) - fcn_to_call = "{0}{1}Api".format(group.capitalize(), - version.capitalize()) - k8s_api = getattr(client, fcn_to_call)(k8s_client) - # Replace CamelCased action_type into snake_case - kind = yml_object["kind"] - kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind) - kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower() - # Decide which namespace we are going to put the object in, - # if any - if "namespace" in yml_object["metadata"]: - namespace = yml_object["metadata"]["namespace"] - else: - namespace = "default" - # Expect the user to create namespaced objects more often - if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): - resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( - body=yml_object, namespace=namespace, **kwargs) - else: - resp = getattr(k8s_api, "create_{0}".format(kind))( - body=yml_object, **kwargs) - if verbose: - print("{0} created. status='{1}'".format(kind, str(resp.status))) - return k8s_api + yml_document_all = yaml.load_all(f) + # Load all documents from a single YAML file + for yml_document in yml_document_all: + # If it is a list type, will need to iterate its items + if "List" in yml_document["kind"]: + # Could be "List" or "Pod/Service/...List" + # This is a list type. iterate within its items + kind = yml_document["kind"].replace("List", "") + for yml_object in yml_document["items"]: + # Mitigate cases when server returns a xxxList object + # See kubernetes-client/python#586 + if kind is not "": + yml_object["apiVersion"] = yml_document["apiVersion"] + yml_object["kind"] = yml_document["kind"] + try: + k8s_api_all.append(create_from_yaml_single_item( + k8s_client, yml_object, verbose, **kwargs)) + except client.rest.ApiException as api_exception: + print( + "Error when creating {0}/{1}".format( + yml_object["kind"], + yml_object["metadata"]["name"]), + api_exception) + else: + # This is a single object. Call the single item method + try: + k8s_api_all.append(create_from_yaml_single_item( + k8s_client, yml_document, verbose, **kwargs)) + except client.rest.ApiException as api_exception: + print( + "Error when creating {0}/{1}".format( + yml_document["kind"], + yml_document["metadata"]["name"]), + api_exception) + if output_list is False: + if len(k8s_api_all) == 1: + return k8s_api_all[0] + return k8s_api_all + + +def create_from_yaml_single_item( + k8s_client, yml_object, verbose=False, **kwargs): + group, _, version = yml_object["apiVersion"].partition("/") + if version == "": + version = group + group = "core" + # Take care for the case e.g. api_type is "apiextensions.k8s.io" + # Only replace the last instance + group = "".join(group.rsplit(".k8s.io", 1)) + fcn_to_call = "{0}{1}Api".format(group.capitalize(), + version.capitalize()) + k8s_api = getattr(client, fcn_to_call)(k8s_client) + # Replace CamelCased action_type into snake_case + kind = yml_object["kind"] + kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind) + kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower() + # Decide which namespace we are going to put the object in, + # if any + if "namespace" in yml_object["metadata"]: + namespace = yml_object["metadata"]["namespace"] + else: + namespace = "default" + # Expect the user to create namespaced objects more often + if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( + body=yml_object, namespace=namespace, **kwargs) + else: + resp = getattr(k8s_api, "create_{0}".format(kind))( + body=yml_object, **kwargs) + if verbose: + print("{0} created. status='{1}'".format(kind, str(resp.status))) + return k8s_api