class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command( 'execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command( 'fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local'][ 'Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local'][ 'Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local'][ 'Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local'][ 'Fortinet_SSLProxy'].del_param('certificate') self.device.commit() def get_facts(self): system_status = execute_get(self.device, 'get system status') performance_status = execute_get(self.device, 'get system performance status') interfaces = execute_get(self.device, 'get system interface | grep ==') interface_list = [x.split()[2] for x in interfaces.keys()] domain = execute_get(self.device, 'get system dns | grep domain')['domain'] return { 'vendor': 'Fortigate', 'os_version': system_status['Version'].split(',')[0].split()[1], 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': system_status['Serial-Number'], 'model': system_status['Version'].split(',')[0].split()[0], 'hostname': system_status['Hostname'], 'fqdn': '{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): interface_statistics = dict() self.device.load_config('system interface') for iface in self.get_facts()['interface_list']: try: hw_output = execute_get( self.device, 'diagnose hardware deviceinfo nic {}'.format(iface), auto=True) ifs = self._get_tab_separated_interfaces(hw_output) ifs['is_enabled'] = self.device.running_config[ 'system interface'][iface].get_param('status') != 'down' ifs['description'] = self.device.running_config[ 'system interface'][iface].get_param('description') ifs['last_flapped'] = None #ifs['mode'] = 'routed' ifs['last_flapped'] = None interface_statistics[iface] = ifs except CommandExecutionException: interface_statistics[iface] = self._get_unsupported_interfaces( ) return interface_statistics # def get_bgp_neighbors(self): # bgp_sum = self.device.execute_command('get router info bgp sum') # re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") # neighbors = {n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n)} # # peers = dict() # # self.device.load_config('router bgp') # # for neighbor, parameters in neighbors.iteritems(): # neigh_conf = self.device.running_config['router bgp']['neighbor']['{}'.format(neighbor)] # peers[neighbor] = dict() # peers[neighbor]['remote_as'] = int(neigh_conf.get_param('remote-as')) # peers[neighbor]['is_enabled'] = neigh_conf.get_param('shutdown') != 'enable' or False # peers[neighbor]['is_up'] = 'never' != parameters[7] or False # peers[neighbor]['uptime'] = convert_uptime_string_seconds(parameters[7]) # peers[neighbor]['accepted_prefixes'] = int(self.device.execute_command('get router info bgp neighbor {} | grep "accepted prefixes"'.format(neighbor))[0].split()[0]) # peers[neighbor]['sent_prefixes'] = int(self.device.execute_command('get router info bgp neighbor {} | grep "announced prefixes"'.format(neighbor))[0].split()[0]) # # received = self.device.execute_command('get router info bgp neighbors {} received-routes | grep prefixes'.format(neighbor))[0].split() # if len(received) > 0: # peers[neighbor]['received_prefixes'] = int(received[-1]) # else: # # Soft-reconfig is not enabled # peers[neighbor]['received_prefixes'] = 0 # # return { # 'default': { # 'local_as': int(bgp_sum[0].split()[7]), # 'router_id': bgp_sum[0].split()[3], # 'peers': peers # } # } def get_lldp_neighbors(self): return {}
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command('execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command('fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('certificate') self.device.commit() def get_facts(self): system_status = execute_get(self.device, 'get system status') performance_status = execute_get(self.device, 'get system performance status') interfaces = execute_get(self.device, 'get system interface | grep ==') interface_list = [x.split()[2] for x in interfaces.keys()] domain = execute_get(self.device, 'get system dns | grep domain')['domain'] return { 'vendor': 'Fortigate', 'os_version': system_status['Version'].split(',')[0].split()[1], 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': system_status['Serial-Number'], 'model': system_status['Version'].split(',')[0].split()[0], 'hostname': system_status['Hostname'], 'fqdn': '{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): interface_statistics = dict() self.device.load_config('system interface') for iface in self.get_facts()['interface_list']: try: hw_output = execute_get(self.device, 'diagnose hardware deviceinfo nic {}'.format(iface), auto=True) ifs = self._get_tab_separated_interfaces(hw_output) ifs['is_enabled'] = self.device.running_config['system interface'][iface].get_param('status') != 'down' ifs['description'] = self.device.running_config['system interface'][iface].get_param('description') ifs['last_flapped'] = None #ifs['mode'] = 'routed' ifs['last_flapped'] = None interface_statistics[iface] = ifs except CommandExecutionException: interface_statistics[iface] = self._get_unsupported_interfaces() return interface_statistics # def get_bgp_neighbors(self): # bgp_sum = self.device.execute_command('get router info bgp sum') # re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") # neighbors = {n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n)} # # peers = dict() # # self.device.load_config('router bgp') # # for neighbor, parameters in neighbors.iteritems(): # neigh_conf = self.device.running_config['router bgp']['neighbor']['{}'.format(neighbor)] # peers[neighbor] = dict() # peers[neighbor]['remote_as'] = int(neigh_conf.get_param('remote-as')) # peers[neighbor]['is_enabled'] = neigh_conf.get_param('shutdown') != 'enable' or False # peers[neighbor]['is_up'] = 'never' != parameters[7] or False # peers[neighbor]['uptime'] = convert_uptime_string_seconds(parameters[7]) # peers[neighbor]['accepted_prefixes'] = int(self.device.execute_command('get router info bgp neighbor {} | grep "accepted prefixes"'.format(neighbor))[0].split()[0]) # peers[neighbor]['sent_prefixes'] = int(self.device.execute_command('get router info bgp neighbor {} | grep "announced prefixes"'.format(neighbor))[0].split()[0]) # # received = self.device.execute_command('get router info bgp neighbors {} received-routes | grep prefixes'.format(neighbor))[0].split() # if len(received) > 0: # peers[neighbor]['received_prefixes'] = int(received[-1]) # else: # # Soft-reconfig is not enabled # peers[neighbor]['received_prefixes'] = 0 # # return { # 'default': { # 'local_as': int(bgp_sum[0].split()[7]), # 'router_id': bgp_sum[0].split()[3], # 'peers': peers # } # } def get_lldp_neighbors(self): return {}
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60, optional_args=None): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password, timeout=timeout) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command('execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command('fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('certificate') self.device.commit() def get_facts(self): system_status = execute_get(self.device, 'get system status') performance_status = execute_get(self.device, 'get system performance status') interfaces = execute_get(self.device, 'get system interface | grep ==') interface_list = [x.split()[2] for x in interfaces.keys()] domain = execute_get(self.device, 'get system dns | grep domain')['domain'] return { 'vendor': unicode('Fortigate'), 'os_version': unicode(system_status['Version'].split(',')[0].split()[1]), 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': unicode(system_status['Serial-Number']), 'model': unicode(system_status['Version'].split(',')[0].split()[0]), 'hostname': unicode(system_status['Hostname']), 'fqdn': u'{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): cmd_prefix = '' try: cmd_data = self.device.execute_command('diagnose hardware deviceinfo nic') except CommandExecutionException: cmd_data = self.device.execute_command('conf global\n diagnose hardware deviceinfo nic ') cmd_prefix = 'conf global\n' interface_list = [x.replace('\t', '') for x in cmd_data if x.startswith('\t')] interface_statistics = {} for interface in interface_list: if_data = self.device.execute_command(cmd_prefix + 'diagnose hardware deviceinfo nic {}'.format(interface)) parsed_data = {} if interface.startswith('mgmt'): for line in if_data: if line.startswith('Speed'): if line.split('\t')[-1].split(' ')[0].isdigit(): parsed_data['speed'] = int(line.split('\t')[-1].split(' ')[0]) else: parsed_data['speed'] = -1 elif line.startswith('Link'): parsed_data['is_up'] = line.split('\t')[-1] is 'up' elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = unicode(line.split('\t')[-1]) parsed_data['is_enabled'] = True parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 else: for line in if_data: if line.startswith('Admin'): parsed_data['is_enabled'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Status'): parsed_data['is_up'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Speed'): parsed_data['speed'] = int(line.split(':')[-1]) elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = unicode(line.split(' ')[-1]) parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 interface_statistics[interface] = parsed_data return interface_statistics @staticmethod def _search_line_in_lines(search, lines): for l in lines: if search in l: return l def get_bgp_neighbors(self): families = ['ipv4', 'ipv6'] terms = dict({'accepted_prefixes': 'accepted', 'sent_prefixes': 'announced'}) command_sum = 'get router info bgp sum' command_detail = 'get router info bgp neighbor {}' command_received = 'get router info bgp neighbors {} received-routes | grep prefixes ' peers = dict() bgp_sum = self.device.execute_command(command_sum) re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") neighbors = {n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n)} self.device.load_config('router bgp') for neighbor, parameters in neighbors.iteritems(): logger.debug('NEW PEER') neigh_conf = self.device.running_config['router bgp']['neighbor']['{}'.format(neighbor)] neighbor_dict = peers.get(neighbor, dict()) if not neighbor_dict: neighbor_dict['local_as'] = int(bgp_sum[0].split()[7]) neighbor_dict['remote_as'] = int(neigh_conf.get_param('remote-as')) neighbor_dict['is_up'] = 'never' != parameters[7] or False neighbor_dict['is_enabled'] = neigh_conf.get_param('shutdown') != 'enable' or False neighbor_dict['description'] = u'' neighbor_dict['uptime'] = convert_uptime_string_seconds(parameters[7]) neighbor_dict['address_family'] = dict() neighbor_dict['address_family']['ipv4'] = dict() neighbor_dict['address_family']['ipv6'] = dict() detail_output = [x.lower() for x in self.device.execute_command(command_detail.format(neighbor))] m = re.search('remote router id (.+?)\n', '\n'.join(detail_output)) if m: neighbor_dict['remote_id'] = unicode(m.group(1)) else: raise Exception('cannot find remote router id for %s' % neighbor) for family in families: # find block x = detail_output.index(' for address family: {} unicast'.format(family)) block = detail_output[x:] for term, fortiname in terms.iteritems(): text = self._search_line_in_lines('%s prefixes' % fortiname, block) t = [int(s) for s in text.split() if s.isdigit()][0] neighbor_dict['address_family'][family][term] = t received = self.device.execute_command( command_received.format(neighbor))[0].split() if len(received) > 0: neighbor_dict['address_family'][family]['received_prefixes'] = received[-1] else: # Soft-reconfig is not enabled neighbor_dict['address_family'][family]['received_prefixes'] = 0 peers[neighbor] = neighbor_dict return { 'global': { 'router_id': unicode(bgp_sum[0].split()[3]), 'peers': peers } } def get_interfaces_counters(self): cmd = self.device.execute_command('fnsysctl ifconfig') if_name = None interface_counters = dict() for line in cmd: data = line.split('\t') if (data[0] == '' or data[0] == ' ') and len(data) == 1: continue elif data[0] != '': if_name = data[0] interface_counters[if_name] = dict() elif (data[1].startswith('RX packets') or data[1].startswith('TX packets')) and if_name: if_data = data[1].split(' ') direction = if_data[0].lower() interface_counters[if_name][direction + '_unicast_packets'] = int(if_data[1].split(':')[1]) interface_counters[if_name][direction + '_errors'] = int(if_data[2].split(':')[1]) interface_counters[if_name][direction + '_discards'] = int(if_data[2].split(':')[1]) interface_counters[if_name][direction + '_multicast_packets'] = -1 interface_counters[if_name][direction + '_broadcast_packets'] = -1 elif data[1].startswith('RX bytes'): if_data = data[1].split(' ') interface_counters[if_name]['rx_octets'] = int(if_data[1].split(':')[1]) try: interface_counters[if_name]['tx_octets'] = int(if_data[6].split(':')[1]) except IndexError: interface_counters[if_name]['tx_octets'] = int(if_data[7].split(':')[1]) return interface_counters def get_lldp_neighbors(self): return {} def get_environment(self): def parse_string(line): return re.sub(' +', ' ', line.lower()) def search_disabled(line): m = re.search("(.+?) (.+?) alarm=(.+?) \(scanning disabled\)", line) return m.group(2) def search_normal(line): m = re.search("(.+?) (.+?) alarm=(.+?) value=(.+?) threshold_status=(.+?)", line) return m def get_fans(fan_lines): output = dict() for fan_line in fan_lines: if 'disabled' in fan_line: name = search_disabled(fan_line) output[name] = dict(status=False) continue m = search_normal(fan_line) output[m.group(2)] = dict(status=True) return output def get_cpu(cpu_lines): output = dict() for l in cpu_lines: m = re.search('(.+?) states: (.+?)% user (.+?)% system (.+?)% nice (.+?)% idle', l) cpuname = m.group(1) idle = m.group(5) output[cpuname] = { '%usage': 100.0 - int(idle) } return output def get_memory(memory_line): total, used = int(memory_line[1]) >> 20, int(memory_line[2]) >> 20 # convert from byte to MB return dict(available_ram=total, used_ram=used) def get_temperature(temperature_lines, detail_block): output = dict() for temp_line in temperature_lines: if 'disabled' in temp_line: sensor_name = search_disabled(temp_line) output[sensor_name] = {'is_alert': False, 'is_critical': False, 'temperature': 0.0} continue m = search_normal(temp_line) sensor_name, temp_value, status = m.group(2), m.group(4), int(m.group(5)) is_alert = True if status == 1 else False # find block fullline = self._search_line_in_lines(sensor_name, detail_block) index_line = detail_block.index(fullline) sensor_block = detail_block[index_line:] v = int(self._search_line_in_lines('upper_non_recoverable', sensor_block).split('=')[1]) output[sensor_name] = dict(temperature=float(temp_value), is_alert=is_alert, is_critical=True if v > temp_value else False) return output out = dict() sensors_block = [parse_string(x) for x in self.device.execute_command('execute sensor detail') if x] # temp temp_lines = [x for x in sensors_block if any([True for y in ['dts', 'temp', 'adt7490'] if y in x])] out['temperature'] = get_temperature(temp_lines, sensors_block) # fans out['fans'] = get_fans([x for x in sensors_block if 'fan' in x and 'temp' not in x]) # cpu out['cpu'] = get_cpu( [x for x in self.device.execute_command('get system performance status | grep CPU')[1:] if x]) # memory memory_command = 'diag hard sys mem | grep Mem:' t = [x for x in re.split('\s+', self.device.execute_command(memory_command)[0]) if x] out['memory'] = get_memory(t) # power, not implemented sensors = [x.split()[1] for x in sensors_block if x.split()[0].isdigit()] psus = {x for x in sensors if x.startswith('ps')} out['power'] = {t: {'status': True, 'capacity': -1.0, 'output': -1.0} for t in psus} return out
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password, timeout=timeout) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command('execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command('fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('certificate') self.device.commit() def get_facts(self): system_status = execute_get(self.device, 'get system status') performance_status = execute_get(self.device, 'get system performance status') interfaces = execute_get(self.device, 'get system interface | grep ==') interface_list = [x.split()[2] for x in interfaces.keys()] domain = execute_get(self.device, 'get system dns | grep domain')['domain'] return { 'vendor': unicode('Fortigate'), 'os_version': unicode(system_status['Version'].split(',')[0].split()[1]), 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': unicode(system_status['Serial-Number']), 'model': unicode(system_status['Version'].split(',')[0].split()[0]), 'hostname': unicode(system_status['Hostname']), 'fqdn': u'{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): interface_statistics = dict() self.device.load_config('system interface') for iface in self.get_facts()['interface_list']: try: hw_output = execute_get(self.device, 'diagnose hardware deviceinfo nic {}'.format(iface), auto=True) ifs = self._get_tab_separated_interfaces(hw_output) ifs['is_enabled'] = self.device.running_config['system interface'][iface].get_param('status') != 'down' ifs['description'] = self.device.running_config['system interface'][iface].get_param('description') ifs['last_flapped'] = None # ifs['mode'] = 'routed' ifs['last_flapped'] = None interface_statistics[iface] = ifs except CommandExecutionException: interface_statistics[iface] = self._get_unsupported_interfaces() return interface_statistics def get_bgp_neighbors(self): def search_line_in_lines(search, lines): for l in lines: if search in l: return l families = ['ipv4', 'ipv6'] terms = dict({'accepted_prefixes': 'accepted', 'sent_prefixes': 'announced'}) command_sum = 'get router info bgp sum' command_detail = 'get router info bgp neighbor {}' command_received = 'get router info bgp neighbors {} received-routes | grep prefixes ' peers = dict() bgp_sum = self.device.execute_command(command_sum) re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") neighbors = {n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n)} self.device.load_config('router bgp') for neighbor, parameters in neighbors.iteritems(): logger.debug('NEW PEER') neigh_conf = self.device.running_config['router bgp']['neighbor']['{}'.format(neighbor)] neighbor_dict = peers.get(neighbor, dict()) if not neighbor_dict: neighbor_dict['local_as'] = int(bgp_sum[0].split()[7]) neighbor_dict['remote_as'] = int(neigh_conf.get_param('remote-as')) neighbor_dict['is_up'] = 'never' != parameters[7] or False neighbor_dict['is_enabled'] = neigh_conf.get_param('shutdown') != 'enable' or False neighbor_dict['description'] = u'' neighbor_dict['uptime'] = convert_uptime_string_seconds(parameters[7]) neighbor_dict['address_family'] = dict() neighbor_dict['address_family']['ipv4'] = dict() neighbor_dict['address_family']['ipv6'] = dict() detail_output = [x.lower() for x in self.device.execute_command(command_detail.format(neighbor))] m = re.search('remote router id (.+?)\n', '\n'.join(detail_output)) if m: neighbor_dict['remote_id'] = unicode(m.group(1)) else: raise Exception('cannot find remote router id for %s' % neighbor) for family in families: # find block x = detail_output.index(' for address family: {} unicast'.format(family)) block = detail_output[x:] for term, fortiname in terms.iteritems(): text = search_line_in_lines('%s prefixes' % fortiname, block) t = [int(s) for s in text.split() if s.isdigit()][0] neighbor_dict['address_family'][family][term] = t received = self.device.execute_command( command_received.format(neighbor))[0].split() if len(received) > 0: neighbor_dict['address_family'][family]['received_prefixes'] = received[-1] else: # Soft-reconfig is not enabled neighbor_dict['address_family'][family]['received_prefixes'] = 0 peers[neighbor] = neighbor_dict return { 'global': { 'router_id': unicode(bgp_sum[0].split()[3]), 'peers': peers } } def get_interfaces_counters(self): cmd = self.device.execute_command('fnsysctl ifconfig') if_name = None interface_counters = dict() for line in cmd: data = line.split('\t') if (data[0] == '' or data[0] == ' ') and len(data) == 1: continue elif data[0] != '': if_name = data[0] interface_counters[if_name] = dict() elif (data[1].startswith('RX packets') or data[1].startswith('TX packets')) and if_name: if_data = data[1].split(' ') direction = if_data[0].lower() interface_counters[if_name][direction + '_unicast_packets'] = if_data[1].split(':')[1] interface_counters[if_name][direction + '_errors'] = if_data[2].split(':')[1] interface_counters[if_name][direction + '_discards'] = if_data[2].split(':')[1] interface_counters[if_name][direction + '_multicast_packets'] = -1 interface_counters[if_name][direction + '_broadcast_packets'] = -1 elif data[1].startswith('RX bytes'): if_data = data[1].split(' ') interface_counters[if_name]['rx_octets'] = if_data[1].split(':')[1] try: interface_counters[if_name]['tx_octets'] = if_data[6].split(':')[1] except IndexError: interface_counters[if_name]['tx_octets'] = if_data[7].split(':')[1] return interface_counters def get_lldp_neighbors(self): return {}
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60, optional_args=None): self.hostname = hostname self.username = username self.password = password if optional_args is not None: self.vdom = optional_args.get('fortios_vdom', None) else: self.vdom = None self.device = FortiOS(hostname, username=username, password=password, timeout=timeout, vdom=self.vdom) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def is_alive(self): """Returns a flag with the state of the SSH connection.""" return {'is_alive': self.device.ssh.get_transport().is_active()} def _execute_command_with_vdom(self, command, vdom=None): # If the user doesn't specify a particular vdom we use the default vdom for the object. vdom = vdom or self.vdom if vdom == 'global' and self.vdom is not None: # If vdom is global we go to the global vdom, execute the commands # and then back to the root. There is a catch, if the device doesn't # have vdoms enabled we have to execute the command in the root command = 'conf global\n{command}\nend'.format(command=command) # We skip the lines telling us that we changed vdom return self.device.execute_command(command)[1:-2] elif vdom not in ['global', None]: # If we have a vdom we change to the vdom, execute # the commands and then exit back to the root command = 'conf vdom\nedit {vdom}\n{command}\nend'.format( vdom=vdom, command=command) # We skip the lines telling us that we changed vdom return self.device.execute_command(command)[3:-2] else: # If there is no vdom we just execute the command return self.device.execute_command(command) def _get_command_with_vdom(self, cmd, separator=':', auto=False, vdom=None): output = self._execute_command_with_vdom(cmd, vdom) if auto: if ':' in output[0]: separator = ':' elif '\t' in output[0]: separator = '\t' else: raise Exception( 'Unknown separator for block:\n{}'.format(output)) return colon_separated_string_to_dict('\n'.join(output), separator) def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self._execute_command_with_vdom( 'execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self._execute_command_with_vdom('fnsysctl ls -l data2/config', vdom=None) rollback_file = output[-2].split()[-1] rollback_config = self._execute_command_with_vdom( 'fnsysctl cat data2/config/{rollback_file}'.format(rollback_file)) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].\ del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].\ del_param('certificate') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].\ del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].\ del_param('certificate') self.device.commit() def get_config(self, retrieve="all"): """get_config implementation for FortiOS.""" get_startup = retrieve == "all" or retrieve == "startup" get_running = retrieve == "all" or retrieve == "running" get_candidate = retrieve == "all" or retrieve == "candidate" if retrieve == "all" or get_running: result = self._execute_command_with_vdom('show') text_result = '\n'.join(result) return { 'startup': u"", 'running': py23_compat.text_type(text_result), 'candidate': u"", } elif get_startup or get_candidate: return { 'startup': u"", 'running': u"", 'candidate': u"", } def get_facts(self): system_status = self._get_command_with_vdom('get system status', vdom='global') performance_status = self._get_command_with_vdom( 'get system performance status', vdom='global') interfaces = self._execute_command_with_vdom( 'get system interface | grep ==', vdom='global') interface_list = [ x.split()[2] for x in interfaces if x.strip() is not '' ] domain = self._get_command_with_vdom('get system dns | grep domain', vdom='global')['domain'] return { 'vendor': py23_compat.text_type('Fortigate'), 'os_version': py23_compat.text_type( system_status['Version'].split(',')[0].split()[1]), 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': py23_compat.text_type(system_status['Serial-Number']), 'model': py23_compat.text_type( system_status['Version'].split(',')[0].split()[0]), 'hostname': py23_compat.text_type(system_status['Hostname']), 'fqdn': u'{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): cmd_data = self._execute_command_with_vdom( 'diagnose hardware deviceinfo nic', vdom='global') interface_list = [ x.replace('\t', '') for x in cmd_data if x.startswith('\t') ] interface_statistics = {} for interface in interface_list: if_data = self._execute_command_with_vdom( 'diagnose hardware deviceinfo nic {}'.format(interface), vdom='global') parsed_data = {} if interface.startswith('mgmt'): for line in if_data: if line.startswith('Speed'): if line.split('\t')[-1].split(' ')[0].isdigit(): parsed_data['speed'] = int( line.split('\t')[-1].split(' ')[0]) else: parsed_data['speed'] = -1 elif line.startswith('Link'): parsed_data['is_up'] = line.split('\t')[-1] is 'up' elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = py23_compat.text_type( line.split('\t')[-1]) parsed_data['is_enabled'] = True parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 else: for line in if_data: if line.startswith('Admin'): parsed_data['is_enabled'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Status'): parsed_data['is_up'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Speed'): parsed_data['speed'] = int(line.split(':')[-1]) elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = py23_compat.text_type( line.split(' ')[-1]) parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 interface_statistics[interface] = parsed_data return interface_statistics @staticmethod def _search_line_in_lines(search, lines): for l in lines: if search in l: return l def get_firewall_policies(self): cmd = self._execute_command_with_vdom('show firewall policy') policy = dict() policy_id = None default_policy = dict() position = 1 for line in cmd: policy_data = line.strip() if policy_data.find("edit") == 0: policy_id = policy_data.split()[1] policy[policy_id] = dict() if policy_id is not None: if len(policy_data.split()) > 2: policy_setting = policy_data.split()[1] policy[policy_id][policy_setting] = policy_data.split( )[2].replace("\"", "") for key in policy: enabled = 'status' in policy[key] logtraffic = policy[key]['logtraffic'] if 'logtraffic' in policy[ key] else False action = 'permit' if 'action' in policy[key] else 'reject' policy_item = dict() default_policy[key] = list() policy_item['position'] = position policy_item['packet_hits'] = -1 policy_item['byte_hits'] = -1 policy_item['id'] = py23_compat.text_type(key) policy_item['enabled'] = enabled policy_item['schedule'] = py23_compat.text_type( policy[key]['schedule']) policy_item['log'] = py23_compat.text_type(logtraffic) policy_item['l3_src'] = py23_compat.text_type( policy[key]['srcaddr']) policy_item['l3_dst'] = py23_compat.text_type( policy[key]['dstaddr']) policy_item['service'] = py23_compat.text_type( policy[key]['service']) policy_item['src_zone'] = py23_compat.text_type( policy[key]['srcintf']) policy_item['dst_zone'] = py23_compat.text_type( policy[key]['dstintf']) policy_item['action'] = py23_compat.text_type(action) default_policy[key].append(policy_item) position = position + 1 return default_policy def get_bgp_neighbors(self): families = ['ipv4', 'ipv6'] terms = dict({ 'accepted_prefixes': 'accepted', 'sent_prefixes': 'announced' }) command_sum = 'get router info bgp sum' command_detail = 'get router info bgp neighbor {}' command_received = 'get router info bgp neighbors {} received-routes | grep prefixes ' peers = dict() bgp_sum = self._execute_command_with_vdom(command_sum) re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") neighbors = { n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n) } self.device.load_config('router bgp') for neighbor, parameters in neighbors.items(): logger.debug('NEW PEER') neigh_conf = self.device.running_config['router bgp']['neighbor'][ '{}'.format(neighbor)] neighbor_dict = peers.get(neighbor, dict()) if not neighbor_dict: neighbor_dict['local_as'] = int(bgp_sum[0].split()[7]) neighbor_dict['remote_as'] = int( neigh_conf.get_param('remote-as')) neighbor_dict['is_up'] = 'never' != parameters[7] or False neighbor_dict['is_enabled'] = neigh_conf.get_param( 'shutdown') != 'enable' or False neighbor_dict['description'] = u'' neighbor_dict['uptime'] = convert_uptime_string_seconds( parameters[7]) neighbor_dict['address_family'] = dict() neighbor_dict['address_family']['ipv4'] = dict() neighbor_dict['address_family']['ipv6'] = dict() detail_output = [ x.lower() for x in self._execute_command_with_vdom( command_detail.format(neighbor)) ] m = re.search('remote router id (.+?)\n', '\n'.join(detail_output)) if m: neighbor_dict['remote_id'] = py23_compat.text_type(m.group(1)) else: raise Exception('cannot find remote router id for %s' % neighbor) for family in families: # find block x = detail_output.index( ' for address family: {} unicast'.format(family)) block = detail_output[x:] for term, fortiname in terms.items(): text = self._search_line_in_lines( '%s prefixes' % fortiname, block) t = [int(s) for s in text.split() if s.isdigit()][0] neighbor_dict['address_family'][family][term] = t received = self._execute_command_with_vdom( command_received.format(neighbor))[0].split() if len(received) > 0: neighbor_dict['address_family'][family][ 'received_prefixes'] = received[-1] else: # Soft-reconfig is not enabled neighbor_dict['address_family'][family][ 'received_prefixes'] = 0 peers[neighbor] = neighbor_dict return { 'global': { 'router_id': py23_compat.text_type(bgp_sum[0].split()[3]), 'peers': peers } } def get_interfaces_counters(self): cmd = self._execute_command_with_vdom('fnsysctl ifconfig', vdom=None) if_name = None interface_counters = dict() for line in cmd: data = line.split('\t') if (data[0] == '' or data[0] == ' ') and len(data) == 1: continue elif data[0] != '': if_name = data[0] interface_counters[if_name] = dict() elif (data[1].startswith('RX packets') or data[1].startswith('TX packets')) and if_name: if_data = data[1].split(' ') direction = if_data[0].lower() interface_counters[if_name][direction + '_unicast_packets'] = \ int(if_data[1].split(':')[1]) interface_counters[if_name][direction + '_errors'] = int( if_data[2].split(':')[1]) interface_counters[if_name][direction + '_discards'] = int( if_data[2].split(':')[1]) interface_counters[if_name][direction + '_multicast_packets'] = -1 interface_counters[if_name][direction + '_broadcast_packets'] = -1 elif data[1].startswith('RX bytes'): if_data = data[1].split(' ') interface_counters[if_name]['rx_octets'] = int( if_data[1].split(':')[1]) try: interface_counters[if_name]['tx_octets'] = int( if_data[6].split(':')[1]) except IndexError: interface_counters[if_name]['tx_octets'] = int( if_data[7].split(':')[1]) return interface_counters def get_environment(self): def parse_string(line): return re.sub(' +', ' ', line.lower()) def search_disabled(line): m = re.search("(.+?) (.+?) alarm=(.+?) \(scanning disabled\)", line) return m.group(2) def search_normal(line): m = re.search( "(.+?) (.+?) alarm=(.+?) value=(.+?) threshold_status=(.+?)", line) return m def get_fans(fan_lines): output = dict() for fan_line in fan_lines: if 'disabled' in fan_line: name = search_disabled(fan_line) output[name] = dict(status=False) continue m = search_normal(fan_line) output[m.group(2)] = dict(status=True) return output def get_cpu(cpu_lines): output = dict() for l in cpu_lines: m = re.search( '(.+?) states: (.+?)% user (.+?)% system (.+?)% nice (.+?)% idle', l) cpuname = m.group(1) idle = m.group(5) output[cpuname] = {'%usage': 100.0 - int(idle)} return output def get_memory(memory_line): total, used = int(memory_line[1]) >> 20, int( memory_line[2]) >> 20 # byte to MB return dict(available_ram=total, used_ram=used) def get_temperature(temperature_lines, detail_block): output = dict() for temp_line in temperature_lines: if 'disabled' in temp_line: sensor_name = search_disabled(temp_line) output[sensor_name] = { 'is_alert': False, 'is_critical': False, 'temperature': 0.0 } continue m = search_normal(temp_line) sensor_name, temp_value, status = m.group(2), m.group(4), int( m.group(5)) is_alert = True if status == 1 else False # find block fullline = self._search_line_in_lines(sensor_name, detail_block) index_line = detail_block.index(fullline) sensor_block = detail_block[index_line:] v = int( self._search_line_in_lines('upper_non_recoverable', sensor_block).split('=')[1]) temp_value = int(temp_value) output[sensor_name] = dict( temperature=float(temp_value), is_alert=is_alert, is_critical=True if temp_value > v else False) return output out = dict() sensors_block = [ parse_string(x) for x in self._execute_command_with_vdom('execute sensor detail', vdom='global') if x ] # temp temp_lines = [ x for x in sensors_block if any([True for y in ['dts', 'temp', 'adt7490'] if y in x]) ] out['temperature'] = get_temperature(temp_lines, sensors_block) # fans out['fans'] = get_fans( [x for x in sensors_block if 'fan' in x and 'temp' not in x]) # cpu out['cpu'] = get_cpu([ x for x in self._execute_command_with_vdom( 'get system performance status | grep CPU', vdom='global')[1:] if x ]) # memory memory_command = 'diag hard sys mem | grep Mem:' t = [ x for x in re.split( '\s+', self._execute_command_with_vdom(memory_command, vdom='global') [0]) if x ] out['memory'] = get_memory(t) # power, not implemented sensors = [ x.split()[1] for x in sensors_block if x.split()[0].isdigit() ] psus = {x for x in sensors if x.startswith('ps')} out['power'] = { t: { 'status': True, 'capacity': -1.0, 'output': -1.0 } for t in psus } return out
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password, timeout=timeout) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command( 'execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command( 'fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local'][ 'Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local'][ 'Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local'][ 'Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local'][ 'Fortinet_SSLProxy'].del_param('certificate') self.device.commit() def get_facts(self): system_status = execute_get(self.device, 'get system status') performance_status = execute_get(self.device, 'get system performance status') interfaces = execute_get(self.device, 'get system interface | grep ==') interface_list = [x.split()[2] for x in interfaces.keys()] domain = execute_get(self.device, 'get system dns | grep domain')['domain'] return { 'vendor': unicode('Fortigate'), 'os_version': unicode(system_status['Version'].split(',')[0].split()[1]), 'uptime': convert_uptime_string_seconds(performance_status['Uptime']), 'serial_number': unicode(system_status['Serial-Number']), 'model': unicode(system_status['Version'].split(',')[0].split()[0]), 'hostname': unicode(system_status['Hostname']), 'fqdn': u'{}.{}'.format(system_status['Hostname'], domain), 'interface_list': interface_list } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { 'is_up': ('up' in output['State'] and 'up' or 'down'), 'speed': output['Speed'], 'mac_adddress': output['Current_HWaddr'] } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { 'is_up': None, 'is_enabled': None, 'description': None, 'last_flapped': None, 'mode': None, 'speed': None, 'mac_address': None } def get_interfaces(self): cmd_prefix = '' try: cmd_data = self.device.execute_command( 'diagnose hardware deviceinfo nic') except CommandExecutionException: cmd_data = self.device.execute_command( 'conf global\n diagnose hardware deviceinfo nic ') cmd_prefix = 'conf global\n' interface_list = [ x.replace('\t', '') for x in cmd_data if x.startswith('\t') ] interface_statistics = {} for interface in interface_list: if_data = self.device.execute_command( cmd_prefix + 'diagnose hardware deviceinfo nic {}'.format(interface)) parsed_data = {} if interface.startswith('mgmt'): for line in if_data: if line.startswith('Speed'): if line.split('\t')[-1].split(' ')[0].isdigit(): parsed_data['speed'] = int( line.split('\t')[-1].split(' ')[0]) else: parsed_data['speed'] = -1 elif line.startswith('Link'): parsed_data['is_up'] = line.split('\t')[-1] is 'up' elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = unicode( line.split('\t')[-1]) parsed_data['is_enabled'] = True parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 else: for line in if_data: if line.startswith('Admin'): parsed_data['is_enabled'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Status'): parsed_data['is_up'] = line.split(':')[-1] is 'up' elif line.startswith('PHY Speed'): parsed_data['speed'] = int(line.split(':')[-1]) elif line.startswith('Current_HWaddr'): parsed_data['mac_address'] = unicode( line.split(' ')[-1]) parsed_data['description'] = u'' parsed_data['last_flapped'] = -1.0 interface_statistics[interface] = parsed_data return interface_statistics @staticmethod def _search_line_in_lines(search, lines): for l in lines: if search in l: return l def get_bgp_neighbors(self): families = ['ipv4', 'ipv6'] terms = dict({ 'accepted_prefixes': 'accepted', 'sent_prefixes': 'announced' }) command_sum = 'get router info bgp sum' command_detail = 'get router info bgp neighbor {}' command_received = 'get router info bgp neighbors {} received-routes | grep prefixes ' peers = dict() bgp_sum = self.device.execute_command(command_sum) re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") neighbors = { n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n) } self.device.load_config('router bgp') for neighbor, parameters in neighbors.iteritems(): logger.debug('NEW PEER') neigh_conf = self.device.running_config['router bgp']['neighbor'][ '{}'.format(neighbor)] neighbor_dict = peers.get(neighbor, dict()) if not neighbor_dict: neighbor_dict['local_as'] = int(bgp_sum[0].split()[7]) neighbor_dict['remote_as'] = int( neigh_conf.get_param('remote-as')) neighbor_dict['is_up'] = 'never' != parameters[7] or False neighbor_dict['is_enabled'] = neigh_conf.get_param( 'shutdown') != 'enable' or False neighbor_dict['description'] = u'' neighbor_dict['uptime'] = convert_uptime_string_seconds( parameters[7]) neighbor_dict['address_family'] = dict() neighbor_dict['address_family']['ipv4'] = dict() neighbor_dict['address_family']['ipv6'] = dict() detail_output = [ x.lower() for x in self.device.execute_command( command_detail.format(neighbor)) ] m = re.search('remote router id (.+?)\n', '\n'.join(detail_output)) if m: neighbor_dict['remote_id'] = unicode(m.group(1)) else: raise Exception('cannot find remote router id for %s' % neighbor) for family in families: # find block x = detail_output.index( ' for address family: {} unicast'.format(family)) block = detail_output[x:] for term, fortiname in terms.iteritems(): text = self._search_line_in_lines( '%s prefixes' % fortiname, block) t = [int(s) for s in text.split() if s.isdigit()][0] neighbor_dict['address_family'][family][term] = t received = self.device.execute_command( command_received.format(neighbor))[0].split() if len(received) > 0: neighbor_dict['address_family'][family][ 'received_prefixes'] = received[-1] else: # Soft-reconfig is not enabled neighbor_dict['address_family'][family][ 'received_prefixes'] = 0 peers[neighbor] = neighbor_dict return { 'global': { 'router_id': unicode(bgp_sum[0].split()[3]), 'peers': peers } } def get_interfaces_counters(self): cmd = self.device.execute_command('fnsysctl ifconfig') if_name = None interface_counters = dict() for line in cmd: data = line.split('\t') if (data[0] == '' or data[0] == ' ') and len(data) == 1: continue elif data[0] != '': if_name = data[0] interface_counters[if_name] = dict() elif (data[1].startswith('RX packets') or data[1].startswith('TX packets')) and if_name: if_data = data[1].split(' ') direction = if_data[0].lower() interface_counters[if_name][direction + '_unicast_packets'] = int( if_data[1].split(':')[1]) interface_counters[if_name][direction + '_errors'] = int( if_data[2].split(':')[1]) interface_counters[if_name][direction + '_discards'] = int( if_data[2].split(':')[1]) interface_counters[if_name][direction + '_multicast_packets'] = -1 interface_counters[if_name][direction + '_broadcast_packets'] = -1 elif data[1].startswith('RX bytes'): if_data = data[1].split(' ') interface_counters[if_name]['rx_octets'] = int( if_data[1].split(':')[1]) try: interface_counters[if_name]['tx_octets'] = int( if_data[6].split(':')[1]) except IndexError: interface_counters[if_name]['tx_octets'] = int( if_data[7].split(':')[1]) return interface_counters def get_lldp_neighbors(self): return {} def get_environment(self): def parse_string(line): return re.sub(' +', ' ', line.lower()) def search_disabled(line): m = re.search("(.+?) (.+?) alarm=(.+?) \(scanning disabled\)", line) return m.group(2) def search_normal(line): m = re.search( "(.+?) (.+?) alarm=(.+?) value=(.+?) threshold_status=(.+?)", line) return m def get_fans(fan_lines): output = dict() for fan_line in fan_lines: if 'disabled' in fan_line: name = search_disabled(fan_line) output[name] = dict(status=False) continue m = search_normal(fan_line) output[m.group(2)] = dict(status=True) return output def get_cpu(cpu_lines): output = dict() for l in cpu_lines: m = re.search( '(.+?) states: (.+?)% user (.+?)% system (.+?)% nice (.+?)% idle', l) cpuname = m.group(1) idle = m.group(5) output[cpuname] = {'%usage': 100.0 - int(idle)} return output def get_memory(memory_line): total, used = int(memory_line[1]) >> 20, int( memory_line[2]) >> 20 # convert from byte to MB return dict(available_ram=total, used_ram=used) def get_temperature(temperature_lines, detail_block): output = dict() for temp_line in temperature_lines: if 'disabled' in temp_line: sensor_name = search_disabled(temp_line) output[sensor_name] = { 'is_alert': False, 'is_critical': False, 'temperature': 0.0 } continue m = search_normal(temp_line) sensor_name, temp_value, status = m.group(2), m.group(4), int( m.group(5)) is_alert = True if status == 1 else False # find block fullline = self._search_line_in_lines(sensor_name, detail_block) index_line = detail_block.index(fullline) sensor_block = detail_block[index_line:] v = int( self._search_line_in_lines('upper_non_recoverable', sensor_block).split('=')[1]) output[sensor_name] = dict( temperature=float(temp_value), is_alert=is_alert, is_critical=True if v > temp_value else False) return output out = dict() sensors_block = [ parse_string(x) for x in self.device.execute_command('execute sensor detail') if x ] # temp temp_lines = [ x for x in sensors_block if any([True for y in ['dts', 'temp', 'adt7490'] if y in x]) ] out['temperature'] = get_temperature(temp_lines, sensors_block) # fans out['fans'] = get_fans( [x for x in sensors_block if 'fan' in x and 'temp' not in x]) # cpu out['cpu'] = get_cpu([ x for x in self.device.execute_command( 'get system performance status | grep CPU')[1:] if x ]) # memory memory_command = 'diag hard sys mem | grep Mem:' t = [ x for x in re.split('\s+', self.device.execute_command(memory_command)[0]) if x ] out['memory'] = get_memory(t) # power, not implemented sensors = [ x.split()[1] for x in sensors_block if x.split()[0].isdigit() ] psus = {x for x in sensors if x.startswith('ps')} out['power'] = { t: { 'status': True, 'capacity': -1.0, 'output': -1.0 } for t in psus } return out
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60, optional_args=None): self.hostname = hostname self.username = username self.password = password if optional_args is not None: self.vdom = optional_args.pop("fortios_vdom", None) else: self.vdom = None self.device = FortiOS(hostname, username=username, password=password, timeout=timeout, vdom=self.vdom) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def execute_command_with_vdom(self, command, vdom=None): # If the user doesn't specify a particular vdom we use the default vdom for the object. vdom = vdom or self.vdom if vdom == "global" and self.vdom is not None: # If vdom is global we go to the global vdom, execute the commands and then back to the root. # There is a catch, if the device doesn't have vdoms enabled we have to execute the command in the root command = "conf global\n{command}\nend".format(command=command) # We skip the lines telling us that we changed vdom return self.device.execute_command(command)[1:-2] elif vdom not in ["global", None]: # If we have a vdom we change to the vdom, execute the commands and then exit back to the root command = "conf vdom\nedit {vdom}\n{command}\nend".format(vdom=vdom, command=command) # We skip the lines telling us that we changed vdom return self.device.execute_command(command)[3:-2] else: # If there is no vdom we just execute the command return self.device.execute_command(command) def get_command_with_vdom(self, cmd, separator=":", auto=False, vdom=None): output = self.execute_command_with_vdom(cmd, vdom) if auto: if ":" in output[0]: separator = ":" elif "\t" in output[0]: separator = "\t" else: raise Exception("Unknown separator for block:\n{}".format(output)) return colon_separated_string_to_dict("\n".join(output), separator) def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig("candidate") self.device.running_config = FortiConfig("running") self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig("candidate") self.device.running_config = FortiConfig("running") self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.execute_command_with_vdom("execute backup config flash commit_with_napalm") self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig("candidate") self.device.load_config(in_candidate=True) def rollback(self): output = self.execute_command_with_vdom("fnsysctl ls -l data2/config", vdom=None) rollback_file = output[-2].split()[-1] rollback_config = self.execute_command_with_vdom( "fnsysctl cat data2/config/{rollback_file}".format(rollback_file) ) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config["vpn certificate local"]["Fortinet_CA_SSLProxy"].del_param("private-key") self.device.candidate_config["vpn certificate local"]["Fortinet_CA_SSLProxy"].del_param("certificate") self.device.candidate_config["vpn certificate local"]["Fortinet_SSLProxy"].del_param("private-key") self.device.candidate_config["vpn certificate local"]["Fortinet_SSLProxy"].del_param("certificate") self.device.commit() def get_facts(self): system_status = self.get_command_with_vdom("get system status", vdom="global") performance_status = self.get_command_with_vdom("get system performance status", vdom="global") interfaces = self.execute_command_with_vdom("get system interface | grep ==", vdom="global") interface_list = [x.split()[2] for x in interfaces if x.strip() is not ""] domain = self.get_command_with_vdom("get system dns | grep domain", vdom="global")["domain"] return { "vendor": unicode("Fortigate"), "os_version": unicode(system_status["Version"].split(",")[0].split()[1]), "uptime": convert_uptime_string_seconds(performance_status["Uptime"]), "serial_number": unicode(system_status["Serial-Number"]), "model": unicode(system_status["Version"].split(",")[0].split()[0]), "hostname": unicode(system_status["Hostname"]), "fqdn": u"{}.{}".format(system_status["Hostname"], domain), "interface_list": interface_list, } @staticmethod def _get_tab_separated_interfaces(output): interface_statistics = { "is_up": ("up" in output["State"] and "up" or "down"), "speed": output["Speed"], "mac_adddress": output["Current_HWaddr"], } return interface_statistics @staticmethod def _get_unsupported_interfaces(): return { "is_up": None, "is_enabled": None, "description": None, "last_flapped": None, "mode": None, "speed": None, "mac_address": None, } def get_interfaces(self): cmd_data = self.execute_command_with_vdom("diagnose hardware deviceinfo nic", vdom="global") interface_list = [x.replace("\t", "") for x in cmd_data if x.startswith("\t")] interface_statistics = {} for interface in interface_list: if_data = self.execute_command_with_vdom( "diagnose hardware deviceinfo nic {}".format(interface), vdom="global" ) parsed_data = {} if interface.startswith("mgmt"): for line in if_data: if line.startswith("Speed"): if line.split("\t")[-1].split(" ")[0].isdigit(): parsed_data["speed"] = int(line.split("\t")[-1].split(" ")[0]) else: parsed_data["speed"] = -1 elif line.startswith("Link"): parsed_data["is_up"] = line.split("\t")[-1] is "up" elif line.startswith("Current_HWaddr"): parsed_data["mac_address"] = unicode(line.split("\t")[-1]) parsed_data["is_enabled"] = True parsed_data["description"] = u"" parsed_data["last_flapped"] = -1.0 else: for line in if_data: if line.startswith("Admin"): parsed_data["is_enabled"] = line.split(":")[-1] is "up" elif line.startswith("PHY Status"): parsed_data["is_up"] = line.split(":")[-1] is "up" elif line.startswith("PHY Speed"): parsed_data["speed"] = int(line.split(":")[-1]) elif line.startswith("Current_HWaddr"): parsed_data["mac_address"] = unicode(line.split(" ")[-1]) parsed_data["description"] = u"" parsed_data["last_flapped"] = -1.0 interface_statistics[interface] = parsed_data return interface_statistics @staticmethod def _search_line_in_lines(search, lines): for l in lines: if search in l: return l def get_bgp_neighbors(self): families = ["ipv4", "ipv6"] terms = dict({"accepted_prefixes": "accepted", "sent_prefixes": "announced"}) command_sum = "get router info bgp sum" command_detail = "get router info bgp neighbor {}" command_received = "get router info bgp neighbors {} received-routes | grep prefixes " peers = dict() bgp_sum = self.execute_command_with_vdom(command_sum) re_neigh = re.compile("^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") neighbors = {n.split()[0]: n.split()[1:] for n in bgp_sum if re.match(re_neigh, n)} self.device.load_config("router bgp") for neighbor, parameters in neighbors.iteritems(): logger.debug("NEW PEER") neigh_conf = self.device.running_config["router bgp"]["neighbor"]["{}".format(neighbor)] neighbor_dict = peers.get(neighbor, dict()) if not neighbor_dict: neighbor_dict["local_as"] = int(bgp_sum[0].split()[7]) neighbor_dict["remote_as"] = int(neigh_conf.get_param("remote-as")) neighbor_dict["is_up"] = "never" != parameters[7] or False neighbor_dict["is_enabled"] = neigh_conf.get_param("shutdown") != "enable" or False neighbor_dict["description"] = u"" neighbor_dict["uptime"] = convert_uptime_string_seconds(parameters[7]) neighbor_dict["address_family"] = dict() neighbor_dict["address_family"]["ipv4"] = dict() neighbor_dict["address_family"]["ipv6"] = dict() detail_output = [x.lower() for x in self.execute_command_with_vdom(command_detail.format(neighbor))] m = re.search("remote router id (.+?)\n", "\n".join(detail_output)) if m: neighbor_dict["remote_id"] = unicode(m.group(1)) else: raise Exception("cannot find remote router id for %s" % neighbor) for family in families: # find block x = detail_output.index(" for address family: {} unicast".format(family)) block = detail_output[x:] for term, fortiname in terms.iteritems(): text = self._search_line_in_lines("%s prefixes" % fortiname, block) t = [int(s) for s in text.split() if s.isdigit()][0] neighbor_dict["address_family"][family][term] = t received = self.execute_command_with_vdom(command_received.format(neighbor))[0].split() if len(received) > 0: neighbor_dict["address_family"][family]["received_prefixes"] = received[-1] else: # Soft-reconfig is not enabled neighbor_dict["address_family"][family]["received_prefixes"] = 0 peers[neighbor] = neighbor_dict return {"global": {"router_id": unicode(bgp_sum[0].split()[3]), "peers": peers}} def get_interfaces_counters(self): cmd = self.execute_command_with_vdom("fnsysctl ifconfig", vdom=None) if_name = None interface_counters = dict() for line in cmd: data = line.split("\t") if (data[0] == "" or data[0] == " ") and len(data) == 1: continue elif data[0] != "": if_name = data[0] interface_counters[if_name] = dict() elif (data[1].startswith("RX packets") or data[1].startswith("TX packets")) and if_name: if_data = data[1].split(" ") direction = if_data[0].lower() interface_counters[if_name][direction + "_unicast_packets"] = int(if_data[1].split(":")[1]) interface_counters[if_name][direction + "_errors"] = int(if_data[2].split(":")[1]) interface_counters[if_name][direction + "_discards"] = int(if_data[2].split(":")[1]) interface_counters[if_name][direction + "_multicast_packets"] = -1 interface_counters[if_name][direction + "_broadcast_packets"] = -1 elif data[1].startswith("RX bytes"): if_data = data[1].split(" ") interface_counters[if_name]["rx_octets"] = int(if_data[1].split(":")[1]) try: interface_counters[if_name]["tx_octets"] = int(if_data[6].split(":")[1]) except IndexError: interface_counters[if_name]["tx_octets"] = int(if_data[7].split(":")[1]) return interface_counters def get_lldp_neighbors(self): return {} def get_environment(self): def parse_string(line): return re.sub(" +", " ", line.lower()) def search_disabled(line): m = re.search("(.+?) (.+?) alarm=(.+?) \(scanning disabled\)", line) return m.group(2) def search_normal(line): m = re.search("(.+?) (.+?) alarm=(.+?) value=(.+?) threshold_status=(.+?)", line) return m def get_fans(fan_lines): output = dict() for fan_line in fan_lines: if "disabled" in fan_line: name = search_disabled(fan_line) output[name] = dict(status=False) continue m = search_normal(fan_line) output[m.group(2)] = dict(status=True) return output def get_cpu(cpu_lines): output = dict() for l in cpu_lines: m = re.search("(.+?) states: (.+?)% user (.+?)% system (.+?)% nice (.+?)% idle", l) cpuname = m.group(1) idle = m.group(5) output[cpuname] = {"%usage": 100.0 - int(idle)} return output def get_memory(memory_line): total, used = int(memory_line[1]) >> 20, int(memory_line[2]) >> 20 # convert from byte to MB return dict(available_ram=total, used_ram=used) def get_temperature(temperature_lines, detail_block): output = dict() for temp_line in temperature_lines: if "disabled" in temp_line: sensor_name = search_disabled(temp_line) output[sensor_name] = {"is_alert": False, "is_critical": False, "temperature": 0.0} continue m = search_normal(temp_line) sensor_name, temp_value, status = m.group(2), m.group(4), int(m.group(5)) is_alert = True if status == 1 else False # find block fullline = self._search_line_in_lines(sensor_name, detail_block) index_line = detail_block.index(fullline) sensor_block = detail_block[index_line:] v = int(self._search_line_in_lines("upper_non_recoverable", sensor_block).split("=")[1]) output[sensor_name] = dict( temperature=float(temp_value), is_alert=is_alert, is_critical=True if v > temp_value else False ) return output out = dict() sensors_block = [ parse_string(x) for x in self.execute_command_with_vdom("execute sensor detail", vdom="global") if x ] # temp temp_lines = [x for x in sensors_block if any([True for y in ["dts", "temp", "adt7490"] if y in x])] out["temperature"] = get_temperature(temp_lines, sensors_block) # fans out["fans"] = get_fans([x for x in sensors_block if "fan" in x and "temp" not in x]) # cpu out["cpu"] = get_cpu( [ x for x in self.execute_command_with_vdom("get system performance status | grep CPU", vdom="global")[1:] if x ] ) # memory memory_command = "diag hard sys mem | grep Mem:" t = [x for x in re.split("\s+", self.execute_command_with_vdom(memory_command, vdom="global")[0]) if x] out["memory"] = get_memory(t) # power, not implemented sensors = [x.split()[1] for x in sensors_block if x.split()[0].isdigit()] psus = {x for x in sensors if x.startswith("ps")} out["power"] = {t: {"status": True, "capacity": -1.0, "output": -1.0} for t in psus} return out
class FortiOSDriver(NetworkDriver): def __init__(self, hostname, username, password): self.hostname = hostname self.username = username self.password = password self.device = FortiOS(hostname, username=username, password=password) self.config_replace = False def open(self): self.device.open() def close(self): self.device.close() def _load_config(self, filename, config): if filename is None: configuration = config else: with open(filename) as f: configuration = f.read() self.device.load_config(in_candidate=True, config_text=configuration) def load_replace_candidate(self, filename=None, config=None): self.config_replace = True self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) self.device.load_config(empty_candidate=True) def load_merge_candidate(self, filename=None, config=None): self.config_replace = False self.device.candidate_config = FortiConfig('candidate') self.device.running_config = FortiConfig('running') self._load_config(filename, config) for block in self.device.candidate_config.get_block_names(): try: self.device.load_config(path=block, empty_candidate=True) except CommandExecutionException as e: raise MergeConfigException(e.message) def compare_config(self): return self.device.compare_config() def commit_config(self): try: self.device.execute_command('execute backup config flash commit_with_napalm') self.device.commit() self.discard_config() except FailedCommit as e: if self.config_replace: raise ReplaceConfigException(e.message) else: raise MergeConfigException(e.message) def discard_config(self): self.device.candidate_config = FortiConfig('candidate') self.device.load_config(in_candidate=True) def rollback(self): output = self.device.execute_command('fnsysctl ls -l data2/config') rollback_file = output[-2].split()[-1] rollback_config = self.device.execute_command('fnsysctl cat data2/config/%s' % rollback_file) self.device.load_config(empty_candidate=True) self.load_replace_candidate(config=rollback_config) self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_CA_SSLProxy'].del_param('certificate') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('private-key') self.device.candidate_config['vpn certificate local']['Fortinet_SSLProxy'].del_param('certificate') self.device.commit()