kubevirt: add kubevirt_pvc module with CDI support (#53327)

pull/53633/head
Mariusz Mazur 6 years ago committed by John R Barker
parent 272fa9ead4
commit c2699e2a57

@ -0,0 +1,395 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: kubevirt_pvc
short_description: Manage PVCs on Kubernetes
version_added: "2.8"
author: KubeVirt Team (@kubevirt)
description:
- Use Openshift Python SDK to manage PVCs on Kubernetes
- Support Containerized Data Importer out of the box
options:
resource_definition:
description:
- "A partial YAML definition of the PVC object being created/updated. Here you can define Kubernetes
PVC Resource parameters not covered by this module's parameters."
- "NOTE: I(resource_definition) has lower priority than module parameters. If you try to define e.g.
I(metadata.namespace) here, that value will be ignored and I(namespace) used instead."
aliases:
- definition
- inline
type: dict
state:
description:
- "Determines if an object should be created, patched, or deleted. When set to C(present), an object will be
created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to
C(present), an existing object will be patched, if its attributes differ from those specified using
module options and I(resource_definition)."
default: present
choices:
- present
- absent
force:
description:
- If set to C(True), and I(state) is C(present), an existing object will be replaced.
default: false
type: bool
merge_type:
description:
- Whether to override the default patch merge approach with a specific type.
- "This defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters
on resource kinds that combine Custom Resources and built-in resources."
- See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
- If more than one merge_type is given, the merge_types will be tried in order
choices:
- json
- merge
- strategic-merge
type: list
name:
description:
- Use to specify a PVC object name.
required: true
type: str
namespace:
description:
- Use to specify a PVC object namespace.
required: true
type: str
annotations:
description:
- Annotations attached to this object.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
type: dict
labels:
description:
- Labels attached to this object.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
type: dict
selector:
description:
- A label query over volumes to consider for binding.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
type: dict
access_modes:
description:
- Contains the desired access modes the volume should have.
- "More info: U(https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes)"
type: list
size:
description:
- How much storage to allocate to the PVC.
type: str
aliases:
- storage
storage_class_name:
description:
- Name of the StorageClass required by the claim.
- "More info: U(https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1)"
type: str
volume_mode:
description:
- "This defines what type of volume is required by the claim. Value of Filesystem is implied when not
included in claim spec. This is an alpha feature of kubernetes and may change in the future."
type: str
volume_name:
description:
- This is the binding reference to the PersistentVolume backing this claim.
type: str
cdi_source:
description:
- "If data is to be copied onto the PVC using the Containerized Data Importer you can specify the source of
the data (along with any additional configuration) as well as it's format."
- "Valid source types are: blank, http, s3, registry, pvc and upload. The last one requires using the
M(kubevirt_cdi_upload) module to actually perform an upload."
- "Source data format is specified using the optional I(content_type). Valid options are C(kubevirt)
(default; raw image) and C(archive) (tar.gz)."
- "This uses the DataVolume source syntax:
U(https://github.com/kubevirt/containerized-data-importer/blob/master/doc/datavolumes.md#https3registry-source)"
type: dict
extends_documentation_fragment:
- k8s_auth_options
requirements:
- python >= 2.7
- openshift >= 0.8.2
'''
EXAMPLES = '''
- name: Create a PVC and import data from an external source
kubevirt_pvc:
name: pvc1
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
http:
url: https://www.source.example/path/of/data/vm.img
# If the URL points to a tar.gz containing the disk image, ucomment the line below:
#content_type: archive
- name: Create a PVC as a clone from a different PVC
kubevirt_pvc:
name: pvc2
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
pvc:
namespace: source-ns
name: source-pvc
- name: Create a PVC ready for data upload
kubevirt_pvc:
name: pvc3
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
upload: yes
# You need the kubevirt_cdi_upload module to actually upload something
- name: Create a PVC with a blank raw image
kubevirt_pvc:
name: pvc4
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
blank: yes
- name: Create a PVC and fill it with data from a container
kubevirt_pvc:
name: pvc5
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
registry:
url: "docker://kubevirt/fedora-cloud-registry-disk-demo"
'''
RETURN = '''
result:
description:
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
returned: success
type: complex
contains:
api_version:
description: The versioned schema of this representation of an object.
returned: success
type: str
kind:
description: Represents the REST resource this object represents.
returned: success
type: str
metadata:
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
returned: success
type: complex
spec:
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
returned: success
type: complex
status:
description: Current status details for the object.
returned: success
type: complex
items:
description: Returned only when multiple yaml documents are passed to src or resource_definition
returned: when resource_definition or src contains list of objects
type: list
duration:
description: elapsed time of task in seconds
returned: when C(wait) is true
type: int
sample: 48
'''
import copy
import traceback
from collections import defaultdict
from ansible.module_utils.k8s.common import AUTH_ARG_SPEC
from ansible.module_utils.k8s.raw import KubernetesRawModule
from ansible.module_utils.kubevirt import virtdict, KubeVirtRawModule
PVC_ARG_SPEC = {
'name': {'required': True},
'namespace': {'required': True},
'state': {
'type': 'str',
'choices': [
'present', 'absent'
],
'default': 'present'
},
'force': {
'type': 'bool',
'default': False,
},
'merge_type': {
'type': 'list',
'choices': ['json', 'merge', 'strategic-merge']
},
'resource_definition': {
'type': 'dict',
'aliases': ['definition', 'inline']
},
'labels': {'type': 'dict'},
'annotations': {'type': 'dict'},
'selector': {'type': 'dict'},
'access_modes': {'type': 'list'},
'size': {
'type': 'str',
'aliases': ['storage']
},
'storage_class_name': {'type': 'str'},
'volume_mode': {'type': 'str'},
'volume_name': {'type': 'str'},
'cdi_source': {'type': 'dict'}
}
class KubevirtPVC(KubernetesRawModule):
def __init__(self):
super(KubevirtPVC, self).__init__()
@property
def argspec(self):
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
argument_spec.update(PVC_ARG_SPEC)
return argument_spec
def _parse_cdi_source(self, _cdi_src, metadata):
cdi_src = copy.deepcopy(_cdi_src)
annotations = metadata['annotations']
labels = metadata['labels']
valid_content_types = ('kubevirt', 'archive')
valid_sources = ('http', 's3', 'pvc', 'upload', 'blank', 'registry')
if 'content_type' in cdi_src:
content_type = cdi_src.pop('content_type')
if content_type not in valid_content_types:
raise ValueError("cdi_source.content_type must be one of {0}, not: '{1}'".format(
valid_content_types, content_type))
annotations['cdi.kubevirt.io/storage.contentType'] = content_type
if len(cdi_src) != 1:
raise ValueError("You must specify exactly one valid CDI source, not {0}: {1}".format(len(cdi_src), tuple(cdi_src.keys())))
src_type = tuple(cdi_src.keys())[0]
src_spec = cdi_src[src_type]
if src_type not in valid_sources:
raise ValueError("Got an invalid CDI source type: '{0}', must be one of {1}".format(src_type, valid_sources))
# True for all cases save one
labels['app'] = 'containerized-data-importer'
if src_type == 'upload':
annotations['cdi.kubevirt.io/storage.upload.target'] = ''
elif src_type == 'blank':
annotations['cdi.kubevirt.io/storage.import.source'] = 'none'
elif src_type == 'pvc':
if not isinstance(src_spec, dict) or sorted(src_spec.keys()) != ['name', 'namespace']:
raise ValueError("CDI Source 'pvc' requires specifying 'name' and 'namespace' (and nothing else)")
labels['app'] = 'host-assisted-cloning'
annotations['k8s.io/CloneRequest'] = '{0}/{1}'.format(src_spec['namespace'], src_spec['name'])
elif src_type in ('http', 's3', 'registry'):
if not isinstance(src_spec, dict) or 'url' not in src_spec:
raise ValueError("CDI Source '{0}' requires specifying 'url'".format(src_type))
unknown_params = set(src_spec.keys()).difference(set(('url', 'secretRef', 'certConfigMap')))
if unknown_params:
raise ValueError("CDI Source '{0}' does not know recognize params: {1}".format(src_type, tuple(unknown_params)))
annotations['cdi.kubevirt.io/storage.import.source'] = src_type
annotations['cdi.kubevirt.io/storage.import.endpoint'] = src_spec['url']
if 'secretRef' in src_spec:
annotations['cdi.kubevirt.io/storage.import.secretName'] = src_spec['secretRef']
if 'certConfigMap' in src_spec:
annotations['cdi.kubevirt.io/storage.import.certConfigMap'] = src_spec['certConfigMap']
def execute_module(self):
KIND = 'PersistentVolumeClaim'
API = 'v1'
definition = virtdict()
definition['kind'] = KIND
definition['apiVersion'] = API
metadata = definition['metadata']
metadata['name'] = self.params.get('name')
metadata['namespace'] = self.params.get('namespace')
if self.params.get('annotations'):
metadata['annotations'] = self.params.get('annotations')
if self.params.get('labels'):
metadata['labels'] = self.params.get('labels')
if self.params.get('cdi_source'):
self._parse_cdi_source(self.params.get('cdi_source'), metadata)
spec = definition['spec']
if self.params.get('access_modes'):
spec['accessModes'] = self.params.get('access_modes')
if self.params.get('size'):
spec['resources']['requests']['storage'] = self.params.get('size')
if self.params.get('storage_class_name'):
spec['storageClassName'] = self.params.get('storage_class_name')
if self.params.get('selector'):
spec['selector'] = self.params.get('selector')
if self.params.get('volume_mode'):
spec['volumeMode'] = self.params.get('volume_mode')
if self.params.get('volume_name'):
spec['volumeName'] = self.params.get('volume_name')
# 'resource_definition:' has lower priority than module parameters
definition = dict(KubeVirtRawModule.merge_dicts(self.resource_definitions[0], definition))
self.client = self.get_api_client()
resource = self.find_resource(KIND, API, fail=True)
definition = self.set_defaults(resource, definition)
result = self.perform_action(resource, definition)
self.exit_json(**result)
def main():
module = KubevirtPVC()
try:
module.execute_module()
except Exception as e:
module.fail_json(msg=str(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()
Loading…
Cancel
Save