1#!/usr/bin/python
2#
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19ANSIBLE_METADATA = {'metadata_version': '1.1',
20                    'status': ['preview'],
21                    'supported_by': 'community'}
22
23DOCUMENTATION = """
24---
25module: ce_vxlan_global
26version_added: "2.4"
27short_description: Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices.
28description:
29    - Manages global attributes of VXLAN and bridge domain on HUAWEI CloudEngine devices.
30author: QijunPan (@QijunPan)
31notes:
32    - Recommended connection is C(network_cli).
33    - This module also works with C(local) connections for legacy playbooks.
34options:
35    bridge_domain_id:
36        description:
37            - Specifies a bridge domain ID.
38              The value is an integer ranging from 1 to 16777215.
39    tunnel_mode_vxlan:
40        description:
41            - Set the tunnel mode to VXLAN when configuring the VXLAN feature.
42        choices: ['enable', 'disable']
43    nvo3_prevent_loops:
44        description:
45            - Loop prevention of VXLAN traffic in non-enhanced mode.
46              When the device works in non-enhanced mode,
47              inter-card forwarding of VXLAN traffic may result in loops.
48        choices: ['enable', 'disable']
49    nvo3_acl_extend:
50        description:
51            - Enabling or disabling the VXLAN ACL extension function.
52        choices: ['enable', 'disable']
53    nvo3_gw_enhanced:
54        description:
55            - Configuring the Layer 3 VXLAN Gateway to Work in Non-loopback Mode.
56        choices: ['l2', 'l3']
57    nvo3_service_extend:
58        description:
59            - Enabling or disabling the VXLAN service extension function.
60        choices: ['enable', 'disable']
61    nvo3_eth_trunk_hash:
62        description:
63            - Eth-Trunk from load balancing VXLAN packets in optimized mode.
64        choices: ['enable','disable']
65    nvo3_ecmp_hash:
66        description:
67            - Load balancing of VXLAN packets through ECMP in optimized mode.
68        choices: ['enable', 'disable']
69    state:
70        description:
71            - Determines whether the config should be present or not
72              on the device.
73        default: present
74        choices: ['present', 'absent']
75"""
76
77EXAMPLES = '''
78- name: vxlan global module test
79  hosts: ce128
80  connection: local
81  gather_facts: no
82  vars:
83    cli:
84      host: "{{ inventory_hostname }}"
85      port: "{{ ansible_ssh_port }}"
86      username: "{{ username }}"
87      password: "{{ password }}"
88      transport: cli
89
90  tasks:
91
92  - name: Create bridge domain and set tunnel mode to VXLAN
93    ce_vxlan_global:
94      bridge_domain_id: 100
95      nvo3_acl_extend: enable
96      provider: "{{ cli }}"
97'''
98
99RETURN = '''
100proposed:
101    description: k/v pairs of parameters passed into module
102    returned: verbose mode
103    type: dict
104    sample: {"bridge_domain_id": "100", "nvo3_acl_extend": "enable", state="present"}
105existing:
106    description: k/v pairs of existing configuration
107    returned: verbose mode
108    type: dict
109    sample: {"bridge_domain": {"80", "90"}, "nvo3_acl_extend": "disable"}
110end_state:
111    description: k/v pairs of configuration after module execution
112    returned: verbose mode
113    type: dict
114    sample: {"bridge_domain_id": {"80", "90", "100"}, "nvo3_acl_extend": "enable"}
115updates:
116    description: commands sent to the device
117    returned: always
118    type: list
119    sample: ["bridge-domain 100",
120             "ip tunnel mode vxlan"]
121changed:
122    description: check to see if a change was made on the device
123    returned: always
124    type: bool
125    sample: true
126'''
127
128import re
129from ansible.module_utils.basic import AnsibleModule
130from ansible.module_utils.network.cloudengine.ce import load_config
131from ansible.module_utils.network.cloudengine.ce import ce_argument_spec
132from ansible.module_utils.connection import exec_command
133
134
135def is_config_exist(cmp_cfg, test_cfg):
136    """is configuration exist?"""
137
138    if not cmp_cfg or not test_cfg:
139        return False
140
141    return bool(test_cfg in cmp_cfg)
142
143
144def get_nvo3_gw_enhanced(cmp_cfg):
145    """get the Layer 3 VXLAN Gateway to Work in Non-loopback Mode """
146
147    get = re.findall(
148        r"assign forward nvo3-gateway enhanced (l[2|3])", cmp_cfg)
149    if not get:
150        return None
151    else:
152        return get[0]
153
154
155class VxlanGlobal(object):
156    """
157    Manages global attributes of VXLAN and bridge domain.
158    """
159
160    def __init__(self, argument_spec):
161        self.spec = argument_spec
162        self.module = None
163        self.init_module()
164
165        # module input info
166        self.tunnel_mode_vxlan = self.module.params['tunnel_mode_vxlan']
167        self.nvo3_prevent_loops = self.module.params['nvo3_prevent_loops']
168        self.nvo3_acl_extend = self.module.params['nvo3_acl_extend']
169        self.nvo3_gw_enhanced = self.module.params['nvo3_gw_enhanced']
170        self.nvo3_service_extend = self.module.params['nvo3_service_extend']
171        self.nvo3_eth_trunk_hash = self.module.params['nvo3_eth_trunk_hash']
172        self.nvo3_ecmp_hash = self.module.params['nvo3_ecmp_hash']
173        self.bridge_domain_id = self.module.params['bridge_domain_id']
174        self.state = self.module.params['state']
175
176        # state
177        self.config = ""  # current config
178        self.bd_info = list()
179        self.changed = False
180        self.updates_cmd = list()
181        self.commands = list()
182        self.results = dict()
183        self.proposed = dict()
184        self.existing = dict()
185        self.end_state = dict()
186
187    def init_module(self):
188        """init module"""
189
190        self.module = AnsibleModule(
191            argument_spec=self.spec, supports_check_mode=True)
192
193    def cli_load_config(self, commands):
194        """load config by cli"""
195
196        if not self.module.check_mode:
197            load_config(self.module, commands)
198
199    def get_config(self, flags=None):
200        """Retrieves the current config from the device or cache
201        """
202        flags = [] if flags is None else flags
203
204        cmd = 'display current-configuration '
205        cmd += ' '.join(flags)
206        cmd = cmd.strip()
207
208        rc, out, err = exec_command(self.module, cmd)
209        if rc != 0:
210            self.module.fail_json(msg=err)
211        cfg = str(out).strip()
212
213        return cfg
214
215    def get_current_config(self):
216        """get current configuration"""
217
218        flags = list()
219        exp = " include-default | include vxlan|assign | exclude undo"
220        flags.append(exp)
221        return self.get_config(flags)
222
223    def cli_add_command(self, command, undo=False):
224        """add command to self.update_cmd and self.commands"""
225
226        if undo and command.lower() not in ["quit", "return"]:
227            cmd = "undo " + command
228        else:
229            cmd = command
230
231        self.commands.append(cmd)          # set to device
232        if command.lower() not in ["quit", "return"]:
233            self.updates_cmd.append(cmd)   # show updates result
234
235    def get_bd_list(self):
236        """get bridge domain list"""
237        flags = list()
238        bd_info = list()
239        exp = " include-default | include bridge-domain | exclude undo"
240        flags.append(exp)
241        bd_str = self.get_config(flags)
242        if not bd_str:
243            return bd_info
244        bd_num = re.findall(r'bridge-domain\s*([0-9]+)', bd_str)
245        bd_info.extend(bd_num)
246        return bd_info
247
248    def config_bridge_domain(self):
249        """manage bridge domain"""
250
251        if not self.bridge_domain_id:
252            return
253
254        cmd = "bridge-domain %s" % self.bridge_domain_id
255        exist = self.bridge_domain_id in self.bd_info
256        if self.state == "present":
257            if not exist:
258                self.cli_add_command(cmd)
259                self.cli_add_command("quit")
260        else:
261            if exist:
262                self.cli_add_command(cmd, undo=True)
263
264    def config_tunnel_mode(self):
265        """config tunnel mode vxlan"""
266
267        # ip tunnel mode vxlan
268        if self.tunnel_mode_vxlan:
269            cmd = "ip tunnel mode vxlan"
270            exist = is_config_exist(self.config, cmd)
271            if self.tunnel_mode_vxlan == "enable":
272                if not exist:
273                    self.cli_add_command(cmd)
274            else:
275                if exist:
276                    self.cli_add_command(cmd, undo=True)
277
278    def config_assign_forward(self):
279        """config assign forward command"""
280
281        # [undo] assign forward nvo3-gateway enhanced {l2|l3)
282        if self.nvo3_gw_enhanced:
283            cmd = "assign forward nvo3-gateway enhanced %s" % self.nvo3_gw_enhanced
284            exist = is_config_exist(self.config, cmd)
285            if self.state == "present":
286                if not exist:
287                    self.cli_add_command(cmd)
288            else:
289                if exist:
290                    self.cli_add_command(cmd, undo=True)
291
292        # [undo] assign forward nvo3 f-linecard compatibility enable
293        if self.nvo3_prevent_loops:
294            cmd = "assign forward nvo3 f-linecard compatibility enable"
295            exist = is_config_exist(self.config, cmd)
296            if self.nvo3_prevent_loops == "enable":
297                if not exist:
298                    self.cli_add_command(cmd)
299            else:
300                if exist:
301                    self.cli_add_command(cmd, undo=True)
302
303        # [undo] assign forward nvo3 acl extend enable
304        if self.nvo3_acl_extend:
305            cmd = "assign forward nvo3 acl extend enable"
306            exist = is_config_exist(self.config, cmd)
307            if self.nvo3_acl_extend == "enable":
308                if not exist:
309                    self.cli_add_command(cmd)
310            else:
311                if exist:
312                    self.cli_add_command(cmd, undo=True)
313
314        # [undo] assign forward nvo3 service extend enable
315        if self.nvo3_service_extend:
316            cmd = "assign forward nvo3 service extend enable"
317            exist = is_config_exist(self.config, cmd)
318            if self.nvo3_service_extend == "enable":
319                if not exist:
320                    self.cli_add_command(cmd)
321            else:
322                if exist:
323                    self.cli_add_command(cmd, undo=True)
324
325        # assign forward nvo3 eth-trunk hash {enable|disable}
326        if self.nvo3_eth_trunk_hash:
327            cmd = "assign forward nvo3 eth-trunk hash enable"
328            exist = is_config_exist(self.config, cmd)
329            if self.nvo3_eth_trunk_hash == "enable":
330                if not exist:
331                    self.cli_add_command(cmd)
332            else:
333                if exist:
334                    self.cli_add_command(cmd, undo=True)
335
336        # [undo] assign forward nvo3 ecmp hash enable
337        if self.nvo3_ecmp_hash:
338            cmd = "assign forward nvo3 ecmp hash enable"
339            exist = is_config_exist(self.config, cmd)
340            if self.nvo3_ecmp_hash == "enable":
341                if not exist:
342                    self.cli_add_command(cmd)
343            else:
344                if exist:
345                    self.cli_add_command(cmd, undo=True)
346
347    def check_params(self):
348        """Check all input params"""
349
350        # bridge domain id check
351        if self.bridge_domain_id:
352            if not self.bridge_domain_id.isdigit():
353                self.module.fail_json(
354                    msg="Error: bridge domain id is not digit.")
355            if int(self.bridge_domain_id) < 1 or int(self.bridge_domain_id) > 16777215:
356                self.module.fail_json(
357                    msg="Error: bridge domain id is not in the range from 1 to 16777215.")
358
359    def get_proposed(self):
360        """get proposed info"""
361
362        if self.tunnel_mode_vxlan:
363            self.proposed["tunnel_mode_vxlan"] = self.tunnel_mode_vxlan
364        if self.nvo3_prevent_loops:
365            self.proposed["nvo3_prevent_loops"] = self.nvo3_prevent_loops
366        if self.nvo3_acl_extend:
367            self.proposed["nvo3_acl_extend"] = self.nvo3_acl_extend
368        if self.nvo3_gw_enhanced:
369            self.proposed["nvo3_gw_enhanced"] = self.nvo3_gw_enhanced
370        if self.nvo3_service_extend:
371            self.proposed["nvo3_service_extend"] = self.nvo3_service_extend
372        if self.nvo3_eth_trunk_hash:
373            self.proposed["nvo3_eth_trunk_hash"] = self.nvo3_eth_trunk_hash
374        if self.nvo3_ecmp_hash:
375            self.proposed["nvo3_ecmp_hash"] = self.nvo3_ecmp_hash
376        if self.bridge_domain_id:
377            self.proposed["bridge_domain_id"] = self.bridge_domain_id
378        self.proposed["state"] = self.state
379
380    def get_existing(self):
381        """get existing info"""
382
383        self.existing["bridge_domain"] = self.bd_info
384
385        cmd = "ip tunnel mode vxlan"
386        exist = is_config_exist(self.config, cmd)
387        if exist:
388            self.existing["tunnel_mode_vxlan"] = "enable"
389        else:
390            self.existing["tunnel_mode_vxlan"] = "disable"
391
392        cmd = "assign forward nvo3 f-linecard compatibility enable"
393        exist = is_config_exist(self.config, cmd)
394        if exist:
395            self.existing["nvo3_prevent_loops"] = "enable"
396        else:
397            self.existing["nvo3_prevent_loops"] = "disable"
398
399        cmd = "assign forward nvo3 acl extend enable"
400        exist = is_config_exist(self.config, cmd)
401        if exist:
402            self.existing["nvo3_acl_extend"] = "enable"
403        else:
404            self.existing["nvo3_acl_extend"] = "disable"
405
406        self.existing["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced(
407            self.config)
408
409        cmd = "assign forward nvo3 service extend enable"
410        exist = is_config_exist(self.config, cmd)
411        if exist:
412            self.existing["nvo3_service_extend"] = "enable"
413        else:
414            self.existing["nvo3_service_extend"] = "disable"
415
416        cmd = "assign forward nvo3 eth-trunk hash enable"
417        exist = is_config_exist(self.config, cmd)
418        if exist:
419            self.existing["nvo3_eth_trunk_hash"] = "enable"
420        else:
421            self.existing["nvo3_eth_trunk_hash"] = "disable"
422
423        cmd = "assign forward nvo3 ecmp hash enable"
424        exist = is_config_exist(self.config, cmd)
425        if exist:
426            self.existing["nvo3_ecmp_hash"] = "enable"
427        else:
428            self.existing["nvo3_ecmp_hash"] = "disable"
429
430    def get_end_state(self):
431        """get end state info"""
432
433        config = self.get_current_config()
434
435        self.end_state["bridge_domain"] = self.get_bd_list()
436
437        cmd = "ip tunnel mode vxlan"
438        exist = is_config_exist(config, cmd)
439        if exist:
440            self.end_state["tunnel_mode_vxlan"] = "enable"
441        else:
442            self.end_state["tunnel_mode_vxlan"] = "disable"
443
444        cmd = "assign forward nvo3 f-linecard compatibility enable"
445        exist = is_config_exist(config, cmd)
446        if exist:
447            self.end_state["nvo3_prevent_loops"] = "enable"
448        else:
449            self.end_state["nvo3_prevent_loops"] = "disable"
450
451        cmd = "assign forward nvo3 acl extend enable"
452        exist = is_config_exist(config, cmd)
453        if exist:
454            self.end_state["nvo3_acl_extend"] = "enable"
455        else:
456            self.end_state["nvo3_acl_extend"] = "disable"
457
458        self.end_state["nvo3_gw_enhanced"] = get_nvo3_gw_enhanced(config)
459
460        cmd = "assign forward nvo3 service extend enable"
461        exist = is_config_exist(config, cmd)
462        if exist:
463            self.end_state["nvo3_service_extend"] = "enable"
464        else:
465            self.end_state["nvo3_service_extend"] = "disable"
466
467        cmd = "assign forward nvo3 eth-trunk hash enable"
468        exist = is_config_exist(config, cmd)
469        if exist:
470            self.end_state["nvo3_eth_trunk_hash"] = "enable"
471        else:
472            self.end_state["nvo3_eth_trunk_hash"] = "disable"
473
474        cmd = "assign forward nvo3 ecmp hash enable"
475        exist = is_config_exist(config, cmd)
476        if exist:
477            self.end_state["nvo3_ecmp_hash"] = "enable"
478        else:
479            self.end_state["nvo3_ecmp_hash"] = "disable"
480        if self.existing == self.end_state:
481            self.changed = True
482
483    def work(self):
484        """worker"""
485
486        self.check_params()
487        self.config = self.get_current_config()
488        self.bd_info = self.get_bd_list()
489        self.get_existing()
490        self.get_proposed()
491
492        # deal present or absent
493        self.config_bridge_domain()
494        self.config_tunnel_mode()
495        self.config_assign_forward()
496        if self.commands:
497            self.cli_load_config(self.commands)
498            self.changed = True
499
500        self.get_end_state()
501        self.results['changed'] = self.changed
502        self.results['proposed'] = self.proposed
503        self.results['existing'] = self.existing
504        self.results['end_state'] = self.end_state
505        if self.changed:
506            self.results['updates'] = self.updates_cmd
507        else:
508            self.results['updates'] = list()
509
510        self.module.exit_json(**self.results)
511
512
513def main():
514    """Module main"""
515
516    argument_spec = dict(
517        tunnel_mode_vxlan=dict(required=False, type='str',
518                               choices=['enable', 'disable']),
519        nvo3_prevent_loops=dict(required=False, type='str',
520                                choices=['enable', 'disable']),
521        nvo3_acl_extend=dict(required=False, type='str',
522                             choices=['enable', 'disable']),
523        nvo3_gw_enhanced=dict(required=False, type='str',
524                              choices=['l2', 'l3']),
525        nvo3_service_extend=dict(required=False, type='str',
526                                 choices=['enable', 'disable']),
527        nvo3_eth_trunk_hash=dict(required=False, type='str',
528                                 choices=['enable', 'disable']),
529        nvo3_ecmp_hash=dict(required=False, type='str',
530                            choices=['enable', 'disable']),
531        bridge_domain_id=dict(required=False, type='str'),
532        state=dict(required=False, default='present',
533                   choices=['present', 'absent'])
534    )
535    argument_spec.update(ce_argument_spec)
536    module = VxlanGlobal(argument_spec)
537    module.work()
538
539
540if __name__ == '__main__':
541    main()
542