1#!/usr/local/bin/python3.8
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
19from __future__ import (absolute_import, division, print_function)
20__metaclass__ = type
21
22DOCUMENTATION = '''
23---
24module: ce_ospf
25short_description: Manages configuration of an OSPF instance on HUAWEI CloudEngine switches.
26description:
27    - Manages configuration of an OSPF instance on HUAWEI CloudEngine switches.
28author: QijunPan (@QijunPan)
29notes:
30    - This module requires the netconf system service be enabled on the remote device being managed.
31    - Recommended connection is C(netconf).
32    - This module also works with C(local) connections for legacy playbooks.
33options:
34    process_id:
35        description:
36            - Specifies a process ID.
37              The value is an integer ranging from 1 to 4294967295.
38        required: true
39    area:
40        description:
41            - Specifies the area ID. The area with the area-id being 0 is a backbone area.
42              Valid values are a string, formatted as an IP address
43              (i.e. "0.0.0.0") or as an integer between 1 and 4294967295.
44    addr:
45        description:
46            - Specifies the address of the network segment where the interface resides.
47              The value is in dotted decimal notation.
48    mask:
49        description:
50            - IP network wildcard bits in decimal format between 0 and 32.
51    auth_mode:
52        description:
53            - Specifies the authentication type.
54        choices: ['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple']
55    auth_text_simple:
56        description:
57            - Specifies a password for simple authentication.
58              The value is a string of 1 to 8 characters.
59    auth_key_id:
60        description:
61            - Authentication key id when C(auth_mode) is 'hmac-sha256', 'md5' or 'hmac-md5.
62              Valid value is an integer is in the range from 1 to 255.
63    auth_text_md5:
64        description:
65            - Specifies a password for MD5, HMAC-MD5, or HMAC-SHA256 authentication.
66              The value is a string of 1 to 255 case-sensitive characters, spaces not supported.
67    nexthop_addr:
68        description:
69            - IPv4 address for configure next-hop address's weight.
70              Valid values are a string, formatted as an IP address.
71    nexthop_weight:
72        description:
73            - Indicates the weight of the next hop.
74              The smaller the value is, the higher the preference of the route is.
75              It is an integer that ranges from 1 to 254.
76    max_load_balance:
77        description:
78            - The maximum number of paths for forward packets over multiple paths.
79              Valid value is an integer in the range from 1 to 64.
80    state:
81        description:
82            - Determines whether the config should be present or not
83              on the device.
84        default: present
85        choices: ['present','absent']
86'''
87
88EXAMPLES = '''
89- name: Ospf module test
90  hosts: cloudengine
91  connection: local
92  gather_facts: no
93  vars:
94    cli:
95      host: "{{ inventory_hostname }}"
96      port: "{{ ansible_ssh_port }}"
97      username: "{{ username }}"
98      password: "{{ password }}"
99      transport: cli
100
101  tasks:
102
103  - name: Configure ospf
104    community.network.ce_ospf:
105      process_id: 1
106      area: 100
107      state: present
108      provider: "{{ cli }}"
109'''
110
111RETURN = '''
112proposed:
113    description: k/v pairs of parameters passed into module
114    returned: verbose mode
115    type: dict
116    sample: {"process_id": "1", "area": "100"}
117existing:
118    description: k/v pairs of existing configuration
119    returned: verbose mode
120    type: dict
121    sample: {"process_id": "1", "areas": [], "nexthops":[], "max_load_balance": "32"}
122end_state:
123    description: k/v pairs of configuration after module execution
124    returned: verbose mode
125    type: dict
126    sample: {"process_id": "1",
127             "areas": [{"areaId": "0.0.0.100", "areaType": "Normal"}],
128             "nexthops":[], "max_load_balance": "32"}
129updates:
130    description: commands sent to the device
131    returned: always
132    type: list
133    sample: ["ospf 1", "area 0.0.0.100"]
134changed:
135    description: check to see if a change was made on the device
136    returned: always
137    type: bool
138    sample: true
139'''
140
141from xml.etree import ElementTree
142from ansible.module_utils.basic import AnsibleModule
143from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config, ce_argument_spec
144
145CE_NC_GET_OSPF = """
146    <filter type="subtree">
147      <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
148        <ospfv2comm>
149          <ospfSites>
150            <ospfSite>
151              <processId>%s</processId>
152              <routerId></routerId>
153              <vrfName></vrfName>
154              <ProcessTopologys>
155                <ProcessTopology>
156                  <nexthopMTs></nexthopMTs>
157                  <maxLoadBalancing></maxLoadBalancing>
158                </ProcessTopology>
159              </ProcessTopologys>
160              <areas>
161                <area>
162                  <areaId></areaId>
163                  <areaType></areaType>
164                  <authenticationMode></authenticationMode>
165                  <authTextSimple></authTextSimple>
166                  <keyId></keyId>
167                  <authTextMd5></authTextMd5>
168                  <networks>
169                    <network>
170                      <ipAddress></ipAddress>
171                      <wildcardMask></wildcardMask>
172                    </network>
173                  </networks>
174                </area>
175              </areas>
176            </ospfSite>
177          </ospfSites>
178        </ospfv2comm>
179      </ospfv2>
180    </filter>
181"""
182
183CE_NC_CREATE_PROCESS = """
184    <config>
185      <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
186        <ospfv2comm>
187          <ospfSites>
188            <ospfSite operation="merge">
189              <processId>%s</processId>
190            </ospfSite>
191          </ospfSites>
192        </ospfv2comm>
193      </ospfv2>
194    </config>
195"""
196
197CE_NC_DELETE_PROCESS = """
198    <config>
199      <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
200        <ospfv2comm>
201          <ospfSites>
202            <ospfSite operation="delete">
203              <processId>%s</processId>
204            </ospfSite>
205          </ospfSites>
206        </ospfv2comm>
207      </ospfv2>
208    </config>
209"""
210
211CE_NC_XML_BUILD_MERGE_PROCESS = """
212    <config>
213      <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
214        <ospfv2comm>
215          <ospfSites>
216            <ospfSite operation="merge">
217              <processId>%s</processId>
218              %s
219            </ospfSite>
220          </ospfSites>
221        </ospfv2comm>
222      </ospfv2>
223    </config>
224"""
225
226CE_NC_XML_BUILD_PROCESS = """
227    <config>
228      <ospfv2 xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
229        <ospfv2comm>
230          <ospfSites>
231            <ospfSite>
232              <processId>%s</processId>
233              %s
234            </ospfSite>
235          </ospfSites>
236        </ospfv2comm>
237      </ospfv2>
238    </config>
239"""
240
241CE_NC_XML_BUILD_MERGE_AREA = """
242              <areas>
243                <area operation="merge">
244                  <areaId>%s</areaId>
245                  %s
246                </area>
247              </areas>
248"""
249
250CE_NC_XML_BUILD_DELETE_AREA = """
251              <areas>
252                <area operation="delete">
253                  <areaId>%s</areaId>
254                  %s
255                </area>
256              </areas>
257"""
258
259CE_NC_XML_BUILD_AREA = """
260              <areas>
261                <area>
262                  <areaId>%s</areaId>
263                  %s
264                </area>
265              </areas>
266"""
267
268CE_NC_XML_SET_AUTH_MODE = """
269                  <authenticationMode>%s</authenticationMode>
270"""
271CE_NC_XML_SET_AUTH_TEXT_SIMPLE = """
272                  <authTextSimple>%s</authTextSimple>
273"""
274
275CE_NC_XML_SET_AUTH_MD5 = """
276                  <keyId>%s</keyId>
277                  <authTextMd5>%s</authTextMd5>
278"""
279
280
281CE_NC_XML_MERGE_NETWORKS = """
282                  <networks>
283                    <network operation="merge">
284                      <ipAddress>%s</ipAddress>
285                      <wildcardMask>%s</wildcardMask>
286                    </network>
287                  </networks>
288"""
289
290CE_NC_XML_DELETE_NETWORKS = """
291                  <networks>
292                    <network operation="delete">
293                      <ipAddress>%s</ipAddress>
294                      <wildcardMask>%s</wildcardMask>
295                    </network>
296                  </networks>
297"""
298
299CE_NC_XML_SET_LB = """
300                <maxLoadBalancing>%s</maxLoadBalancing>
301"""
302
303
304CE_NC_XML_BUILD_MERGE_TOPO = """
305            <ProcessTopologys>
306              <ProcessTopology operation="merge">
307              <topoName>base</topoName>
308              %s
309              </ProcessTopology>
310            </ProcessTopologys>
311
312"""
313
314CE_NC_XML_BUILD_TOPO = """
315            <ProcessTopologys>
316              <ProcessTopology >
317              <topoName>base</topoName>
318              %s
319              </ProcessTopology>
320            </ProcessTopologys>
321
322"""
323
324CE_NC_XML_MERGE_NEXTHOP = """
325                <nexthopMTs>
326                  <nexthopMT operation="merge">
327                    <ipAddress>%s</ipAddress>
328                    <weight>%s</weight>
329                  </nexthopMT>
330                </nexthopMTs>
331"""
332
333CE_NC_XML_DELETE_NEXTHOP = """
334                <nexthopMTs>
335                  <nexthopMT operation="delete">
336                    <ipAddress>%s</ipAddress>
337                  </nexthopMT>
338                </nexthopMTs>
339"""
340
341
342class OSPF(object):
343    """
344    Manages configuration of an ospf instance.
345    """
346
347    def __init__(self, argument_spec):
348        self.spec = argument_spec
349        self.module = None
350        self.init_module()
351
352        # module input info
353        self.process_id = self.module.params['process_id']
354        self.area = self.module.params['area']
355        self.addr = self.module.params['addr']
356        self.mask = self.module.params['mask']
357        self.auth_mode = self.module.params['auth_mode']
358        self.auth_text_simple = self.module.params['auth_text_simple']
359        self.auth_key_id = self.module.params['auth_key_id']
360        self.auth_text_md5 = self.module.params['auth_text_md5']
361        self.nexthop_addr = self.module.params['nexthop_addr']
362        self.nexthop_weight = self.module.params['nexthop_weight']
363        self.max_load_balance = self.module.params['max_load_balance']
364        self.state = self.module.params['state']
365
366        # ospf info
367        self.ospf_info = dict()
368
369        # state
370        self.changed = False
371        self.updates_cmd = list()
372        self.results = dict()
373        self.proposed = dict()
374        self.existing = dict()
375        self.end_state = dict()
376
377    def init_module(self):
378        """ init module """
379
380        required_together = [
381            ("addr", "mask"),
382            ("auth_key_id", "auth_text_md5"),
383            ("nexthop_addr", "nexthop_weight")
384        ]
385        self.module = AnsibleModule(
386            argument_spec=self.spec, required_together=required_together, supports_check_mode=True)
387
388    def check_response(self, xml_str, xml_name):
389        """Check if response message is already succeed."""
390
391        if "<ok/>" not in xml_str:
392            self.module.fail_json(msg='Error: %s failed.' % xml_name)
393
394    def get_wildcard_mask(self):
395        """convert mask length to ip address wildcard mask, i.e. 24 to 0.0.0.255"""
396
397        mask_int = ["255"] * 4
398        length = int(self.mask)
399
400        if length > 32:
401            self.module.fail_json(msg='IPv4 ipaddress mask length is invalid')
402        if length < 8:
403            mask_int[0] = str(int(~(0xFF << (8 - length % 8)) & 0xFF))
404        if length >= 8:
405            mask_int[0] = '0'
406            mask_int[1] = str(int(~(0xFF << (16 - (length % 16))) & 0xFF))
407        if length >= 16:
408            mask_int[1] = '0'
409            mask_int[2] = str(int(~(0xFF << (24 - (length % 24))) & 0xFF))
410        if length >= 24:
411            mask_int[2] = '0'
412            mask_int[3] = str(int(~(0xFF << (32 - (length % 32))) & 0xFF))
413        if length == 32:
414            mask_int[3] = '0'
415
416        return '.'.join(mask_int)
417
418    def get_area_ip(self):
419        """convert integer to ip address"""
420
421        if not self.area.isdigit():
422            return self.area
423
424        addr_int = ['0'] * 4
425        addr_int[0] = str(((int(self.area) & 0xFF000000) >> 24) & 0xFF)
426        addr_int[1] = str(((int(self.area) & 0x00FF0000) >> 16) & 0xFF)
427        addr_int[2] = str(((int(self.area) & 0x0000FF00) >> 8) & 0XFF)
428        addr_int[3] = str(int(self.area) & 0xFF)
429
430        return '.'.join(addr_int)
431
432    def get_ospf_dict(self, process_id):
433        """ get one ospf attributes dict."""
434
435        ospf_info = dict()
436        conf_str = CE_NC_GET_OSPF % process_id
437        xml_str = get_nc_config(self.module, conf_str)
438        if "<data/>" in xml_str:
439            return ospf_info
440
441        xml_str = xml_str.replace('\r', '').replace('\n', '').\
442            replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\
443            replace('xmlns="http://www.huawei.com/netconf/vrp"', "")
444
445        # get process base info
446        root = ElementTree.fromstring(xml_str)
447        ospfsite = root.find("ospfv2/ospfv2comm/ospfSites/ospfSite")
448        if ospfsite:
449            for site in ospfsite:
450                if site.tag in ["processId", "routerId", "vrfName"]:
451                    ospf_info[site.tag] = site.text
452
453        # get Topology info
454        topo = root.find(
455            "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology")
456        if topo:
457            for eles in topo:
458                if eles.tag in ["maxLoadBalancing"]:
459                    ospf_info[eles.tag] = eles.text
460
461        # get nexthop info
462        ospf_info["nexthops"] = list()
463        nexthops = root.findall(
464            "ospfv2/ospfv2comm/ospfSites/ospfSite/ProcessTopologys/ProcessTopology/nexthopMTs/nexthopMT")
465        if nexthops:
466            for nexthop in nexthops:
467                nh_dict = dict()
468                for ele in nexthop:
469                    if ele.tag in ["ipAddress", "weight"]:
470                        nh_dict[ele.tag] = ele.text
471                ospf_info["nexthops"].append(nh_dict)
472
473        # get areas info
474        ospf_info["areas"] = list()
475        areas = root.findall(
476            "ospfv2/ospfv2comm/ospfSites/ospfSite/areas/area")
477        if areas:
478            for area in areas:
479                area_dict = dict()
480                for ele in area:
481                    if ele.tag in ["areaId", "authTextSimple", "areaType",
482                                   "authenticationMode", "keyId", "authTextMd5"]:
483                        area_dict[ele.tag] = ele.text
484                    if ele.tag == "networks":
485                        # get networks info
486                        area_dict["networks"] = list()
487                        for net in ele:
488                            net_dict = dict()
489                            for net_ele in net:
490                                if net_ele.tag in ["ipAddress", "wildcardMask"]:
491                                    net_dict[net_ele.tag] = net_ele.text
492                            area_dict["networks"].append(net_dict)
493
494                ospf_info["areas"].append(area_dict)
495        return ospf_info
496
497    def is_area_exist(self):
498        """is ospf area exist"""
499        if not self.ospf_info:
500            return False
501        for area in self.ospf_info["areas"]:
502            if area["areaId"] == self.get_area_ip():
503                return True
504
505        return False
506
507    def is_network_exist(self):
508        """is ospf area network exist"""
509        if not self.ospf_info:
510            return False
511
512        for area in self.ospf_info["areas"]:
513            if area["areaId"] == self.get_area_ip():
514                if not area.get("networks"):
515                    return False
516                for network in area.get("networks"):
517                    if network["ipAddress"] == self.addr and network["wildcardMask"] == self.get_wildcard_mask():
518                        return True
519        return False
520
521    def is_nexthop_exist(self):
522        """is ospf nexthop exist"""
523
524        if not self.ospf_info:
525            return False
526        for nexthop in self.ospf_info["nexthops"]:
527            if nexthop["ipAddress"] == self.nexthop_addr:
528                return True
529
530        return False
531
532    def is_nexthop_change(self):
533        """is ospf nexthop change"""
534        if not self.ospf_info:
535            return True
536
537        for nexthop in self.ospf_info["nexthops"]:
538            if nexthop["ipAddress"] == self.nexthop_addr:
539                if nexthop["weight"] == self.nexthop_weight:
540                    return False
541                else:
542                    return True
543
544        return True
545
546    def create_process(self):
547        """Create ospf process"""
548
549        xml_area = ""
550        self.updates_cmd.append("ospf %s" % self.process_id)
551        xml_create = CE_NC_CREATE_PROCESS % self.process_id
552        set_nc_config(self.module, xml_create)
553
554        # nexthop weight
555        xml_nh = ""
556        if self.nexthop_addr:
557            xml_nh = CE_NC_XML_MERGE_NEXTHOP % (
558                self.nexthop_addr, self.nexthop_weight)
559            self.updates_cmd.append("nexthop %s weight %s" % (
560                self.nexthop_addr, self.nexthop_weight))
561
562        # max load balance
563        xml_lb = ""
564        if self.max_load_balance:
565            xml_lb = CE_NC_XML_SET_LB % self.max_load_balance
566            self.updates_cmd.append(
567                "maximum load-balancing %s" % self.max_load_balance)
568
569        xml_topo = ""
570        if xml_lb or xml_nh:
571            xml_topo = CE_NC_XML_BUILD_TOPO % (xml_nh + xml_lb)
572
573        if self.area:
574            self.updates_cmd.append("area %s" % self.get_area_ip())
575            xml_auth = ""
576            xml_network = ""
577
578            # networks
579            if self.addr and self.mask:
580                xml_network = CE_NC_XML_MERGE_NETWORKS % (
581                    self.addr, self.get_wildcard_mask())
582                self.updates_cmd.append("network %s %s" % (
583                    self.addr, self.get_wildcard_mask()))
584
585            # authentication mode
586            if self.auth_mode:
587                xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode
588                if self.auth_mode == "none":
589                    self.updates_cmd.append("undo authentication-mode")
590                else:
591                    self.updates_cmd.append(
592                        "authentication-mode %s" % self.auth_mode)
593                if self.auth_mode == "simple" and self.auth_text_simple:
594                    xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple
595                    self.updates_cmd.pop()
596                    self.updates_cmd.append(
597                        "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple))
598                if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]:
599                    if self.auth_key_id and self.auth_text_md5:
600                        xml_auth += CE_NC_XML_SET_AUTH_MD5 % (
601                            self.auth_key_id, self.auth_text_md5)
602                        self.updates_cmd.pop()
603                        self.updates_cmd.append(
604                            "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5))
605            if xml_network or xml_auth or not self.is_area_exist():
606                xml_area += CE_NC_XML_BUILD_MERGE_AREA % (
607                    self.get_area_ip(), xml_network + xml_auth)
608
609        xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % (
610            self.process_id, xml_topo + xml_area)
611        recv_xml = set_nc_config(self.module, xml_str)
612        self.check_response(recv_xml, "CREATE_PROCESS")
613        self.changed = True
614
615    def delete_process(self):
616        """Delete ospf process"""
617
618        xml_str = CE_NC_DELETE_PROCESS % self.process_id
619        recv_xml = set_nc_config(self.module, xml_str)
620        self.check_response(recv_xml, "DELETE_PROCESS")
621        self.updates_cmd.append("undo ospf %s" % self.process_id)
622        self.changed = True
623
624    def merge_process(self):
625        """merge ospf process"""
626
627        xml_area = ""
628        xml_str = ""
629        self.updates_cmd.append("ospf %s" % self.process_id)
630
631        # nexthop weight
632        xml_nh = ""
633        if self.nexthop_addr and self.is_nexthop_change():
634            xml_nh = CE_NC_XML_MERGE_NEXTHOP % (
635                self.nexthop_addr, self.nexthop_weight)
636            self.updates_cmd.append("nexthop %s weight %s" % (
637                self.nexthop_addr, self.nexthop_weight))
638
639        # max load balance
640        xml_lb = ""
641        if self.max_load_balance and self.ospf_info.get("maxLoadBalancing") != self.max_load_balance:
642            xml_lb = CE_NC_XML_SET_LB % self.max_load_balance
643            self.updates_cmd.append(
644                "maximum load-balancing %s" % self.max_load_balance)
645
646        xml_topo = ""
647        if xml_lb or xml_nh:
648            xml_topo = CE_NC_XML_BUILD_MERGE_TOPO % (xml_nh + xml_lb)
649
650        if self.area:
651            self.updates_cmd.append("area %s" % self.get_area_ip())
652            xml_network = ""
653            xml_auth = ""
654            if self.addr and self.mask:
655                if not self.is_network_exist():
656                    xml_network += CE_NC_XML_MERGE_NETWORKS % (
657                        self.addr, self.get_wildcard_mask())
658                    self.updates_cmd.append("network %s %s" % (
659                        self.addr, self.get_wildcard_mask()))
660
661            # NOTE: for security, authentication config will always be update
662            if self.auth_mode:
663                xml_auth += CE_NC_XML_SET_AUTH_MODE % self.auth_mode
664                if self.auth_mode == "none":
665                    self.updates_cmd.append("undo authentication-mode")
666                else:
667                    self.updates_cmd.append(
668                        "authentication-mode %s" % self.auth_mode)
669                if self.auth_mode == "simple" and self.auth_text_simple:
670                    xml_auth += CE_NC_XML_SET_AUTH_TEXT_SIMPLE % self.auth_text_simple
671                    self.updates_cmd.pop()
672                    self.updates_cmd.append(
673                        "authentication-mode %s %s" % (self.auth_mode, self.auth_text_simple))
674                if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]:
675                    if self.auth_key_id and self.auth_text_md5:
676                        xml_auth += CE_NC_XML_SET_AUTH_MD5 % (
677                            self.auth_key_id, self.auth_text_md5)
678                        self.updates_cmd.pop()
679                        self.updates_cmd.append(
680                            "authentication-mode %s %s %s" % (self.auth_mode, self.auth_key_id, self.auth_text_md5))
681            if xml_network or xml_auth or not self.is_area_exist():
682                xml_area += CE_NC_XML_BUILD_MERGE_AREA % (
683                    self.get_area_ip(), xml_network + xml_auth)
684            elif self.is_area_exist():
685                self.updates_cmd.pop()  # remove command: area
686            else:
687                pass
688
689        if xml_area or xml_topo:
690            xml_str = CE_NC_XML_BUILD_MERGE_PROCESS % (
691                self.process_id, xml_topo + xml_area)
692            recv_xml = set_nc_config(self.module, xml_str)
693            self.check_response(recv_xml, "MERGE_PROCESS")
694            self.changed = True
695
696    def remove_area_network(self):
697        """remvoe ospf area network"""
698
699        if not self.is_network_exist():
700            return
701
702        xml_network = CE_NC_XML_DELETE_NETWORKS % (
703            self.addr, self.get_wildcard_mask())
704        xml_area = CE_NC_XML_BUILD_AREA % (self.get_area_ip(), xml_network)
705        xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area)
706        recv_xml = set_nc_config(self.module, xml_str)
707        self.check_response(recv_xml, "DELETE_AREA_NETWORK")
708        self.updates_cmd.append("ospf %s" % self.process_id)
709        self.updates_cmd.append("area %s" % self.get_area_ip())
710        self.updates_cmd.append("undo network %s %s" %
711                                (self.addr, self.get_wildcard_mask()))
712        self.changed = True
713
714    def remove_area(self):
715        """remove ospf area"""
716
717        if not self.is_area_exist():
718            return
719
720        xml_area = CE_NC_XML_BUILD_DELETE_AREA % (self.get_area_ip(), "")
721        xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_area)
722        recv_xml = set_nc_config(self.module, xml_str)
723        self.check_response(recv_xml, "DELETE_AREA")
724        self.updates_cmd.append("ospf %s" % self.process_id)
725        self.updates_cmd.append("undo area %s" % self.get_area_ip())
726        self.changed = True
727
728    def remove_nexthop(self):
729        """remove ospf nexthop weight"""
730
731        if not self.is_nexthop_exist():
732            return
733
734        xml_nh = CE_NC_XML_DELETE_NEXTHOP % self.nexthop_addr
735        xml_topo = CE_NC_XML_BUILD_TOPO % xml_nh
736        xml_str = CE_NC_XML_BUILD_PROCESS % (self.process_id, xml_topo)
737        recv_xml = set_nc_config(self.module, xml_str)
738        self.check_response(recv_xml, "DELETE_NEXTHOP_WEIGHT")
739        self.updates_cmd.append("ospf %s" % self.process_id)
740        self.updates_cmd.append("undo nexthop %s" % self.nexthop_addr)
741        self.changed = True
742
743    def is_valid_v4addr(self, addr):
744        """check is ipv4 addr is valid"""
745
746        if addr.find('.') != -1:
747            addr_list = addr.split('.')
748            if len(addr_list) != 4:
749                return False
750            for each_num in addr_list:
751                if not each_num.isdigit():
752                    return False
753                if int(each_num) > 255:
754                    return False
755            return True
756
757        return False
758
759    def convert_ip_to_network(self):
760        """convert ip to subnet address"""
761
762        ip_list = self.addr.split('.')
763        mask_list = self.get_wildcard_mask().split('.')
764
765        for i in range(len(ip_list)):
766            ip_list[i] = str((int(ip_list[i]) & (~int(mask_list[i]))) & 0xff)
767
768        self.addr = '.'.join(ip_list)
769
770    def check_params(self):
771        """Check all input params"""
772
773        # process_id check
774        if not self.process_id.isdigit():
775            self.module.fail_json(msg="Error: process_id is not digit.")
776        if int(self.process_id) < 1 or int(self.process_id) > 4294967295:
777            self.module.fail_json(
778                msg="Error: process_id must be an integer between 1 and 4294967295.")
779
780        if self.area:
781            # area check
782            if self.area.isdigit():
783                if int(self.area) < 0 or int(self.area) > 4294967295:
784                    self.module.fail_json(
785                        msg="Error: area id (Integer) must be between 0 and 4294967295.")
786
787            else:
788                if not self.is_valid_v4addr(self.area):
789                    self.module.fail_json(msg="Error: area id is invalid.")
790
791            # area network check
792            if self.addr:
793                if not self.is_valid_v4addr(self.addr):
794                    self.module.fail_json(
795                        msg="Error: network addr is invalid.")
796                if not self.mask.isdigit():
797                    self.module.fail_json(
798                        msg="Error: network mask is not digit.")
799                if int(self.mask) < 0 or int(self.mask) > 32:
800                    self.module.fail_json(
801                        msg="Error: network mask is invalid.")
802
803            # area authentication check
804            if self.state == "present" and self.auth_mode:
805                if self.auth_mode == "simple":
806                    if self.auth_text_simple and len(self.auth_text_simple) > 8:
807                        self.module.fail_json(
808                            msg="Error: auth_text_simple is not in the range from 1 to 8.")
809                if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]:
810                    if self.auth_key_id:
811                        if not self.auth_key_id.isdigit():
812                            self.module.fail_json(
813                                msg="Error: auth_key_id is not digit.")
814                        if int(self.auth_key_id) < 1 or int(self.auth_key_id) > 255:
815                            self.module.fail_json(
816                                msg="Error: auth_key_id is not in the range from 1 to 255.")
817                    if self.auth_text_md5 and len(self.auth_text_md5) > 255:
818                        self.module.fail_json(
819                            msg="Error: auth_text_md5 is not in the range from 1 to 255.")
820
821        # process max load balance check
822        if self.state == "present" and self.max_load_balance:
823            if not self.max_load_balance.isdigit():
824                self.module.fail_json(
825                    msg="Error: max_load_balance is not digit.")
826            if int(self.max_load_balance) < 1 or int(self.max_load_balance) > 64:
827                self.module.fail_json(
828                    msg="Error: max_load_balance is not in the range from 1 to 64.")
829
830        # process nexthop weight check
831        if self.nexthop_addr:
832            if not self.is_valid_v4addr(self.nexthop_addr):
833                self.module.fail_json(msg="Error: nexthop_addr is invalid.")
834            if not self.nexthop_weight.isdigit():
835                self.module.fail_json(
836                    msg="Error: nexthop_weight is not digit.")
837            if int(self.nexthop_weight) < 1 or int(self.nexthop_weight) > 254:
838                self.module.fail_json(
839                    msg="Error: nexthop_weight is not in the range from 1 to 254.")
840
841        if self.addr:
842            self.convert_ip_to_network()
843
844    def get_proposed(self):
845        """get proposed info"""
846
847        self.proposed["process_id"] = self.process_id
848        self.proposed["area"] = self.area
849        if self.area:
850            self.proposed["addr"] = self.addr
851            self.proposed["mask"] = self.mask
852            if self.auth_mode:
853                self.proposed["auth_mode"] = self.auth_mode
854                if self.auth_mode == "simple":
855                    self.proposed["auth_text_simple"] = self.auth_text_simple
856                if self.auth_mode in ["hmac-sha256", "hmac-sha256", "md5"]:
857                    self.proposed["auth_key_id"] = self.auth_key_id
858                    self.proposed["auth_text_md5"] = self.auth_text_md5
859
860        if self.nexthop_addr:
861            self.proposed["nexthop_addr"] = self.nexthop_addr
862            self.proposed["nexthop_weight"] = self.nexthop_weight
863        self.proposed["max_load_balance"] = self.max_load_balance
864        self.proposed["state"] = self.state
865
866    def get_existing(self):
867        """get existing info"""
868
869        if not self.ospf_info:
870            return
871
872        self.existing["process_id"] = self.process_id
873        self.existing["areas"] = self.ospf_info["areas"]
874        self.existing["nexthops"] = self.ospf_info["nexthops"]
875        self.existing["max_load_balance"] = self.ospf_info.get(
876            "maxLoadBalancing")
877
878    def get_end_state(self):
879        """get end state info"""
880
881        ospf_info = self.get_ospf_dict(self.process_id)
882
883        if not ospf_info:
884            return
885
886        self.end_state["process_id"] = self.process_id
887        self.end_state["areas"] = ospf_info["areas"]
888        self.end_state["nexthops"] = ospf_info["nexthops"]
889        self.end_state["max_load_balance"] = ospf_info.get("maxLoadBalancing")
890
891        if self.end_state == self.existing:
892            if not self.auth_text_simple and not self.auth_text_md5:
893                self.changed = False
894
895    def work(self):
896        """worker"""
897
898        self.check_params()
899        self.ospf_info = self.get_ospf_dict(self.process_id)
900        self.get_existing()
901        self.get_proposed()
902
903        # deal present or absent
904        if self.state == "present":
905            if not self.ospf_info:
906                # create ospf process
907                self.create_process()
908            else:
909                # merge ospf
910                self.merge_process()
911        else:
912            if self.ospf_info:
913                if self.area:
914                    if self.addr:
915                        # remove ospf area network
916                        self.remove_area_network()
917                    else:
918                        # remove ospf area
919                        self.remove_area()
920                if self.nexthop_addr:
921                    # remove ospf nexthop weight
922                    self.remove_nexthop()
923
924                if not self.area and not self.nexthop_addr:
925                    # remove ospf process
926                    self.delete_process()
927            else:
928                self.module.fail_json(msg='Error: ospf process does not exist')
929
930        self.get_end_state()
931        self.results['changed'] = self.changed
932        self.results['proposed'] = self.proposed
933        self.results['existing'] = self.existing
934        self.results['end_state'] = self.end_state
935        if self.changed:
936            self.results['updates'] = self.updates_cmd
937        else:
938            self.results['updates'] = list()
939
940        self.module.exit_json(**self.results)
941
942
943def main():
944    """Module main"""
945
946    argument_spec = dict(
947        process_id=dict(required=True, type='str'),
948        area=dict(required=False, type='str'),
949        addr=dict(required=False, type='str'),
950        mask=dict(required=False, type='str'),
951        auth_mode=dict(required=False,
952                       choices=['none', 'hmac-sha256', 'md5', 'hmac-md5', 'simple'], type='str'),
953        auth_text_simple=dict(required=False, type='str', no_log=True),
954        auth_key_id=dict(required=False, type='str'),
955        auth_text_md5=dict(required=False, type='str', no_log=True),
956        nexthop_addr=dict(required=False, type='str'),
957        nexthop_weight=dict(required=False, type='str'),
958        max_load_balance=dict(required=False, type='str'),
959        state=dict(required=False, default='present',
960                   choices=['present', 'absent'])
961    )
962    argument_spec.update(ce_argument_spec)
963    module = OSPF(argument_spec)
964    module.work()
965
966
967if __name__ == '__main__':
968    main()
969