Update kubevirt_vm with new feautures (#52311)

This commit is contained in:
Ondra Machacek 2019-02-18 10:42:23 +01:00 committed by Abhijeet Kasurde
parent 664e6fb9c8
commit 435e0c5dba
3 changed files with 143 additions and 32 deletions

View file

@ -5,6 +5,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from collections import defaultdict from collections import defaultdict
from distutils.version import Version
from ansible.module_utils.k8s.common import list_dict_str from ansible.module_utils.k8s.common import list_dict_str
from ansible.module_utils.k8s.raw import KubernetesRawModule from ansible.module_utils.k8s.raw import KubernetesRawModule
@ -16,8 +17,10 @@ except ImportError:
# Handled in k8s common: # Handled in k8s common:
pass pass
import re
API_VERSION = 'kubevirt.io/v1alpha3' MAX_SUPPORTED_API_VERSION = 'v1alpha3'
API_GROUP = 'kubevirt.io'
VM_COMMON_ARG_SPEC = { VM_COMMON_ARG_SPEC = {
@ -34,11 +37,12 @@ VM_COMMON_ARG_SPEC = {
'type': 'path', 'type': 'path',
}, },
'namespace': {}, 'namespace': {},
'api_version': {'type': 'str', 'default': API_VERSION, 'aliases': ['api', 'version']}, 'api_version': {'type': 'str', 'default': '%s/%s' % (API_GROUP, MAX_SUPPORTED_API_VERSION), 'aliases': ['api', 'version']},
'merge_type': {'type': 'list', 'choices': ['json', 'merge', 'strategic-merge']}, 'merge_type': {'type': 'list', 'choices': ['json', 'merge', 'strategic-merge']},
'wait': {'type': 'bool', 'default': True}, 'wait': {'type': 'bool', 'default': True},
'wait_timeout': {'type': 'int', 'default': 120}, 'wait_timeout': {'type': 'int', 'default': 120},
'memory': {'type': 'str'}, 'memory': {'type': 'str'},
'cpu_cores': {'type': 'int'},
'disks': {'type': 'list'}, 'disks': {'type': 'list'},
'labels': {'type': 'dict'}, 'labels': {'type': 'dict'},
'interfaces': {'type': 'list'}, 'interfaces': {'type': 'list'},
@ -54,9 +58,63 @@ def virtdict():
return defaultdict(virtdict) return defaultdict(virtdict)
class KubeAPIVersion(Version):
component_re = re.compile(r'(\d+ | [a-z]+)', re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def parse(self, vstring):
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x]
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
errmsg = "version '{0}' does not conform to kubernetes api versioning guidelines".format(vstring)
c = components
if len(c) not in (2, 4) or c[0] != 'v' or not isinstance(c[1], int):
raise ValueError(errmsg)
if len(c) == 4 and (c[2] not in ('alpha', 'beta') or not isinstance(c[3], int)):
raise ValueError(errmsg)
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "KubeAPIVersion ('{0}')".format(str(self))
def _cmp(self, other):
if isinstance(other, str):
other = KubeAPIVersion(other)
myver = self.version
otherver = other.version
for ver in myver, otherver:
if len(ver) == 2:
ver.extend(['zeta', 9999])
if myver == otherver:
return 0
if myver < otherver:
return -1
if myver > otherver:
return 1
# python2 compatibility
def __cmp__(self, other):
return self._cmp(other)
class KubeVirtRawModule(KubernetesRawModule): class KubeVirtRawModule(KubernetesRawModule):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.api_version = API_VERSION
super(KubeVirtRawModule, self).__init__(*args, **kwargs) super(KubeVirtRawModule, self).__init__(*args, **kwargs)
@staticmethod @staticmethod
@ -99,8 +157,7 @@ class KubeVirtRawModule(KubernetesRawModule):
def _define_cloud_init(self, cloud_init_nocloud, template_spec): def _define_cloud_init(self, cloud_init_nocloud, template_spec):
""" """
Takes the user's cloud_init_nocloud parameter and fill it in kubevirt Takes the user's cloud_init_nocloud parameter and fill it in kubevirt
API strucuture. The name of the volume is hardcoded to ansiblecloudinitvolume API strucuture. The name for disk is hardcoded to ansiblecloudinitdisk.
and the name for disk is hardcoded to ansiblecloudinitdisk.
""" """
if cloud_init_nocloud: if cloud_init_nocloud:
if not template_spec['volumes']: if not template_spec['volumes']:
@ -108,10 +165,9 @@ class KubeVirtRawModule(KubernetesRawModule):
if not template_spec['domain']['devices']['disks']: if not template_spec['domain']['devices']['disks']:
template_spec['domain']['devices']['disks'] = [] template_spec['domain']['devices']['disks'] = []
template_spec['volumes'].append({'name': 'ansiblecloudinitvolume', 'cloudInitNoCloud': cloud_init_nocloud}) template_spec['volumes'].append({'name': 'ansiblecloudinitdisk', 'cloudInitNoCloud': cloud_init_nocloud})
template_spec['domain']['devices']['disks'].append({ template_spec['domain']['devices']['disks'].append({
'name': 'ansiblecloudinitdisk', 'name': 'ansiblecloudinitdisk',
'volumeName': 'ansiblecloudinitvolume',
'disk': {'bus': 'virtio'}, 'disk': {'bus': 'virtio'},
}) })
@ -125,7 +181,9 @@ class KubeVirtRawModule(KubernetesRawModule):
spec_interfaces = [] spec_interfaces = []
for i in interfaces: for i in interfaces:
spec_interfaces.append(dict((k, v) for k, v in i.items() if k != 'network')) spec_interfaces.append(dict((k, v) for k, v in i.items() if k != 'network'))
template_spec['domain']['devices']['interfaces'] = spec_interfaces if 'interfaces' not in template_spec['domain']['devices']:
template_spec['domain']['devices']['interfaces'] = []
template_spec['domain']['devices']['interfaces'].extend(spec_interfaces)
# Extract networks k8s specification from interfaces list passed to Ansible: # Extract networks k8s specification from interfaces list passed to Ansible:
spec_networks = [] spec_networks = []
@ -133,7 +191,9 @@ class KubeVirtRawModule(KubernetesRawModule):
net = i['network'] net = i['network']
net['name'] = i['name'] net['name'] = i['name']
spec_networks.append(net) spec_networks.append(net)
template_spec['networks'] = spec_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):
""" """
@ -145,7 +205,9 @@ class KubeVirtRawModule(KubernetesRawModule):
spec_disks = [] spec_disks = []
for d in disks: for d in disks:
spec_disks.append(dict((k, v) for k, v in d.items() if k != 'volume')) spec_disks.append(dict((k, v) for k, v in d.items() if k != 'volume'))
template_spec['domain']['devices']['disks'] = spec_disks if 'disks' not in template_spec['domain']['devices']:
template_spec['domain']['devices']['disks'] = []
template_spec['domain']['devices']['disks'].extend(spec_disks)
# Extract volumes k8s specification from disks list passed to Ansible: # Extract volumes k8s specification from disks list passed to Ansible:
spec_volumes = [] spec_volumes = []
@ -153,24 +215,40 @@ class KubeVirtRawModule(KubernetesRawModule):
volume = d['volume'] volume = d['volume']
volume['name'] = d['name'] volume['name'] = d['name']
spec_volumes.append(volume) spec_volumes.append(volume)
template_spec['volumes'] = spec_volumes if 'volumes' not in template_spec:
template_spec['volumes'] = []
template_spec['volumes'].extend(spec_volumes)
def execute_crud(self, kind, definition, template): def find_supported_resource(self, kind):
""" Module execution """ results = self.client.resources.search(kind=kind, group=API_GROUP)
if not results:
self.fail('Failed to find resource {0} in {1}'.format(kind, API_GROUP))
sr = sorted(results, key=lambda r: KubeAPIVersion(r.api_version), reverse=True)
for r in sr:
if KubeAPIVersion(r.api_version) <= KubeAPIVersion(MAX_SUPPORTED_API_VERSION):
return r
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):
self.client = self.get_api_client() self.client = self.get_api_client()
disks = self.params.get('disks', []) disks = params.get('disks', [])
memory = self.params.get('memory') memory = params.get('memory')
labels = self.params.get('labels') cpu_cores = params.get('cpu_cores')
interfaces = self.params.get('interfaces') labels = params.get('labels')
cloud_init_nocloud = self.params.get('cloud_init_nocloud') interfaces = params.get('interfaces')
machine_type = self.params.get('machine_type') cloud_init_nocloud = params.get('cloud_init_nocloud')
machine_type = params.get('machine_type')
template_spec = template['spec'] template_spec = template['spec']
# Merge additional flat parameters: # Merge additional flat parameters:
if memory: if memory:
template_spec['domain']['resources']['requests']['memory'] = memory template_spec['domain']['resources']['requests']['memory'] = memory
if cpu_cores is not None:
template_spec['domain']['cpu']['cores'] = cpu_cores
if labels: if labels:
template['metadata']['labels'] = labels template['metadata']['labels'] = labels
@ -188,6 +266,28 @@ class KubeVirtRawModule(KubernetesRawModule):
# Perform create/absent action: # Perform create/absent action:
definition = dict(self.merge_dicts(self.resource_definitions[0], definition)) definition = dict(self.merge_dicts(self.resource_definitions[0], definition))
resource = self.find_resource(kind, self.api_version, fail=True) resource = self.find_supported_resource(kind)
return dict(self.merge_dicts(self.resource_definitions[0], definition))
def construct_vm_definition(self, kind, definition, template):
definition = self._construct_vm_definition(kind, definition, template, self.params)
resource = self.find_supported_resource(kind)
definition = self.set_defaults(resource, definition)
return resource, definition
def construct_vm_template_definition(self, kind, definition, template, params):
definition = self._construct_vm_definition(kind, definition, template, params)
resource = self.find_resource(kind, definition['apiVersion'], fail=True)
# Set defaults:
definition['kind'] = kind
definition['metadata']['name'] = params.get('name')
definition['metadata']['namespace'] = params.get('namespace')
return resource, definition
def execute_crud(self, kind, definition):
""" Module execution """
resource = self.find_supported_resource(kind)
definition = self.set_defaults(resource, definition) definition = self.set_defaults(resource, definition)
return self.perform_action(resource, definition) return self.perform_action(resource, definition)

