Update kubevirt_vm with new feautures (#52311)
This commit is contained in:
parent
664e6fb9c8
commit
435e0c5dba
3 changed files with 143 additions and 32 deletions
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue