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, dest_ipv6_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.dest_ipv6_mac_addr  = dest_ipv6_mac_addr
199        self.ipv4_addr      = ipv4_addr
200        self.ipv6_addr      = ipv6_addr
201        self.pair_parent    = None     # a pointer to CDualIfObj which holds this interface and its pair-complement
202
203    def __get_and_increment_id (self):
204        self._obj_id = CIfObj._obj_id
205        CIfObj._obj_id += 1
206
207    def get_name (self):
208        return self.if_name
209
210    def get_src_mac_addr (self):
211        return self.src_mac_addr
212
213    def get_dest_mac (self):
214        return self.dest_mac_addr
215
216    def get_ipv6_dest_mac (self):
217        if self.dest_mac_addr != 0:
218            return self.dest_mac_addr
219        else:
220            return self.dest_ipv6_mac_addr
221
222    def get_id (self):
223        return self._obj_id
224
225    def get_if_type (self):
226        return self.if_type
227
228    def get_ipv4_addr (self):
229        return self.ipv4_addr
230
231    def get_ipv6_addr (self):
232        return self.ipv6_addr
233
234    def set_ipv4_addr (self, addr):
235        self.ipv4_addr = addr
236
237    def set_ipv6_addr (self, addr):
238        self.ipv6_addr = addr
239
240    def set_pair_parent (self, dual_if_obj):
241        self.pair_parent = dual_if_obj
242
243    def get_pair_parent (self):
244        return self.pair_parent
245
246    def is_client (self):
247        return (self.if_type == IFType.Client)
248
249    def is_server (self):
250        return (self.if_type == IFType.Server)
251
252    pass
253
254
255class CDualIfObj(object):
256    _obj_id = 0
257
258    def __init__(self, vrf_name, client_if_obj, server_if_obj):
259        self.__get_and_increment_id()
260        self.vrf_name       = vrf_name
261        self.client_if      = client_if_obj
262        self.server_if      = server_if_obj
263
264        # link if_objects to its parent dual_if
265        self.client_if.set_pair_parent(self)
266        self.server_if.set_pair_parent(self)
267        pass
268
269    def __get_and_increment_id (self):
270        self._obj_id = CDualIfObj._obj_id
271        CDualIfObj._obj_id += 1
272
273    def get_id (self):
274        return self._obj_id
275
276    def get_vrf_name (self):
277        return self.vrf_name
278
279    def is_duplicated (self):
280        return self.vrf_name != None
281
282class CIfManager(object):
283    _ipv4_gen = misc_methods.get_network_addr()
284    _ipv6_gen = misc_methods.get_network_addr(ip_type = 'ipv6')
285
286    def __init__(self):
287        self.interfarces     = OrderedDict()
288        self.dual_intf       = []
289        self.full_device_cfg = None
290
291    def __add_if_to_manager (self, if_obj):
292        self.interfarces[if_obj.get_name()] = if_obj
293
294    def __add_dual_if_to_manager (self, dual_if_obj):
295        self.dual_intf.append(dual_if_obj)
296
297    def __get_ipv4_net_client_addr(self, ipv4_addr):
298        return misc_methods.get_single_net_client_addr (ipv4_addr)
299
300    def __get_ipv6_net_client_addr(self, ipv6_addr):
301        return misc_methods.get_single_net_client_addr (ipv6_addr, {'7' : 1}, ip_type = 'ipv6')
302
303    def load_config (self, device_config_obj):
304        self.full_device_cfg = device_config_obj
305        # first, erase all current config
306        self.interfarces.clear()
307        del self.dual_intf[:]
308
309        # than, load the configuration
310        intf_config = device_config_obj.get_interfaces_cfg()
311
312        # finally, parse the information into data-structures
313        for intf_pair in intf_config:
314            # generate network addresses for client side, and initialize client if object
315            tmp_ipv4_addr = self.__get_ipv4_net_client_addr (next(CIfManager._ipv4_gen)[0])
316            tmp_ipv6_addr = self.__get_ipv6_net_client_addr (next(CIfManager._ipv6_gen))
317
318            if 'dest_mac_addr' in intf_pair['client']:
319                client_dest_mac = intf_pair['client']['dest_mac_addr']
320            else:
321                client_dest_mac = 0
322            if 'dest_ipv6_mac_addr' in intf_pair['client']:
323                client_dest_ipv6_mac = intf_pair['client']['dest_ipv6_mac_addr']
324            else:
325                client_dest_ipv6_mac = 0
326            client_obj = CIfObj(if_name = intf_pair['client']['name'],
327                ipv4_addr = tmp_ipv4_addr,
328                ipv6_addr = tmp_ipv6_addr,
329                src_mac_addr  = intf_pair['client']['src_mac_addr'],
330                dest_mac_addr = client_dest_mac,
331                dest_ipv6_mac_addr = client_dest_ipv6_mac,
332                if_type   = IFType.Client)
333
334            # generate network addresses for server side, and initialize server if object
335            tmp_ipv4_addr = self.__get_ipv4_net_client_addr (next(CIfManager._ipv4_gen)[0])
336            tmp_ipv6_addr = self.__get_ipv6_net_client_addr (next(CIfManager._ipv6_gen))
337
338            if 'dest_mac_addr' in intf_pair['server']:
339                server_dest_mac = intf_pair['server']['dest_mac_addr']
340            else:
341                server_dest_mac = 0
342            if 'dest_ipv6_mac_addr' in intf_pair['server']:
343                server_dest_ipv6_mac = intf_pair['server']['dest_ipv6_mac_addr']
344            else:
345                server_dest_ipv6_mac = 0
346            server_obj = CIfObj(if_name = intf_pair['server']['name'],
347                ipv4_addr = tmp_ipv4_addr,
348                ipv6_addr = tmp_ipv6_addr,
349                src_mac_addr  = intf_pair['server']['src_mac_addr'],
350                dest_mac_addr = server_dest_mac,
351                dest_ipv6_mac_addr = server_dest_ipv6_mac,
352                if_type   = IFType.Server)
353
354            dual_intf_obj = CDualIfObj(vrf_name = intf_pair['vrf_name'],
355                client_if_obj = client_obj,
356                server_if_obj = server_obj)
357
358            # update single interfaces pointers
359            client_obj.set_pair_parent(dual_intf_obj)
360            server_obj.set_pair_parent(dual_intf_obj)
361
362            # finally, update the data-structures with generated objects
363            self.__add_if_to_manager(client_obj)
364            self.__add_if_to_manager(server_obj)
365            self.__add_dual_if_to_manager(dual_intf_obj)
366
367
368    def get_if_list (self, if_type = IFType.All, is_duplicated = None):
369        result = []
370        for if_name,if_obj in self.interfarces.items():
371            if (if_type == IFType.All) or ( if_obj.get_if_type() == if_type) :
372                if (is_duplicated is None) or (if_obj.get_pair_parent().is_duplicated() == is_duplicated):
373                    # append this if_obj only if matches both IFType and is_duplicated conditions
374                    result.append(if_obj)
375        return result
376
377    def get_duplicated_if (self):
378        result = []
379        for dual_if_obj in self.dual_intf:
380            if dual_if_obj.get_vrf_name() is not None :
381                result.extend( (dual_if_obj.client_if, dual_if_obj.server_if) )
382        return result
383
384    def get_dual_if_list (self, is_duplicated = None):
385        result = []
386        for dual_if in self.dual_intf:
387            if (is_duplicated is None) or (dual_if.is_duplicated() == is_duplicated):
388                result.append(dual_if)
389        return result
390
391    def dump_if_config (self):
392        if self.full_device_cfg is None:
393            print("Device configuration isn't loaded.\nPlease load config and try again.")
394        else:
395            self.full_device_cfg.dump_config()
396
397
398class AuthError(Exception):
399    pass
400
401class CIosTelnet(telnetlib.Telnet):
402    AuthError = AuthError
403
404    # wrapper for compatibility with Python2/3, convert input to bytes
405    def str_to_bytes_wrapper(self, func, text, *args, **kwargs):
406        if type(text) in (list, tuple):
407            text = [elem.encode('ascii') if type(elem) is str else elem for elem in text]
408        res = func(self, text.encode('ascii') if type(text) is str else text, *args, **kwargs)
409        return res.decode() if type(res) is bytes else res
410
411    def read_until(self, *args, **kwargs):
412        return self.str_to_bytes_wrapper(telnetlib.Telnet.read_until, *args, **kwargs)
413
414    def write(self, *args, **kwargs):
415        return self.str_to_bytes_wrapper(telnetlib.Telnet.write, *args, **kwargs)
416
417    def expect(self, *args, **kwargs):
418        res = self.str_to_bytes_wrapper(telnetlib.Telnet.expect, *args, **kwargs)
419        return [elem.decode() if type(elem) is bytes else elem for elem in res]
420
421    def __init__ (self, host, line_pass, en_pass, port = 23, str_wait = "#"):
422        telnetlib.Telnet.__init__(self)
423        self.host           = host
424        self.port           = port
425        self.line_passwd    = line_pass
426        self.enable_passwd  = en_pass
427        self.pr             = str_wait
428#       self.set_debuglevel (1)
429        try:
430            self.open(self.host,self.port, timeout = 5)
431            self.read_until("word:",1)
432            self.write("{line_pass}\n".format(line_pass = self.line_passwd) )
433            res = self.read_until(">",1)
434            if 'Password' in res:
435                raise AuthError('Invalid line password was provided')
436            self.write("enable 15\n")
437            self.read_until("d:",1)
438            self.write("{en_pass}\n".format(en_pass = self.enable_passwd) )
439            res = self.read_until(self.pr,1)
440            if 'Password' in res:
441                raise AuthError('Invalid en password was provided')
442            self.write_ios_cmd(['terminal length 0'])
443
444        except socket.timeout:
445            raise socket.timeout('A timeout error has occured.\nCheck platform connectivity or the hostname defined in the config file')
446        except Exception as inst:
447            raise
448
449    def write_ios_cmd (self, cmd_list, result_from = 0, timeout = 60, **kwargs):
450        assert (isinstance (cmd_list, list) == True)
451        self.read_until(self.pr, timeout = 1)
452
453        res = ''
454        if 'read_until' in kwargs:
455            wf = kwargs['read_until']
456        else:
457            wf = self.pr
458
459        for idx, cmd in enumerate(cmd_list):
460            start_time = time.time()
461            self.write(cmd+'\r\n')
462            if kwargs.get('debug_mode'):
463                print('-->\n%s' % cmd)
464            if type(wf) is list:
465                output = self.expect(wf, timeout)[2]
466            else:
467                output = self.read_until(wf, timeout)
468            if idx >= result_from:
469                res += output
470            if kwargs.get('debug_mode'):
471                print('<-- (%ss)\n%s' % (round(time.time() - start_time, 2), output))
472            if time.time() - start_time > timeout - 1:
473                raise Exception('Timeout while performing telnet command: %s' % cmd)
474        if 'Invalid' in res:
475            print('Warning: telnet command probably failed.\nCommand: %s\nResponse: %s' % (cmd_list, res))
476#       return res.split('\r\n')
477        return res  # return the received response as a string, each line is seperated by '\r\n'.
478
479
480if __name__ == "__main__":
481#   dev_cfg = CDeviceCfg('config/config.yaml')
482#   print dev_cfg.get_platform_connection_data()
483#   telnet = CIosTelnet( **(dev_cfg.get_platform_connection_data() ) )
484
485#   if_mng  = CIfManager()
486#   if_mng.load_config(dev_cfg)
487#   if_mng.dump_config()
488    pass
489