From 6fe0215c8f78d6cd22aaac8175c20d3e92bd850d Mon Sep 17 00:00:00 2001 From: maorlipchuk Date: Tue, 16 Jan 2018 14:14:29 +0200 Subject: [PATCH] Add additional mapping attributes for VM/Template registration (#32835) * Add VnicProfileMapping to register VM Add vnic profile mappings to be supported in vm registration * Add VnicProfileMapping to register template Add vnic profile mappings to be supported in template registration * Add reassign bad macs to register VM Add reassign bad macs to be supported in vm registration. * Add additional mappings params for VM registration As part of the effort to support DR with oVirt the "Register" operation is being added with a new mapping parameter that describes the configuration of the registration. The idea of supporting DR site to site in oVirt is to have 2 active setups using storage replication between the primary setup and the secondary setup. Both setups will have active DCs, clusters, and hosts, although those will not be identical. The user can define a mapping which will be used to recover its setup. Each mapping can be used to map any VM's attribute stored in the OVF with its correlated entity. For example, there could be a primary setup with a VM configured on cluster A. We also keep an active secondary setup which only have cluster B. Cluster B is compatible for that VM and in case of a DR scenario theoretically the storage domain can be imported to the secondary setup and the use can register the VM to cluster B. In that case, we can automate the recovery process by defining a cluster mapping, so once the entity will be registered its OVF will indicate it belongs to cluster A but the mapping which will be sent will indicate that cluster B should be valid for every thing that is configured on cluster A. The engine should do the switch, and register the VM to cluster B in the secondary site. Cluster mapping is just one example. The following list describes the different mappings which were introduced: LUN mapping Role mapping Permissions mapping Affinity group mapping Affinity label mapping Each mapping will be used for its specific OVF's data once the register operation will take place in the engine. * Add additional mappings params for Template registration As part of the effort to support DR with oVirt the "Register" operation is being added with a new mapping parameter that describes the configuration of the registration. The idea of supporting DR site to site in oVirt is to have 2 active setups using storage replication between the primary setup and the secondary setup. Both setups will have active DCs, clusters, and hosts, although those will not be identical. The user can define a mapping which will be used to recover its setup. Each mapping can be used to map any Template's attribute stored in the OVF with its correlated entity. For example, there could be a primary setup with a Template configured on cluster A. We also keep an active secondary setup which only have cluster B. Cluster B is compatible for that Template and in case of a DR scenario theoretically the storage domain can be imported to the secondary setup and the use can register the Template to cluster B. In that case, we can automate the recovery process by defining a cluster mapping, so once the entity will be registered its OVF will indicate it belongs to cluster A but the mapping which will be sent will indicate that cluster B should be valid for every thing that is configured on cluster A. The engine should do the switch, and register the Template to cluster B in the secondary site. Cluster mapping is just one example. The following list describes the different mappings which were introduced: Role mapping Permissions mapping Each mapping will be used for its specific OVF's data once the register operation will take place in the engine. * Add support for update OVF store Add support for task of update OVF store in a storage domain. --- .../cloud/ovirt/ovirt_storage_domains.py | 17 +- .../modules/cloud/ovirt/ovirt_templates.py | 145 ++++++++- lib/ansible/modules/cloud/ovirt/ovirt_vms.py | 278 +++++++++++++++++- 3 files changed, 434 insertions(+), 6 deletions(-) diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py b/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py index 6a15d071205..ef280505568 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py @@ -42,9 +42,10 @@ options: - "Name of the storage domain to manage. (Not required when state is I(imported))" state: description: - - "Should the storage domain be present/absent/maintenance/unattached/imported" + - "Should the storage domain be present/absent/maintenance/unattached/imported/update_ovf_store" - "I(imported) is supported since version 2.4." - choices: ['present', 'absent', 'maintenance', 'unattached'] + - "I(update_ovf_store) is supported since version 2.5, currently if C(wait) is (true), we don't wait for update." + choices: ['present', 'absent', 'maintenance', 'unattached', 'update_ovf_store'] default: present description: description: @@ -203,6 +204,11 @@ EXAMPLES = ''' address: 10.34.63.199 path: /path/export +# Update OVF_STORE: +- ovirt_storage_domains: + state: update_ovf_store + name: domain + # Create ISO NFS storage domain - ovirt_storage_domains: name: myiso @@ -523,7 +529,7 @@ def control_state(sd_module): def main(): argument_spec = ovirt_full_argument_spec( state=dict( - choices=['present', 'absent', 'maintenance', 'unattached', 'imported'], + choices=['present', 'absent', 'maintenance', 'unattached', 'imported', 'update_ovf_store'], default='present', ), id=dict(default=None), @@ -602,7 +608,10 @@ def main(): storage_domain=storage_domains_service.service(ret['id']).get() ) ret['changed'] = storage_domains_module.changed - + elif state == 'update_ovf_store': + ret = storage_domains_module.action( + action='update_ovf_store' + ) module.exit_json(**ret) except Exception as e: module.fail_json(msg=str(e), exception=traceback.format_exc()) diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_templates.py b/lib/ansible/modules/cloud/ovirt/ovirt_templates.py index 2acb7660801..f3c5657ace1 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_templates.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_templates.py @@ -64,6 +64,38 @@ options: description: - "Boolean indication whether to allow partial registration of a template when C(state) is registered." version_added: "2.4" + vnic_profile_mappings: + description: + - "Mapper which maps an external virtual NIC profile to one that exists in the engine when C(state) is registered. + vnic_profile is described by the following dictionary:" + - "C(source_network_name): The network name of the source network." + - "C(source_profile_name): The prfile name related to the source network." + - "C(target_profile_id): The id of the target profile id to be mapped to in the engine." + version_added: "2.5" + cluster_mappings: + description: + - "Mapper which maps cluster name between Template's OVF and the destination cluster this Template should be registered to, + relevant when C(state) is registered. + Cluster mapping is described by the following dictionary:" + - "C(source_name): The name of the source cluster." + - "C(dest_name): The name of the destination cluster." + version_added: "2.5" + role_mappings: + description: + - "Mapper which maps role name between Template's OVF and the destination role this Template should be registered to, + relevant when C(state) is registered. + Role mapping is described by the following dictionary:" + - "C(source_name): The name of the source role." + - "C(dest_name): The name of the destination role." + version_added: "2.5" + domain_mappings: + description: + - "Mapper which maps aaa domain name between Template's OVF and the destination aaa domain this Template should be registered to, + relevant when C(state) is registered. + The aaa domain mapping is described by the following dictionary:" + - "C(source_name): The name of the source aaa domain." + - "C(dest_name): The name of the destination aaa domain." + version_added: "2.5" exclusive: description: - "When C(state) is I(exported) this parameter indicates if the existing templates with the @@ -154,6 +186,36 @@ EXAMPLES = ''' cluster: mycluster id: 1111-1111-1111-1111 +# Register template with vnic profile mappings +- ovirt_templates: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + vnic_profile_mappings: + - source_network_name: mynetwork + source_profile_name: mynetwork + target_profile_id: 3333-3333-3333-3333 + - source_network_name: mynetwork2 + source_profile_name: mynetwork2 + target_profile_id: 4444-4444-4444-4444 + +# Register template with mapping +- ovirt_templates: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + role_mappings: + - source_name: Role_A + dest_name: Role_B + domain_mappings: + - source_name: Domain_A + dest_name: Domain_B + cluster_mappings: + - source_name: cluster_A + dest_name: cluster_B + # Import image from Glance s a template - ovirt_templates: state: imported @@ -246,6 +308,74 @@ class TemplatesModule(BaseModule): self._service = self._connection.system_service().templates_service() +def _get_role_mappings(module): + roleMappings = list() + + for roleMapping in module.params['role_mappings']: + roleMappings.append( + otypes.RegistrationRoleMapping( + from_=otypes.Role( + name=roleMapping['source_name'], + ) if roleMapping['source_name'] else None, + to=otypes.Role( + name=roleMapping['dest_name'], + ) if roleMapping['dest_name'] else None, + ) + ) + return roleMappings + + +def _get_domain_mappings(module): + domainMappings = list() + + for domainMapping in module.params['domain_mappings']: + domainMappings.append( + otypes.RegistrationDomainMapping( + from_=otypes.Domain( + name=domainMapping['source_name'], + ) if domainMapping['source_name'] else None, + to=otypes.Domain( + name=domainMapping['dest_name'], + ) if domainMapping['dest_name'] else None, + ) + ) + return domainMappings + + +def _get_cluster_mappings(module): + clusterMappings = list() + + for clusterMapping in module.params['cluster_mappings']: + clusterMappings.append( + otypes.RegistrationClusterMapping( + from_=otypes.Cluster( + name=clusterMapping['source_name'], + ), + to=otypes.Cluster( + name=clusterMapping['dest_name'], + ), + ) + ) + return clusterMappings + + +def _get_vnic_profile_mappings(module): + vnicProfileMappings = list() + + for vnicProfileMapping in module.params['vnic_profile_mappings']: + vnicProfileMappings.append( + otypes.VnicProfileMapping( + source_network_name=vnicProfileMapping['source_network_name'], + source_network_profile_name=vnicProfileMapping['source_profile_name'], + target_vnic_profile=otypes.VnicProfile( + id=vnicProfileMapping['target_profile_id'], + ) if vnicProfileMapping['target_profile_id'] else None, + ) + ) + + return vnicProfileMappings + + def main(): argument_spec = ovirt_full_argument_spec( state=dict( @@ -268,6 +398,10 @@ def main(): image_disk=dict(default=None, aliases=['glance_image_disk_name']), template_image_disk_name=dict(default=None), seal=dict(type='bool'), + vnic_profile_mappings=dict(default=[], type='list'), + cluster_mappings=dict(default=[], type='list'), + role_mappings=dict(default=[], type='list'), + domain_mappings=dict(default=[], type='list'), ) module = AnsibleModule( argument_spec=argument_spec, @@ -388,7 +522,16 @@ def main(): allow_partial_import=module.params['allow_partial_import'], cluster=otypes.Cluster( name=module.params['cluster'] - ) if module.params['cluster'] else None + ) if module.params['cluster'] else None, + vnic_profile_mappings=_get_vnic_profile_mappings(module) + if module.params['vnic_profile_mappings'] else None, + registration_configuration=otypes.RegistrationConfiguration( + cluster_mappings=_get_cluster_mappings(module), + role_mappings=_get_role_mappings(module), + domain_mappings=_get_domain_mappings(module), + ) if (module.params['cluster_mappings'] + or module.params['role_mappings'] + or module.params['domain_mappings']) else None ) if module.params['wait']: diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py index 9550601866b..f85be4ce00c 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py @@ -47,6 +47,65 @@ options: description: - Boolean indication whether to allow partial registration of Virtual Machine when C(state) is registered. version_added: "2.4" + vnic_profile_mappings: + description: + - "Mapper which maps an external virtual NIC profile to one that exists in the engine when C(state) is registered. + vnic_profile is described by the following dictionary:" + - "C(source_network_name): The network name of the source network." + - "C(source_profile_name): The prfile name related to the source network." + - "C(target_profile_id): The id of the target profile id to be mapped to in the engine." + version_added: "2.5" + cluster_mappings: + description: + - "Mapper which maps cluster name between VM's OVF and the destination cluster this VM should be registered to, + relevant when C(state) is registered. + Cluster mapping is described by the following dictionary:" + - "C(source_name): The name of the source cluster." + - "C(dest_name): The name of the destination cluster." + version_added: "2.5" + role_mappings: + description: + - "Mapper which maps role name between VM's OVF and the destination role this VM should be registered to, + relevant when C(state) is registered. + Role mapping is described by the following dictionary:" + - "C(source_name): The name of the source role." + - "C(dest_name): The name of the destination role." + version_added: "2.5" + domain_mappings: + description: + - "Mapper which maps aaa domain name between VM's OVF and the destination aaa domain this VM should be registered to, + relevant when C(state) is registered. + The aaa domain mapping is described by the following dictionary:" + - "C(source_name): The name of the source aaa domain." + - "C(dest_name): The name of the destination aaa domain." + version_added: "2.5" + affinity_group_mappings: + description: + - "Mapper which maps affinty name between VM's OVF and the destination affinity this VM should be registered to, + relevant when C(state) is registered." + version_added: "2.5" + affinity_label_mappings: + description: + - "Mappper which maps affinity label name between VM's OVF and the destination label this VM should be registered to, + relevant when C(state) is registered." + version_added: "2.5" + lun_mappings: + description: + - "Mapper which maps lun between VM's OVF and the destination lun this VM should contain, relevant when C(state) is registered. + lun_mappings is described by the following dictionary: + - C(logical_unit_id): The logical unit number to identify a logical unit, + - C(logical_unit_port): The port being used to connect with the LUN disk. + - C(logical_unit_portal): The portal being used to connect with the LUN disk. + - C(logical_unit_address): The address of the block storage host. + - C(logical_unit_target): The iSCSI specification located on an iSCSI server + - C(logical_unit_username): Username to be used to connect to the block storage host. + - C(logical_unit_password): Password to be used to connect to the block storage host. + - C(storage_type): The storage type which the LUN reside on (iscsi or fcp)" + version_added: "2.5" + reassign_bad_macs: + description: + - "Boolean indication whether to reassign bad macs when C(state) is registered." + version_added: "2.5" template: description: - Name of the template, which should be used to create Virtual Machine. @@ -420,6 +479,56 @@ EXAMPLES = ''' cluster: mycluster id: 1111-1111-1111-1111 +- name: Register VM with vnic profile mappings and reassign bad macs + ovirt_vms: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + vnic_profile_mappings: + - source_network_name: mynetwork + source_profile_name: mynetwork + target_profile_id: 3333-3333-3333-3333 + - source_network_name: mynetwork2 + source_profile_name: mynetwork2 + target_profile_id: 4444-4444-4444-4444 + reassign_bad_macs: "True" + +- name: Register VM with mappings + ovirt_vms: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + role_mappings: + - source_name: Role_A + dest_name: Role_B + domain_mappings: + - source_name: Domain_A + dest_name: Domain_B + lun_mappings: + - source_storage_type: iscsi + source_logical_unit_id: 1IET_000d0001 + source_logical_unit_port: 3260 + source_logical_unit_portal: 1 + source_logical_unit_address: 10.34.63.203 + source_logical_unit_target: iqn.2016-08-09.brq.str-01:omachace + dest_storage_type: iscsi + dest_logical_unit_id: 1IET_000d0002 + dest_logical_unit_port: 3260 + dest_logical_unit_portal: 1 + dest_logical_unit_address: 10.34.63.204 + dest_logical_unit_target: iqn.2016-08-09.brq.str-02:omachace + affinity_group_mappings: + - source_name: Affinity_A + dest_name: Affinity_B + affinity_label_mappings: + - source_name: Label_A + dest_name: Label_B + cluster_mappings: + - source_name: cluster_A + dest_name: cluster_B + - name: Creates a stateless VM which will always use latest template version ovirt_vms: name: myvm @@ -1019,6 +1128,148 @@ class VmsModule(BaseModule): self.changed = True +def _get_role_mappings(module): + roleMappings = list() + + for roleMapping in module.params['role_mappings']: + roleMappings.append( + otypes.RegistrationRoleMapping( + from_=otypes.Role( + name=roleMapping['source_name'], + ) if roleMapping['source_name'] else None, + to=otypes.Role( + name=roleMapping['dest_name'], + ) if roleMapping['dest_name'] else None, + ) + ) + return roleMappings + + +def _get_affinity_group_mappings(module): + affinityGroupMappings = list() + + for affinityGroupMapping in module.params['affinity_group_mappings']: + affinityGroupMappings.append( + otypes.RegistrationAffinityGroupMapping( + from_=otypes.AffinityGroup( + name=affinityGroupMapping['source_name'], + ) if affinityGroupMapping['source_name'] else None, + to=otypes.AffinityGroup( + name=affinityGroupMapping['dest_name'], + ) if affinityGroupMapping['dest_name'] else None, + ) + ) + return affinityGroupMappings + + +def _get_affinity_label_mappings(module): + affinityLabelMappings = list() + + for affinityLabelMapping in module.params['affinity_label_mappings']: + affinityLabelMappings.append( + otypes.RegistrationAffinityLabelMapping( + from_=otypes.AffinityLabel( + name=affinityLabelMapping['source_name'], + ) if affinityLabelMapping['source_name'] else None, + to=otypes.AffinityLabel( + name=affinityLabelMapping['dest_name'], + ) if affinityLabelMapping['dest_name'] else None, + ) + ) + return affinityLabelMappings + + +def _get_domain_mappings(module): + domainMappings = list() + + for domainMapping in module.params['domain_mappings']: + domainMappings.append( + otypes.RegistrationDomainMapping( + from_=otypes.Domain( + name=domainMapping['source_name'], + ) if domainMapping['source_name'] else None, + to=otypes.Domain( + name=domainMapping['dest_name'], + ) if domainMapping['dest_name'] else None, + ) + ) + return domainMappings + + +def _get_lun_mappings(module): + lunMappings = list() + for lunMapping in module.params['lun_mappings']: + lunMappings.append( + otypes.RegistrationLunMapping( + from_=otypes.Disk( + lun_storage=otypes.HostStorage( + type=otypes.StorageType(lunMapping['source_storage_type']) + if (lunMapping['source_storage_type'] in + ['iscsi', 'fcp']) else None, + logical_units=[ + otypes.LogicalUnit( + id=lunMapping['source_logical_unit_id'], + ) + ], + ), + ) if lunMapping['source_logical_unit_id'] else None, + to=otypes.Disk( + lun_storage=otypes.HostStorage( + type=otypes.StorageType(lunMapping['dest_storage_type']) + if (lunMapping['dest_storage_type'] in + ['iscsi', 'fcp']) else None, + logical_units=[ + otypes.LogicalUnit( + id=lunMapping['dest_logical_unit_id'], + port=lunMapping['dest_logical_unit_port'], + portal=lunMapping['dest_logical_unit_portal'], + address=lunMapping['dest_logical_unit_address'], + target=lunMapping['dest_logical_unit_target'], + password=lunMapping['dest_logical_unit_password'], + username=lunMapping['dest_logical_unit_username'], + ) + ], + ), + ) if lunMapping['dest_logical_unit_id'] else None, + ), + ), + return lunMappings + + +def _get_cluster_mappings(module): + clusterMappings = list() + + for clusterMapping in module.params['cluster_mappings']: + clusterMappings.append( + otypes.RegistrationClusterMapping( + from_=otypes.Cluster( + name=clusterMapping['source_name'], + ), + to=otypes.Cluster( + name=clusterMapping['dest_name'], + ) if clusterMapping['dest_name'] else None, + ) + ) + return clusterMappings + + +def _get_vnic_profile_mappings(module): + vnicProfileMappings = list() + + for vnicProfileMapping in module.params['vnic_profile_mappings']: + vnicProfileMappings.append( + otypes.VnicProfileMapping( + source_network_name=vnicProfileMapping['source_network_name'], + source_network_profile_name=vnicProfileMapping['source_profile_name'], + target_vnic_profile=otypes.VnicProfile( + id=vnicProfileMapping['target_profile_id'], + ) if vnicProfileMapping['target_profile_id'] else None, + ) + ) + + return vnicProfileMappings + + def import_vm(module, connection): vms_service = connection.system_service().vms_service() if search_by_name(vms_service, module.params['name']) is not None: @@ -1190,6 +1441,14 @@ def main(): ]), cd_iso=dict(type='str'), boot_devices=dict(type='list'), + vnic_profile_mappings=dict(default=[], type='list'), + cluster_mappings=dict(default=[], type='list'), + role_mappings=dict(default=[], type='list'), + affinity_group_mappings=dict(default=[], type='list'), + affinity_label_mappings=dict(default=[], type='list'), + lun_mappings=dict(default=[], type='list'), + domain_mappings=dict(default=[], type='list'), + reassign_bad_macs=dict(default=None, type='bool'), high_availability=dict(type='bool'), lease=dict(type='str'), stateless=dict(type='bool'), @@ -1375,7 +1634,24 @@ def main(): allow_partial_import=module.params['allow_partial_import'], cluster=otypes.Cluster( name=module.params['cluster'] - ) if module.params['cluster'] else None + ) if module.params['cluster'] else None, + vnic_profile_mappings=_get_vnic_profile_mappings(module) + if module.params['vnic_profile_mappings'] else None, + reassign_bad_macs=module.params['reassign_bad_macs'] + if module.params['reassign_bad_macs'] is not None else None, + registration_configuration=otypes.RegistrationConfiguration( + cluster_mappings=_get_cluster_mappings(module), + role_mappings=_get_role_mappings(module), + domain_mappings=_get_domain_mappings(module), + lun_mappings=_get_lun_mappings(module), + affinity_group_mappings=_get_affinity_group_mappings(module), + affinity_label_mappings=_get_affinity_label_mappings(module), + ) if (module.params['cluster_mappings'] + or module.params['role_mappings'] + or module.params['domain_mappings'] + or module.params['lun_mappings'] + or module.params['affinity_group_mappings'] + or module.params['affinity_label_mappings']) else None ) if module.params['wait']: