From 6170f3a6f6c2ac8501e49f0ceab1816190b16297 Mon Sep 17 00:00:00 2001 From: zhongjun2 Date: Sun, 13 Oct 2019 00:15:02 +0800 Subject: [PATCH] Add route module (#60189) * Add route module * Update hwc_vpc_route.py * Update hwc_vpc_route.py * Update hwc_vpc_route.py * Update main.yml * Update hwc_vpc_route.py * Update hwc_vpc_route.py * Update hwc.py * Update hwc_vpc_route.py --- .../modules/cloud/huawei/hwc_vpc_route.py | 439 ++++++++++++++++++ .../integration/targets/hwc_vpc_route/aliases | 1 + .../targets/hwc_vpc_route/tasks/main.yml | 150 ++++++ 3 files changed, 590 insertions(+) create mode 100644 lib/ansible/modules/cloud/huawei/hwc_vpc_route.py create mode 100644 test/integration/targets/hwc_vpc_route/aliases create mode 100644 test/integration/targets/hwc_vpc_route/tasks/main.yml diff --git a/lib/ansible/modules/cloud/huawei/hwc_vpc_route.py b/lib/ansible/modules/cloud/huawei/hwc_vpc_route.py new file mode 100644 index 0000000000..5770067ee4 --- /dev/null +++ b/lib/ansible/modules/cloud/huawei/hwc_vpc_route.py @@ -0,0 +1,439 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2019 Huawei +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +############################################################################### +# Documentation +############################################################################### + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ["preview"], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: hwc_vpc_route +description: + - vpc route management. +short_description: Creates a resource of Vpc/Route in Huawei Cloud +notes: + - If I(id) option is provided, it takes precedence over I(destination), I(vpc_id), I(type) and I(next_hop) for route selection. + - I(destination), I(vpc_id), I(type) and I(next_hop) are used for route selection. If more than one route with this options exists, execution is aborted. + - No parameter support updating. If one of option is changed, the module will create a new resource. +version_added: '2.10' +author: Huawei Inc. (@huaweicloud) +requirements: + - keystoneauth1 >= 3.6.0 +options: + state: + description: + - Whether the given object should exist in Huawei Cloud. + type: str + choices: ['present', 'absent'] + default: 'present' + destination: + description: + - Specifies the destination IP address or CIDR block. + type: str + required: true + next_hop: + description: + - Specifies the next hop. The value is VPC peering connection ID. + type: str + required: true + vpc_id: + description: + - Specifies the VPC ID to which route is added. + type: str + required: true + type: + description: + - Specifies the type of route. + type: str + required: false + default: 'peering' +extends_documentation_fragment: hwc +''' + +EXAMPLES = ''' +# create a peering connect +- name: create a local vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_local" + register: vpc1 +- name: create a peering vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_peering" + register: vpc2 +- name: create a peering connect + hwc_vpc_peering_connect: + local_vpc_id: "{{ vpc1.id }}" + name: "ansible_network_peering_test" + filters: + - "name" + peering_vpc: + vpc_id: "{{ vpc2.id }}" + register: connect +- name: create a route + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" +''' + +RETURN = ''' + id: + description: + - UUID of the route. + type: str + returned: success + destination: + description: + - Specifies the destination IP address or CIDR block. + type: str + returned: success + next_hop: + description: + - Specifies the next hop. The value is VPC peering connection ID. + type: str + returned: success + vpc_id: + description: + - Specifies the VPC ID to which route is added. + type: str + returned: success + type: + description: + - Specifies the type of route. + type: str + returned: success +''' + +from ansible.module_utils.hwc_utils import ( + Config, HwcClientException, HwcModule, are_different_dicts, build_path, + get_region, is_empty_value, navigate_value) + + +def build_module(): + return HwcModule( + argument_spec=dict( + state=dict(default='present', choices=['present', 'absent'], + type='str'), + destination=dict(type='str', required=True), + next_hop=dict(type='str', required=True), + vpc_id=dict(type='str', required=True), + type=dict(type='str', default='peering'), + id=dict(type='str') + ), + supports_check_mode=True, + ) + + +def main(): + """Main function""" + + module = build_module() + config = Config(module, "vpc") + + try: + resource = None + if module.params.get("id"): + resource = get_resource_by_id(config) + if module.params['state'] == 'present': + opts = user_input_parameters(module) + if are_different_dicts(resource, opts): + raise Exception( + "Cannot change option from (%s) to (%s) for an" + " existing route.(%s)" % (resource, opts, + config.module.params.get( + 'id'))) + else: + v = search_resource(config) + if len(v) > 1: + raise Exception("Found more than one resource(%s)" % ", ".join([ + navigate_value(i, ["id"]) for i in v])) + + if len(v) == 1: + resource = update_properties(module, {"read": v[0]}, None) + module.params['id'] = navigate_value(resource, ["id"]) + + result = {} + changed = False + if module.params['state'] == 'present': + if resource is None: + if not module.check_mode: + resource = create(config) + changed = True + + result = resource + else: + if resource: + if not module.check_mode: + delete(config) + changed = True + + except Exception as ex: + module.fail_json(msg=str(ex)) + + else: + result['changed'] = changed + module.exit_json(**result) + + +def user_input_parameters(module): + return { + "destination": module.params.get("destination"), + "next_hop": module.params.get("next_hop"), + "type": module.params.get("type"), + "vpc_id": module.params.get("vpc_id"), + "id": module.params.get("id"), + } + + +def create(config): + module = config.module + client = config.client(get_region(module), "network", "project") + opts = user_input_parameters(module) + + params = build_create_parameters(opts) + r = send_create_request(module, params, client) + module.params['id'] = navigate_value(r, ["route", "id"]) + + result = update_properties(module, {"read": fill_resp_body(r)}, None) + return result + + +def delete(config): + module = config.module + client = config.client(get_region(module), "network", "project") + + send_delete_request(module, None, client) + + +def get_resource_by_id(config, exclude_output=False): + module = config.module + client = config.client(get_region(module), "network", "project") + + res = {} + + r = send_read_request(module, client) + res["read"] = fill_resp_body(r) + + result = update_properties(module, res, None, exclude_output) + return result + + +def _build_query_link(opts): + query_params = [] + + v = navigate_value(opts, ["type"]) + if v: + query_params.append("type=" + str(v)) + + v = navigate_value(opts, ["destination"]) + if v: + query_params.append("destination=" + str(v)) + + v = navigate_value(opts, ["vpc_id"]) + if v: + query_params.append("vpc_id=" + str(v)) + + query_link = "?marker={marker}&limit=10" + if query_params: + query_link += "&" + "&".join(query_params) + + return query_link + + +def search_resource(config): + module = config.module + client = config.client(get_region(module), "network", "project") + opts = user_input_parameters(module) + identity_obj = _build_identity_object(opts) + query_link = _build_query_link(opts) + link = "v2.0/vpc/routes" + query_link + + result = [] + p = {'marker': ''} + while True: + url = link.format(**p) + r = send_list_request(module, client, url) + if not r: + break + + for item in r: + item = fill_list_resp_body(item) + if not are_different_dicts(identity_obj, item): + result.append(item) + + if len(result) > 1: + break + + p['marker'] = r[-1].get('id') + + return result + + +def build_create_parameters(opts): + params = dict() + + v = navigate_value(opts, ["destination"], None) + if not is_empty_value(v): + params["destination"] = v + + v = navigate_value(opts, ["next_hop"], None) + if not is_empty_value(v): + params["nexthop"] = v + + v = navigate_value(opts, ["type"], None) + if not is_empty_value(v): + params["type"] = v + + v = navigate_value(opts, ["vpc_id"], None) + if not is_empty_value(v): + params["vpc_id"] = v + + if not params: + return params + + params = {"route": params} + + return params + + +def send_create_request(module, params, client): + url = "v2.0/vpc/routes" + try: + r = client.post(url, params) + except HwcClientException as ex: + msg = ("module(hwc_vpc_route): error running " + "api(create), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return r + + +def send_delete_request(module, params, client): + url = build_path(module, "v2.0/vpc/routes/{id}") + + try: + r = client.delete(url, params) + except HwcClientException as ex: + msg = ("module(hwc_vpc_route): error running " + "api(delete), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return r + + +def send_read_request(module, client): + url = build_path(module, "v2.0/vpc/routes/{id}") + + r = None + try: + r = client.get(url) + except HwcClientException as ex: + msg = ("module(hwc_vpc_route): error running " + "api(read), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return navigate_value(r, ["route"], None) + + +def fill_resp_body(body): + result = dict() + + result["destination"] = body.get("destination") + + result["id"] = body.get("id") + + result["nexthop"] = body.get("nexthop") + + result["type"] = body.get("type") + + result["vpc_id"] = body.get("vpc_id") + + return result + + +def update_properties(module, response, array_index, exclude_output=False): + r = user_input_parameters(module) + + v = navigate_value(response, ["read", "destination"], array_index) + r["destination"] = v + + v = navigate_value(response, ["read", "nexthop"], array_index) + r["next_hop"] = v + + v = navigate_value(response, ["read", "type"], array_index) + r["type"] = v + + v = navigate_value(response, ["read", "vpc_id"], array_index) + r["vpc_id"] = v + + v = navigate_value(response, ["read", "id"], array_index) + r["id"] = v + + return r + + +def send_list_request(module, client, url): + + r = None + try: + r = client.get(url) + except HwcClientException as ex: + msg = ("module(hwc_vpc_route): error running " + "api(list), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return navigate_value(r, ["routes"], None) + + +def _build_identity_object(all_opts): + result = dict() + + v = navigate_value(all_opts, ["destination"], None) + result["destination"] = v + + v = navigate_value(all_opts, ["id"], None) + result["id"] = v + + v = navigate_value(all_opts, ["next_hop"], None) + result["nexthop"] = v + + v = navigate_value(all_opts, ["type"], None) + result["type"] = v + + v = navigate_value(all_opts, ["vpc_id"], None) + result["vpc_id"] = v + + return result + + +def fill_list_resp_body(body): + result = dict() + + result["destination"] = body.get("destination") + + result["id"] = body.get("id") + + result["nexthop"] = body.get("nexthop") + + result["type"] = body.get("type") + + result["vpc_id"] = body.get("vpc_id") + + return result + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/hwc_vpc_route/aliases b/test/integration/targets/hwc_vpc_route/aliases new file mode 100644 index 0000000000..ad7ccf7ada --- /dev/null +++ b/test/integration/targets/hwc_vpc_route/aliases @@ -0,0 +1 @@ +unsupported diff --git a/test/integration/targets/hwc_vpc_route/tasks/main.yml b/test/integration/targets/hwc_vpc_route/tasks/main.yml new file mode 100644 index 0000000000..1910155a2e --- /dev/null +++ b/test/integration/targets/hwc_vpc_route/tasks/main.yml @@ -0,0 +1,150 @@ +--- +# Pre-test setup +- name: create a vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_local" + state: present + register: vpc1 +- name: create a vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_peering" + state: present + register: vpc2 +- name: create a peering connect + hwc_vpc_peering_connect: + local_vpc_id: "{{ vpc1.id }}" + name: "ansible_network_peering_test" + filters: + - "name" + peering_vpc: + vpc_id: "{{ vpc2.id }}" + state: present + register: connect +- name: delete a route + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: absent +#---------------------------------------------------------- +- name: create a route (check mode) + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + check_mode: yes + register: result +- assert: + that: + - not result.id + - result.changed +#---------------------------------------------------------- +- name: create a route + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: present + register: result +- name: assert changed is true + assert: + that: + result is changed +# ---------------------------------------------------------- +- name: create a route (idemponent) + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: present + check_mode: yes + register: result +- name: idemponent + assert: + that: + - not result.changed +# ----------------------------------------------------------- +- name: create a route that already exists + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: present + register: result +- name: assert changed is false + assert: + that: + - result.failed == 0 + - result.changed == false +#---------------------------------------------------------- +- name: delete a route (check mode) + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: absent + check_mode: yes +#---------------------------------------------------------- +- name: delete a route + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: absent + register: result +- name: assert changed is true + assert: + that: + result is changed +#---------------------------------------------------------- +- name: delete a (idemponent) + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: absent + check_mode: yes + register: result +- name: not changed + assert: + that: + not result.changed +# ---------------------------------------------------------------------------- +- name: delete a route that does not exist + hwc_vpc_route: + vpc_id: "{{ vpc1.id }}" + destination: "192.168.0.0/16" + next_hop: "{{ connect.id }}" + state: absent + register: result +- name: assert changed is false + assert: + that: + - result.failed == 0 + - result.changed == false +#--------------------------------------------------------- +# Post-test teardown +- name: delete a peering connect + hwc_vpc_peering_connect: + local_vpc_id: "{{ vpc1.id }}" + name: "ansible_network_peering_test" + filters: + - "name" + peering_vpc: + vpc_id: "{{ vpc2.id }}" + state: absent + register: connect +- name: delete a vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_peering" + state: absent + register: vpc2 +- name: delete a vpc + hwc_network_vpc: + cidr: "192.168.0.0/16" + name: "ansible_network_vpc_test_local" + state: absent + register: vpc1