mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #8769/d9b0c42f backport][stable-9] Add one_vnet module (#8838)
Add one_vnet module (#8769)
* Add one_vnet module
* Add one_vnet integration tests
* Update BOTMETA.yml
* Update aliases
---------
Co-authored-by: Александр Бакановский <abakanovskii@astralinux.ru>
(cherry picked from commit d9b0c42f5f
)
Co-authored-by: alexander <79072457+abakanovskii@users.noreply.github.com>
This commit is contained in:
parent
21ed66a097
commit
adda8d3113
4 changed files with 616 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -974,6 +974,8 @@ files:
|
|||
maintainers: $team_opennebula
|
||||
$modules/one_host.py:
|
||||
maintainers: rvalle
|
||||
$modules/one_vnet.py:
|
||||
maintainers: abakanovskii
|
||||
$modules/oneandone_:
|
||||
maintainers: aajdinov edevenport
|
||||
$modules/onepassword_info.py:
|
||||
|
|
434
plugins/modules/one_vnet.py
Normal file
434
plugins/modules/one_vnet.py
Normal file
|
@ -0,0 +1,434 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2024, Alexander Bakanovskii <skottttt228@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_vnet
|
||||
short_description: Manages OpenNebula virtual networks
|
||||
version_added: 9.4.0
|
||||
author: "Alexander Bakanovskii (@abakanovskii)"
|
||||
requirements:
|
||||
- pyone
|
||||
description:
|
||||
- Manages virtual networks in OpenNebula.
|
||||
attributes:
|
||||
check_mode:
|
||||
support: partial
|
||||
details:
|
||||
- Note that check mode always returns C(changed=true) for existing networks, even if the network would not actually change.
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
id:
|
||||
description:
|
||||
- A O(id) of the network you would like to manage.
|
||||
- If not set then a new network will be created with the given O(name).
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- A O(name) of the network you would like to manage. If a network with
|
||||
the given name does not exist it will be created, otherwise it will be
|
||||
managed by this module.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- A string containing the network template contents.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- V(present) - state that is used to manage the network.
|
||||
- V(absent) - delete the network.
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
type: str
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.opennebula
|
||||
- community.general.attributes
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Make sure the network is present by ID
|
||||
community.general.one_vnet:
|
||||
id: 0
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Make sure the network is present by name
|
||||
community.general.one_vnet:
|
||||
name: opennebula-bridge
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Create a new or update an existing network
|
||||
community.general.one_vnet:
|
||||
name: bridge-network
|
||||
template: |
|
||||
VN_MAD = "bridge"
|
||||
BRIDGE = "br0"
|
||||
BRIDGE_TYPE = "linux"
|
||||
AR=[
|
||||
TYPE = "IP4",
|
||||
IP = 192.0.2.50,
|
||||
SIZE = "20"
|
||||
]
|
||||
DNS = 192.0.2.1
|
||||
GATEWAY = 192.0.2.1
|
||||
|
||||
- name: Delete the network by ID
|
||||
community.general.one_vnet:
|
||||
id: 0
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: The network id.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 153
|
||||
name:
|
||||
description: The network name.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: app1
|
||||
template:
|
||||
description: The parsed network template.
|
||||
type: dict
|
||||
returned: when O(state=present)
|
||||
sample:
|
||||
BRIDGE: onebr.1000
|
||||
BRIDGE_TYPE: linux
|
||||
DESCRIPTION: sampletext
|
||||
PHYDEV: eth0
|
||||
SECURITY_GROUPS: 0
|
||||
VLAN_ID: 1000
|
||||
VN_MAD: 802.1Q
|
||||
user_id:
|
||||
description: The network's user name.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 1
|
||||
user_name:
|
||||
description: The network's user id.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: oneadmin
|
||||
group_id:
|
||||
description: The network's group id.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 1
|
||||
group_name:
|
||||
description: The network's group name.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: one-users
|
||||
owner_id:
|
||||
description: The network's owner id.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 143
|
||||
owner_name:
|
||||
description: The network's owner name.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: ansible-test
|
||||
permissions:
|
||||
description: The network's permissions.
|
||||
type: dict
|
||||
returned: when O(state=present)
|
||||
contains:
|
||||
owner_u:
|
||||
description: The network's owner USAGE permissions.
|
||||
type: str
|
||||
sample: 1
|
||||
owner_m:
|
||||
description: The network's owner MANAGE permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
owner_a:
|
||||
description: The network's owner ADMIN permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
group_u:
|
||||
description: The network's group USAGE permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
group_m:
|
||||
description: The network's group MANAGE permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
group_a:
|
||||
description: The network's group ADMIN permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
other_u:
|
||||
description: The network's other users USAGE permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
other_m:
|
||||
description: The network's other users MANAGE permissions.
|
||||
type: str
|
||||
sample: 0
|
||||
other_a:
|
||||
description: The network's other users ADMIN permissions
|
||||
type: str
|
||||
sample: 0
|
||||
sample:
|
||||
owner_u: 1
|
||||
owner_m: 0
|
||||
owner_a: 0
|
||||
group_u: 0
|
||||
group_m: 0
|
||||
group_a: 0
|
||||
other_u: 0
|
||||
other_m: 0
|
||||
other_a: 0
|
||||
clusters:
|
||||
description: The network's clusters.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample: [0, 100]
|
||||
bridge:
|
||||
description: The network's bridge interface.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: br0
|
||||
bridge_type:
|
||||
description: The network's bridge type.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: linux
|
||||
parent_network_id:
|
||||
description: The network's parent network id.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 1
|
||||
vm_mad:
|
||||
description: The network's VM_MAD.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: bridge
|
||||
phydev:
|
||||
description: The network's physical device (NIC).
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: eth0
|
||||
vlan_id:
|
||||
description: The network's VLAN tag.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 1000
|
||||
outer_vlan_id:
|
||||
description: The network's outer VLAN tag.
|
||||
type: int
|
||||
returned: when O(state=present)
|
||||
sample: 1000
|
||||
vrouters:
|
||||
description: The network's list of virtual routers IDs.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample: [0, 1]
|
||||
ar_pool:
|
||||
description: The network's list of ar_pool.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample:
|
||||
- ar_id: 0
|
||||
ip: 192.0.2.1
|
||||
mac: 6c:1e:46:01:cd:d1
|
||||
size: 20
|
||||
type: IP4
|
||||
- ar_id: 1
|
||||
allocated: 0
|
||||
ip: 198.51.100.1
|
||||
mac: 5d:9b:c0:9e:f6:e5
|
||||
size: 20
|
||||
type: IP4
|
||||
'''
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.opennebula import OpenNebulaModule
|
||||
|
||||
|
||||
class NetworksModule(OpenNebulaModule):
|
||||
|
||||
def __init__(self):
|
||||
argument_spec = dict(
|
||||
id=dict(type='int', required=False),
|
||||
name=dict(type='str', required=False),
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
template=dict(type='str', required=False),
|
||||
)
|
||||
|
||||
mutually_exclusive = [
|
||||
['id', 'name']
|
||||
]
|
||||
|
||||
required_one_of = [('id', 'name')]
|
||||
|
||||
required_if = [
|
||||
['state', 'present', ['template']]
|
||||
]
|
||||
|
||||
OpenNebulaModule.__init__(self,
|
||||
argument_spec,
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_one_of=required_one_of,
|
||||
required_if=required_if)
|
||||
|
||||
def run(self, one, module, result):
|
||||
params = module.params
|
||||
id = params.get('id')
|
||||
name = params.get('name')
|
||||
desired_state = params.get('state')
|
||||
template_data = params.get('template')
|
||||
|
||||
self.result = {}
|
||||
|
||||
template = self.get_template_instance(id, name)
|
||||
needs_creation = False
|
||||
if not template and desired_state != 'absent':
|
||||
if id:
|
||||
module.fail_json(msg="There is no template with id=" + str(id))
|
||||
else:
|
||||
needs_creation = True
|
||||
|
||||
if desired_state == 'absent':
|
||||
self.result = self.delete_template(template)
|
||||
else:
|
||||
if needs_creation:
|
||||
self.result = self.create_template(name, template_data)
|
||||
else:
|
||||
self.result = self.update_template(template, template_data)
|
||||
|
||||
self.exit()
|
||||
|
||||
def get_template(self, predicate):
|
||||
# -2 means "Resources belonging to all users"
|
||||
# the other two parameters are used for pagination, -1 for both essentially means "return all"
|
||||
pool = self.one.vnpool.info(-2, -1, -1)
|
||||
|
||||
for template in pool.VMTEMPLATE:
|
||||
if predicate(template):
|
||||
return template
|
||||
|
||||
return None
|
||||
|
||||
def get_template_by_id(self, template_id):
|
||||
return self.get_template(lambda template: (template.ID == template_id))
|
||||
|
||||
def get_template_by_name(self, name):
|
||||
return self.get_template(lambda template: (template.NAME == name))
|
||||
|
||||
def get_template_instance(self, requested_id, requested_name):
|
||||
if requested_id:
|
||||
return self.get_template_by_id(requested_id)
|
||||
else:
|
||||
return self.get_template_by_name(requested_name)
|
||||
|
||||
def get_networks_ar_pool(self, template):
|
||||
ar_pool = []
|
||||
for ar in template.AR_POOL:
|
||||
ar_pool.append({
|
||||
# These params will always be present
|
||||
'ar_id': ar['AR_ID'],
|
||||
'mac': ar['MAC'],
|
||||
'size': ar['SIZE'],
|
||||
'type': ar['TYPE'],
|
||||
# These are optional so firstly check for presence
|
||||
# and if not present set value to Null
|
||||
'allocated': getattr(ar, 'ALLOCATED', 'Null'),
|
||||
'ip': getattr(ar, 'IP', 'Null'),
|
||||
'global_prefix': getattr(ar, 'GLOBAL_PREFIX', 'Null'),
|
||||
'parent_network_ar_id': getattr(ar, 'PARENT_NETWORK_AR_ID', 'Null'),
|
||||
'ula_prefix': getattr(ar, 'ULA_PREFIX', 'Null'),
|
||||
'vn_mad': getattr(ar, 'VN_MAD', 'Null'),
|
||||
})
|
||||
return ar_pool
|
||||
|
||||
def get_template_info(self, template):
|
||||
info = {
|
||||
'id': template.ID,
|
||||
'name': template.NAME,
|
||||
'template': template.TEMPLATE,
|
||||
'user_name': template.UNAME,
|
||||
'user_id': template.UID,
|
||||
'group_name': template.GNAME,
|
||||
'group_id': template.GID,
|
||||
'permissions': {
|
||||
'owner_u': template.PERMISSIONS.OWNER_U,
|
||||
'owner_m': template.PERMISSIONS.OWNER_M,
|
||||
'owner_a': template.PERMISSIONS.OWNER_A,
|
||||
'group_u': template.PERMISSIONS.GROUP_U,
|
||||
'group_m': template.PERMISSIONS.GROUP_M,
|
||||
'group_a': template.PERMISSIONS.GROUP_A,
|
||||
'other_u': template.PERMISSIONS.OTHER_U,
|
||||
'other_m': template.PERMISSIONS.OTHER_M,
|
||||
'other_a': template.PERMISSIONS.OTHER_A
|
||||
},
|
||||
'clusters': template.CLUSTERS.ID,
|
||||
'bridge': template.BRIDGE,
|
||||
'bride_type': template.BRIDGE_TYPE,
|
||||
'parent_network_id': template.PARENT_NETWORK_ID,
|
||||
'vm_mad': template.VM_MAD,
|
||||
'phydev': template.PHYDEV,
|
||||
'vlan_id': template.VLAN_ID,
|
||||
'outer_vlan_id': template.OUTER_VLAN_ID,
|
||||
'used_leases': template.USED_LEASES,
|
||||
'vrouters': template.VROUTERS.ID,
|
||||
'ar_pool': self.get_networks_ar_pool(template)
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
def create_template(self, name, template_data):
|
||||
if not self.module.check_mode:
|
||||
self.one.vn.allocate("NAME = \"" + name + "\"\n" + template_data)
|
||||
|
||||
result = self.get_template_info(self.get_template_by_name(name))
|
||||
result['changed'] = True
|
||||
|
||||
return result
|
||||
|
||||
def update_template(self, template, template_data):
|
||||
if not self.module.check_mode:
|
||||
# 0 = replace the whole template
|
||||
self.one.vn.update(template.ID, template_data, 0)
|
||||
|
||||
result = self.get_template_info(self.get_template_by_id(template.ID))
|
||||
if self.module.check_mode:
|
||||
# Unfortunately it is not easy to detect if the template would have changed, therefore always report a change here.
|
||||
result['changed'] = True
|
||||
else:
|
||||
# if the previous parsed template data is not equal to the updated one, this has changed
|
||||
result['changed'] = template.TEMPLATE != result['template']
|
||||
|
||||
return result
|
||||
|
||||
def delete_template(self, template):
|
||||
if not template:
|
||||
return {'changed': False}
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.one.vn.delete(template.ID)
|
||||
|
||||
return {'changed': True}
|
||||
|
||||
|
||||
def main():
|
||||
NetworksModule().run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
7
tests/integration/targets/one_vnet/aliases
Normal file
7
tests/integration/targets/one_vnet/aliases
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/generic/1
|
||||
cloud/opennebula
|
||||
disabled # FIXME - when this is fixed, also re-enable the generic tests in CI!
|
173
tests/integration/targets/one_vnet/tasks/main.yml
Normal file
173
tests/integration/targets/one_vnet/tasks/main.yml
Normal file
|
@ -0,0 +1,173 @@
|
|||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Create a new template
|
||||
- name: Create a new network
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: bridge-network
|
||||
template: |
|
||||
VN_MAD = "bridge"
|
||||
BRIDGE = "br0"
|
||||
BRIDGE_TYPE = "linux"
|
||||
AR=[
|
||||
TYPE = "IP4",
|
||||
IP = "192.0.2.2",
|
||||
SIZE = "20"
|
||||
]
|
||||
DNS = "192.0.2.1"
|
||||
GATEWAY = "192.0.2.1"
|
||||
register: result
|
||||
|
||||
- name: Assert that network is created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
|
||||
# Updating a network
|
||||
- name: Update an existing network
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: bridge-network
|
||||
template: |
|
||||
VN_MAD = "bridge"
|
||||
BRIDGE = "br0"
|
||||
BRIDGE_TYPE = "linux"
|
||||
AR=[
|
||||
TYPE = "IP4",
|
||||
IP = "192.0.2.2",
|
||||
SIZE = "20"
|
||||
]
|
||||
DNS = "192.0.2.220"
|
||||
GATEWAY = "192.0.2.1"
|
||||
register: result
|
||||
|
||||
- name: Assert that network is changed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
# Testing idempotence using the same template as in previous task
|
||||
- name: Update an existing network with the same changes again
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: bridge-network
|
||||
template: |
|
||||
VN_MAD = "bridge"
|
||||
BRIDGE = "br0"
|
||||
BRIDGE_TYPE = "linux"
|
||||
AR=[
|
||||
TYPE = "IP4",
|
||||
IP = "192.0.2.2",
|
||||
SIZE = "20"
|
||||
]
|
||||
DNS = "192.0.2.220"
|
||||
GATEWAY = "192.0.2.1"
|
||||
register: result
|
||||
|
||||
- name: Assert that network is not changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
|
||||
# Deletion of networks
|
||||
- name: Delete a nonexisting network
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: i-do-not-exists
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that network is not changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete an existing network
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: bridge-network
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that network was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
# Trying to run with wrong arguments
|
||||
- name: Try to create use network with state=present and without the template parameter
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: bridge-network
|
||||
state: present
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Assert that it failed because network is missing
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
|
||||
- name: Try to create network with template but without specifying the name parameter
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
id: 0
|
||||
state: present
|
||||
template: |
|
||||
VN_MAD = "bridge"
|
||||
BRIDGE = "br0"
|
||||
BRIDGE_TYPE = "linux"
|
||||
AR=[
|
||||
TYPE = "IP4",
|
||||
IP = "192.0.2.2",
|
||||
SIZE = "20"
|
||||
]
|
||||
DNS = "192.0.2.220"
|
||||
GATEWAY = "192.0.2.1"
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Assert that it failed because name is required for initial creation
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
|
||||
- name: Try to use both ID and name at the same time
|
||||
one_vnet:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name:
|
||||
id: 0
|
||||
state: present
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Assert that it failed because you can use only one at the time
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
Loading…
Reference in a new issue