def append_node(self, new_node: Node = None): """ Append a new node to the database, adding it to sector list accordingly. :param new_node: the node to be added. :return: True if the node was successfully added. False, otherwise. :raise DifferentSectorNodeError: the user tries to append a node that is already in another sector. :raise TypeError: node given as parameter is None or its name is not valid. """ if new_node is None or new_node.name is None or new_node.name == "": raise TypeError( "Node given as parameter is None or its name is not valid.") self.nodesListMutex.acquire() # Trying to add a node that exists in other sector try: old_node = self.get_node_by_name(new_node.name) # node already exists if old_node.sector != new_node.sector: # node was initially added on another sector. self.nodesListMutex.release() raise DifferentSectorNodeError( "Cannot append node to sector {} if it already belongs to {}" .format(new_node.sector, old_node.sector)) # Remove old node self.db.delete(old_node.get_key()) self.db.lrem(name=new_node.sector, value=old_node.get_key(), count=0) except DataNotFoundError: pass if not self.db.exists(new_node.get_key()): self.db.lpush(new_node.sector, new_node.get_key()) node_key, node_value = new_node.to_dict() self.db.set(node_key, node_value) success = self.db.set(str(new_node.ip_address), new_node.get_key()) self.nodesListMutex.release() return success
class BBB: """ A class to represent a Beaglebone host. """ CONFIG_JSON_PATH = '/opt/device.json' def __init__(self, path, interface='eth0'): """ Creates a new object instance. :param path: the configuration file's location """ # Creates the objects that wrap the host's settings. self.node = Node() self.logger = logging.getLogger('BBB') # Parameters that define absolute locations inside the host self.configuration_file_path = path # The interface used (ethernet port on the Beaglebone). self.interface_name = interface self.read_node_parameters() self.node.type = Type.from_code(Type.UNDEFINED) self.node.update_state(NodeState.CONNECTED) self.node.ip_address = str( ipaddress.ip_address(self.get_ip_address()[0])) self.current_config_json_mtime = None # Load the data from the cfg file. self.check_config_json() def check_config_json(self): """ Verify if the version loaded of the file config.json is the lattest available. If not, load again. """ if os.path.exists(BBB.CONFIG_JSON_PATH): config_json_mtime = os.path.getmtime(BBB.CONFIG_JSON_PATH) if self.current_config_json_mtime == None or config_json_mtime != self.current_config_json_mtime: with open(BBB.CONFIG_JSON_PATH, 'r') as f: config = json.load(f) self.current_config_json_mtime = config_json_mtime self.node.type.code = int(config['device']) self.node.details = '{}\tbaudrate={}'.format( config['details'], config['baudrate']) self.node.config_time = config['time'] self.write_node_configuration() def get_current_config(self): """ Returns a dictionary containing the host's information and the command type. :return: message representing the current configuration. """ self.check_config_json() dict_res = self.node.to_dict() return {'comm': Command.PING, 'n': dict_res[1]} def reboot(self): """ Reboots this node. """ self.logger.info( "Setting state to reboot ... Waiting for the next ping ...") self.node.update_state(NodeState.REBOOTING) time.sleep(3.) self.logger.info("Rebooting system.") os.system('reboot') def update_hostname(self, new_hostname): """ Updates the host with anew hostname. """ old_hostname = self.node.name.replace(':', '--') new_hostname = new_hostname.replace(':', '--') if old_hostname != new_hostname: self.logger.info("Updating current hostname from {} to {}.".format( old_hostname, new_hostname)) with open("/etc/hostname", "w") as hostnameFile: hostnameFile.write(new_hostname) hostnameFile.close() os.system("hostname {}".format(new_hostname)) self.node.name = new_hostname def update_ip_address(self, dhcp_manual, new_ip_address="", new_mask="", new_gateway=""): """ Updates the host with a new ip address """ if self.node.ip_address != new_ip_address: if new_ip_address != "": self.logger.info( "Updating current ip address from {} to {}, mask {}, default gateway {}." .format(self.node.ip_address, new_ip_address, new_mask, new_gateway)) else: self.logger.info( "Updating current ip address from {} to DHCP.".format( self.node.ip_address)) self.change_ip_address(dhcp_manual, new_ip_address, new_mask, new_gateway) self.node.ip_address = self.get_ip_address()[0] def read_node_parameters(self): """ Reads current node parameters, editing the name to include ':' where needed. """ try: self.read_node_configuration() except IOError: self.logger.error( "Configuration file not found. Adopting default values.") name = subprocess.check_output(["hostname" ]).decode('utf-8').strip('\n') self.node.name = name.replace('--', ':') def read_node_configuration(self): """ Reads the current node configuration from a binary file with the pickle module. """ with open(self.configuration_file_path, 'rb') as file: self.node = pickle.load(file) file.close() self.logger.info("Node configuration file read successfully.") def write_node_configuration(self): """ Writes the current node configuration to a binary file with the pickle module. Overrides old files. """ with open(self.configuration_file_path, 'wb') as file: file.write(pickle.dumps(self.node)) file.close() self.logger.info("Node configuration file updated successfully.") def get_ip_address(self): """ Get the host's IP address with the 'ip addr' Linux command. :return: a tuple containing the host's ip address and network address. """ command_out = subprocess.check_output( "ip addr show dev {} scope global".format( self.interface_name).split()).decode('utf-8') lines = command_out.split('\n') address_line = lines[2].split()[1] return ipaddress.IPv4Address(address_line[0:address_line.index('/')]), \ ipaddress.IPv4Network(address_line, strict=False) def change_ip_address(self, dhcp_manual, new_ip_address="", new_mask="", new_gateway=""): """ Execute the connmanclt tool to change the host' IP address. :param dchp_manual: either if its a DHCP ("dhcp") ou STATIC IP ("manual") :param new_ip_address: the new IP address. An ipaddress.IPv4Address object. :param net_address: new sub-network address. An ipaddress.IPv4Network object. :param default_gateway_address: the new default gateway :raise TypeError: new_ip_address or net_address are None or are neither ipaddress nor string objects. """ service = self.get_connman_service_name() self.logger.debug("Service for interface {} is {}.".format( self.interface_name, service)) if new_ip_address != "": self.logger.info( 'Changing current IP address from {} to {}'.format( self.get_ip_address()[0], new_ip_address)) if new_gateway is None: new_gateway = Sector.get_default_gateway_of_address( new_ip_address) else: self.logger.info( 'Changing current IP address from {} to DHCP'.format( self.get_ip_address()[0])) subprocess.check_output([ 'connmanctl config {} --ipv4 {} {} {} {}'.format( service, dhcp_manual, new_ip_address, new_mask, new_gateway) ], shell=True) time.sleep(2) self.logger.debug('IP address after update is {}'.format( self.get_ip_address()[0])) def update_nameservers(self, nameserver_1="", nameserver_2=""): service = self.get_connman_service_name() self.logger.info('Changing DSN server to {} and {}'.format( nameserver_1, nameserver_2)) subprocess.check_output([ 'connmanctl config {} --nameservers {} {}'.format( service, nameserver_1, nameserver_2) ], shell=True) def get_connman_service_name(self): """ Returns the service name assigned to manage an interface. @fixme: services with spaces on their names won't be detected! :return: A service's name. :raise ValueError: service is not found. """ services = subprocess.check_output(['connmanctl services'], stderr=subprocess.STDOUT, shell=True).decode('utf-8') for service in services.split(): if service.startswith('ethernet_'): service_properties = subprocess.check_output( ['connmanctl services ' + service], stderr=subprocess.STDOUT, shell=True).decode('utf-8') for prop in service_properties.split('\n'): if prop.strip().startswith('Ethernet'): data = prop.split('[')[1].strip()[:-1].split(',') for d_info in data: d_info = d_info.strip() if d_info.startswith('Interface'): if d_info == 'Interface={}'.format( self.interface_name): return service raise ValueError( 'Connmanctl service could not be found for interface {}'.format( self.interface_name))