From a4144e15c060beaf22ff35778f3516b0e0f6527a Mon Sep 17 00:00:00 2001 From: Ondra Machacek Date: Tue, 21 May 2019 09:31:59 +0200 Subject: [PATCH] kubevirt_vm: Improve create VM from template (#55927) --- lib/ansible/module_utils/kubevirt.py | 67 ++++++++++++------- .../modules/cloud/kubevirt/kubevirt_vm.py | 32 +++++++-- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/lib/ansible/module_utils/kubevirt.py b/lib/ansible/module_utils/kubevirt.py index 247e498b931..9c8db11ed76 100644 --- a/lib/ansible/module_utils/kubevirt.py +++ b/lib/ansible/module_utils/kubevirt.py @@ -10,6 +10,7 @@ from distutils.version import Version from ansible.module_utils.k8s.common import list_dict_str from ansible.module_utils.k8s.raw import KubernetesRawModule +import copy import re MAX_SUPPORTED_API_VERSION = 'v1alpha3' @@ -126,21 +127,28 @@ class KubeVirtRawModule(KubernetesRawModule): super(KubeVirtRawModule, self).__init__(*args, **kwargs) @staticmethod - def merge_dicts(x, y): + def merge_dicts(x, yy): """ This function merge two dictionaries, where the first dict has higher priority in merging two same keys. """ - for k in set(x.keys()).union(y.keys()): - if k in x and k in y: - if isinstance(x[k], dict) and isinstance(y[k], dict): - yield (k, dict(KubeVirtRawModule.merge_dicts(x[k], y[k]))) + if not yy: + yy = {} + + if not isinstance(yy, list): + yy = [yy] + + for y in yy: + for k in set(x.keys()).union(y.keys()): + if k in x and k in y: + if isinstance(x[k], dict) and isinstance(y[k], dict): + yield (k, dict(KubeVirtRawModule.merge_dicts(x[k], y[k]))) + else: + yield (k, x[k]) + elif k in x: + yield (k, x[k]) else: yield (k, y[k]) - elif k in x: - yield (k, x[k]) - else: - yield (k, y[k]) def get_resource(self, resource): try: @@ -215,16 +223,23 @@ class KubeVirtRawModule(KubernetesRawModule): 'disk': {'bus': 'virtio'}, }) - def _define_interfaces(self, interfaces, template_spec): + def _define_interfaces(self, interfaces, template_spec, defaults): """ Takes interfaces parameter of Ansible and create kubevirt API interfaces and networks strucutre out from it. """ + if not interfaces and defaults and 'interfaces' in defaults: + interfaces = copy.deepcopy(defaults['interfaces']) + for d in interfaces: + d['network'] = defaults['networks'][0] + if interfaces: # Extract interfaces k8s specification from interfaces list passed to Ansible: spec_interfaces = [] for i in interfaces: - spec_interfaces.append(dict((k, v) for k, v in i.items() if k != 'network')) + spec_interfaces.append( + dict(self.merge_dicts(dict((k, v) for k, v in i.items() if k != 'network'), defaults['interfaces'])) + ) if 'interfaces' not in template_spec['domain']['devices']: template_spec['domain']['devices']['interfaces'] = [] template_spec['domain']['devices']['interfaces'].extend(spec_interfaces) @@ -234,21 +249,28 @@ class KubeVirtRawModule(KubernetesRawModule): for i in interfaces: net = i['network'] net['name'] = i['name'] - spec_networks.append(net) + spec_networks.append(dict(self.merge_dicts(net, defaults['networks']))) if 'networks' not in template_spec: template_spec['networks'] = [] template_spec['networks'].extend(spec_networks) - def _define_disks(self, disks, template_spec): + def _define_disks(self, disks, template_spec, defaults): """ Takes disks parameter of Ansible and create kubevirt API disks and volumes strucutre out from it. """ + if not disks and defaults and 'disks' in defaults: + disks = copy.deepcopy(defaults['disks']) + for d in disks: + d['volume'] = defaults['volumes'][0] + if disks: # Extract k8s specification from disks list passed to Ansible: spec_disks = [] for d in disks: - spec_disks.append(dict((k, v) for k, v in d.items() if k != 'volume')) + spec_disks.append( + dict(self.merge_dicts(dict((k, v) for k, v in d.items() if k != 'volume'), defaults['disks'])) + ) if 'disks' not in template_spec['domain']['devices']: template_spec['domain']['devices']['disks'] = [] template_spec['domain']['devices']['disks'].extend(spec_disks) @@ -258,7 +280,7 @@ class KubeVirtRawModule(KubernetesRawModule): for d in disks: volume = d['volume'] volume['name'] = d['name'] - spec_volumes.append(volume) + spec_volumes.append(dict(self.merge_dicts(volume, defaults['volumes']))) if 'volumes' not in template_spec: template_spec['volumes'] = [] template_spec['volumes'].extend(spec_volumes) @@ -274,7 +296,7 @@ class KubeVirtRawModule(KubernetesRawModule): self.fail("API versions {0} are too recent. Max supported is {1}/{2}.".format( str([r.api_version for r in sr]), API_GROUP, MAX_SUPPORTED_API_VERSION)) - def _construct_vm_definition(self, kind, definition, template, params): + def _construct_vm_definition(self, kind, definition, template, params, defaults=None): self.client = self.get_api_client() disks = params.get('disks', []) @@ -343,7 +365,7 @@ class KubeVirtRawModule(KubernetesRawModule): template_spec['domain']['devices']['autoattachGraphicsDevice'] = not headless # Define disks - self._define_disks(disks, template_spec) + self._define_disks(disks, template_spec, defaults) # Define cloud init disk if defined: # Note, that this must be called after _define_disks, so the cloud_init @@ -351,18 +373,15 @@ class KubeVirtRawModule(KubernetesRawModule): self._define_cloud_init(cloud_init_nocloud, template_spec) # Define interfaces: - self._define_interfaces(interfaces, template_spec) + self._define_interfaces(interfaces, template_spec, defaults) # Define datavolumes: self._define_datavolumes(datavolumes, definition['spec']) - # Perform create/absent action: - definition = dict(self.merge_dicts(self.resource_definitions[0], definition)) - resource = self.find_supported_resource(kind) - return dict(self.merge_dicts(self.resource_definitions[0], definition)) + return dict(self.merge_dicts(definition, self.resource_definitions[0])) - def construct_vm_definition(self, kind, definition, template): - definition = self._construct_vm_definition(kind, definition, template, self.params) + def construct_vm_definition(self, kind, definition, template, defaults=None): + definition = self._construct_vm_definition(kind, definition, template, self.params, defaults) resource = self.find_supported_resource(kind) definition = self.set_defaults(resource, definition) return resource, definition diff --git a/lib/ansible/modules/cloud/kubevirt/kubevirt_vm.py b/lib/ansible/modules/cloud/kubevirt/kubevirt_vm.py index 149112e67b7..37a3d0871ef 100644 --- a/lib/ansible/modules/cloud/kubevirt/kubevirt_vm.py +++ b/lib/ansible/modules/cloud/kubevirt/kubevirt_vm.py @@ -232,8 +232,6 @@ kubevirt_vm: import copy import traceback -from ansible.module_utils.k8s.common import AUTH_ARG_SPEC - from ansible.module_utils.k8s.common import AUTH_ARG_SPEC from ansible.module_utils.kubevirt import ( virtdict, @@ -253,7 +251,7 @@ VM_ARG_SPEC = { }, 'datavolumes': {'type': 'list'}, 'template': {'type': 'str'}, - 'template_parameters': {'type': 'dict'}, + 'template_parameters': {'type': 'dict', 'default': {}}, } # Which params (can) modify 'spec:' contents of a VM: @@ -332,11 +330,31 @@ class KubeVirtVM(KubeVirtRawModule): return changed, k8s_obj + def _process_template_defaults(self, proccess_template, processedtemplate, defaults): + def set_template_default(default_name, default_name_index, definition_spec): + default_value = proccess_template['metadata']['annotations'][default_name] + if default_value: + values = definition_spec[default_name_index] + default_values = [d for d in values if d.get('name') == default_value] + defaults[default_name_index] = default_values + if definition_spec[default_name_index] is None: + definition_spec[default_name_index] = [] + definition_spec[default_name_index].extend([d for d in values if d.get('name') != default_value]) + + devices = processedtemplate['spec']['template']['spec']['domain']['devices'] + spec = processedtemplate['spec']['template']['spec'] + + set_template_default('defaults.template.cnv.io/disk', 'disks', devices) + set_template_default('defaults.template.cnv.io/volume', 'volumes', spec) + set_template_default('defaults.template.cnv.io/nic', 'interfaces', devices) + set_template_default('defaults.template.cnv.io/network', 'networks', spec) + def construct_definition(self, kind, our_state, ephemeral): definition = virtdict() processedtemplate = {} # Construct the API object definition: + defaults = {'disks': [], 'volumes': [], 'interfaces': [], 'networks': []} vm_template = self.params.get('template') if vm_template: # Find the template the VM should be created from: @@ -353,14 +371,16 @@ class KubeVirtVM(KubeVirtRawModule): processedtemplates_res = self.client.resources.get(api_version='template.openshift.io/v1', kind='Template', name='processedtemplates') processedtemplate = processedtemplates_res.create(proccess_template.to_dict()).to_dict()['objects'][0] + # Process defaults of the template: + self._process_template_defaults(proccess_template, processedtemplate, defaults) + if not ephemeral: definition['spec']['running'] = our_state == 'running' template = definition if ephemeral else definition['spec']['template'] template['metadata']['labels']['vm.cnv.io/name'] = self.params.get('name') - dummy, definition = self.construct_vm_definition(kind, definition, template) - definition = dict(self.merge_dicts(processedtemplate, definition)) + dummy, definition = self.construct_vm_definition(kind, definition, template, defaults) - return definition + return dict(self.merge_dicts(definition, processedtemplate)) def execute_module(self): # Parse parameters specific to this module: