Repository: ansible/ansible
Pull request: ovirt vmpools creating vm options
##### SUMMARY
<!--- Describe the change below, including rationale and design decisions -->
ovirt vmpools creating vm options
@machacekondra what other options should I add?

<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
Fixes #55344
##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Feature Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
ovirt
##### ADDITIONAL INFORMATION
<!--- Include additional information to help people understand the change here -->
<!--- A step-by-step reproduction of the problem is helpful if there is no related issue -->

<!--- Paste verbatim command output below, e.g. before and after your change -->
```paste below

```


Problem statement:
[RFE] Extend ovirt_pool Module - VM Definitions
<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->

The ovirt_pool module needs to be able to provide and set multiple features similar to the ovirt_vm.

Need to be able to set multiple pieces of information (USB Redirect, Networking/vNICS, Cloud-Init, etc.). Essentially, this is missing the rest of the features that can be specified from the RHVM UI.

##### ISSUE TYPE
- Feature Idea
Extend the **ovirt_pool** module to support VM features like:

vnic_profile_mappings
usb_support
timezone
sso
soundcard_enabled
smartcard_enabled
nics
memory_max
memory
memory_guaranteed
cloud_init_persist
cloud_init_nics
cloud_init
ballooning_enabled

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->

ovirt_pool

Pieces to add are from the ovirt_vm module.

vnic_profile_mappings
usb_support
timezone
sso
soundcard_enabled
smartcard_enabled
nics
memory_max
memory
memory_guaranteed
cloud_init_persist
cloud_init_nics
cloud_init
ballooning_enabled

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->

This feature would provide the full functionality for the **ovirt_pool** module allowing items to be changed or set differently than the VM that the template was based on. Usage would be similar to that of the ovirt_vm module.

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can also paste gist.github.com links for larger files -->

Hints from issue discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/misc/cloud_init_data_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/misc/cloud_init_data_facts.py)
* [lib/ansible/modules/cloud/ovirt/ovirt_vm.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/ovirt/ovirt_vm.py)
* [lib/ansible/modules/system/timezone.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/timezone.py)
* [lib/ansible/plugins/cache/memory.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/cache/memory.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @indrajitr @jasperla @machacekondra @mnecas @mwperina @resmo @tmshn
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Commit message: ovirt vm pools update docs

Patch to review:
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_vmpool.py b/lib/ansible/modules/cloud/ovirt/ovirt_vmpool.py
index a2f2adfdc13aae..f7383ca7bdc721 100644
--- a/lib/ansible/modules/cloud/ovirt/ovirt_vmpool.py
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_vmpool.py
@@ -81,6 +81,92 @@
         description:
             - "Number of VMs in the pool."
             - "Default value is set by engine."
+    vm:
+        description:
+            - "For creating vm poool without editing template."
+            - "Note: You can use C(vm) only for creating vm pool."
+        type: dict
+        suboption:
+            comment:
+                description:
+                    - Comment of the Virtual Machine.
+            timezone:
+                description:
+                    - Sets time zone offset of the guest hardware clock.
+                    - For example C(Etc/GMT)
+            memory:
+                description:
+                    - Amount of memory of the Virtual Machine. Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - Default value is set by engine.
+            memory_guaranteed:
+                description:
+                    - Amount of minimal guaranteed memory of the Virtual Machine.
+                      Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - C(memory_guaranteed) parameter can't be lower than C(memory) parameter.
+                    - Default value is set by engine.
+            memory_max:
+                description:
+                    - Upper bound of virtual machine memory up to which memory hot-plug can be performed.
+                      Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - Default value is set by engine.
+            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
+            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
 extends_documentation_fragment: ovirt
 '''
 
@@ -88,8 +174,8 @@
 # Examples don't contain auth parameter for simplicity,
 # look at ovirt_auth module to see how to reuse authentication:
 
-# Create VM pool from template
-- ovirt_vmpool:
+- name: Create VM pool from template
+  ovirt_vmpool:
     cluster: mycluster
     name: myvmpool
     template: rhel7
@@ -97,15 +183,50 @@
     prestarted: 2
     vm_per_user: 1
 
-# Remove vmpool, note that all VMs in pool will be stopped and removed:
-- ovirt_vmpool:
+- name: Remove vmpool, note that all VMs in pool will be stopped and removed
+  ovirt_vmpool:
     state: absent
     name: myvmpool
 
-# Change Pool Name
-- ovirt_vmpool:
+- name: Change Pool Name
+  ovirt_vmpool:
     id: 00000000-0000-0000-0000-000000000000
     name: "new_pool_name"
+
+- name: Create pool with vm
+  ovirt_vmpool:
+    cluster: mycluster
+    name: vmpool
+    template: blank
+    vm_count: 2
+    prestarted: 1
+    vm_per_user: 1
+    vm:
+      memory: 4GiB
+      memory_guaranteed: 4GiB
+      memory_max: 10GiB
+      comment: vncomment
+      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
+      nics:
+        - name: nicname
+          interface: virtio
+          profile_name: network
+
 '''
 
 RETURN = '''
@@ -138,12 +259,18 @@
     get_link_name,
     ovirt_full_argument_spec,
     wait,
+    convert_to_bytes,
+    search_by_name,
 )
 
 
 class VmPoolsModule(BaseModule):
+    def __init__(self, *args, **kwargs):
+        super(VmPoolsModule, self).__init__(*args, **kwargs)
+        self._initialization = None
 
     def build_entity(self):
+        vm = self.param('vm')
         return otypes.VmPool(
             id=self._module.params['id'],
             name=self._module.params['name'],
@@ -161,8 +288,148 @@ def build_entity(self):
             type=otypes.VmPoolType(
                 self._module.params['type']
             ) if self._module.params['type'] else None,
+            vm=self.build_vm(vm)
+        )
+
+    def build_vm(self, vm):
+        return otypes.Vm(
+            comment=vm.get('comment'),
+            memory=convert_to_bytes(
+                vm.get('memory')
+            ) if vm.get('memory') else None,
+            memory_policy=otypes.MemoryPolicy(
+                guaranteed=convert_to_bytes(vm.get('memory_guaranteed')),
+                max=convert_to_bytes(vm.get('memory_max')),
+            ) if any((
+                vm.get('memory_guaranteed'),
+                vm.get('memory_max')
+            )) else None,
+            initialization=self.get_initialization(vm),
+            display=otypes.Display(
+                smartcard_enabled=vm.get('smartcard_enabled')
+            ) if vm.get('smartcard_enabled') is not None else None,
+            sso=(
+                otypes.Sso(
+                    methods=[otypes.Method(id=otypes.SsoMethod.GUEST_AGENT)] if vm.get('sso') else []
+                )
+            ) if vm.get('sso') is not None else None,
+            time_zone=otypes.TimeZone(
+                name=vm.get('timezone'),
+            ) if vm.get('timezone') else None,
         )
 
+    def get_initialization(self, vm):
+        if self._initialization is not None:
+            return self._initialization
+
+        sysprep = vm.get('sysprep')
+        cloud_init = vm.get('cloud_init')
+        cloud_init_nics = vm.get('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 get_vms(self, entity):
+        vms = self._connection.system_service().vms_service().list()
+        resp = []
+        for vm in vms:
+            if vm.vm_pool is not None and vm.vm_pool.id == entity.id:
+                resp.append(vm)
+        return resp
+
+    def post_create(self, entity):
+        vm_param = self.param('vm')
+        if vm_param is not None and vm_param.get('nics') is not None:
+            vms = self.get_vms(entity)
+            for vm in vms:
+                self.__attach_nics(vm, vm_param)
+
+    def __attach_nics(self, entity, vm_param):
+        # Attach NICs to VM, if specified:
+        vms_service = self._connection.system_service().vms_service()
+        nics_service = vms_service.service(entity.id).nics_service()
+        for nic in vm_param.get('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_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 update_check(self, entity):
         return (
             equal(self._module.params.get('name'), entity.name) and
@@ -186,6 +453,7 @@ def main():
         template=dict(default=None),
         cluster=dict(default=None),
         description=dict(default=None),
+        vm=dict(default=None, type='dict'),
         comment=dict(default=None),
         vm_per_user=dict(default=None, type='int'),
         prestarted=dict(default=None, type='int'),


Code review comments:
diff_hunk: @@ -81,31 +81,152 @@
         description:
             - "Number of VMs in the pool."
             - "Default value is set by engine."
+    vm:
+        description:
+            - "For creating vm poool without editing template."
+            - "Note: You can use C(vm) only for creating vm pool."
+        type: dict
+        suboption:
+            comment:
+                description:
+                    - Comment of the Virtual Machine.
+            timezone:
+                description:
+                    - Sets time zone offset of the guest hardware clock.
+                    - For example C(Etc/GMT)
+            memory:
+                description:
+                    - Amount of memory of the Virtual Machine. Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - Default value is set by engine.
+            memory_guaranteed:
+                description:
+                    - Amount of minimal guaranteed memory of the Virtual Machine.
+                      Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - C(memory_guaranteed) parameter can't be lower than C(memory) parameter.
+                    - Default value is set by engine.
+            memory_max:
+                description:
+                    - Upper bound of virtual machine memory up to which memory hot-plug can be performed.
+                      Prefix uses IEC 60027-2 standard (for example 1GiB, 1024MiB).
+                    - Default value is set by engine.
+            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
+            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
 extends_documentation_fragment: ovirt
 '''
 
 EXAMPLES = '''
 # Examples don't contain auth parameter for simplicity,
 # look at ovirt_auth module to see how to reuse authentication:
 
-# Create VM pool from template
-- ovirt_vmpool:
+- name: Create VM pool from template
+  ovirt_vmpool:
     cluster: mycluster
     name: myvmpool
     template: rhel7
     vm_count: 2
     prestarted: 2
     vm_per_user: 1
 
-# Remove vmpool, note that all VMs in pool will be stopped and removed:
-- ovirt_vmpool:
+- name: Remove vmpool, note that all VMs in pool will be stopped and removed
+  ovirt_vmpool:
     state: absent
     name: myvmpool
 
-# Change Pool Name
-- ovirt_vmpool:
+- name: Change Pool Name
+  ovirt_vmpool:
     id: 00000000-0000-0000-0000-000000000000
     name: "new_pool_name"
+
+- name: Create pool with vm
path: lib/ansible/modules/cloud/ovirt/ovirt_vmpool.py
text: @user1:
Create vm pool and override the pool values.