1# -*- coding: utf-8 -*-
2# Copyright 2019 Red Hat
3# GNU General Public License v3.0+
4# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6"""
7The exos legacy fact class
8It is in this file the configuration is collected from the device
9for a given resource, parsed, and the facts tree is populated
10based on the configuration.
11"""
12
13from __future__ import absolute_import, division, print_function
14__metaclass__ = type
15
16
17import re
18import json
19
20from ansible.module_utils.network.exos.exos import run_commands
21from ansible.module_utils.basic import AnsibleModule
22from ansible.module_utils.six import iteritems
23
24
25class FactsBase(object):
26
27    COMMANDS = list()
28
29    def __init__(self, module):
30        self.module = module
31        self.facts = dict()
32        self.warnings = list()
33        self.responses = None
34
35    def populate(self):
36        self.responses = run_commands(self.module, self.COMMANDS)
37
38    def run(self, cmd):
39        return run_commands(self.module, cmd)
40
41
42class Default(FactsBase):
43
44    COMMANDS = [
45        'show version',
46        'show switch'
47    ]
48
49    def populate(self):
50        super(Default, self).populate()
51        data = self.responses[0]
52        if data:
53            self.facts['version'] = self.parse_version(data)
54            self.facts['serialnum'] = self.parse_serialnum(data)
55
56        data = self.responses[1]
57        if data:
58            self.facts['model'] = self.parse_model(data)
59            self.facts['hostname'] = self.parse_hostname(data)
60
61    def parse_version(self, data):
62        match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data)
63        if match:
64            return match.group(1)
65
66    def parse_model(self, data):
67        match = re.search(r'System Type:\s+(.*$)', data, re.M)
68        if match:
69            return match.group(1)
70
71    def parse_hostname(self, data):
72        match = re.search(r'SysName:\s+(\S+)', data, re.M)
73        if match:
74            return match.group(1)
75
76    def parse_serialnum(self, data):
77        match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M)
78        if match:
79            return match.group(1)
80        # For stack, return serial number of the first switch in the stack.
81        match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M)
82        if match:
83            return match.group(1)
84        # Handle unique formatting for VM
85        match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M)
86        if match:
87            return match.group(1)
88
89
90class Hardware(FactsBase):
91
92    COMMANDS = [
93        'show memory'
94    ]
95
96    def populate(self):
97        super(Hardware, self).populate()
98        data = self.responses[0]
99        if data:
100            self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0))
101            self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0))
102
103    def parse_memtotal(self, data):
104        match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M)
105        if match:
106            return match.group(1)
107        # Handle unique formatting for VM
108        match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M)
109        if match:
110            return match.group(1)
111
112    def parse_memfree(self, data):
113        match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M)
114        if match:
115            return match.group(1)
116
117
118class Config(FactsBase):
119
120    COMMANDS = ['show configuration detail']
121
122    def populate(self):
123        super(Config, self).populate()
124        data = self.responses[0]
125        if data:
126            self.facts['config'] = data
127
128
129class Interfaces(FactsBase):
130
131    COMMANDS = [
132        'show switch',
133        {'command': 'show port config', 'output': 'json'},
134        {'command': 'show port description', 'output': 'json'},
135        {'command': 'show vlan detail', 'output': 'json'},
136        {'command': 'show lldp neighbors', 'output': 'json'}
137    ]
138
139    def populate(self):
140        super(Interfaces, self).populate()
141
142        self.facts['all_ipv4_addresses'] = list()
143        self.facts['all_ipv6_addresses'] = list()
144
145        data = self.responses[0]
146        if data:
147            sysmac = self.parse_sysmac(data)
148
149        data = self.responses[1]
150        if data:
151            self.facts['interfaces'] = self.populate_interfaces(data, sysmac)
152
153        data = self.responses[2]
154        if data:
155            self.populate_interface_descriptions(data)
156
157        data = self.responses[3]
158        if data:
159            self.populate_vlan_interfaces(data, sysmac)
160
161        data = self.responses[4]
162        if data:
163            self.facts['neighbors'] = self.parse_neighbors(data)
164
165    def parse_sysmac(self, data):
166        match = re.search(r'System MAC:\s+(\S+)', data, re.M)
167        if match:
168            return match.group(1)
169
170    def populate_interfaces(self, interfaces, sysmac):
171        facts = dict()
172        for elem in interfaces:
173            intf = dict()
174
175            if 'show_ports_config' not in elem:
176                continue
177
178            key = str(elem['show_ports_config']['port'])
179
180            if elem['show_ports_config']['linkState'] == 2:
181                # Link state is "not present", don't include
182                continue
183
184            intf['type'] = 'Ethernet'
185            intf['macaddress'] = sysmac
186            intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg'])
187            intf['bandwidth'] = str(elem['show_ports_config']['speedActual'])
188            intf['duplex_configured'] = elem['show_ports_config']['duplexCfg']
189            intf['duplex'] = elem['show_ports_config']['duplexActual']
190            if elem['show_ports_config']['linkState'] == 1:
191                intf['lineprotocol'] = 'up'
192            else:
193                intf['lineprotocol'] = 'down'
194            if elem['show_ports_config']['portState'] == 1:
195                intf['operstatus'] = 'up'
196            else:
197                intf['operstatus'] = 'admin down'
198
199            facts[key] = intf
200        return facts
201
202    def populate_interface_descriptions(self, data):
203        for elem in data:
204            if 'show_ports_description' not in elem:
205                continue
206            key = str(elem['show_ports_description']['port'])
207
208            if 'descriptionString' in elem['show_ports_description']:
209                desc = elem['show_ports_description']['descriptionString']
210                self.facts['interfaces'][key]['description'] = desc
211
212    def populate_vlan_interfaces(self, data, sysmac):
213        for elem in data:
214            if 'vlanProc' in elem:
215                key = elem['vlanProc']['name1']
216                if key not in self.facts['interfaces']:
217                    intf = dict()
218                    intf['type'] = 'VLAN'
219                    intf['macaddress'] = sysmac
220                    self.facts['interfaces'][key] = intf
221
222                if elem['vlanProc']['ipAddress'] != '0.0.0.0':
223                    self.facts['interfaces'][key]['ipv4'] = list()
224                    addr = elem['vlanProc']['ipAddress']
225                    subnet = elem['vlanProc']['maskForDisplay']
226                    ipv4 = dict(address=addr, subnet=subnet)
227                    self.add_ip_address(addr, 'ipv4')
228                    self.facts['interfaces'][key]['ipv4'].append(ipv4)
229
230            if 'rtifIpv6Address' in elem:
231                key = elem['rtifIpv6Address']['rtifName']
232                if key not in self.facts['interfaces']:
233                    intf = dict()
234                    intf['type'] = 'VLAN'
235                    intf['macaddress'] = sysmac
236                    self.facts['interfaces'][key] = intf
237                self.facts['interfaces'][key]['ipv6'] = list()
238                addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/')
239                ipv6 = dict(address=addr, subnet=subnet)
240                self.add_ip_address(addr, 'ipv6')
241                self.facts['interfaces'][key]['ipv6'].append(ipv6)
242
243    def add_ip_address(self, address, family):
244        if family == 'ipv4':
245            if address not in self.facts['all_ipv4_addresses']:
246                self.facts['all_ipv4_addresses'].append(address)
247        else:
248            if address not in self.facts['all_ipv6_addresses']:
249                self.facts['all_ipv6_addresses'].append(address)
250
251    def parse_neighbors(self, data):
252        facts = dict()
253        for elem in data:
254            if 'lldpPortNbrInfoShort' not in elem:
255                continue
256            intf = str(elem['lldpPortNbrInfoShort']['port'])
257            if intf not in facts:
258                facts[intf] = list()
259            fact = dict()
260            fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName']
261            fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID'])
262            facts[intf].append(fact)
263        return facts
264