1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2017, Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7ANSIBLE_METADATA = {'metadata_version': '1.1', 8 'status': ['preview'], 9 'supported_by': 'community'} 10 11DOCUMENTATION = ''' 12--- 13module: ovirt_nic 14short_description: Module to manage network interfaces of Virtual Machines in oVirt/RHV 15version_added: "2.3" 16author: 17- Ondra Machacek (@machacekondra) 18description: 19 - Module to manage network interfaces of Virtual Machines in oVirt/RHV. 20options: 21 id: 22 description: 23 - "ID of the nic to manage." 24 version_added: "2.8" 25 name: 26 description: 27 - Name of the network interface to manage. 28 required: true 29 vm: 30 description: 31 - Name of the Virtual Machine to manage. 32 - You must provide either C(vm) parameter or C(template) parameter. 33 template: 34 description: 35 - Name of the template to manage. 36 - You must provide either C(vm) parameter or C(template) parameter. 37 version_added: "2.4" 38 state: 39 description: 40 - Should the Virtual Machine NIC be present/absent/plugged/unplugged. 41 choices: [ absent, plugged, present, unplugged ] 42 default: present 43 network: 44 description: 45 - Logical network to which the VM network interface should use, 46 by default Empty network is used if network is not specified. 47 profile: 48 description: 49 - Virtual network interface profile to be attached to VM network interface. 50 - When not specified and network has only single profile it will be auto-selected, otherwise you must specify profile. 51 interface: 52 description: 53 - "Type of the network interface. For example e1000, pci_passthrough, rtl8139, rtl8139_virtio, spapr_vlan or virtio." 54 - "It's required parameter when creating the new NIC." 55 mac_address: 56 description: 57 - Custom MAC address of the network interface, by default it's obtained from MAC pool. 58 linked: 59 description: 60 - Defines if the NIC is linked to the virtual machine. 61 type: bool 62 version_added: "2.9" 63extends_documentation_fragment: ovirt 64''' 65 66EXAMPLES = ''' 67# Examples don't contain auth parameter for simplicity, 68# look at ovirt_auth module to see how to reuse authentication: 69 70- name: Add NIC to VM 71 ovirt_nic: 72 state: present 73 vm: myvm 74 name: mynic 75 interface: e1000 76 mac_address: 00:1a:4a:16:01:56 77 profile: ovirtmgmt 78 network: ovirtmgmt 79 80- name: Plug NIC to VM 81 ovirt_nic: 82 state: plugged 83 vm: myvm 84 name: mynic 85 86- name: Unplug NIC from VM 87 ovirt_nic: 88 state: unplugged 89 linked: false 90 vm: myvm 91 name: mynic 92 93- name: Add NIC to template 94 ovirt_nic: 95 auth: "{{ ovirt_auth }}" 96 state: present 97 template: my_template 98 name: nic1 99 interface: virtio 100 profile: ovirtmgmt 101 network: ovirtmgmt 102 103- name: Remove NIC from VM 104 ovirt_nic: 105 state: absent 106 vm: myvm 107 name: mynic 108 109# Change NIC Name 110- ovirt_nic: 111 id: 00000000-0000-0000-0000-000000000000 112 name: "new_nic_name" 113 vm: myvm 114''' 115 116RETURN = ''' 117id: 118 description: ID of the network interface which is managed 119 returned: On success if network interface is found. 120 type: str 121 sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c 122nic: 123 description: "Dictionary of all the network interface attributes. Network interface attributes can be found on your oVirt/RHV instance 124 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/nic." 125 returned: On success if network interface is found. 126 type: dict 127''' 128 129try: 130 import ovirtsdk4.types as otypes 131except ImportError: 132 pass 133 134import traceback 135 136from ansible.module_utils.basic import AnsibleModule 137from ansible.module_utils.ovirt import ( 138 BaseModule, 139 check_sdk, 140 create_connection, 141 equal, 142 get_link_name, 143 ovirt_full_argument_spec, 144 search_by_name, 145) 146 147 148class EntityNicsModule(BaseModule): 149 150 def __init__(self, *args, **kwargs): 151 super(EntityNicsModule, self).__init__(*args, **kwargs) 152 self.vnic_id = None 153 154 @property 155 def vnic_id(self): 156 return self._vnic_id 157 158 @vnic_id.setter 159 def vnic_id(self, vnic_id): 160 self._vnic_id = vnic_id 161 162 def build_entity(self): 163 return otypes.Nic( 164 id=self._module.params.get('id'), 165 name=self._module.params.get('name'), 166 interface=otypes.NicInterface( 167 self._module.params.get('interface') 168 ) if self._module.params.get('interface') else None, 169 vnic_profile=otypes.VnicProfile( 170 id=self.vnic_id, 171 ) if self.vnic_id else None, 172 mac=otypes.Mac( 173 address=self._module.params.get('mac_address') 174 ) if self._module.params.get('mac_address') else None, 175 linked=self.param('linked') if self.param('linked') is not None else None, 176 ) 177 178 def update_check(self, entity): 179 if self._module.params.get('vm'): 180 return ( 181 equal(self._module.params.get('interface'), str(entity.interface)) and 182 equal(self._module.params.get('linked'), entity.linked) and 183 equal(self._module.params.get('name'), str(entity.name)) and 184 equal(self._module.params.get('profile'), get_link_name(self._connection, entity.vnic_profile)) and 185 equal(self._module.params.get('mac_address'), entity.mac.address) 186 ) 187 elif self._module.params.get('template'): 188 return ( 189 equal(self._module.params.get('interface'), str(entity.interface)) and 190 equal(self._module.params.get('linked'), entity.linked) and 191 equal(self._module.params.get('name'), str(entity.name)) and 192 equal(self._module.params.get('profile'), get_link_name(self._connection, entity.vnic_profile)) 193 ) 194 195 196def get_vnics(networks_service, network, connection): 197 resp = [] 198 vnic_services = connection.system_service().vnic_profiles_service() 199 for vnic in vnic_services.list(): 200 if vnic.network.id == network.id: 201 resp.append(vnic) 202 return resp 203 204 205def main(): 206 argument_spec = ovirt_full_argument_spec( 207 state=dict(type='str', default='present', choices=['absent', 'plugged', 'present', 'unplugged']), 208 vm=dict(type='str'), 209 id=dict(default=None), 210 template=dict(type='str'), 211 name=dict(type='str', required=True), 212 interface=dict(type='str'), 213 profile=dict(type='str'), 214 network=dict(type='str'), 215 mac_address=dict(type='str'), 216 linked=dict(type='bool'), 217 ) 218 module = AnsibleModule( 219 argument_spec=argument_spec, 220 supports_check_mode=True, 221 required_one_of=[['vm', 'template']], 222 ) 223 224 check_sdk(module) 225 226 try: 227 # Locate the service that manages the virtual machines and use it to 228 # search for the NIC: 229 auth = module.params.pop('auth') 230 connection = create_connection(auth) 231 entity_name = None 232 233 if module.params.get('vm'): 234 # Locate the VM, where we will manage NICs: 235 entity_name = module.params.get('vm') 236 collection_service = connection.system_service().vms_service() 237 elif module.params.get('template'): 238 entity_name = module.params.get('template') 239 collection_service = connection.system_service().templates_service() 240 241 # TODO: We have to modify the search_by_name function to accept raise_error=True/False, 242 entity = search_by_name(collection_service, entity_name) 243 if entity is None: 244 raise Exception("Vm/Template '%s' was not found." % entity_name) 245 246 service = collection_service.service(entity.id) 247 cluster_id = entity.cluster 248 249 nics_service = service.nics_service() 250 entitynics_module = EntityNicsModule( 251 connection=connection, 252 module=module, 253 service=nics_service, 254 ) 255 256 # Find vNIC id of the network interface (if any): 257 if module.params['network']: 258 profile = module.params.get('profile') 259 cluster_name = get_link_name(connection, cluster_id) 260 dcs_service = connection.system_service().data_centers_service() 261 dc = dcs_service.list(search='Clusters.name=%s' % cluster_name)[0] 262 networks_service = dcs_service.service(dc.id).networks_service() 263 network = next( 264 (n for n in networks_service.list() 265 if n.name == module.params['network']), 266 None 267 ) 268 if network is None: 269 raise Exception( 270 "Network '%s' was not found in datacenter '%s'." % ( 271 module.params['network'], 272 dc.name 273 ) 274 ) 275 if profile: 276 for vnic in connection.system_service().vnic_profiles_service().list(): 277 if vnic.name == profile and vnic.network.id == network.id: 278 entitynics_module.vnic_id = vnic.id 279 else: 280 # When not specified which vnic use ovirtmgmt/ovirtmgmt 281 vnics = get_vnics(networks_service, network, connection) 282 if len(vnics) == 1: 283 entitynics_module.vnic_id = vnics[0].id 284 else: 285 raise Exception( 286 "You didn't specify any vnic profile. " 287 "Following vnic profiles are in system: '%s', please specify one of them" % ([vnic.name for vnic in vnics]) 288 ) 289 # Handle appropriate action: 290 state = module.params['state'] 291 if state == 'present': 292 ret = entitynics_module.create() 293 elif state == 'absent': 294 ret = entitynics_module.remove() 295 elif state == 'plugged': 296 entitynics_module.create() 297 ret = entitynics_module.action( 298 action='activate', 299 action_condition=lambda nic: not nic.plugged, 300 wait_condition=lambda nic: nic.plugged, 301 ) 302 elif state == 'unplugged': 303 entitynics_module.create() 304 ret = entitynics_module.action( 305 action='deactivate', 306 action_condition=lambda nic: nic.plugged, 307 wait_condition=lambda nic: not nic.plugged, 308 ) 309 310 module.exit_json(**ret) 311 except Exception as e: 312 module.fail_json(msg=str(e), exception=traceback.format_exc()) 313 finally: 314 connection.close(logout=auth.get('token') is None) 315 316 317if __name__ == "__main__": 318 main() 319