diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_template.py b/lib/ansible/modules/cloud/ovirt/ovirt_template.py index a39a628c47..2c7df5978f 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_template.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_template.py @@ -189,6 +189,176 @@ options: - Name for importing Template from storage domain. - If not defined, C(name) will be used. version_added: "2.8" + usb_support: + description: + - "I(True) enable USB support, I(False) to disable it. By default is chosen by oVirt/RHV engine." + type: bool + version_added: "2.9" + timezone: + description: + - Sets time zone offset of the guest hardware clock. + - For example C(Etc/GMT) + version_added: "2.9" + sso: + description: + - "I(True) enable Single Sign On by Guest Agent, I(False) to disable it. By default is chosen by oVirt/RHV engine." + type: bool + version_added: "2.9" + soundcard_enabled: + description: + - "If I(true), the sound card is added to the virtual machine." + type: bool + version_added: "2.9" + smartcard_enabled: + description: + - "If I(true), use smart card authentication." + type: bool + version_added: "2.9" + cloud_init: + description: + - Dictionary with values for Unix-like Virtual Machine initialization using cloud init. + suboptions: + host_name: + description: + - Hostname to be set to Virtual Machine when deployed. + timezone: + description: + - Timezone to be set to Virtual Machine when deployed. + user_name: + description: + - Username to be used to set password to Virtual Machine when deployed. + root_password: + description: + - Password to be set for user specified by C(user_name) parameter. + authorized_ssh_keys: + description: + - Use this SSH keys to login to Virtual Machine. + regenerate_ssh_keys: + description: + - If I(True) SSH keys will be regenerated on Virtual Machine. + type: bool + custom_script: + description: + - Cloud-init script which will be executed on Virtual Machine when deployed. + - This is appended to the end of the cloud-init script generated by any other options. + dns_servers: + description: + - DNS servers to be configured on Virtual Machine. + dns_search: + description: + - DNS search domains to be configured on Virtual Machine. + nic_boot_protocol: + description: + - Set boot protocol of the network interface of Virtual Machine. + choices: ['none', 'dhcp', 'static'] + nic_ip_address: + description: + - If boot protocol is static, set this IP address to network interface of Virtual Machine. + nic_netmask: + description: + - If boot protocol is static, set this netmask to network interface of Virtual Machine. + nic_gateway: + description: + - If boot protocol is static, set this gateway to network interface of Virtual Machine. + nic_name: + description: + - Set name to network interface of Virtual Machine. + nic_on_boot: + description: + - If I(True) network interface will be set to start on boot. + type: bool + version_added: "2.9" + cloud_init_nics: + description: + - List of dictionaries representing network interfaces to be setup by cloud init. + - This option is used, when user needs to setup more network interfaces via cloud init. + - If one network interface is enough, user should use C(cloud_init) I(nic_*) parameters. C(cloud_init) I(nic_*) parameters + are merged with C(cloud_init_nics) parameters. + suboptions: + nic_boot_protocol: + description: + - Set boot protocol of the network interface of Virtual Machine. Can be one of C(none), C(dhcp) or C(static). + nic_ip_address: + description: + - If boot protocol is static, set this IP address to network interface of Virtual Machine. + nic_netmask: + description: + - If boot protocol is static, set this netmask to network interface of Virtual Machine. + nic_gateway: + description: + - If boot protocol is static, set this gateway to network interface of Virtual Machine. + nic_name: + description: + - Set name to network interface of Virtual Machine. + nic_on_boot: + description: + - If I(True) network interface will be set to start on boot. + type: bool + version_added: "2.9" + ballooning_enabled: + description: + - "If I(true), use memory ballooning." + - "Memory balloon is a guest device, which may be used to re-distribute / reclaim the host memory + based on VM needs in a dynamic way. In this way it's possible to create memory over commitment states." + type: bool + version_added: "2.9" + nics: + description: + - List of NICs, which should be attached to Virtual Machine. NIC is described by following dictionary. + suboptions: + name: + description: + - Name of the NIC. + profile_name: + description: + - Profile name where NIC should be attached. + interface: + description: + - Type of the network interface. + choices: ['virtio', 'e1000', 'rtl8139'] + default: 'virtio' + mac_address: + description: + - Custom MAC address of the network interface, by default it's obtained from MAC pool. + version_added: "2.9" + sysprep: + description: + - Dictionary with values for Windows Virtual Machine initialization using sysprep. + suboptions: + host_name: + description: + - Hostname to be set to Virtual Machine when deployed. + active_directory_ou: + description: + - Active Directory Organizational Unit, to be used for login of user. + org_name: + description: + - Organization name to be set to Windows Virtual Machine. + domain: + description: + - Domain to be set to Windows Virtual Machine. + timezone: + description: + - Timezone to be set to Windows Virtual Machine. + ui_language: + description: + - UI language of the Windows Virtual Machine. + system_locale: + description: + - System localization of the Windows Virtual Machine. + input_locale: + description: + - Input localization of the Windows Virtual Machine. + windows_license_key: + description: + - License key to be set to Windows Virtual Machine. + user_name: + description: + - Username to be used for set password to Windows Virtual Machine. + root_password: + description: + - Password to be set for username to Windows Virtual Machine. + version_added: "2.9" extends_documentation_fragment: ovirt ''' @@ -300,6 +470,62 @@ EXAMPLES = ''' vm: rhel7 version: name: subversion + +- name: Template with cloud init + ovirt_template: + name: mytemplate + cluster: Default + memory: 1GiB + cloud_init: + nic_boot_protocol: static + nic_ip_address: 10.34.60.86 + nic_netmask: 255.255.252.0 + nic_gateway: 10.34.63.254 + nic_name: eth1 + nic_on_boot: true + host_name: example.com + custom_script: | + write_files: + - content: | + Hello, world! + path: /tmp/greeting.txt + permissions: '0644' + user_name: root + root_password: super_password + +- name: Template with cloud init, with multiple network interfaces + ovirt_template: + name: mytemplate + cluster: mycluster + cloud_init_nics: + - nic_name: eth0 + nic_boot_protocol: dhcp + nic_on_boot: true + - nic_name: eth1 + nic_boot_protocol: static + nic_ip_address: 10.34.60.86 + nic_netmask: 255.255.252.0 + nic_gateway: 10.34.63.254 + nic_on_boot: true + +- name: Template with timezone and nic + ovirt_template: + cluster: MyCluster + name: mytemplate + timezone: America/Godthab + memory_max: 2Gib + nics: + - name: nic1 + +- name: Template with sysprep + ovirt_vm: + name: windows2012R2_AD + cluster: Default + memory: 3GiB + sysprep: + host_name: windowsad.example.com + user_name: Administrator + root_password: SuperPassword123 ''' RETURN = ''' @@ -341,6 +567,10 @@ from ansible.module_utils.ovirt import ( class TemplatesModule(BaseModule): + def __init__(self, *args, **kwargs): + super(TemplatesModule, self).__init__(*args, **kwargs) + self._initialization = None + def build_entity(self): return otypes.Template( id=self._module.params['id'], @@ -358,26 +588,44 @@ class TemplatesModule(BaseModule): self._module.params['cpu_profile'], ).id ) if self._module.params['cpu_profile'] else None, + display=otypes.Display( + smartcard_enabled=self.param('smartcard_enabled') + ) if self.param('smartcard_enabled') is not None else None, os=otypes.OperatingSystem( type=self.param('operating_system'), ) if self.param('operating_system') else None, memory=convert_to_bytes( self.param('memory') ) if self.param('memory') else None, + soundcard_enabled=self.param('soundcard_enabled'), + usb=( + otypes.Usb(enabled=self.param('usb_support')) + ) if self.param('usb_support') is not None else None, + sso=( + otypes.Sso( + methods=[otypes.Method(id=otypes.SsoMethod.GUEST_AGENT)] if self.param('sso') else [] + ) + ) if self.param('sso') is not None else None, + time_zone=otypes.TimeZone( + name=self.param('timezone'), + ) if self.param('timezone') else None, version=otypes.TemplateVersion( base_template=self._get_base_template(), version_name=self.param('version').get('name'), ) if self.param('version') else None, memory_policy=otypes.MemoryPolicy( guaranteed=convert_to_bytes(self.param('memory_guaranteed')), + ballooning=self.param('ballooning_enabled'), max=convert_to_bytes(self.param('memory_max')), ) if any(( self.param('memory_guaranteed'), + self.param('ballooning_enabled'), self.param('memory_max') )) else None, io=otypes.Io( threads=self.param('io_threads'), ) if self.param('io_threads') is not None else None, + initialization=self.get_initialization(), ) def _get_base_template(self): @@ -388,12 +636,123 @@ class TemplatesModule(BaseModule): id=template.id ) + def post_update(self, entity): + self.post_present(entity.id) + + def post_present(self, entity_id): + # After creation of the VM, attach disks and NICs: + entity = self._service.service(entity_id).get() + self.__attach_nics(entity) + + def __get_vnic_profile_id(self, nic): + """ + Return VNIC profile ID looked up by it's name, because there can be + more VNIC profiles with same name, other criteria of filter is cluster. + """ + vnics_service = self._connection.system_service().vnic_profiles_service() + clusters_service = self._connection.system_service().clusters_service() + cluster = search_by_name(clusters_service, self.param('cluster')) + profiles = [ + profile for profile in vnics_service.list() + if profile.name == nic.get('profile_name') + ] + cluster_networks = [ + net.id for net in self._connection.follow_link(cluster.networks) + ] + try: + return next( + profile.id for profile in profiles + if profile.network.id in cluster_networks + ) + except StopIteration: + raise Exception( + "Profile '%s' was not found in cluster '%s'" % ( + nic.get('profile_name'), + self.param('cluster') + ) + ) + + def __attach_nics(self, entity): + # Attach NICs to VM, if specified: + nics_service = self._service.service(entity.id).nics_service() + for nic in self.param('nics'): + if search_by_name(nics_service, nic.get('name')) is None: + if not self._module.check_mode: + nics_service.add( + otypes.Nic( + name=nic.get('name'), + interface=otypes.NicInterface( + nic.get('interface', 'virtio') + ), + vnic_profile=otypes.VnicProfile( + id=self.__get_vnic_profile_id(nic), + ) if nic.get('profile_name') else None, + mac=otypes.Mac( + address=nic.get('mac_address') + ) if nic.get('mac_address') else None, + ) + ) + self.changed = True + + def get_initialization(self): + if self._initialization is not None: + return self._initialization + + sysprep = self.param('sysprep') + cloud_init = self.param('cloud_init') + cloud_init_nics = self.param('cloud_init_nics') or [] + if cloud_init is not None: + cloud_init_nics.append(cloud_init) + + if cloud_init or cloud_init_nics: + self._initialization = otypes.Initialization( + nic_configurations=[ + otypes.NicConfiguration( + boot_protocol=otypes.BootProtocol( + nic.pop('nic_boot_protocol').lower() + ) if nic.get('nic_boot_protocol') else None, + name=nic.pop('nic_name', None), + on_boot=nic.pop('nic_on_boot', None), + ip=otypes.Ip( + address=nic.pop('nic_ip_address', None), + netmask=nic.pop('nic_netmask', None), + gateway=nic.pop('nic_gateway', None), + ) if ( + nic.get('nic_gateway') is not None or + nic.get('nic_netmask') is not None or + nic.get('nic_ip_address') is not None + ) else None, + ) + for nic in cloud_init_nics + if ( + nic.get('nic_gateway') is not None or + nic.get('nic_netmask') is not None or + nic.get('nic_ip_address') is not None or + nic.get('nic_boot_protocol') is not None or + nic.get('nic_on_boot') is not None + ) + ] if cloud_init_nics else None, + **cloud_init + ) + elif sysprep: + self._initialization = otypes.Initialization( + **sysprep + ) + return self._initialization + def update_check(self, entity): + template_display = entity.display return ( equal(self._module.params.get('cluster'), get_link_name(self._connection, entity.cluster)) and equal(self._module.params.get('description'), entity.description) and equal(self.param('operating_system'), str(entity.os.type)) and equal(self.param('name'), str(entity.name)) and + equal(self.param('smartcard_enabled'), getattr(template_display, 'smartcard_enabled', False)) and + equal(self.param('soundcard_enabled'), entity.soundcard_enabled) and + equal(self.param('ballooning_enabled'), entity.memory_policy.ballooning) and + equal(self.param('sso'), True if entity.sso.methods else False) and + equal(self.param('timezone'), getattr(entity.time_zone, 'name', None)) and + equal(self.param('usb_support'), entity.usb.enabled) and equal(convert_to_bytes(self.param('memory_guaranteed')), entity.memory_policy.guaranteed) and equal(convert_to_bytes(self.param('memory_max')), entity.memory_policy.max) and equal(convert_to_bytes(self.param('memory')), entity.memory) and @@ -524,8 +883,12 @@ def main(): id=dict(default=None), name=dict(default=None), vm=dict(default=None), + timezone=dict(type='str'), description=dict(default=None), + sso=dict(type='bool'), + ballooning_enabled=dict(type='bool', default=None), cluster=dict(default=None), + usb_support=dict(type='bool'), allow_partial_import=dict(default=None, type='bool'), cpu_profile=dict(default=None), clone_permissions=dict(type='bool'), @@ -534,6 +897,8 @@ def main(): exclusive=dict(type='bool'), clone_name=dict(default=None), image_provider=dict(default=None), + soundcard_enabled=dict(type='bool', default=None), + smartcard_enabled=dict(type='bool', default=None), image_disk=dict(default=None, aliases=['glance_image_disk_name']), io_threads=dict(type='int', default=None), template_image_disk_name=dict(default=None), @@ -547,6 +912,10 @@ def main(): memory=dict(type='str'), memory_guaranteed=dict(type='str'), memory_max=dict(type='str'), + nics=dict(type='list', default=[]), + cloud_init=dict(type='dict'), + cloud_init_nics=dict(type='list', default=[]), + sysprep=dict(type='dict'), ) module = AnsibleModule( argument_spec=argument_spec,