def do_test(cli_args, config): """Process 'test' CLI option. Args: connectivity_check: Set if testing for connectivity Returns: None """ # Show host information validate = snmp_manager.Validate(cli_args.host, config.snmp_auth()) snmp_params = validate.credentials() snmp_object = snmp_manager.Interact(snmp_params) if bool(snmp_params) is True: print('\nValid credentials found:\n') print(yaml.dump(snmp_params, default_flow_style=False)) print('\n') # Get SNMP data and print status = snmp_info.Query(snmp_object) yaml_data = status.everything() print(yaml_data) else: # Error, host problems log_message = ( 'Uncontactable host %s or no valid SNMP ' 'credentials found for it.') % (cli_args.host) jm_general.logit(1006, log_message)
def do_test(cli_args, config): """Process 'test' CLI option. Args: connectivity_check: Set if testing for connectivity Returns: None """ # Show host information validate = snmp_manager.Validate(cli_args.host, config.snmp_auth()) snmp_params = validate.credentials() snmp_object = snmp_manager.Interact(snmp_params) if bool(snmp_params) is True: print('\nValid credentials found:\n') print(yaml.dump(snmp_params, default_flow_style=False)) print('\n') # Get SNMP data and print status = snmp_info.Query(snmp_object) yaml_data = status.everything() print(yaml_data) else: # Error, host problems log_message = ('Uncontactable host %s or no valid SNMP ' 'credentials found for it.') % (cli_args.host) jm_general.logit(1006, log_message)
def contactable(self): """Check if device is contactable. Args: device_id: Device ID Returns: contactable: True if a contactable """ # Define key variables contactable = False # Try to reach device try: # If we can poll the SNMP sysObjectID, # then the device is contactable if self.sysobjectid(connectivity_check=True) is not None: contactable = True except Exception as _: # Not contactable contactable = False except: # Log a message log_message = ('Unexpected SNMP error for device %s') % ( self.snmp_params['snmp_hostname']) jm_general.logit(1008, log_message, True) # Return return contactable
def test_logit(self): """Testing method / function logit.""" # Try logging with exit log_message = self.random_string with self.assertRaises(SystemExit): testimport.logit(1000, log_message, True) # Try logging with noexit result = testimport.logit(1000, log_message, False) self.assertEqual(result, None)
def _process_error( connectivity_check=False, session_error_status=None, session_error_index=None, get=False, log_message=None): """Process errors received. Args: connectivity_check: Set if testing for connectivity. Some session errors are ignored so that a null result is returned session_error_status: Error status session_error_index: Error index get: True if formatting the results of an SNMP get Returns: results: """ # Initialize key variables results = {} # Timeout contacting device # (Timeout as OID requested does not exist, not because the # device is uncontactable) # The device must be checked for connectivity before hand. If it # can connect but some additional OID is unavailable, then this is # invoked. This is used to determine whether a device has 64 bit # IFMIB octet counters if connectivity_check is False: if (session_error_status == 0) and ( session_error_index == -24): # Return blank results return results if connectivity_check is True: # Bad SNMP authentication during authentication check if (session_error_status == 0) and ( session_error_index == -4): # Return blank results return results # Device completely off the air (SNMP timeout) if (session_error_status == 0) and ( session_error_index == 0): # Return blank results return results # Otherwise Fail if get is True: action_taken = 'SNMPget' else: action_taken = 'SNMPwalk' log_message = ('%s - %s') % (action_taken, log_message) jm_general.logit(1003, log_message, True)
def run(self): """Update the database using threads.""" while True: # Get the data_dict data_dict = self.queue.get() host = data_dict['host'] config = data_dict['config'] verbose = data_dict['verbose'] temp_dir = data_dict['temp_dir'] # Show host information validate = snmp_manager.Validate(host, config.snmp_auth()) snmp_params = validate.credentials() snmp_object = snmp_manager.Interact(snmp_params) # Verbose output if verbose is True: output = ('Processing on: host %s') % (host) print(output) # Skip invalid, and uncontactable hosts if bool(snmp_params) is False: if verbose is True: log_message = ( 'Uncontactable host %s or no valid SNMP ' 'credentials found for it.') % (host) jm_general.logit(1019, log_message, False) continue # Process if valid if bool(snmp_params) is True: # Get data status = snmp_info.Query(snmp_object) data = status.everything() yaml_string = jm_general.dict2yaml(data) # Dump data temp_file = ('%s/%s.yaml') % (temp_dir, host) with open(temp_file, 'w') as file_handle: file_handle.write(yaml_string) # Verbose output if verbose is True: output = ('Completed run: host %s') % (host) print(output) # Signals to queue job is done self.queue.task_done()
def __init__(self, snmp_parameters): """Function for intializing the class.""" # Initialize key variables self.snmp_params = {} # Assign variables self.snmp_params = snmp_parameters # Fail if snmp_parameters dictionary is empty if snmp_parameters['snmp_version'] is None: log_message = ( 'SNMP version is "None". Non existent host? - %s' '') % (snmp_parameters['snmp_hostname']) jm_general.logit(1004, log_message, True) # Fail if snmp_parameters dictionary is empty if not snmp_parameters: log_message = ('SNMP parameters provided are blank. ' 'Non existent host?') jm_general.logit(1005, log_message, True)
def web_directory(self): """Determine the web_directory. Args: None Returns: value: configured web_directory """ # Get parameter value = self.config_dict['web_directory'] # Check if value exists if os.path.isdir(value) is False: log_message = ('web_directory: "%s" ' 'in configuration doesn\'t exist!') % (value) jm_general.logit(1016, log_message) # Return return value
def web_directory(self): """Determine the web_directory. Args: None Returns: value: configured web_directory """ # Get parameter value = self.config_dict['web_directory'] # Check if value exists if os.path.isdir(value) is False: log_message = ( 'web_directory: "%s" ' 'in configuration doesn\'t exist!') % (value) jm_general.logit(1016, log_message) # Return return value
def __init__(self, config_directory): """Function for intializing the class.""" # Initialize key variables yaml_found = False yaml_from_file = '' all_yaml_read = '' # Check if config_directory exists if os.path.isdir(config_directory) is False: log_message = ( 'Configuration directory "%s" ' 'doesn\'t exist!' % config_directory) jm_general.logit(1009, log_message) # Cycle through list of files in directory for filename in os.listdir(config_directory): # Examine all the '.yaml' files in directory if filename.endswith('.yaml'): # YAML files found yaml_found = True # Read file and add to string file_path = ('%s/%s') % (config_directory, filename) with open(file_path, 'r') as file_handle: yaml_from_file = file_handle.read() # Append yaml from file to all yaml previously read all_yaml_read = ('%s\n%s') % (all_yaml_read, yaml_from_file) # Verify YAML files found in directory if yaml_found is False: log_message = ( 'No files found in directory "%s" with ".yaml" ' 'extension.') % (config_directory) jm_general.logit(1010, log_message) # Return self.config_dict = yaml.load(all_yaml_read)
def __init__(self, config_directory): """Function for intializing the class.""" # Initialize key variables yaml_found = False yaml_from_file = '' all_yaml_read = '' # Check if config_directory exists if os.path.isdir(config_directory) is False: log_message = ('Configuration directory "%s" ' 'doesn\'t exist!' % config_directory) jm_general.logit(1009, log_message) # Cycle through list of files in directory for filename in os.listdir(config_directory): # Examine all the '.yaml' files in directory if filename.endswith('.yaml'): # YAML files found yaml_found = True # Read file and add to string file_path = ('%s/%s') % (config_directory, filename) with open(file_path, 'r') as file_handle: yaml_from_file = file_handle.read() # Append yaml from file to all yaml previously read all_yaml_read = ('%s\n%s') % (all_yaml_read, yaml_from_file) # Verify YAML files found in directory if yaml_found is False: log_message = ('No files found in directory "%s" with ".yaml" ' 'extension.') % (config_directory) jm_general.logit(1010, log_message) # Return self.config_dict = yaml.load(all_yaml_read)
def __init__(self, config, host, ifindices=None): """Initialize class. Args: config: Configuration file object host: Hostname to process ifindices: List of ifindices to process Returns: data_dict: Dict of summary data Summary: IF-MIB A significant portion of this code relies on ifIndex IF-MIB::ifStackStatus information. This is stored under the 'system' key of the device YAML files. According to the official IF-MIB file. ifStackStatus is a "table containing information on the relationships between the multiple sub-layers of network interfaces. In particular, it contains information on which sub-layers run 'on top of' which other sub-layers, where each sub-layer corresponds to a conceptual row in the ifTable. For example, when the sub-layer with ifIndex value x runs over the sub-layer with ifIndex value y, then this table contains: ifStackStatus.x.y=active For each ifIndex value, I, which identifies an active interface, there are always at least two instantiated rows in this table associated with I. For one of these rows, I is the value of ifStackHigherLayer; for the other, I is the value of ifStackLowerLayer. (If I is not involved in multiplexing, then these are the only two rows associated with I.) For example, two rows exist even for an interface which has no others stacked on top or below it: ifStackStatus.0.x=active ifStackStatus.x.0=active" In the case of Juniper equipment, VLAN information is only visible on subinterfaces of the main interface. For example interface ge-0/0/0 won't have VLAN information assigned to it directly. When a VLAN is assigned to this interface, a subinterface ge-0/0/0.0 is automatically created with a non-Ethernet ifType. VLAN related OIDs are only maintained for this new subinterface only. This makes determining an interface's VLAN based on Ethernet ifType more difficult. ifStackStatus maps the ifIndex of the primary interface (ge-0/0/0) to the ifIndex of the secondary interface (ge-0/0/0.0) which manages higher level protocols and data structures such as VLANs and LLDP. The primary interface is referred to as the ifStackLowerLayer and the secondary subinterface is referred to as the ifStackHigherLayer. ================================================================= Layer1 Keys The following Layer1 keys are presented by the ethernet_data method due to this instantiation: jm_nativevlan: A vendor agnostic Native VLAN jm_vlan: A list of vendor agnostic VLANs jm_trunk: A vendor agnostic flag of "True" if the port is a Trunk jm_duplex: A vendor agnostic status code for the duplex setting """ # Initialize key variables self.ports = {} yaml_file = config.snmp_device_file(host) # Fail if yaml file doesn't exist if os.path.isfile(yaml_file) is False: log_message = ('YAML file %s for host %s doesn\'t exist! ' 'Try polling devices first.') % (yaml_file, host) jm_general.logit(1017, log_message) # Read file with open(yaml_file, 'r') as file_handle: yaml_from_file = file_handle.read() yaml_data = yaml.load(yaml_from_file) # Create dict for layer1 Ethernet data for ifindex, metadata in yaml_data['layer1'].items(): # Only process if ifIndex is found in ifindices if ifindices is not None: if int(ifindex) not in ifindices: continue # Process metadata if _is_ethernet(metadata) is True: # Get the ifIndex of the lower layer interface ifstacklowerlayer = ifindex ############################################################# # # This stuff relies on ifstacklowerlayer / ifstackhigherlayer # ############################################################# # Determine the ifIndex for any existing higher # layer subinterfaces whose data could be used # for upper layer2 features such as VLANs and # LAG trunking higherlayers = yaml_data['system']['IF-MIB']['ifStackStatus'][ ifindex] # Update vlan to universal infoset metadata value for higherlayer in higherlayers: # All numeric keys in YAML need to be strings. Prepare # for key checking. ifstackhigherlayer = str(higherlayer) # This is an Ethernet port with no higher level # interfaces. Use lower level ifIndex if ifstackhigherlayer == '0': metadata['jm_vlan'] = _vlan(yaml_data, ifstacklowerlayer) metadata['jm_nativevlan'] = _nativevlan( yaml_data, ifstacklowerlayer) metadata['jm_trunk'] = _trunk(yaml_data, ifstacklowerlayer) else: # Assign native VLAN to higer layer metadata['jm_nativevlan'] = _nativevlan( yaml_data, ifstackhigherlayer) # Update trunk status to universal metadata value metadata['jm_trunk'] = _trunk(yaml_data, ifstackhigherlayer) # This is an Ethernet port with a single higher level # interface if len(higherlayers) == 1: metadata['jm_vlan'] = _vlan( yaml_data, ifstackhigherlayer) # This is an Ethernet port with multiple higher level # interfaces else: metadata['jm_vlan'].extend( _vlan(yaml_data, ifstackhigherlayer)) ############################################################# # # This stuff relies on ifindex # ############################################################# # Update duplex to universal infoset metadata value metadata['jm_duplex'] = _duplex(metadata) # Update ports self.ports[int(ifindex)] = metadata # Get system self.system = yaml_data['system']
def __init__(self, config, host, ifindices=None): """Initialize class. Args: config: Configuration file object host: Hostname to process ifindices: List of ifindices to process Returns: data_dict: Dict of summary data Summary: IF-MIB A significant portion of this code relies on ifIndex IF-MIB::ifStackStatus information. This is stored under the 'system' key of the device YAML files. According to the official IF-MIB file. ifStackStatus is a "table containing information on the relationships between the multiple sub-layers of network interfaces. In particular, it contains information on which sub-layers run 'on top of' which other sub-layers, where each sub-layer corresponds to a conceptual row in the ifTable. For example, when the sub-layer with ifIndex value x runs over the sub-layer with ifIndex value y, then this table contains: ifStackStatus.x.y=active For each ifIndex value, I, which identifies an active interface, there are always at least two instantiated rows in this table associated with I. For one of these rows, I is the value of ifStackHigherLayer; for the other, I is the value of ifStackLowerLayer. (If I is not involved in multiplexing, then these are the only two rows associated with I.) For example, two rows exist even for an interface which has no others stacked on top or below it: ifStackStatus.0.x=active ifStackStatus.x.0=active" In the case of Juniper equipment, VLAN information is only visible on subinterfaces of the main interface. For example interface ge-0/0/0 won't have VLAN information assigned to it directly. When a VLAN is assigned to this interface, a subinterface ge-0/0/0.0 is automatically created with a non-Ethernet ifType. VLAN related OIDs are only maintained for this new subinterface only. This makes determining an interface's VLAN based on Ethernet ifType more difficult. ifStackStatus maps the ifIndex of the primary interface (ge-0/0/0) to the ifIndex of the secondary interface (ge-0/0/0.0) which manages higher level protocols and data structures such as VLANs and LLDP. The primary interface is referred to as the ifStackLowerLayer and the secondary subinterface is referred to as the ifStackHigherLayer. ================================================================= Layer1 Keys The following Layer1 keys are presented by the ethernet_data method due to this instantiation: jm_nativevlan: A vendor agnostic Native VLAN jm_vlan: A list of vendor agnostic VLANs jm_trunk: A vendor agnostic flag of "True" if the port is a Trunk jm_duplex: A vendor agnostic status code for the duplex setting """ # Initialize key variables self.ports = {} yaml_file = config.snmp_device_file(host) # Fail if yaml file doesn't exist if os.path.isfile(yaml_file) is False: log_message = ( 'YAML file %s for host %s doesn\'t exist! ' 'Try polling devices first.') % (yaml_file, host) jm_general.logit(1017, log_message) # Read file with open(yaml_file, 'r') as file_handle: yaml_from_file = file_handle.read() yaml_data = yaml.load(yaml_from_file) # Create dict for layer1 Ethernet data for ifindex, metadata in yaml_data['layer1'].items(): # Only process if ifIndex is found in ifindices if ifindices is not None: if int(ifindex) not in ifindices: continue # Process metadata if _is_ethernet(metadata) is True: # Get the ifIndex of the lower layer interface ifstacklowerlayer = ifindex ############################################################# # # This stuff relies on ifstacklowerlayer / ifstackhigherlayer # ############################################################# # Determine the ifIndex for any existing higher # layer subinterfaces whose data could be used # for upper layer2 features such as VLANs and # LAG trunking higherlayers = yaml_data[ 'system']['IF-MIB']['ifStackStatus'][ifindex] # Update vlan to universal infoset metadata value for higherlayer in higherlayers: # All numeric keys in YAML need to be strings. Prepare # for key checking. ifstackhigherlayer = str(higherlayer) # This is an Ethernet port with no higher level # interfaces. Use lower level ifIndex if ifstackhigherlayer == '0': metadata['jm_vlan'] = _vlan( yaml_data, ifstacklowerlayer) metadata['jm_nativevlan'] = _nativevlan( yaml_data, ifstacklowerlayer) metadata['jm_trunk'] = _trunk( yaml_data, ifstacklowerlayer) else: # Assign native VLAN to higer layer metadata['jm_nativevlan'] = _nativevlan( yaml_data, ifstackhigherlayer) # Update trunk status to universal metadata value metadata['jm_trunk'] = _trunk( yaml_data, ifstackhigherlayer) # This is an Ethernet port with a single higher level # interface if len(higherlayers) == 1: metadata['jm_vlan'] = _vlan( yaml_data, ifstackhigherlayer) # This is an Ethernet port with multiple higher level # interfaces else: metadata['jm_vlan'].extend( _vlan(yaml_data, ifstackhigherlayer)) ############################################################# # # This stuff relies on ifindex # ############################################################# # Update duplex to universal infoset metadata value metadata['jm_duplex'] = _duplex(metadata) # Update ports self.ports[int(ifindex)] = metadata # Get system self.system = yaml_data['system']
def query( self, oid_to_get, get=False, connectivity_check=False, normalized=False): """Do an SNMP query. Args: oid_to_get: OID to walk get: Flag determining whether to do a GET or WALK normalized: If True, then return results as a dict keyed by only the last node of an OID, otherwise return results keyed by the entire OID string. Normalization is useful when trying to create multidimensional dicts where the primary key is a universal value such as IF-MIB::ifIndex or BRIDGE-MIB::dot1dBasePort connectivity_check: Set if testing for connectivity. Some session errors are ignored so that a null result is returned Returns: Dictionary of tuples (OID, value) """ # Initialize variables return_results = {} snmp_params = self.snmp_params # Check if OID is valid valid_format = oid_valid_format(oid_to_get) if valid_format is False: log_message = ('OID %s has an invalid format') % (oid_to_get) jm_general.logit(1000, log_message, True) # Create the object snmp_object = cmdgen.CommandGenerator() # Setup Transport object transport_object = cmdgen.UdpTransportTarget( (snmp_params['snmp_hostname'], snmp_params['snmp_port'])) # Create the auth object authentication_object = _get_auth_object(snmp_params) # Fill the results object by getting OID data try: # Get the data if get is True: (session_error_string, session_error_status, session_error_index, var_binds) = \ snmp_object.getCmd( authentication_object, transport_object, oid_to_get) else: (session_error_string, session_error_status, session_error_index, var_binds) = \ snmp_object.nextCmd( authentication_object, transport_object, oid_to_get) # Do something here except Exception as exception_error: # Check for errors and print out results log_message = ( 'Error occurred during SNMPget on host ' 'OID %s from %s: (%s)') % (oid_to_get, snmp_params['snmp_hostname'], exception_error) jm_general.logit(1001, log_message, True) except: log_message = ('Unexpected error') jm_general.logit(1002, log_message, True) # Crash on error, return blank results if doing certain types of # connectivity checks if session_error_string: log_message = ( 'Error occurred for OID %s on host %s: ' '(%s) ErrorNum: %s, ErrorInd: ' '%s') % (oid_to_get, snmp_params['snmp_hostname'], session_error_string, session_error_status, session_error_index) return _process_error( connectivity_check=connectivity_check, session_error_status=session_error_status, session_error_index=session_error_index, get=get, log_message=log_message) # Format results return_results = _format_results( normalized=normalized, get=get, var_binds=var_binds) # Return return return_results
def make(config, verbose=False): """Process 'pagemaker' CLI option. Args: config: Configuration object verbose: Verbose output if True Returns: None """ # Initialize key variables threads_in_pool = 10 device_file_found = False # Create directory if needed perm_dir = config.web_directory() temp_dir = tempfile.mkdtemp() # Delete all files in temporary directory jm_general.delete_files(temp_dir) # Spawn a pool of threads, and pass them queue instance for _ in range(threads_in_pool): update_thread = PageMaker(THREAD_QUEUE) update_thread.daemon = True update_thread.start() # Get host data and write to file for host in config.hosts(): # Skip if device file not found if os.path.isfile(config.snmp_device_file(host)) is False: log_message = ('No YAML device file for host %s found in %s. ' 'Run toolbox.py with the "poll" option first.' '') % (host, config.snmp_directory()) jm_general.logit(1018, log_message, False) continue else: device_file_found = True #################################################################### # # Define variables that will be required for the database update # We have to initialize the dict during every loop to prevent # data corruption # #################################################################### data_dict = {} data_dict['host'] = host data_dict['config'] = config data_dict['verbose'] = verbose data_dict['temp_dir'] = temp_dir THREAD_QUEUE.put(data_dict) # Do the rest if device_file_found if device_file_found is True: # Wait on the queue until everything has been processed THREAD_QUEUE.join() # PYTHON BUG. Join can occur while threads are still shutting down. # This can create spurious "Exception in thread (most likely raised # during interpreter shutdown)" errors. # The "time.sleep(1)" adds a delay to make sure things really terminate # properly. This seems to be an issue on virtual machines in Dev only time.sleep(1) # Create index file write_file = ('%s/index.html') % (temp_dir) index_html = _index_html(config) with open(write_file, 'w') as file_handle: file_handle.write(index_html) # Cleanup, move temporary files to clean permanent directory. # Delete temporary directory if os.path.isdir(perm_dir): jm_general.delete_files(perm_dir) else: os.makedirs(perm_dir, 0o755) jm_general.move_files(temp_dir, perm_dir) # Clean up os.rmdir(temp_dir)
def make(config, verbose=False): """Process 'pagemaker' CLI option. Args: config: Configuration object verbose: Verbose output if True Returns: None """ # Initialize key variables threads_in_pool = 10 device_file_found = False # Create directory if needed perm_dir = config.web_directory() temp_dir = tempfile.mkdtemp() # Delete all files in temporary directory jm_general.delete_files(temp_dir) # Spawn a pool of threads, and pass them queue instance for _ in range(threads_in_pool): update_thread = PageMaker(THREAD_QUEUE) update_thread.daemon = True update_thread.start() # Get host data and write to file for host in config.hosts(): # Skip if device file not found if os.path.isfile(config.snmp_device_file(host)) is False: log_message = ( 'No YAML device file for host %s found in %s. ' 'Run toolbox.py with the "poll" option first.' '') % (host, config.snmp_directory()) jm_general.logit(1018, log_message, False) continue else: device_file_found = True #################################################################### # # Define variables that will be required for the database update # We have to initialize the dict during every loop to prevent # data corruption # #################################################################### data_dict = {} data_dict['host'] = host data_dict['config'] = config data_dict['verbose'] = verbose data_dict['temp_dir'] = temp_dir THREAD_QUEUE.put(data_dict) # Do the rest if device_file_found if device_file_found is True: # Wait on the queue until everything has been processed THREAD_QUEUE.join() # PYTHON BUG. Join can occur while threads are still shutting down. # This can create spurious "Exception in thread (most likely raised # during interpreter shutdown)" errors. # The "time.sleep(1)" adds a delay to make sure things really terminate # properly. This seems to be an issue on virtual machines in Dev only time.sleep(1) # Create index file write_file = ('%s/index.html') % (temp_dir) index_html = _index_html(config) with open(write_file, 'w') as file_handle: file_handle.write(index_html) # Cleanup, move temporary files to clean permanent directory. # Delete temporary directory if os.path.isdir(perm_dir): jm_general.delete_files(perm_dir) else: os.makedirs(perm_dir, 0o755) jm_general.move_files(temp_dir, perm_dir) # Clean up os.rmdir(temp_dir)