platform_cmd_link.py revision 9c6e6d4b
1#!/router/bin/python
2
3from interfaces_e import IFType
4import CustomLogger
5import misc_methods
6import telnetlib
7import socket
8import time
9from collections import OrderedDict
10
11class CCommandCache(object):
12    def __init__(self):
13        self.__gen_clean_data_structure()
14
15    def __gen_clean_data_structure (self):
16        self.cache =  {"IF"   : OrderedDict(),
17                       "CONF" : [],
18                       "EXEC" : []}
19
20    def __list_append (self, dest_list, cmd):
21        if isinstance(cmd, list):
22            dest_list.extend( cmd )
23        else:
24            dest_list.append( cmd )
25
26    def add (self, cmd_type, cmd, interface = None):
27
28        if interface is not None: # this is an interface ("IF") config command
29            if interface in self.cache['IF']:
30                # interface commands already exists
31                self.__list_append(self.cache['IF'][interface], cmd)
32            else:
33                # no chached commands for this interface
34                self.cache['IF'][interface] = []
35                self.__list_append(self.cache['IF'][interface], cmd)
36        else:                 # this is either a CONF or EXEC command
37            self.__list_append(self.cache[cmd_type.upper()], cmd)
38
39    def dump_config (self):
40        # dump IF config:
41        print("configure terminal")
42        for intf, intf_cmd_list in self.cache['IF'].items():
43            print("interface {if_name}".format( if_name = intf ))
44            print('\n'.join(intf_cmd_list))
45
46        if self.cache['IF']:
47            # add 'exit' note only if if config actually took place
48            print('exit')    # exit to global config mode
49
50        # dump global config
51        if self.cache['CONF']:
52            print('\n'.join(self.cache['CONF']))
53
54        # exit back to en mode
55        print("exit")
56
57        # dump exec config
58        if self.cache['EXEC']:
59            print('\n'.join(self.cache['EXEC']))
60
61    def get_config_list (self):
62        conf_list = []
63
64        conf_list.append("configure terminal")
65        for intf, intf_cmd_list in self.cache['IF'].items():
66            conf_list.append( "interface {if_name}".format( if_name = intf ) )
67            conf_list.extend( intf_cmd_list )
68        if len(conf_list)>1:
69            # add 'exit' note only if if config actually took place
70            conf_list.append("exit")
71
72        conf_list.extend( self.cache['CONF'] )
73        conf_list.append("exit")
74        conf_list.extend( self.cache['EXEC'] )
75
76
77        return conf_list
78
79    def clear_cache (self):
80        # clear all pointers to cache data (erase the data structure)
81        self.cache.clear()
82        # Re-initialize the cache
83        self.__gen_clean_data_structure()
84
85    pass
86
87
88class CCommandLink(object):
89    def __init__(self, silent_mode = False, debug_mode = False):
90        self.history        = []
91        self.virtual_mode   = True
92        self.silent_mode    = silent_mode
93        self.telnet_con     = None
94        self.debug_mode     = debug_mode
95
96
97    def __transmit (self, cmd_list, **kwargs):
98        self.history.extend(cmd_list)
99        if not self.silent_mode:
100            print('\n'.join(cmd_list))   # prompting the pushed platform commands
101        if not self.virtual_mode:
102            # transmit the command to platform.
103            return self.telnet_con.write_ios_cmd(cmd_list, debug_mode = self.debug_mode, **kwargs)
104
105    def run_command (self, cmd_list, **kwargs):
106        response = ''
107        for cmd in cmd_list:
108
109            # check which type of cmd we handle
110            if isinstance(cmd, CCommandCache):
111                tmp_response = self.__transmit( cmd.get_config_list(), **kwargs )   # join the commands with new-line delimiter
112            else:
113                tmp_response = self.__transmit([cmd], **kwargs)
114            if not self.virtual_mode:
115                response += tmp_response
116        return response
117
118    def run_single_command (self, cmd, **kwargs):
119        return self.run_command([cmd], **kwargs)
120
121    def get_history (self, as_string = False):
122        if as_string:
123            return '\n'.join(self.history)
124        else:
125            return self.history
126
127    def clear_history (self):
128        # clear all pointers to history data (erase the data structure)
129        del self.history[:]
130        # Re-initialize the histoyr with clear one
131        self.history = []
132
133    def launch_platform_connectivity (self, device_config_obj):
134        connection_info = device_config_obj.get_platform_connection_data()
135        self.telnet_con     = CIosTelnet( **connection_info )
136        self.virtual_mode   = False # if physical connectivity was successful, toggle virtual mode off
137
138    def close_platform_connection(self):
139        if self.telnet_con is not None:
140            self.telnet_con.close()
141
142
143
144class CDeviceCfg(object):
145    def __init__(self, cfg_yaml_path = None):
146        if cfg_yaml_path is not None:
147            (self.platform_cfg, self.tftp_cfg) = misc_methods.load_complete_config_file(cfg_yaml_path)[1:3]
148
149            self.interfaces_cfg = self.platform_cfg['interfaces'] # extract only the router interface configuration
150
151    def set_platform_config(self, config_dict):
152        self.platform_cfg = config_dict
153        self.interfaces_cfg = self.platform_cfg['interfaces']
154
155    def set_tftp_config(self, tftp_cfg):
156        self.tftp_cfg = tftp_cfg
157
158    def get_interfaces_cfg (self):
159        return self.interfaces_cfg
160
161    def get_ip_address (self):
162        return self.__get_attr('ip_address')
163
164    def get_line_password (self):
165        return self.__get_attr('line_pswd')
166
167    def get_en_password (self):
168        return self.__get_attr('en_pswd')
169
170    def get_mgmt_interface (self):
171        return self.__get_attr('mgmt_interface')
172
173    def get_platform_connection_data (self):
174        return { 'host' : self.get_ip_address(), 'line_pass' : self.get_line_password(), 'en_pass' : self.get_en_password() }
175
176    def get_tftp_info (self):
177        return self.tftp_cfg
178
179    def get_image_name (self):
180        return self.__get_attr('image')
181
182    def __get_attr (self, attr):
183        return self.platform_cfg[attr]
184
185    def dump_config (self):
186        import yaml
187        print(yaml.dump(self.interfaces_cfg, default_flow_style=False))
188
189class CIfObj(object):
190    _obj_id = 0
191
192    def __init__(self, if_name, ipv4_addr, ipv6_addr, src_mac_addr, dest_mac_addr, if_type):
193        self.__get_and_increment_id()
194        self.if_name        = if_name
195        self.if_type        = if_type
196        self.src_mac_addr   = src_mac_addr
197        self.dest_mac_addr  = dest_mac_addr
198        self.ipv4_addr      = ipv4_addr
199        self.ipv6_addr      = ipv6_addr
200        self.pair_parent    = None     # a pointer to CDualIfObj which holds this interface and its pair-complement
201
202    def __get_and_increment_id (self):
203        self._obj_id = CIfObj._obj_id
204        CIfObj._obj_id += 1
205
206    def get_name (self):
207        return self.if_name
208
209    def get_src_mac_addr (self):
210        return self.src_mac_addr
211
212    def get_dest_mac (self):
213        return self.dest_mac_addr
214
215    def get_id (self):
216        return self._obj_id
217
218    def get_if_type (self):
219        return self.if_type
220
221    def get_ipv4_addr (self):
222        return self.ipv4_addr
223
224    def get_ipv6_addr (self):
225        return self.ipv6_addr
226
227    def set_ipv4_addr (self, addr):
228        self.ipv4_addr = addr
229
230    def set_ipv6_addr (self, addr):
231        self.ipv6_addr = addr
232
233    def set_pair_parent (self, dual_if_obj):
234        self.pair_parent = dual_if_obj
235
236    def get_pair_parent (self):
237        return self.pair_parent
238
239    def is_client (self):
240        return (self.if_type == IFType.Client)
241
242    def is_server (self):
243        return (self.if_type == IFType.Server)
244
245    pass
246
247
248class CDualIfObj(object):
249    _obj_id = 0
250
251    def __init__(self, vrf_name, client_if_obj, server_if_obj):
252        self.__get_and_increment_id()
253        self.vrf_name       = vrf_name
254        self.client_if      = client_if_obj
255        self.server_if      = server_if_obj
256
257        # link if_objects to its parent dual_if
258        self.client_if.set_pair_parent(self)
259        self.server_if.set_pair_parent(self)
260        pass
261
262    def __get_and_increment_id (self):
263        self._obj_id = CDualIfObj._obj_id
264        CDualIfObj._obj_id += 1
265
266    def get_id (self):
267        return self._obj_id
268
269    def get_vrf_name (self):
270        return self.vrf_name
271
272    def is_duplicated (self):
273        return self.vrf_name != None
274
275class CIfManager(object):
276    _ipv4_gen = misc_methods.get_network_addr()
277    _ipv6_gen = misc_methods.get_network_addr(ip_type = 'ipv6')
278
279    def __init__(self):
280        self.interfarces     = OrderedDict()
281        self.dual_intf       = []
282        self.full_device_cfg = None
283
284    def __add_if_to_manager (self, if_obj):
285        self.interfarces[if_obj.get_name()] = if_obj
286
287    def __add_dual_if_to_manager (self, dual_if_obj):
288        self.dual_intf.append(dual_if_obj)
289
290    def __get_ipv4_net_client_addr(self, ipv4_addr):
291        return misc_methods.get_single_net_client_addr (ipv4_addr)
292
293    def __get_ipv6_net_client_addr(self, ipv6_addr):
294        return misc_methods.get_single_net_client_addr (ipv6_addr, {'7' : 1}, ip_type = 'ipv6')
295
296    def load_config (self, device_config_obj):
297        self.full_device_cfg = device_config_obj
298        # first, erase all current config
299        self.interfarces.clear()
300        del self.dual_intf[:]
301
302        # than, load the configuration
303        intf_config = device_config_obj.get_interfaces_cfg()
304
305        # finally, parse the information into data-structures
306        for intf_pair in intf_config:
307            # generate network addresses for client side, and initialize client if object
308            tmp_ipv4_addr = self.__get_ipv4_net_client_addr (next(CIfManager._ipv4_gen)[0])
309            tmp_ipv6_addr = self.__get_ipv6_net_client_addr (next(CIfManager._ipv6_gen))
310
311            if 'dest_mac_addr' in intf_pair['client']:
312                client_dest_mac = intf_pair['client']['dest_mac_addr']
313            else:
314                client_dest_mac = 0
315            client_obj = CIfObj(if_name = intf_pair['client']['name'],
316                ipv4_addr = tmp_ipv4_addr,
317                ipv6_addr = tmp_ipv6_addr,
318                src_mac_addr  = intf_pair['client']['src_mac_addr'],
319                dest_mac_addr = client_dest_mac,
320                if_type   = IFType.Client)
321
322            # generate network addresses for server side, and initialize server if object
323            tmp_ipv4_addr = self.__get_ipv4_net_client_addr (next(CIfManager._ipv4_gen)[0])
324            tmp_ipv6_addr = self.__get_ipv6_net_client_addr (next(CIfManager._ipv6_gen))
325
326            if 'dest_mac_addr' in intf_pair['server']:
327                server_dest_mac = intf_pair['server']['dest_mac_addr']
328            else:
329                server_dest_mac = 0
330            server_obj = CIfObj(if_name = intf_pair['server']['name'],
331                ipv4_addr = tmp_ipv4_addr,
332                ipv6_addr = tmp_ipv6_addr,
333                src_mac_addr  = intf_pair['server']['src_mac_addr'],
334                dest_mac_addr = server_dest_mac,
335                if_type   = IFType.Server)
336
337            dual_intf_obj = CDualIfObj(vrf_name = intf_pair['vrf_name'],
338                client_if_obj = client_obj,
339                server_if_obj = server_obj)
340
341            # update single interfaces pointers
342            client_obj.set_pair_parent(dual_intf_obj)
343            server_obj.set_pair_parent(dual_intf_obj)
344
345            # finally, update the data-structures with generated objects
346            self.__add_if_to_manager(client_obj)
347            self.__add_if_to_manager(server_obj)
348            self.__add_dual_if_to_manager(dual_intf_obj)
349
350
351    def get_if_list (self, if_type = IFType.All, is_duplicated = None):
352        result = []
353        for if_name,if_obj in self.interfarces.items():
354            if (if_type == IFType.All) or ( if_obj.get_if_type() == if_type) :
355                if (is_duplicated is None) or (if_obj.get_pair_parent().is_duplicated() == is_duplicated):
356                    # append this if_obj only if matches both IFType and is_duplicated conditions
357                    result.append(if_obj)
358        return result
359
360    def get_duplicated_if (self):
361        result = []
362        for dual_if_obj in self.dual_intf:
363            if dual_if_obj.get_vrf_name() is not None :
364                result.extend( (dual_if_obj.client_if, dual_if_obj.server_if) )
365        return result
366
367    def get_dual_if_list (self, is_duplicated = None):
368        result = []
369        for dual_if in self.dual_intf:
370            if (is_duplicated is None) or (dual_if.is_duplicated() == is_duplicated):
371                result.append(dual_if)
372        return result
373
374    def dump_if_config (self):
375        if self.full_device_cfg is None:
376            print("Device configuration isn't loaded.\nPlease load config and try again.")
377        else:
378            self.full_device_cfg.dump_config()
379
380
381class AuthError(Exception):
382    pass
383
384class CIosTelnet(telnetlib.Telnet):
385    AuthError = AuthError
386
387    # wrapper for compatibility with Python2/3, convert input to bytes
388    def str_to_bytes_wrapper(self, func, text, *args, **kwargs):
389        if type(text) in (list, tuple):
390            text = [elem.encode('ascii') if type(elem) is str else elem for elem in text]
391        res = func(self, text.encode('ascii') if type(text) is str else text, *args, **kwargs)
392        return res.decode() if type(res) is bytes else res
393
394    def read_until(self, *args, **kwargs):
395        return self.str_to_bytes_wrapper(telnetlib.Telnet.read_until, *args, **kwargs)
396
397    def write(self, *args, **kwargs):
398        return self.str_to_bytes_wrapper(telnetlib.Telnet.write, *args, **kwargs)
399
400    def expect(self, *args, **kwargs):
401        res = self.str_to_bytes_wrapper(telnetlib.Telnet.expect, *args, **kwargs)
402        return [elem.decode() if type(elem) is bytes else elem for elem in res]
403
404    def __init__ (self, host, line_pass, en_pass, port = 23, str_wait = "#"):
405        telnetlib.Telnet.__init__(self)
406        self.host           = host
407        self.port           = port
408        self.line_passwd    = line_pass
409        self.enable_passwd  = en_pass
410        self.pr             = str_wait
411#       self.set_debuglevel (1)
412        try:
413            self.open(self.host,self.port, timeout = 5)
414            self.read_until("word:",1)
415            self.write("{line_pass}\n".format(line_pass = self.line_passwd) )
416            res = self.read_until(">",1)
417            if 'Password' in res:
418                raise AuthError('Invalid line password was provided')
419            self.write("enable 15\n")
420            self.read_until("d:",1)
421            self.write("{en_pass}\n".format(en_pass = self.enable_passwd) )
422            res = self.read_until(self.pr,1)
423            if 'Password' in res:
424                raise AuthError('Invalid en password was provided')
425            self.write_ios_cmd(['terminal length 0'])
426
427        except socket.timeout:
428            raise socket.timeout('A timeout error has occured.\nCheck platform connectivity or the hostname defined in the config file')
429        except Exception as inst:
430            raise
431
432    def write_ios_cmd (self, cmd_list, result_from = 0, timeout = 60, **kwargs):
433        assert (isinstance (cmd_list, list) == True)
434        self.read_until(self.pr, timeout = 1)
435
436        res = ''
437        if 'read_until' in kwargs:
438            wf = kwargs['read_until']
439        else:
440            wf = self.pr
441
442        for idx, cmd in enumerate(cmd_list):
443            start_time = time.time()
444            self.write(cmd+'\r\n')
445            if kwargs.get('debug_mode'):
446                print('-->\n%s' % cmd)
447            if type(wf) is list:
448                output = self.expect(wf, timeout)[2]
449            else:
450                output = self.read_until(wf, timeout)
451            if idx >= result_from:
452                res += output
453            if kwargs.get('debug_mode'):
454                print('<-- (%ss)\n%s' % (round(time.time() - start_time, 2), output))
455            if time.time() - start_time > timeout - 1:
456                raise Exception('Timeout while performing telnet command: %s' % cmd)
457        if 'Invalid' in res:
458            print('Warning: telnet command probably failed.\nCommand: %s\nResponse: %s' % (cmd_list, res))
459#       return res.split('\r\n')
460        return res  # return the received response as a string, each line is seperated by '\r\n'.
461
462
463if __name__ == "__main__":
464#   dev_cfg = CDeviceCfg('config/config.yaml')
465#   print dev_cfg.get_platform_connection_data()
466#   telnet = CIosTelnet( **(dev_cfg.get_platform_connection_data() ) )
467
468#   if_mng  = CIfManager()
469#   if_mng.load_config(dev_cfg)
470#   if_mng.dump_config()
471    pass
472