kubevirt_vm: Improve create VM from template (#55927)

This commit is contained in:
Ondra Machacek 2019-05-21 09:31:59 +02:00 committed by Martin Krizek
parent 3b4b2e5021
commit a4144e15c0
2 changed files with 69 additions and 30 deletions

View file

@ -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,17 +127,24 @@ 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.
"""
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, y[k])
yield (k, x[k])
elif k in x:
yield (k, x[k])
else:
@ -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

View file

@ -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: