summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatryk D. Cichy <patryk.d.cichy@gmail.com>2019-03-28 21:53:32 +0100
committerRené Moser <mail@renemoser.net>2019-03-28 21:53:32 +0100
commit43514e9d93455a94521dd0d1f5c29bf9e07ca5bf (patch)
tree6f5ae7be3ec633a3274b2dd760e8049159083a12
parent601d20117d897a467c08ba0508ee17f0623e4ef7 (diff)
downloadansible-43514e9d93455a94521dd0d1f5c29bf9e07ca5bf.tar.gz
Add a new CloudStack module - cs_traffic_type (#54451)
* Add get_physical_network to AnsibleCloudStack * Add new module cs_traffic_type
-rw-r--r--lib/ansible/module_utils/cloudstack.py18
-rw-r--r--lib/ansible/modules/cloud/cloudstack/cs_traffic_type.py324
-rw-r--r--test/integration/targets/cs_traffic_type/aliases2
-rw-r--r--test/integration/targets/cs_traffic_type/meta/main.yml3
-rw-r--r--test/integration/targets/cs_traffic_type/tasks/main.yml173
-rw-r--r--test/units/modules/cloud/cloudstack/test_cs_traffic_type.py129
6 files changed, 649 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py
index eb1d866e08..61758bb937 100644
--- a/lib/ansible/module_utils/cloudstack.py
+++ b/lib/ansible/module_utils/cloudstack.py
@@ -302,6 +302,24 @@ class AnsibleCloudStack:
self._vpc_networks_ids.append(n['id'])
return network_id in self._vpc_networks_ids
+ def get_physical_network(self, key=None):
+ if self.physical_network:
+ return self._get_by_key(key, self.physical_network)
+ physical_network = self.module.params.get('physical_network')
+ args = {
+ 'zoneid': self.get_zone(key='id')
+ }
+ physical_networks = self.query_api('listPhysicalNetworks', **args)
+ if not physical_networks:
+ self.fail_json(msg="No physical networks available.")
+
+ for net in physical_networks['physicalnetwork']:
+ if physical_network in [net['name'], net['id']]:
+ self.physical_network = net
+ self.result['physical_network'] = net['name']
+ return self._get_by_key(key, self.physical_network)
+ self.fail_json(msg="Physical Network '%s' not found" % physical_network)
+
def get_network(self, key=None):
"""Return a network dictionary or the value of given key of."""
if self.network:
diff --git a/lib/ansible/modules/cloud/cloudstack/cs_traffic_type.py b/lib/ansible/modules/cloud/cloudstack/cs_traffic_type.py
new file mode 100644
index 0000000000..4443fb161a
--- /dev/null
+++ b/lib/ansible/modules/cloud/cloudstack/cs_traffic_type.py
@@ -0,0 +1,324 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2019, Patryk D. Cichy <patryk.d.cichy@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: cs_traffic_type
+short_description: Manages traffic types on CloudStack Physical Networks
+description:
+ - Add, remove, update Traffic Types associated with CloudStack Physical Networks.
+extends_documentation_fragment: cloudstack
+version_added: "2.8"
+author:
+ - Patryk Cichy (@PatTheSilent)
+options:
+ physical_network:
+ description:
+ - the name of the Physical Network
+ required: true
+ type: str
+ zone:
+ description:
+ - Name of the zone with the physical network.
+ - Default zone will be used if this is empty.
+ type: str
+ traffic_type:
+ description:
+ - the trafficType to be added to the physical network.
+ required: true
+ choices: [Management, Guest, Public, Storage]
+ type: str
+ state:
+ description:
+ - State of the traffic type
+ choices: [present, absent]
+ default: present
+ type: str
+ hyperv_networklabel:
+ description:
+ - The network name label of the physical device dedicated to this traffic on a HyperV host.
+ type: str
+ isolation_method:
+ description:
+ - Use if the physical network has multiple isolation types and traffic type is public.
+ choices: [vlan, vxlan]
+ type: str
+ kvm_networklabel:
+ description:
+ - The network name label of the physical device dedicated to this traffic on a KVM host.
+ type: str
+ ovm3_networklabel:
+ description:
+ - The network name of the physical device dedicated to this traffic on an OVM3 host.
+ type: str
+ vlan:
+ description:
+ - The VLAN id to be used for Management traffic by VMware host.
+ type: str
+ vmware_networklabel:
+ description:
+ - The network name label of the physical device dedicated to this traffic on a VMware host.
+ type: str
+ xen_networklabel:
+ description:
+ - The network name label of the physical device dedicated to this traffic on a XenServer host.
+ type: str
+ poll_async:
+ description:
+ - Poll async jobs until job has finished.
+ default: yes
+ type: bool
+'''
+
+EXAMPLES = '''
+- name: add a traffic type
+ cs_traffic_type:
+ physical_network: public-network
+ traffic_type: Guest
+ zone: test-zone
+ delegate_to: localhost
+
+- name: update traffic type
+ cs_traffic_type:
+ physical_network: public-network
+ traffic_type: Guest
+ kvm_networklabel: cloudbr0
+ zone: test-zone
+ delegate_to: localhost
+
+- name: remove traffic type
+ cs_traffic_type:
+ physical_network: public-network
+ traffic_type: Public
+ state: absent
+ zone: test-zone
+ delegate_to: localhost
+'''
+
+RETURN = '''
+---
+id:
+ description: ID of the network provider
+ returned: success
+ type: str
+ sample: 659c1840-9374-440d-a412-55ca360c9d3c
+traffic_type:
+ description: the trafficType that was added to the physical network
+ returned: success
+ type: str
+ sample: Public
+hyperv_networklabel:
+ description: The network name label of the physical device dedicated to this traffic on a HyperV host
+ returned: success
+ type: str
+ sample: HyperV Internal Switch
+kvm_networklabel:
+ description: The network name label of the physical device dedicated to this traffic on a KVM host
+ returned: success
+ type: str
+ sample: cloudbr0
+ovm3_networklabel:
+ description: The network name of the physical device dedicated to this traffic on an OVM3 host
+ returned: success
+ type: str
+ sample: cloudbr0
+physical_network:
+ description: the physical network this belongs to
+ returned: success
+ type: str
+ sample: 28ed70b7-9a1f-41bf-94c3-53a9f22da8b6
+vmware_networklabel:
+ description: The network name label of the physical device dedicated to this traffic on a VMware host
+ returned: success
+ type: str
+ sample: Management Network
+xen_networklabel:
+ description: The network name label of the physical device dedicated to this traffic on a XenServer host
+ returned: success
+ type: str
+ sample: xenbr0
+zone:
+ description: Name of zone the physical network is in.
+ returned: success
+ type: str
+ sample: ch-gva-2
+'''
+
+from ansible.module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together
+from ansible.module_utils.basic import AnsibleModule
+
+
+class AnsibleCloudStackTrafficType(AnsibleCloudStack):
+
+ def __init__(self, module):
+ super(AnsibleCloudStackTrafficType, self).__init__(module)
+ self.returns = {
+ 'traffictype': 'traffic_type',
+ 'hypervnetworklabel': 'hyperv_networklabel',
+ 'kvmnetworklabel': 'kvm_networklabel',
+ 'ovm3networklabel': 'ovm3_networklabel',
+ 'physicalnetworkid': 'physical_network',
+ 'vmwarenetworklabel': 'vmware_networklabel',
+ 'xennetworklabel': 'xen_networklabel'
+ }
+
+ self.traffic_type = None
+
+ def _get_label_args(self):
+ label_args = dict()
+ if self.module.params.get('hyperv_networklabel'):
+ label_args.update(dict(hypervnetworklabel=self.module.params.get('hyperv_networklabel')))
+ if self.module.params.get('kvm_networklabel'):
+ label_args.update(dict(kvmnetworklabel=self.module.params.get('kvm_networklabel')))
+ if self.module.params.get('ovm3_networklabel'):
+ label_args.update(dict(ovm3networklabel=self.module.params.get('ovm3_networklabel')))
+ if self.module.params.get('vmware_networklabel'):
+ label_args.update(dict(vmwarenetworklabel=self.module.params.get('vmware_networklabel')))
+ return label_args
+
+ def _get_additional_args(self):
+ additional_args = dict()
+
+ if self.module.params.get('isolation_method'):
+ additional_args.update(dict(isolationmethod=self.module.params.get('isolation_method')))
+
+ if self.module.params.get('vlan'):
+ additional_args.update(dict(vlan=self.module.params.get('vlan')))
+
+ additional_args.update(self._get_label_args())
+
+ return additional_args
+
+ def get_traffic_types(self):
+ args = {
+ 'physicalnetworkid': self.get_physical_network(key='id')
+ }
+ traffic_types = self.query_api('listTrafficTypes', **args)
+ return traffic_types
+
+ def get_traffic_type(self):
+ if self.traffic_type:
+ return self.traffic_type
+
+ traffic_type = self.module.params.get('traffic_type')
+
+ traffic_types = self.get_traffic_types()
+
+ if traffic_types:
+ for t_type in traffic_types['traffictype']:
+ if traffic_type.lower() in [t_type['traffictype'].lower(), t_type['id']]:
+ self.traffic_type = t_type
+ break
+ return self.traffic_type
+
+ def present_traffic_type(self):
+ traffic_type = self.get_traffic_type()
+ if traffic_type:
+ self.traffic_type = self.update_traffic_type()
+ else:
+ self.result['changed'] = True
+ self.traffic_type = self.add_traffic_type()
+
+ return self.traffic_type
+
+ def add_traffic_type(self):
+ traffic_type = self.module.params.get('traffic_type')
+ args = {
+ 'physicalnetworkid': self.get_physical_network(key='id'),
+ 'traffictype': traffic_type
+ }
+ args.update(self._get_additional_args())
+ if not self.module.check_mode:
+ resource = self.query_api('addTrafficType', **args)
+ poll_async = self.module.params.get('poll_async')
+ if poll_async:
+ self.traffic_type = self.poll_job(resource, 'traffictype')
+ return self.traffic_type
+
+ def absent_traffic_type(self):
+ traffic_type = self.get_traffic_type()
+ if traffic_type:
+
+ args = {
+ 'id': traffic_type['id']
+ }
+ self.result['changed'] = True
+ if not self.module.check_mode:
+ resource = self.query_api('deleteTrafficType', **args)
+ poll_async = self.module.params.get('poll_async')
+ if poll_async:
+ self.poll_job(resource, 'traffictype')
+
+ return traffic_type
+
+ def update_traffic_type(self):
+
+ traffic_type = self.get_traffic_type()
+ args = {
+ 'id': traffic_type['id']
+ }
+ args.update(self._get_label_args())
+ if self.has_changed(args, traffic_type):
+ self.result['changed'] = True
+ if not self.module.check_mode:
+ resource = self.query_api('updateTrafficType', **args)
+ poll_async = self.module.params.get('poll_async')
+ if poll_async:
+ self.traffic_type = self.poll_job(resource, 'traffictype')
+
+ return self.traffic_type
+
+
+def setup_module_object():
+ argument_spec = cs_argument_spec()
+ argument_spec.update(dict(
+ physical_network=dict(required=True),
+ zone=dict(),
+ state=dict(choices=['present', 'absent'], default='present'),
+ traffic_type=dict(required=True, choices=['Management', 'Guest', 'Public', 'Storage']),
+ hyperv_networklabel=dict(),
+ isolation_method=dict(choices=['vlan', 'vxlan']),
+ kvm_networklabel=dict(),
+ ovm3_networklabel=dict(),
+ vlan=dict(),
+ vmware_networklabel=dict(),
+ xen_networklabel=dict(),
+ poll_async=dict(type='bool', default=True)
+ ))
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=cs_required_together(),
+ supports_check_mode=True
+ )
+ return module
+
+
+def execute_module(module):
+ actt = AnsibleCloudStackTrafficType(module)
+ state = module.params.get('state')
+
+ if state in ['present']:
+ result = actt.present_traffic_type()
+ else:
+ result = actt.absent_traffic_type()
+
+ return actt.get_result(result)
+
+
+def main():
+ module = setup_module_object()
+ result = execute_module(module)
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/cs_traffic_type/aliases b/test/integration/targets/cs_traffic_type/aliases
new file mode 100644
index 0000000000..c89c86d7d2
--- /dev/null
+++ b/test/integration/targets/cs_traffic_type/aliases
@@ -0,0 +1,2 @@
+cloud/cs
+shippable/cs/group1
diff --git a/test/integration/targets/cs_traffic_type/meta/main.yml b/test/integration/targets/cs_traffic_type/meta/main.yml
new file mode 100644
index 0000000000..e9a5b9eeae
--- /dev/null
+++ b/test/integration/targets/cs_traffic_type/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - cs_common
diff --git a/test/integration/targets/cs_traffic_type/tasks/main.yml b/test/integration/targets/cs_traffic_type/tasks/main.yml
new file mode 100644
index 0000000000..54e155de3a
--- /dev/null
+++ b/test/integration/targets/cs_traffic_type/tasks/main.yml
@@ -0,0 +1,173 @@
+---
+# Create a new zone - the default one is enabled
+- name: assure zone for tests
+ cs_zone:
+ name: cs-test-zone
+ state: present
+ dns1: 8.8.8.8
+ network_type: advanced
+ register: cszone
+
+- name: ensure the zone is disabled
+ cs_zone:
+ name: "{{ cszone.name }}"
+ state: disabled
+ register: cszone
+
+- name: setup a network
+ cs_physical_network:
+ name: net01
+ zone: "{{ cszone.name }}"
+ isolation_method: VLAN
+ broadcast_domain_range: ZONE
+ ignore_errors: true
+ register: pn
+
+
+- name: fail on missing params
+ cs_traffic_type:
+ ignore_errors: true
+ register: tt
+- name: validate fail on missing params
+ assert:
+ that:
+ - tt is failed
+ - 'tt.msg == "missing required arguments: physical_network, traffic_type"'
+
+- name: add a traffic type in check mode
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Guest
+ zone: "{{ pn.zone }}"
+ register: tt
+ check_mode: yes
+- name: validate add a traffic type in check mode
+ assert:
+ that:
+ - tt is changed
+ - tt.zone == pn.zone
+
+- name: add a traffic type
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Guest
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate add a traffic type
+ assert:
+ that:
+ - tt is changed
+ - tt.physical_network == pn.id
+ - tt.traffic_type == 'Guest'
+ - tt.zone == pn.zone
+
+- name: add a traffic type idempotence
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Guest
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate add a traffic type idempotence
+ assert:
+ that:
+ - tt is not changed
+ - tt.physical_network == pn.id
+ - tt.traffic_type == 'Guest'
+ - tt.zone == pn.zone
+
+- name: update traffic type
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Guest
+ kvm_networklabel: cloudbr0
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate update traffic type
+ assert:
+ that:
+ - tt is changed
+ - tt.physical_network == pn.id
+ - tt.traffic_type == 'Guest'
+ - tt.zone == pn.zone
+ - tt.kvm_networklabel == 'cloudbr0'
+
+- name: update traffic type idempotence
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Guest
+ kvm_networklabel: cloudbr0
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate update traffic type idempotence
+ assert:
+ that:
+ - tt is not changed
+ - tt.physical_network == pn.id
+ - tt.traffic_type == 'Guest'
+ - tt.zone == pn.zone
+ - tt.kvm_networklabel == 'cloudbr0'
+
+- name: add a removable traffic type
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Public
+ kvm_networklabel: cloudbr1
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate add a removable traffic type
+ assert:
+ that:
+ - tt is changed
+ - tt.physical_network == pn.id
+ - tt.traffic_type == 'Public'
+ - tt.zone == pn.zone
+ - tt.kvm_networklabel == 'cloudbr1'
+
+- name: remove traffic type in check mode
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Public
+ state: absent
+ zone: "{{ pn.zone }}"
+ check_mode: yes
+ register: tt
+- name: validate remove traffic type in check mode
+ assert:
+ that:
+ - tt is changed
+
+- name: remove traffic type
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Public
+ state: absent
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate remove traffic type
+ assert:
+ that:
+ - tt is changed
+ - tt.zone == pn.zone
+
+- name: remove traffic type idempotence
+ cs_traffic_type:
+ physical_network: "{{ pn.name }}"
+ traffic_type: Public
+ state: absent
+ zone: "{{ pn.zone }}"
+ register: tt
+- name: validate
+ assert:
+ that:
+ - tt is not changed
+ - tt.zone == pn.zone
+
+- name: cleanup
+ block:
+ - cs_physical_network:
+ name: "{{ pn.name }}"
+ zone: "{{ cszone.name }}"
+ state: absent
+ - cs_zone:
+ name: "{{ cszone.name }}"
+ state: absent \ No newline at end of file
diff --git a/test/units/modules/cloud/cloudstack/test_cs_traffic_type.py b/test/units/modules/cloud/cloudstack/test_cs_traffic_type.py
new file mode 100644
index 0000000000..477bc70472
--- /dev/null
+++ b/test/units/modules/cloud/cloudstack/test_cs_traffic_type.py
@@ -0,0 +1,129 @@
+import sys
+
+import units.compat.unittest as unittest
+from units.compat.mock import MagicMock
+from units.compat.unittest import TestCase
+from units.modules.utils import set_module_args
+
+# Exoscale's cs doesn't support Python 2.6
+if sys.version_info[:2] != (2, 6):
+ from ansible.modules.cloud.cloudstack.cs_traffic_type import AnsibleCloudStackTrafficType, setup_module_object
+
+
+EXISTING_TRAFFIC_TYPES_RESPONSE = {
+ "count": 3,
+ "traffictype": [
+ {
+ "id": "9801cf73-5a73-4883-97e4-fa20c129226f",
+ "kvmnetworklabel": "cloudbr0",
+ "physicalnetworkid": "659c1840-9374-440d-a412-55ca360c9d3c",
+ "traffictype": "Management"
+ },
+ {
+ "id": "28ed70b7-9a1f-41bf-94c3-53a9f22da8b6",
+ "kvmnetworklabel": "cloudbr0",
+ "physicalnetworkid": "659c1840-9374-440d-a412-55ca360c9d3c",
+ "traffictype": "Guest"
+ },
+ {
+ "id": "9c05c802-84c0-4eda-8f0a-f681364ffb46",
+ "kvmnetworklabel": "cloudbr0",
+ "physicalnetworkid": "659c1840-9374-440d-a412-55ca360c9d3c",
+ "traffictype": "Storage"
+ }
+ ]
+}
+
+VALID_LIST_NETWORKS_RESPONSE = {
+ "count": 1,
+ "physicalnetwork": [
+ {
+ "broadcastdomainrange": "ZONE",
+ "id": "659c1840-9374-440d-a412-55ca360c9d3c",
+ "name": "eth1",
+ "state": "Enabled",
+ "vlan": "3900-4000",
+ "zoneid": "49acf813-a8dd-4da0-aa53-1d826d6003e7"
+ }
+ ]
+}
+
+VALID_LIST_ZONES_RESPONSE = {
+ "count": 1,
+ "zone": [
+ {
+ "allocationstate": "Enabled",
+ "dhcpprovider": "VirtualRouter",
+ "dns1": "8.8.8.8",
+ "dns2": "8.8.4.4",
+ "guestcidraddress": "10.10.0.0/16",
+ "id": "49acf813-a8dd-4da0-aa53-1d826d6003e7",
+ "internaldns1": "192.168.56.1",
+ "localstorageenabled": True,
+ "name": "DevCloud-01",
+ "networktype": "Advanced",
+ "securitygroupsenabled": False,
+ "tags": [],
+ "zonetoken": "df20d65a-c6c8-3880-9064-4f77de2291ef"
+ }
+ ]
+}
+
+
+base_module_args = {
+ "api_key": "api_key",
+ "api_secret": "very_secret_content",
+ "api_url": "http://localhost:8888/api/client",
+ "kvm_networklabel": "cloudbr0",
+ "physical_network": "eth1",
+ "poll_async": True,
+ "state": "present",
+ "traffic_type": "Guest",
+ "zone": "DevCloud-01"
+}
+
+
+class TestAnsibleCloudstackTraffiType(TestCase):
+
+ @unittest.skipUnless(sys.version_info[:2] >= (2, 7), "Exoscale's cs doesn't support Python 2.6")
+ def test_module_is_created_sensibly(self):
+ set_module_args(base_module_args)
+ module = setup_module_object()
+ assert module.params['traffic_type'] == 'Guest'
+
+ @unittest.skipUnless(sys.version_info[:2] >= (2, 7), "Exoscale's cs doesn't support Python 2.6")
+ def test_update_called_when_traffic_type_exists(self):
+ set_module_args(base_module_args)
+ module = setup_module_object()
+ actt = AnsibleCloudStackTrafficType(module)
+ actt.get_traffic_type = MagicMock(return_value=EXISTING_TRAFFIC_TYPES_RESPONSE['traffictype'][0])
+ actt.update_traffic_type = MagicMock()
+ actt.present_traffic_type()
+ self.assertTrue(actt.update_traffic_type.called)
+
+ @unittest.skipUnless(sys.version_info[:2] >= (2, 7), "Exoscale's cs doesn't support Python 2.6")
+ def test_update_not_called_when_traffic_type_doesnt_exist(self):
+ set_module_args(base_module_args)
+ module = setup_module_object()
+ actt = AnsibleCloudStackTrafficType(module)
+ actt.get_traffic_type = MagicMock(return_value=None)
+ actt.update_traffic_type = MagicMock()
+ actt.add_traffic_type = MagicMock()
+ actt.present_traffic_type()
+ self.assertFalse(actt.update_traffic_type.called)
+ self.assertTrue(actt.add_traffic_type.called)
+
+ @unittest.skipUnless(sys.version_info[:2] >= (2, 7), "Exoscale's cs doesn't support Python 2.6")
+ def test_traffic_type_returned_if_exists(self):
+ set_module_args(base_module_args)
+ module = setup_module_object()
+ actt = AnsibleCloudStackTrafficType(module)
+ actt.get_physical_network = MagicMock(return_value=VALID_LIST_NETWORKS_RESPONSE['physicalnetwork'][0])
+ actt.get_traffic_types = MagicMock(return_value=EXISTING_TRAFFIC_TYPES_RESPONSE)
+ tt = actt.present_traffic_type()
+ self.assertTrue(tt.get('kvmnetworklabel') == base_module_args['kvm_networklabel'])
+ self.assertTrue(tt.get('traffictype') == base_module_args['traffic_type'])
+
+
+if __name__ == '__main__':
+ unittest.main()