1#
2# -*- coding: utf-8 -*-
3# Copyright 2019 Red Hat
4# GNU General Public License v3.0+
5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6"""
7The iosxr_ospfv2 class
8It is in this file where the current configuration (as dict)
9is compared to the provided configuration (as dict) and the command set
10necessary to bring the current configuration to it's desired end-state is
11created
12"""
13from __future__ import absolute_import, division, print_function
14
15__metaclass__ = type
16
17from copy import deepcopy
18from ansible.module_utils.six import iteritems
19from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.facts import (
20    Facts,
21)
22
23from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.rm_templates.ospfv2 import (
24    Ospfv2Template,
25)
26from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import (
27    ResourceModule,
28)
29from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
30    dict_merge,
31)
32
33
34class Ospfv2(ResourceModule):
35    """
36    The ios_ospfv2 class
37    """
38
39    gather_subset = ["!all", "!min"]
40
41    gather_network_resources = ["ospfv2"]
42
43    def __init__(self, module):
44        super(Ospfv2, self).__init__(
45            empty_fact_val={},
46            facts_module=Facts(module),
47            module=module,
48            resource="ospfv2",
49            tmplt=Ospfv2Template(),
50        )
51
52    def execute_module(self):
53        """ Execute the module
54        :rtype: A dictionary
55        :returns: The result from module execution
56        """
57        self.gen_config()
58        self.run_commands()
59
60        return self.result
61
62    def gen_config(self):
63        """ Select the appropriate function based on the state provided
64        :rtype: A list
65        :returns: the commands necessary to migrate the current configuration
66                  to the desired configuration
67        """
68        if self.want:
69            wantd = {
70                (entry["process_id"], entry.get("vrf")): entry
71                for entry in self.want.get("processes", [])
72            }
73        else:
74            wantd = {}
75        if self.have:
76            haved = {
77                (entry["process_id"], entry.get("vrf")): entry
78                for entry in self.have.get("processes", [])
79            }
80        else:
81            haved = {}
82
83        # turn all lists of dicts into dicts prior to merge
84        for thing in wantd, haved:
85            for _pid, proc in iteritems(thing):
86                for area in proc.get("areas", []):
87                    virtual_link = {
88                        entry["id"]: entry
89                        for entry in area.get("virtual_link", [])
90                    }
91                    if bool(virtual_link):
92                        area["virtual_link"] = virtual_link
93                    ranges = {
94                        entry["address"]: entry
95                        for entry in area.get("ranges", [])
96                    }
97                    if bool(ranges):
98                        area["ranges"] = ranges
99
100                proc["areas"] = {
101                    entry["area_id"]: entry for entry in proc.get("areas", [])
102                }
103                if proc.get("distribute_list"):
104                    if "acls" in proc.get("distribute_list"):
105                        proc["distribute_list"]["acls"] = {
106                            entry["name"]: entry
107                            for entry in proc["distribute_list"].get(
108                                "acls", []
109                            )
110                        }
111
112        # if state is merged, merge want onto have
113        if self.state == "merged":
114            wantd = dict_merge(haved, wantd)
115
116        # if state is deleted, limit the have to anything in want
117        # set want to nothing
118        if self.state == "deleted":
119            haved = {
120                k: v for k, v in iteritems(haved) if k in wantd or not wantd
121            }
122            wantd = {}
123
124        # delete processes first so we do run into "more than one" errors
125        if self.state == "deleted":
126            haved_del = deepcopy(haved)
127            want_process = {}
128            for k, t_want in iteritems(haved_del):
129                want_process["process_id"] = t_want.get("process_id")
130                if not (len(t_want) == 2 and not t_want.get("areas")):
131                    self._compare(want=want_process, have=haved_del.get(k, {}))
132        if self.state == "overridden":
133            haved_del = deepcopy(haved)
134            want = {}
135            for k, t_want in iteritems(haved_del):
136                if k not in wantd:
137                    want["process_id"] = t_want.get("process_id")
138                    if not (len(t_want) == 2 and not t_want.get("areas")):
139                        self._compare(want=want, have=haved_del.get(k, {}))
140
141        for k, want in iteritems(wantd):
142            self._compare(want=want, have=haved.pop(k, {}))
143
144    def _compare(self, want, have):
145        parsers = [
146            "bfd",
147            "cost",
148            "weight",
149            "passive",
150            "priority",
151            "protocol",
152            "auto_cost",
153            "bandwidth",
154            "flood_reduction",
155            "default_metric",
156            "default_weight",
157            "router_id",
158            "demand_circuit",
159            "packet_size",
160            "transmit_delay",
161            "summary_in",
162            "external_out",
163            "dead_interval",
164            "hello_interval",
165            "authentication",
166            "adjacency_stagger",
167            "retransmit_interval",
168            "mtu_ignore",
169            "bfd.fast_detect",
170            "capability",
171            "capability.opaque",
172            "admin_distance",
173            "ospf_distance",
174            "address_family_unicast",
175            "loopback_stub_network",
176            "authentication.message_digest",
177            "default_information_originate",
178            "link_down_fast_detect",
179            "nsr",
180            "database_filter",
181            "log_adjacency",
182            "distribute_bgp_ls",
183            "distribute_link_state",
184            "max_lsa",
185            "max_metric",
186            "mpls_ldp",
187            "mpls_traffic_eng",
188            "microloop_avoidance",
189            "prefix_suppression",
190            "protocol_shutdown",
191            "timers.lsa",
192            "timers.graceful_shutdown",
193            "throttle.lsa_all",
194            "throttle.spf",
195            "throttle.fast_reroute",
196            "timers.pacing_flood",
197        ]
198
199        if want != have:
200            self.addcmd(want or have, "pid", False)
201            self.compare(parsers, want, have)
202            self._areas_compare(want, have)
203
204    def _areas_compare(self, want, have):
205        wareas = want.get("areas", {})
206        hareas = have.get("areas", {})
207        for name, entry in iteritems(wareas):
208            self._area_compare(want=entry, have=hareas.pop(name, {}))
209        for name, entry in iteritems(hareas):
210            self._area_compare(want={}, have=entry)
211
212    def _area_compare(self, want, have):
213        parsers = [
214            "area.authentication",
215            "area.authentication_key",
216            "area.authentication.message_digest",
217            "area.mpls_traffic_eng",
218            "area.mpls_ldp",
219            "area.bfd",
220            "area.bfd.fast_detect",
221            "area.nssa",
222            "area.nssa.default_information_originate",
223            "area.nssa.translate",
224            "area.default_cost",
225            "area.stub",
226            "area.ranges",
227            "area.cost",
228            "area.dead_interval",
229            "area.hello_interval",
230            "area.transmit_delay",
231            "area.mtu_ignore",
232            "area.packet_size",
233            "area.priority",
234            "area.weight",
235            "area.external_out",
236            "area.summary_in",
237            "area.demand_circuit",
238            "area.passive",
239        ]
240        self.compare(parsers=parsers, want=want, have=have)
241        self._areas_compare_virtual_link(want, have)
242
243    def _areas_compare_virtual_link(self, want, have):
244        wvlinks = want.get("virtual_link", {})
245        hvlinks = have.get("virtual_link", {})
246        for name, entry in iteritems(wvlinks):
247            self._area_compare_virtual_link(
248                want=entry, have=hvlinks.pop(name, {})
249            )
250        for name, entry in iteritems(hvlinks):
251            self._area_compare_virtual_link(want={}, have=entry)
252
253    def _area_compare_virtual_link(self, want, have):
254        parsers = [
255            "virtual_link.authentication",
256            "virtual_link.authentication_key",
257            "virtual_link.authentication.message_digest",
258            "virtual_link.hello_interval",
259            "virtual_link.dead_interval",
260            "virtual_link.retransmit_interval",
261        ]
262        self.compare(parsers=parsers, want=want, have=have)
263