View file

@ -81,6 +81,7 @@ EXAMPLES = '''
name: myvm name: myvm
namespace: vms namespace: vms
memory: 64M memory: 64M
cpu_cores: 1
disks: disks:
- name: containerdisk - name: containerdisk
volume: volume:
@ -158,6 +159,7 @@ EXAMPLES = '''
namespace: vms namespace: vms
memory: 1024M memory: 1024M
cloud_init_nocloud: cloud_init_nocloud:
#cloud-config
userData: |- userData: |-
password: fedora password: fedora
chpasswd: { expire: False } chpasswd: { expire: False }
@ -180,9 +182,9 @@ EXAMPLES = '''
RETURN = ''' RETURN = '''
kubevirt_vm: kubevirt_vm:
description: description:
- The virtual machine dictionary specification returned by the API. - The virtual machine dictionary specification returned by the API.
- "This dictionary contains all values returned by the KubeVirt API all options - "This dictionary contains all values returned by the KubeVirt API all options
are described here U(https://kubevirt.io/api-reference/master/definitions.html#_v1_virtualmachine)" are described here U(https://kubevirt.io/api-reference/master/definitions.html#_v1_virtualmachine)"
returned: success returned: success
type: complex type: complex
contains: {} contains: {}
@ -192,6 +194,8 @@ kubevirt_vm:
import copy import copy
import traceback import traceback
from ansible.module_utils.k8s.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC
try: try:
from openshift.dynamic.client import ResourceInstance from openshift.dynamic.client import ResourceInstance
except ImportError: except ImportError:
@ -203,10 +207,8 @@ from ansible.module_utils.kubevirt import (
virtdict, virtdict,
KubeVirtRawModule, KubeVirtRawModule,
VM_COMMON_ARG_SPEC, VM_COMMON_ARG_SPEC,
API_VERSION,
) )
VM_ARG_SPEC = { VM_ARG_SPEC = {
'ephemeral': {'type': 'bool', 'default': False}, 'ephemeral': {'type': 'bool', 'default': False},
'state': { 'state': {
@ -224,7 +226,8 @@ class KubeVirtVM(KubeVirtRawModule):
@property @property
def argspec(self): def argspec(self):
""" argspec property builder """ """ argspec property builder """
argument_spec = copy.deepcopy(AUTH_ARG_SPEC) argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
argument_spec.update(VM_COMMON_ARG_SPEC) argument_spec.update(VM_COMMON_ARG_SPEC)
argument_spec.update(VM_ARG_SPEC) argument_spec.update(VM_ARG_SPEC)
return argument_spec return argument_spec
@ -234,7 +237,7 @@ class KubeVirtVM(KubeVirtRawModule):
self.patch_resource(resource, definition, existing, self.name, self.namespace, merge_type='merge') self.patch_resource(resource, definition, existing, self.name, self.namespace, merge_type='merge')
if wait: if wait:
resource = self.find_resource('VirtualMachineInstance', self.api_version, fail=True) resource = self.find_supported_resource('VirtualMachineInstance')
w, stream = self._create_stream(resource, self.namespace, wait_timeout) w, stream = self._create_stream(resource, self.namespace, wait_timeout)
if wait and stream is not None: if wait and stream is not None:
@ -264,13 +267,13 @@ class KubeVirtVM(KubeVirtRawModule):
wait_timeout = self.params.get('wait_timeout') wait_timeout = self.params.get('wait_timeout')
resource_version = self.params.get('resource_version') resource_version = self.params.get('resource_version')
resource_vm = self.find_resource('VirtualMachine', self.api_version) resource_vm = self.find_supported_resource('VirtualMachine')
existing = self.get_resource(resource_vm) existing = self.get_resource(resource_vm)
if resource_version and resource_version != existing.metadata.resourceVersion: if resource_version and resource_version != existing.metadata.resourceVersion:
return False return False
existing_running = False existing_running = False
resource_vmi = self.find_resource('VirtualMachineInstance', self.api_version) resource_vmi = self.find_supported_resource('VirtualMachineInstance')
existing_running_vmi = self.get_resource(resource_vmi) existing_running_vmi = self.get_resource(resource_vmi)
if existing_running_vmi and hasattr(existing_running_vmi.status, 'phase'): if existing_running_vmi and hasattr(existing_running_vmi.status, 'phase'):
existing_running = existing_running_vmi.status.phase == 'Running' existing_running = existing_running_vmi.status.phase == 'Running'
@ -300,7 +303,8 @@ class KubeVirtVM(KubeVirtRawModule):
# Execute the CURD of VM: # Execute the CURD of VM:
template = definition['spec']['template'] template = definition['spec']['template']
kind = 'VirtualMachineInstance' if ephemeral else 'VirtualMachine' kind = 'VirtualMachineInstance' if ephemeral else 'VirtualMachine'
result = self.execute_crud(kind, definition, template) dummy, definition = self.construct_vm_definition(kind, definition, template)
result = self.execute_crud(kind, definition)
changed = result['changed'] changed = result['changed']
# Manage state of the VM: # Manage state of the VM:
@ -320,7 +324,6 @@ class KubeVirtVM(KubeVirtRawModule):
def main(): def main():
module = KubeVirtVM() module = KubeVirtVM()
try: try:
module.api_version = API_VERSION
module.execute_module() module.execute_module()
except Exception as e: except Exception as e:
module.fail_json(msg=str(e), exception=traceback.format_exc()) module.fail_json(msg=str(e), exception=traceback.format_exc())

View file

@ -14,6 +14,12 @@ options:
- "I(True) if the module should wait for the resource to get into desired state." - "I(True) if the module should wait for the resource to get into desired state."
type: bool type: bool
default: yes default: yes
kind:
description:
- Use to specify an object model. Use to create, delete, or discover an object without providing a full
resource definition. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a
specific object. If I(resource definition) is provided, the I(kind) from the I(resource_definition)
will override this option.
force: force:
description: description:
- If set to C(no), and I(state) is C(present), an existing object will be replaced. - If set to C(no), and I(state) is C(present), an existing object will be replaced.
@ -55,7 +61,9 @@ options:
is simply C(strategic-merge). is simply C(strategic-merge).
type: list type: list
choices: [ json, merge, strategic-merge ] choices: [ json, merge, strategic-merge ]
cpu_cores:
description:
- "Number of CPU cores."
requirements: requirements:
- python >= 2.7 - python >= 2.7
- openshift >= 0.8.2 - openshift >= 0.8.2