def load(self, path, ifaces): """ Load the configuration. If configuration is not installed yet, initialise them with the given interfaces and install them. Args: path: Path for the bundle, the configuration should be located under "data" directory. ifaces: A list of interfaces name. """ self.model = ModelInitiator("ethernet", path, backup_interval=-1) if not self.model.db: raise IOError("Cannot load any configuration.") # Initialise the interfaces # TODO: 2nd iface's type is "LAN"; another is "WAN" if 1 == len(self.model.db) and "id" not in self.model.db[0]: _logger.debug("factory install") default_db = self.model.db.pop() ip_3_def = int(default_db["ip"].split(".")[2]) - 1 for iface in ifaces: ifaddr = ip.ifaddresses(iface) db = copy.deepcopy(default_db) db["name"] = iface db["id"] = int(iface.replace("eth", "")) + 1 ip_3 = ip_3_def + db["id"] db["ip"] = "192.168.%d.127" % ip_3 db["subnet"] = "192.168.%d.0" % ip_3 db["gateway"] = "192.168.%d.254" % ip_3 db["status"] = True if ifaddr["link"] == 1 else False db["mac"] = ifaddr["mac"] self.model.db.append(db) self.save()
class Example(Sanji): def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) """ Config will be located at ./data/example.json If ./data/example.json not exist, modelInitiator will create from ./example.json.factory and program could access dict object from self.model.db (modelInitiator is optional, you can handle data/config by your own) """ self.model = ModelInitiator("example", path_root) # Check daemon status here and start/stop it. @Route(methods="get", resource="/example") def get(self, message, response): # Response dict # response code => http status code # data => payload return response(code=200, data=self.model.db) @Route(methods="put", resource="/example") def put(self, message, response): # Assign incomming dict object to self.model.db self.model.db = message.data # Save to ./data/example.json self.model.save_db() # Response current dict object # response code default = 200 return response(data=self.model.db)
class TestModelInitiatorClass(unittest.TestCase): """ " Test class """ model_name = "test_myself" model_path = "/tmp/sanji-sdk/tests/test_myself" model_db_folder = "/tmp/sanji-sdk/tests/test_myself/data" model_factory_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.factory.json" model_db = "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json" def setUp(self): """ " Prepare """ os.makedirs(self.model_path) self.model_initaitor = ModelInitiator(self.model_name, self.model_path) def tearDown(self): """ " Clean up """ if os.path.exists(self.model_path): shutil.rmtree(self.model_path) self.model_initaitor = None def test_init(self): """ " Test __init__() """ self.assertEquals(self.model_initaitor.model_name, self.model_name) def test_mkdir(self): """ " It Should generate a data folder. """ result = self.model_initaitor.mkdir() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db_folder)) def test_create_db(self): """ " It should generate a factory db. """ self.model_initaitor.mkdir() try: with open(self.model_factory_db, 'a'): os.utime(self.model_factory_db, None) except Exception: self.fail("Maybe there is no folder to create file.") result = self.model_initaitor.create_db() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db)) self.db_type = "sql" result = self.model_initaitor.create_db() self.assertFalse(result)
def test_db_manager(self): # case 1: existing self.model_initiator.save_db() self.model_initiator.db_manager() self.assertEqual(self.model_initiator.db_status, "existing") # case 2: factory with patch( "sanji.model_initiator.ModelInitiator.create_db") as create_db: create_db.return_value = 1 if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=-1) self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "factory") # case 3: backup with patch( "sanji.model_initiator.ModelInitiator.create_db") as create_db: create_db.return_value = 1 self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator.db_manager() self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "backup")
def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) """ Config will be located at ./data/example.json If ./data/example.json not exist, modelInitiator will create from ./example.json.factory and program could access dict object from self.model.db (modelInitiator is optional, you can handle data/config by your own) """ self.model = ModelInitiator("example", path_root)
def __init__(self, name, path, schema=None, model_cls=dict): self.model_cls = model_cls self.schema = schema if schema is not None and not isinstance(schema, Schema): raise TypeError("schema should be instance of voluptuous.Schema") if not issubclass(model_cls, dict): raise TypeError("model_cls should be derivative dict class") self.model = ModelInitiator(model_name=name, model_path=path) self._batch = ModelBatch(self.model)
def setUp(self): """ " Prepare """ factory_data = {"name": "factory"} if not os.path.exists(self.model_db_folder): os.makedirs(self.model_db_folder) with open(self.model_factory_db, "w") as fp: json.dump(factory_data, fp, indent=4) self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=-1)
def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("route", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save()
def test_init(self): """ " Test __init__() """ # case 1: check name self.assertEquals(self.model_initiator.model_name, self.model_name) # case 2: thread self.model_initiator = None self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=1) self.assertEquals(self.model_initiator.model_name, self.model_name)
def test_db_manager(self): # case 1: existing self.model_initiator.save_db() self.model_initiator.db_manager() self.assertEqual(self.model_initiator.db_status, "existing") # case 2: factory with patch( "sanji.model_initiator.ModelInitiator.create_db")as create_db: create_db.return_value = 1 if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=-1) self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "factory") # case 3: backup with patch( "sanji.model_initiator.ModelInitiator.create_db")as create_db: create_db.return_value = 1 self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator.db_manager() self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "backup")
def load(self, path, ifaces): """ Load the configuration. If configuration is not installed yet, initialise them with the given interfaces and install them. Args: path: Path for the bundle, the configuration should be located under "data" directory. ifaces: A list of interfaces name. """ self.model = ModelInitiator("ethernet", path, backup_interval=-1) if not self.model.db: raise IOError("Cannot load any configuration.") # Initialise the interfaces # TODO: 2nd iface's type is "LAN"; another is "WAN" if 1 == len(self.model.db) and "id" not in self.model.db[0]: _logger.debug("factory install") default_db = self.model.db.pop() ip_3_def = int(default_db["ip"].split(".")[2]) - 1 for iface in ifaces: ifaddr = ip.ifaddresses(iface) db = copy.deepcopy(default_db) db["name"] = iface db["id"] = int(iface.replace("eth", "")) + 1 ip_3 = ip_3_def + db["id"] db["ip"] = "192.168.%d.127" % ip_3 db["subnet"] = "192.168.%d.0" % ip_3 db["gateway"] = "192.168.%d.254" % ip_3 db["currentStatus"] = ifaddr["link"] db["mac"] = ifaddr["mac"] self.model.db.append(db) self.save()
def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.status = status.Status(name="status", path=path_root) self.properties = ModelInitiator( model_name="properties", model_path=path_root) # Check aliasName if self.properties.db.get("aliasName", "$ModelName") == "$ModelName": self.set_alias()
def setUp(self): path_root = os.path.dirname(os.path.realpath(__file__)) + "/../../" try: os.unlink(path_root + "data/ntp.json") os.unlink(path_root + "data/ntp.json.backup") except: pass self.model = ModelInitiator("ntp", path_root) self.ntp = Ntp(self.model)
def setUp(self): """ " Prepare """ factory_data = {"name": "factory"} if not os.path.exists(self.model_db_folder): os.makedirs(self.model_db_folder) with open(self.model_factory_db, "w") as fp: json.dump(factory_data, fp, indent=4) self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=-1)
def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("cellular", path_root) self.model.db[0] = Index.CONF_SCHEMA(self.model.db[0]) self._dev_name = None self._mgr = None self._vnstat = None self.__init_monit_config( enable=(self.model.db[0]["enable"] and self.model.db[0]["keepalive"]["enable"] and True and self.model.db[0]["keepalive"]["reboot"]["enable"] and True), target_host=self.model.db[0]["keepalive"]["targetHost"], iface=self._dev_name, cycles=self.model.db[0]["keepalive"]["reboot"]["cycles"]) self._init_thread = Thread(name="sanji.cellular.init_thread", target=self.__initial_procedure) self._init_thread.daemon = True self._init_thread.start()
def test_init(self): """ " Test __init__() """ # case 1: check name self.assertEquals(self.model_initiator.model_name, self.model_name) # case 2: thread self.model_initiator = None self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=1) self.assertEquals(self.model_initiator.model_name, self.model_name)
def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("dns", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save()
def __init__(self, name, path, schema=None, model_cls=dict): self.model_cls = model_cls self.schema = schema if schema is not None and not isinstance(schema, Schema): raise TypeError("schema should be instance of voluptuous.Schema") if not issubclass(model_cls, dict): raise TypeError("model_cls should be derivative dict class") self.model = ModelInitiator( model_name=name, model_path=path ) self._batch = ModelBatch(self.model)
class Cellular(Sanji): search_router_pattern =\ re.compile(ur'option routers ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)') search_dns_pattern =\ re.compile(ur'option domain-name-servers (.*);') search_ip_pattern =\ re.compile(ur'fixed-address ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)') search_subnet_pattern =\ re.compile(ur'option subnet-mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)') search_name_pattern =\ re.compile(ur'interface "([a-z]+[0-9])"') def search_name(self, filetext): name = re.search(self.search_name_pattern, filetext) if name: logger.debug("name is %s" % name.group(1)) return name.group(1) else: return 'N/A' def search_router(self, filetext): router = re.search(self.search_router_pattern, filetext) if router: logger.debug("router is %s" % router.group(1)) return router.group(1) else: return 'N/A' def search_dns(self, filetext): dns = re.search(self.search_dns_pattern, filetext) if dns: logger.debug("dns is %s" % dns.group(1)) return dns.group(1) else: return 'N/A' def search_ip(self, filetext): ip = re.search(self.search_ip_pattern, filetext) if ip: logger.debug("ip is %s" % ip.group(1)) return ip.group(1) else: return 'N/A' def search_subnet(self, filetext): subnet = re.search(self.search_subnet_pattern, filetext) if subnet: logger.debug("subnet is %s" % subnet.group(1)) return subnet.group(1) else: return 'N/A' def is_target_device_appear(self, name): if os.path.exists(name): return True else: return False def is_leases_file_appear(self): try: with open('/var/lib/dhcp/dhclient.leases', 'r') as leases: filetext = leases.read() return filetext except Exception: logger.debug("File open failure") return '' def reconnect_if_disconnected(self): for model in self.model.db: if self.is_target_device_appear(model['modemPort']): dev_id = str(model['id']) # update signal model['signal'] = self.get_signal_by_id(dev_id) logger.debug("Signal %s on device path %s" % (model['signal'], model['modemPort'])) # check network availability # if network status down, turn up if model['enable'] == 1: logger.debug("Enable is 1") if self.get_status_by_id(dev_id) == \ "'disconnected'": logger.debug("Start connect") self.set_offline_by_id(dev_id) self.set_online_by_id(dev_id) # update info according to dhclient.leases filetext = self.is_leases_file_appear() if filetext: # parse name model['name'] = self.search_name(filetext) # parse router model['router'] = self.search_router(filetext) # parse dns model['dns'] = self.search_dns(filetext) # parse ip model['ip'] = self.search_ip(filetext) # parse subnet model['subnet'] = self.search_subnet(filetext) self.model.save_db() else: if self.get_status_by_id(dev_id) == "'connected'": self.set_offline_by_id(dev_id) else: model['signal'] = 99 def get_signal_by_id(self, dev_id): try: tmp = subprocess.check_output( "qmicli -p -d /dev/cdc-wdm" + dev_id + " --nas-get-signal-info | grep RSSI \ | cut -d \"'\" -f 2 \ | cut -d \" \" -f 1 \ |tr -d [:cntrl:]", shell=True) if tmp.isdigit(): return tmp else: return 99 except Exception: return 99 def get_status_by_id(self, dev_id): try: tmp = subprocess.check_output("qmicli -p -d /dev/cdc-wdm" + dev_id + " --wds-get-packet-service-status\ |awk '{print $4}'|\ tr -d [:space:]", shell=True) if tmp == 'connected': return tmp else: return 'disconnected' except Exception: return 'disconnected' def set_online_by_id(self, dev_id): try: subprocess.check_output("rm -rf /var/lib/dhcp/dhclient.leases", shell=True) subprocess.check_output("qmi-network /dev/cdc-wdm" + dev_id + " start", shell=True) subprocess.check_output("dhclient wwan" + dev_id, shell=True) return 'success' except Exception: return 'fail' def set_offline_by_id(self, dev_id): try: subprocess.check_output("dhclient -r wwan" + dev_id, shell=True) subprocess.check_output("qmi-network /dev/cdc-wdm" + dev_id + " stop", shell=True) return 'success' except Exception: return 'fail' def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("cellular", path_root) @Route(methods="get", resource="/network/cellulars") def get_root(self, message, response): return response(code=200, data=self.model.db) @Route(methods="get", resource="/network/cellulars/:id") def get_root_by_id(self, message, response): if int(message.param['id']) > len(self.model.db): return response(code=400, data={ "message": "No such id resources."}) else: return response(code=200, data=self.model.db [int(message.param['id'])]) @Route(methods="put", resource="/network/cellulars/:id") def put_root_by_id(self, message, response): if not hasattr(message, "data"): return response(code=400, data={"message": "Invalid Input."}) is_match_param = 0 id = int(message.param['id']) if "enable" in message.data: self.model.db[id]["enable"] = message.data["enable"] is_match_param = 1 if "apn" in message.data: self.model.db[id]["apn"] = message.data["apn"] is_match_param = 1 if "username" in message.data: self.model.db[id]["username"] = message.data["username"] is_match_param = 1 if "name" in message.data: self.model.db[id]["name"] = message.data["name"] is_match_param = 1 if "dialNumber" in message.data: self.model.db[id]["dialNumber"] = message.data["dialNumber"] is_match_param = 1 if "password" in message.data: self.model.db[id]["password"] = message.data["password"] is_match_param = 1 if "pinCode" in message.data: self.model.db[id]["pinCode"] = message.data["pinCode"] is_match_param = 1 if "enableAuth" in message.data: self.model.db[id]["enableAuth"] = message.data["enableAuth"] is_match_param = 1 if is_match_param == 0: return response(code=400, data={"message": "No such resources."}) self.model.save_db() return response(code=200, data=self.model.db[id]) def run(self): while True: self.reconnect_if_disconnected() sleep(30)
def setUp(self): """ " Prepare """ os.makedirs(self.model_path) self.model_initaitor = ModelInitiator(self.model_name, self.model_path)
class Ethernet(Sanji): """ A model to handle Ethernet interfaces' configuration. Attributes: model: Ethernet interfaces' database with json format. """ def init(self, *args, **kwargs): try: # pragma: no cover bundle_env = kwargs["bundle_env"] except KeyError: bundle_env = os.getenv("BUNDLE_ENV", "debug") self.path_root = os.path.abspath(os.path.dirname(__file__)) if bundle_env == "debug": # pragma: no cover self.path_root = "%s/tests" % self.path_root # Find all ethernet interfaces and load the configuration ifaces = ip.interfaces() ifaces = [x for x in ifaces if x.startswith("eth")] if 0 == len(ifaces): _logger.info("No interfaces to be configured.") self.stop() raise ValueError("No interfaces to be configured.") try: self.load(self.path_root, ifaces) except: self.stop() raise IOError("Cannot load any configuration.") # Apply the configuration for iface in self.model.db: self.apply(iface) def run(self): for iface in self.model.db: iface["type"] = "eth" iface["mode"] = "dhcp" if iface["enableDhcp"] else "static" self.publish.event.put("/network/interfaces/{}".format( iface["name"]), data=iface) def load(self, path, ifaces): """ Load the configuration. If configuration is not installed yet, initialise them with the given interfaces and install them. Args: path: Path for the bundle, the configuration should be located under "data" directory. ifaces: A list of interfaces name. """ self.model = ModelInitiator("ethernet", path, backup_interval=-1) if not self.model.db: raise IOError("Cannot load any configuration.") # Initialise the interfaces # TODO: 2nd iface's type is "LAN"; another is "WAN" if 1 == len(self.model.db) and "id" not in self.model.db[0]: _logger.debug("factory install") default_db = self.model.db.pop() ip_3_def = int(default_db["ip"].split(".")[2]) - 1 for iface in ifaces: ifaddr = ip.ifaddresses(iface) db = copy.deepcopy(default_db) db["name"] = iface db["id"] = int(iface.replace("eth", "")) + 1 ip_3 = ip_3_def + db["id"] db["ip"] = "192.168.%d.127" % ip_3 db["subnet"] = "192.168.%d.0" % ip_3 db["gateway"] = "192.168.%d.254" % ip_3 db["status"] = True if ifaddr["link"] == 1 else False db["mac"] = ifaddr["mac"] self.model.db.append(db) self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def apply(self, data): """ Apply the configuration to an interface. Args: data: Information for the interface to be applied (with dictionary format) """ iface = "eth%d" % (data["id"] - 1) ip.ifupdown(iface, True if data["enable"] else False) if not data["enable"]: return if data["enableDhcp"]: ip.ifconfig(iface, True, script="%s/hooks/dhclient-script" % self.path_root) else: ip.ifconfig(iface, False, data["ip"], data["netmask"], data["gateway"]) def read(self, id, restart=False, config=True): """ Read the setting for an interface. Args: id: Interface id, interface name will be eth(id+1). """ for data in self.model.db: if data["id"] == id: break else: return None # deepcopy to prevent settings be modified data = copy.deepcopy(data) if not restart and "restart" in data: data.pop("restart") iface = "eth%d" % (data["id"] - 1) ifaddr = ip.ifaddresses(iface) data["status"] = True if ifaddr["link"] == 1 else False data["mac"] = ifaddr["mac"] # """Use configuration data instead of realtime retrieving if True is config: return data data["ip"] = "" data["netmask"] = "" data["subnet"] = "" data["broadcast"] = "" # data["gateway"] = "" if ifaddr["inet"] and len(ifaddr["inet"]): data["ip"] = ifaddr["inet"][0]["ip"] data["netmask"] = ifaddr["inet"][0]["netmask"] if "subnet" in ifaddr["inet"][0]: data["subnet"] = ifaddr["inet"][0]["subnet"] else: data.pop("subnet") if "broadcast" in ifaddr["inet"][0]: data["broadcast"] = ifaddr["inet"][0]["broadcast"] elif "broadcast" in data: data.pop("broadcast") # """ return data @staticmethod def schema_validate(message): """ Validate the received data, ensure the schema is correct. """ # TODO: ip validation schema = Schema( { Required("id"): Range(min=1), Required("enable"): bool, Required("enableDhcp"): bool, Optional("wan"): bool, Optional("ip"): Any(str, unicode), Optional("netmask"): Any(str, unicode), Optional("gateway"): Any(str, unicode), Optional("dns"): [Any(str, unicode)], Extra: object }, extra=REMOVE_EXTRA) if not hasattr(message, "data"): raise KeyError("Invalid input: \"data\" attribute is required.") if type(message.data) is list: if 0 == len(message.data): raise KeyError("Invalid input: empty \"data\".") for item in message.data: try: schema(item) except Exception, e: raise KeyError("Invalid input: %s." % e) if type(message.data) is dict: try: schema(message.data) except Exception, e: raise KeyError("Invalid input: %s." % e)
def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("cellular", path_root)
class Dns(Sanji): CONFIG_PATH = "/etc/resolv.conf" IFACE_SCHEMA = Schema({ Required("name"): All(str, Length(1, 255)), Required("dns"): [Any("", All(str, Length(0, 15)))] }, extra=REMOVE_EXTRA) PUT_DB_SCHEMA = Schema({ Required("source"): All(str, Length(1, 255)), Required("dns"): [Any("", All(str, Length(0, 15)))] }, extra=REMOVE_EXTRA) PUT_DNS_SCHEMA = Schema({ Optional("enableFixed"): bool, Optional("fixedDns"): [Any("", All(str, Length(0, 15)))] }, extra=REMOVE_EXTRA) def init(self, *args, **kwargs): try: # pragma: no cover bundle_env = kwargs["bundle_env"] except KeyError: bundle_env = os.getenv("BUNDLE_ENV", "debug") # load configuration self.path_root = os.path.abspath(os.path.dirname(__file__)) if bundle_env == "debug": # pragma: no cover self.path_root = "%s/tests" % self.path_root try: self.load(self.path_root) except: self.stop() raise IOError("Cannot load any configuration.") # initialize DNS database self.dns_db = [] if "fixedDns" in self.model.db: self.add_dns_list( {"source": "fixed", "dns": self.model.db["fixedDns"]}) def run(self): try: self.update_config() except Exception as e: _logger.warning("Failed to update %s: %s" % (Dns.CONFIG_PATH, e)) def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("dns", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def get_dns_list(self, source): """ Get DNS list by source from database. Args: source: source which the DNS list belongs to. """ for entry in self.dns_db: if source == entry["source"]: return entry return None def set_dns_list(self, obj, update=True): """ Update DNS list by source from database. Args: obj: a dictionary with "source" and "dns" list, for example: { "source": "eth0", "dns": ["8.8.8.8", "8.8.4.4"] } """ for entry in self.dns_db: if obj["source"] == entry["source"]: entry["dns"] = obj["dns"] return entry return self.add_dns_list(obj, update) def add_dns_list(self, obj, update=True): """ Add DNS list by source into database and update setting if required. Args: obj: a dictionary with "source" and "dns" list, for example: { "source": "eth0", "dns": ["8.8.8.8", "8.8.4.4"] } """ entry = self.get_dns_list(obj["source"]) if entry: entry["dns"] = obj["dns"] else: self.dns_db.append(obj) # update config if data updated if update and "source" in self.model.db \ and obj["source"] == self.model.db["source"]: self.update_config() def remove_dns_list(self, source): """ Remove DNS list by source from database. Args: source: source for the DNS list belongs to. """ self.dns_db[:] = \ [i for i in self.dns_db if i.get("source") != source] def _generate_config(self): """ Generate /etc/resolv.conf content. Priority: 1. fixed DNS 2. temporary DNS 3. by source """ resolv = "" data = self.get_current_dns() if "dns" not in data: return resolv for server in data["dns"]: if server != "": resolv = resolv + ("nameserver %s\n" % server) return resolv def _write_config(self, resolv): """ Write DNS configurations into DNS file (/etc/resolv.conf). Args: resolv_info: Text content for DNS information. """ with open(Dns.CONFIG_PATH, "w") as f: f.write(resolv) def update_config(self): """ Update the DNS configuration by settings. """ self._write_config(self._generate_config()) def get_current_dns(self): """ Get current DNS settings, include fixed information. { "enableFixed": false, "fixedDns": ["8.8.8.8", "8.8.4.4"], "source": "eth0", "dns": ["192.168.50.33", "192.168.50.36"] } """ data = copy.deepcopy(self.model.db) if "enableFixed" not in data: data["enableFixed"] = False if data["enableFixed"] is True: data["source"] = "fixed" if "source" in data: dns = self.get_dns_list(data["source"]) if dns and "dns" in dns: data["dns"] = copy.copy(dns["dns"]) elif data["enableFixed"] is True: data["dns"] = data["fixedDns"] return data @Route(methods="get", resource="/network/dns") def _get_current_dns(self, message, response): data = self.get_current_dns() return response(data=data) def set_current_dns(self, data): """ Update current DNS configuration by message. """ # add to DNS database if data include both source and dns list # fixed DNS updated later if "source" in data and "dns" in data and data["source"] != "fixed": self.add_dns_list(data, False) # update settings self.model.db.pop("dns", None) if "enableFixed" not in self.model.db: self.model.db["enableFixed"] = False source = None if "source" not in data else data.pop("source") dnslist = None if "dns" not in data else data.pop("dns") if source and source != "fixed": self.model.db["source"] = source elif source is None and dnslist: self.model.db.pop("source", None) self.model.db["dns"] = dnslist self.model.db.update(data) self.save() # update fixed dns = {} dns["source"] = "fixed" if "fixedDns" in self.model.db: dns["dns"] = self.model.db["fixedDns"] else: dns["dns"] = [] self.set_dns_list(dns) self.update_config() @Route(methods="put", resource="/network/dns", schema=PUT_DNS_SCHEMA) def _put_current_dns(self, message, response): try: self.set_current_dns(message.data) except Exception as e: return response(code=400, data={"message": e.message}) return response(data=message.data) @Route(methods="get", resource="/network/dns/db") def _get_dns_database(self, message, response): return response(data=self.dns_db) def set_dns_database(self, message, response): """ Update DNS database batch or by source. """ if type(message.data) is list: for dns in message.data: self.add_dns_list(dns) elif type(message.data) is dict: self.add_dns_list(message.data) else: return response(code=400, data={"message": "Wrong type of DNS database."}) return response(data=self.dns_db) @Route(methods="put", resource="/network/dns/db") def _put_dns_database(self, message, response): return self.set_dns_database(message, response) @Route(methods="put", resource="/network/interfaces/:name") def _event_network_interface(self, message): """ Listen interface event to update the dns database and settings. """ if not(hasattr(message, "data")): raise ValueError("Data cannot be None or empty.") try: self.IFACE_SCHEMA(message.data) except Exception as e: raise e _logger.debug("[/network/interfaces] interface: %s, dns: %s" % (message.param["name"], message.data["dns"])) dns = {"source": message.param["name"], "dns": message.data["dns"]} self.add_dns_list(dns) @Route(methods="put", resource="/network/wan") def _event_network_wan(self, message): """ Listen wan event to update the dns settings. """ try: self.set_current_dns({"source": message.data["interface"]}) except Exception as e: _logger.info("[/network/wan] %s".format(e.message))
class Index(Sanji): PUT_SCHEMA = Schema( { "timezone": All(str, Length(8)), "ntp": { "enable": All(int, Range(min=0, max=1)), "server": All(str, Length(1, 2048)), "interval": All(int, Range(min=60, max=60 * 60 * 24 * 30)) } }, extra=REMOVE_EXTRA) def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("ntp", path_root) self.ntp = Ntp(self.model) @Route(methods="get", resource="/system/time") def get(self, message, response): realtime_data = {"time": SysTime.get_system_time()} return response(data=dict(self.model.db.items() + realtime_data.items())) @Route(methods="put", resource="/system/time", schema=PUT_SCHEMA) def put(self, message, response): rc = None try: # update ntp settings if "ntp" in message.data: rc = self.ntp.update(message.data["ntp"]) if rc is False: raise RuntimeWarning("Update ntp settings failed.") self.model.db["ntp"] = dict(self.model.db["ntp"].items() + message.data["ntp"].items()) # change timezone if "timezone" in message.data: rc = SysTime.set_system_timezone(message.data["timezone"]) if rc is False: raise RuntimeWarning("Change timezone failed.") self.model.db["timezone"] = message.data["timezone"] # manual change sys time if "time" in message.data: if self.model.db["ntp"]["enable"] == 1: _logger.debug("NTP enabled. skipping time setup.") else: rc = SysTime.set_system_time(message.data["time"]) if rc is False: raise RuntimeWarning("Change system time failed.") if rc is None: return response(code=400, data={"message": "No input paramters."}) else: self.model.save_db() except Exception as e: _logger.debug(e, exc_info=True) code = 400 if not isinstance(e, RuntimeWarning) else 500 return response(code=code, data={"message": str(e)}) realtime_data = {"time": SysTime.get_system_time()} # operation successed return response(data=dict(self.model.db.items() + realtime_data.items()))
class TestModelInitiatorClass(unittest.TestCase): """ " Test class """ model_name = "test_myself" model_path = "/tmp/sanji-sdk/tests/test_myself" model_db_folder = "/tmp/sanji-sdk/tests/test_myself/data" model_factory_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json.factory" model_backup_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json.backup" model_db = "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json" def setUp(self): """ " Prepare """ factory_data = {"name": "factory"} if not os.path.exists(self.model_db_folder): os.makedirs(self.model_db_folder) with open(self.model_factory_db, "w") as fp: json.dump(factory_data, fp, indent=4) self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=-1) def tearDown(self): """ " Clean up """ self.model_initiator.stop_backup() self.model_initiator = None if os.path.exists(self.model_path): removeall(self.model_db_folder) def test_init(self): """ " Test __init__() """ # case 1: check name self.assertEquals(self.model_initiator.model_name, self.model_name) # case 2: thread self.model_initiator = None self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=1) self.assertEquals(self.model_initiator.model_name, self.model_name) def test_db_manager(self): # case 1: existing self.model_initiator.save_db() self.model_initiator.db_manager() self.assertEqual(self.model_initiator.db_status, "existing") # case 2: factory with patch( "sanji.model_initiator.ModelInitiator.create_db") as create_db: create_db.return_value = 1 if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator = ModelInitiator(self.model_name, self.model_path, backup_interval=-1) self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "factory") # case 3: backup with patch( "sanji.model_initiator.ModelInitiator.create_db") as create_db: create_db.return_value = 1 self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator.db_manager() self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "backup") def test_create_db(self): """ " It should generate a factory db if there is no db. """ if os.path.exists(self.model_db_folder): shutil.rmtree(self.model_db_folder) os.makedirs(self.model_db_folder) try: with open(self.model_factory_db, "a"): os.utime(self.model_factory_db, None) except Exception: self.fail("Maybe there is no folder to create file.") # case 1: general case result = self.model_initiator.create_db() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db)) # case 2: no factory db if os.path.exists(self.model_db): os.remove(self.model_db) if os.path.exists(self.model_factory_db): os.remove(self.model_factory_db) with self.assertRaises(RuntimeError): result = self.model_initiator.create_db() # case 3: sql type self.db_type = "sql" with self.assertRaises(RuntimeError): result = self.model_initiator.create_db() def test_recover_db(self): # case 1: Check file which restore from backup db self.model_initiator.db = {} self.model_initiator.db["name"] = "backup" self.model_initiator.db["type"] = "json" self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.assertFalse(os.path.exists(self.model_db)) self.model_initiator.recover_db(self.model_backup_db) self.assertTrue(os.path.exists(self.model_db)) # case 2: Check data which restore from backup db with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "backup", "type": "json"}) # case 3: Check file which restore from factory db if os.path.exists(self.model_db): os.remove(self.model_db) self.assertFalse(os.path.exists(self.model_db)) self.model_initiator.recover_db(self.model_factory_db) self.assertTrue(os.path.exists(self.model_db)) # case 4: Check data which restore from factory db with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "factory"}) # case 5: no file try: self.assertRaises( self.model_initiator.recover_db("/tmp/1234555555.txt")) except IOError: pass else: self.fail("No file to load but pass.") def test_backup_db(self): """ " Test backup db """ if os.path.exists(self.model_backup_db): os.remove(self.model_backup_db) # case 1: Check file exist self.model_initiator.backup_db() self.assertTrue(os.path.exists(self.model_backup_db)) # case 2: Check data with open(self.model_backup_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "factory"}) def test_load_db(self): """ " It should load json db as a dictionary. """ # case 1: No folder self.model_initiator.db = None self.assertEqual(type(self.model_initiator.db), type(None)) self.model_initiator.create_db() try: with open(self.model_factory_db, "a"): os.utime(self.model_factory_db, None) except Exception: self.fail("Maybe there is no folder to create file.") # case 2: data data = {"enable": 1} with open(self.model_factory_db, "w") as fp: json.dump(data, fp, indent=4) shutil.copyfile(self.model_factory_db, self.model_db) self.model_initiator.load_db() self.assertEqual(self.model_initiator.db, data) # case 3: if os.path.exists(self.model_db): os.remove(self.model_db) try: self.assertRaises(self.model_initiator.load_db()) except Exception: pass def test_save_db(self): """ " Test save db """ # case 1: data of saving self.model_initiator.db = {} self.model_initiator.db["name"] = "John" self.model_initiator.db["age"] = 33 self.model_initiator.save_db() db_data = None with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "John", "age": 33}) # case 2: non dictionary or array type. self.model_initiator.db = "string type" rc = self.model_initiator.save_db() self.assertFalse(rc) # case 3: open with no file. (coverage) if os.path.exists(self.model_db_folder): shutil.rmtree(self.model_db_folder) try: self.assertRaises(self.model_initiator.save_db()) except Exception: pass @patch("sanji.model_initiator.Thread") def test_start_backup_thread_is_not_alive(self, Thread): self.model_initiator._backup_thread.is_alive = Mock(return_value=False) Thread.return_value = MagicMock(daemon=False, start=Mock()) self.model_initiator.start_backup() self.assertTrue(self.model_initiator._backup_thread.daemon) self.model_initiator._backup_thread.start.assert_called_once_with() def test_start_backup_thread_is_alive(self): with self.assertRaises(RuntimeError): self.model_initiator._backup_thread.is_alive =\ Mock(return_value=True) self.model_initiator.start_backup() self.model_initiator.stop_backup = Mock() def test_stop_backup(self): rc = self.model_initiator.stop_backup() self.assertFalse(rc) self.model_initiator.start_backup() rc = self.model_initiator.stop_backup() self.assertTrue(rc) self.model_initaitor.db_type = "json" self.model_initaitor.factory_json_db_path = "/1231231235566" result = self.model_initaitor.create_db() self.assertFalse(result)
class Model(object): def __init__(self, name, path, schema=None, model_cls=dict): self.model_cls = model_cls self.schema = schema if schema is not None and not isinstance(schema, Schema): raise TypeError("schema should be instance of voluptuous.Schema") if not issubclass(model_cls, dict): raise TypeError("model_cls should be derivative dict class") self.model = ModelInitiator(model_name=name, model_path=path) self._batch = ModelBatch(self.model) def batch(self): return self._batch def _cast_model(self, obj): return self.model_cls(obj) @property def maxId(self): """int: current max id of objects""" if len(self.model.db) == 0: return 0 return max(map(lambda obj: obj["id"], self.model.db)) def validation(self, instance): """Valid input instance is vaild or not Args: Object: input instance Returns: Object: Instance after vaildation or original instance if schema is None Raises: Error: If vaildation failed """ if self.schema is None: return instance return self.schema(instance) def add(self, obj): """Add a object Args: Object: Object will be added Returns: Object: Object with id Raises: TypeError: If add object is not a dict MultipleInvalid: If input object is invaild """ if not isinstance(obj, dict): raise TypeError("Add object should be a dict object") obj = self.validation(obj) obj["id"] = self.maxId + 1 obj = self._cast_model(obj) self.model.db.append(obj) if not self._batch.enable.is_set(): self.model.save_db() return obj def get(self, id): """Get a object by id Args: id (int): Object id Returns: Object: Object with specified id None: If object not found """ for obj in self.model.db: if obj["id"] == id: return self._cast_model(obj) return None def remove(self, id): """Remove a object by id Args: id (int): Object's id should be deleted Returns: len(int): affected rows """ before_len = len(self.model.db) self.model.db = [t for t in self.model.db if t["id"] != id] if not self._batch.enable.is_set(): self.model.save_db() return before_len - len(self.model.db) def removeAll(self): """Remove all objects Returns: len(int): affected rows """ before_len = len(self.model.db) self.model.db = [] if not self._batch.enable.is_set(): self.model.save_db() return before_len - len(self.model.db) def update(self, id, newObj): """Update a object Args: id (int): Target Object ID newObj (object): New object will be merged into original object Returns: Object: Updated object None: If specified object id is not found MultipleInvalid: If input object is invaild """ newObj = self.validation(newObj) for obj in self.model.db: if obj["id"] != id: continue newObj.pop("id", None) obj.update(newObj) obj = self._cast_model(obj) if not self._batch.enable.is_set(): self.model.save_db() return obj return None def set(self, id, newObj): """Set a object Args: id (int): Target Object ID newObj (object): New object will be set Returns: Object: New object None: If specified object id is not found MultipleInvalid: If input object is invaild """ newObj = self.validation(newObj) for index in xrange(0, len(self.model.db)): if self.model.db[index]["id"] != id: continue newObj["id"] = id self.model.db[index] = self._cast_model(newObj) if not self._batch.enable.is_set(): self.model.save_db() return self.model.db[index] return None def getAll(self): """Get all objects Returns: List: list of all objects """ objs = [] for obj in self.model.db: objs.append(self._cast_model(obj)) return objs
class IPRoute(Sanji): """ A model to handle IP Route configuration. Attributes: model: database with json format. """ update_interval = 60 def init(self, *args, **kwargs): try: # pragma: no cover self.bundle_env = kwargs["bundle_env"] except KeyError: self.bundle_env = os.getenv("BUNDLE_ENV", "debug") path_root = os.path.abspath(os.path.dirname(__file__)) if self.bundle_env == "debug": # pragma: no cover path_root = "%s/tests" % path_root self.interfaces = [] try: self.load(path_root) except: self.stop() raise IOError("Cannot load any configuration.") def run(self): while True: sleep(self.update_interval) try: self.try_update_default(self.model.db) except Exception as e: _logger.debug(e) def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("route", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def list_interfaces(self): """ List available interfaces. """ # retrieve all interfaces try: ifaces = ip.addr.interfaces() except: return {} # list connected interfaces data = [] for iface in ifaces: try: iface_info = ip.addr.ifaddresses(iface) except: continue if 1 == iface_info["link"]: inet_ip = [inet["ip"] for inet in iface_info["inet"] if "" != inet["ip"]] if len(inet_ip): data.append(iface) return data def get_default(self): """ Retrieve the default gateway Return: default: dict format with "interface" and/or "gateway" """ gws = netifaces.gateways() default = {} if gws['default'] != {} and netifaces.AF_INET in gws['default']: gw = gws['default'][netifaces.AF_INET] else: return default default["gateway"] = gw[0] default["interface"] = gw[1] return default def update_wan_info(self, interface): """ Update WAN interface to default gateway's interface. Args: default: interface name """ self.publish.event.put("/network/wan", data={"interface": interface}) # TODO: modify DNS to listen `/network/wan` instead res = self.publish.put("/network/dns", data={"source": interface}) if res.code != 200: raise RuntimeWarning(res.data["message"]) def update_default(self, default): """ Update default gateway. If updated failed, should recover to previous one. Args: default: dict format with "interface" required and "gateway" optional. """ # delete the default gateway if not default or ("interface" not in default and "gateway" not in default): ip.route.delete("default") # change the default gateway # FIXME: only "gateway" without interface is also available # FIXME: add "secondary" default route rule else: ip.route.delete("default") if "gateway" in default and "interface" in default: ip.route.add("default", default["interface"], default["gateway"]) elif "interface" in default: ip.route.add("default", default["interface"]) elif "gateway" in default: ip.route.add("default", "", default["gateway"]) else: raise IPRouteError("Invalid default route.") # update DNS if "interface" in default: self.update_wan_info(default["interface"]) def _try_update_default(self, routes): """ Try to update the default gateway. Args: routes: dict format including default gateway interface and secondary default gateway interface. For example: { "default": "wwan0", "secondary": "eth0" } """ ifaces = self.list_interfaces() if not ifaces: raise IPRouteError("Interfaces should be UP.") default = {} if routes["default"] in ifaces: default["interface"] = routes["default"] elif routes["secondary"] in ifaces: default["interface"] = routes["secondary"] else: self.update_default({}) return # find gateway by interface for iface in self.interfaces: if iface["interface"] == default["interface"]: default = iface break current = self.get_default() if current != default: self.update_default(default) def try_update_default(self, routes): with _update_default_lock: try: self._try_update_default(routes) except IPRouteError as e: _logger.debug(e) def update_router(self, interface): """ Save the interface name with its gateway and update the default gateway if needed. If gateway is not specified, use the previous value. Only delete the gateway when gateway attribute is empty. Args: interface: dict format with interface "name" and/or "gateway". """ # update the router information for iface in self.interfaces: if iface["interface"] == interface["name"]: if "gateway" in interface: iface["gateway"] = interface["gateway"] break else: iface = {} iface["interface"] = interface["name"] if "gateway" in interface: iface["gateway"] = interface["gateway"] self.interfaces.append(iface) # check if the default gateway need to be modified self.try_update_default(self.model.db) def set_default(self, default, is_default=True): """ Update default / secondary gateway. """ if is_default: def_type = "default" else: def_type = "secondary" # save the setting # if no interface but has gateway, do not update anything if "interface" in default: self.model.db[def_type] = default["interface"] elif "gateway" not in default: self.model.db[def_type] = "" self.save() try: if is_default: self.update_default(default) except Exception as e: # try database if failed try: self.try_update_default(self.model.db) except IPRouteError as e2: _logger.debug( "Failed to recover the default gateway: {}".format(e2)) error = "Update default gateway failed: {}".format(e) _logger.error(error) raise IPRouteError(error) @Route(methods="get", resource="/network/routes/interfaces") def _get_interfaces(self, message, response): """ Get available interfaces. """ return response(data=self.list_interfaces()) @Route(methods="get", resource="/network/routes/default") def _get_default(self, message, response): """ Get default gateway. """ return response(data=self.get_default()) put_default_schema = Schema({ Optional("interface"): Any(str, unicode), Extra: object}) @Route(methods="put", resource="/network/routes/default") def _put_default(self, message, response, schema=put_default_schema): """ Update the default gateway, delete default gateway if data is None or empty. """ try: self.set_default(message.data) except Exception as e: return response(code=404, data={"message": e}) return response(data=self.get_default()) @Route(methods="put", resource="/network/routes/secondary") def _put_secondary(self, message, response, schema=put_default_schema): """ Update the secondary default gateway, delete default gateway if data is None or empty. """ try: self.set_default(message.data, False) except Exception as e: return response(code=404, data={"message": e}) return response(data=message.data) def set_router_db(self, message, response): """ Update router database batch or by interface. """ if type(message.data) is list: for iface in message.data: self.update_router(iface) return response(data=self.interfaces) elif type(message.data) is dict: self.update_router(message.data) return response(data=message.data) return response(code=400, data={"message": "Wrong type of router database."}) @Route(methods="put", resource="/network/routes/db") def _set_router_db(self, message, response): return self.set_router_db(message, response) @Route(methods="get", resource="/network/routes/db") def _get_router_db(self, message, response): return response(data=self.interfaces) @Route(methods="put", resource="/network/interface") def _event_router_db(self, message): self.update_router(message.data)
class Ethernet(Sanji): """ A model to handle Ethernet interfaces' configuration. Attributes: model: Ethernet interfaces' database with json format. """ def init(self, *args, **kwargs): try: # pragma: no cover bundle_env = kwargs["bundle_env"] except KeyError: bundle_env = os.getenv("BUNDLE_ENV", "debug") self.path_root = os.path.abspath(os.path.dirname(__file__)) if bundle_env == "debug": # pragma: no cover self.path_root = "%s/tests" % self.path_root # Find all ethernet interfaces and load the configuration ifaces = ip.interfaces() ifaces = [x for x in ifaces if x.startswith("eth")] if 0 == len(ifaces): _logger.info("No interfaces to be configured.") self.stop() raise ValueError("No interfaces to be configured.") try: self.load(self.path_root, ifaces) except: self.stop() raise IOError("Cannot load any configuration.") # Apply the configuration for iface in self.model.db: self.apply(iface) def run(self): for iface in self.model.db: self.publish.event.put("/network/interface", data=iface) def load(self, path, ifaces): """ Load the configuration. If configuration is not installed yet, initialise them with the given interfaces and install them. Args: path: Path for the bundle, the configuration should be located under "data" directory. ifaces: A list of interfaces name. """ self.model = ModelInitiator("ethernet", path, backup_interval=-1) if not self.model.db: raise IOError("Cannot load any configuration.") # Initialise the interfaces # TODO: 2nd iface's type is "LAN"; another is "WAN" if 1 == len(self.model.db) and "id" not in self.model.db[0]: _logger.debug("factory install") default_db = self.model.db.pop() ip_3_def = int(default_db["ip"].split(".")[2]) - 1 for iface in ifaces: ifaddr = ip.ifaddresses(iface) db = copy.deepcopy(default_db) db["name"] = iface db["id"] = int(iface.replace("eth", "")) + 1 ip_3 = ip_3_def + db["id"] db["ip"] = "192.168.%d.127" % ip_3 db["subnet"] = "192.168.%d.0" % ip_3 db["gateway"] = "192.168.%d.254" % ip_3 db["currentStatus"] = ifaddr["link"] db["mac"] = ifaddr["mac"] self.model.db.append(db) self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def apply(self, data): """ Apply the configuration to an interface. Args: data: Information for the interface to be applied (with dictionary format) """ iface = "eth%d" % (data["id"]-1) ip.ifupdown(iface, True if data["enable"] else False) if not data["enable"]: return if data["enableDhcp"]: ip.ifconfig(iface, True, script="%s/hooks/dhclient-script" % self.path_root) else: ip.ifconfig(iface, False, data["ip"], data["netmask"], data["gateway"]) def read(self, id, restart=False, config=True): """ Read the setting for an interface. Args: id: Interface id, interface name will be eth(id+1). """ for data in self.model.db: if data["id"] == id: break else: return None # deepcopy to prevent settings be modified data = copy.deepcopy(data) if not restart and "restart" in data: data.pop("restart") iface = "eth%d" % (data["id"]-1) ifaddr = ip.ifaddresses(iface) data["currentStatus"] = ifaddr["link"] data["mac"] = ifaddr["mac"] # """Use configuration data instead of realtime retrieving if True is config: return data data["ip"] = "" data["netmask"] = "" data["subnet"] = "" data["broadcast"] = "" # data["gateway"] = "" if ifaddr["inet"] and len(ifaddr["inet"]): data["ip"] = ifaddr["inet"][0]["ip"] data["netmask"] = ifaddr["inet"][0]["netmask"] if "subnet" in ifaddr["inet"][0]: data["subnet"] = ifaddr["inet"][0]["subnet"] else: data.pop("subnet") if "broadcast" in ifaddr["inet"][0]: data["broadcast"] = ifaddr["inet"][0]["broadcast"] elif "broadcast" in data: data.pop("broadcast") # """ return data @staticmethod def schema_validate(message): """ Validate the received data, ensure the schema is correct. """ # TODO: ip validation schema = Schema({ "id": Range(min=1), "enable": In(frozenset([0, 1])), Optional("wan"): In(frozenset([0, 1])), Optional("enableDhcp"): In(frozenset([0, 1])), Optional("ip"): Any(str, unicode), Optional("netmask"): Any(str, unicode), Optional("subnet"): Any(str, unicode), Optional("gateway"): Any(str, unicode), Optional("dns"): [Any(str, unicode)], Extra: object }, required=True) if not hasattr(message, "data"): raise KeyError("Invalid input: \"data\" attribute is required.") if type(message.data) is list: if 0 == len(message.data): raise KeyError("Invalid input: empty \"data\".") for item in message.data: try: schema(item) except Exception, e: raise KeyError("Invalid input: %s." % e) if type(message.data) is dict: try: schema(message.data) except Exception, e: raise KeyError("Invalid input: %s." % e)
class IPRoute(Sanji): """ A model to handle IP Route configuration. Attributes: model: database with json format. """ update_interval = 60 def init(self, *args, **kwargs): try: # pragma: no cover self.bundle_env = kwargs["bundle_env"] except KeyError: self.bundle_env = os.getenv("BUNDLE_ENV", "debug") path_root = os.path.abspath(os.path.dirname(__file__)) if self.bundle_env == "debug": # pragma: no cover path_root = "%s/tests" % path_root self.interfaces = [] try: self.load(path_root) except: self.stop() raise IOError("Cannot load any configuration.") def run(self): while True: sleep(self.update_interval) try: self.try_update_default(self.model.db) except Exception as e: _logger.debug(e) def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("route", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def list_interfaces(self): """ List available interfaces. """ # retrieve all interfaces try: ifaces = ip.addr.interfaces() except: return {} # list connected interfaces data = [] for iface in ifaces: try: iface_info = ip.addr.ifaddresses(iface) except: continue if 1 == iface_info["link"]: inet_ip = [ inet["ip"] for inet in iface_info["inet"] if "" != inet["ip"] ] if len(inet_ip): data.append(iface) return data def get_default(self): """ Retrieve the default gateway Return: default: dict format with "interface" and/or "gateway" """ gws = netifaces.gateways() default = {} if gws['default'] != {} and netifaces.AF_INET in gws['default']: gw = gws['default'][netifaces.AF_INET] else: return default default["gateway"] = gw[0] default["interface"] = gw[1] return default def update_wan_info(self, interface): """ Update WAN interface to default gateway's interface. Args: default: interface name """ self.publish.event.put("/network/wan", data={"interface": interface}) # TODO: modify DNS to listen `/network/wan` instead res = self.publish.put("/network/dns", data={"source": interface}) if res.code != 200: raise RuntimeWarning(res.data["message"]) def update_default(self, default): """ Update default gateway. If updated failed, should recover to previous one. Args: default: dict format with "interface" required and "gateway" optional. """ # delete the default gateway if not default or ("interface" not in default and "gateway" not in default): ip.route.delete("default") # change the default gateway # FIXME: only "gateway" without interface is also available # FIXME: add "secondary" default route rule else: ip.route.delete("default") if "gateway" in default and "interface" in default: ip.route.add("default", default["interface"], default["gateway"]) elif "interface" in default: ip.route.add("default", default["interface"]) elif "gateway" in default: ip.route.add("default", "", default["gateway"]) else: raise IPRouteError("Invalid default route.") # update DNS if "interface" in default: self.update_wan_info(default["interface"]) def _try_update_default(self, routes): """ Try to update the default gateway. Args: routes: dict format including default gateway interface and secondary default gateway interface. For example: { "default": "wwan0", "secondary": "eth0" } """ ifaces = self.list_interfaces() if not ifaces: raise IPRouteError("Interfaces should be UP.") default = {} if routes["default"] in ifaces: default["interface"] = routes["default"] elif routes["secondary"] in ifaces: default["interface"] = routes["secondary"] else: self.update_default({}) return # find gateway by interface for iface in self.interfaces: if iface["interface"] == default["interface"]: default = iface break current = self.get_default() if current != default: self.update_default(default) def try_update_default(self, routes): with _update_default_lock: try: self._try_update_default(routes) except IPRouteError as e: _logger.debug(e) def update_router(self, interface): """ Save the interface name with its gateway and update the default gateway if needed. If gateway is not specified, use the previous value. Only delete the gateway when gateway attribute is empty. Args: interface: dict format with interface "name" and/or "gateway". """ # update the router information for iface in self.interfaces: if iface["interface"] == interface["name"]: if "gateway" in interface: iface["gateway"] = interface["gateway"] break else: iface = {} iface["interface"] = interface["name"] if "gateway" in interface: iface["gateway"] = interface["gateway"] self.interfaces.append(iface) # check if the default gateway need to be modified self.try_update_default(self.model.db) def set_default(self, default, is_default=True): """ Update default / secondary gateway. """ if is_default: def_type = "default" else: def_type = "secondary" # save the setting # if no interface but has gateway, do not update anything if "interface" in default: self.model.db[def_type] = default["interface"] elif "gateway" not in default: self.model.db[def_type] = "" self.save() try: if is_default: self.update_default(default) except Exception as e: # try database if failed try: self.try_update_default(self.model.db) except IPRouteError as e2: _logger.debug( "Failed to recover the default gateway: {}".format(e2)) error = "Update default gateway failed: {}".format(e) _logger.error(error) raise IPRouteError(error) @Route(methods="get", resource="/network/routes/interfaces") def _get_interfaces(self, message, response): """ Get available interfaces. """ return response(data=self.list_interfaces()) @Route(methods="get", resource="/network/routes/default") def _get_default(self, message, response): """ Get default gateway. """ return response(data=self.get_default()) put_default_schema = Schema({ Optional("interface"): Any(str, unicode), Extra: object }) @Route(methods="put", resource="/network/routes/default") def _put_default(self, message, response, schema=put_default_schema): """ Update the default gateway, delete default gateway if data is None or empty. """ try: self.set_default(message.data) except Exception as e: return response(code=404, data={"message": e}) return response(data=self.get_default()) @Route(methods="put", resource="/network/routes/secondary") def _put_secondary(self, message, response, schema=put_default_schema): """ Update the secondary default gateway, delete default gateway if data is None or empty. """ try: self.set_default(message.data, False) except Exception as e: return response(code=404, data={"message": e}) return response(data=message.data) def set_router_db(self, message, response): """ Update router database batch or by interface. """ if type(message.data) is list: for iface in message.data: self.update_router(iface) return response(data=self.interfaces) elif type(message.data) is dict: self.update_router(message.data) return response(data=message.data) return response(code=400, data={"message": "Wrong type of router database."}) @Route(methods="put", resource="/network/routes/db") def _set_router_db(self, message, response): return self.set_router_db(message, response) @Route(methods="get", resource="/network/routes/db") def _get_router_db(self, message, response): return response(data=self.interfaces) @Route(methods="put", resource="/network/interface") def _event_router_db(self, message): self.update_router(message.data)
def init(self, *args, **kwargs): self.path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("bootstrap", self.path_root) self.modes_config = load_mode_config(self.path_root) self.keeper = SanjiKeeper()
class Index(Sanji): PUT_SCHEMA = Schema({ Optional("time"): Timestamp, Optional("timezone"): All(str, Length(0, 255)), Optional("ntp"): { "enable": bool, "server": All(str, Length(1, 2048)), "interval": All(int, Range(min=60, max=60*60*24*30)) } }, extra=REMOVE_EXTRA) def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("ntp", path_root) self.ntp = Ntp(self.model) @Route(methods="get", resource="/system/time") def get(self, message, response): realtime_data = { "time": SysTime.get_system_time() } return response( data=dict(self.model.db.items() + realtime_data.items())) @Route(methods="get", resource="/system/zoneinfo") def get_zoneinfo(self, message, response): zoneinfo = SysTime.get_system_timezone_list() return response(data=dict(zoneinfo)) @Route(methods="put", resource="/system/time", schema=PUT_SCHEMA) def put(self, message, response): rc = None try: # update ntp settings if "ntp" in message.data: rc = self.ntp.update(message.data["ntp"]) if rc is False: raise RuntimeWarning("Update ntp settings failed.") self.model.db["ntp"] = dict(self.model.db["ntp"].items() + message.data["ntp"].items()) # change timezone if "timezone" in message.data: rc = SysTime.set_system_timezone(message.data["timezone"]) if rc is False: raise RuntimeWarning("Change timezone failed.") self.model.db["timezone"] = message.data["timezone"] # manual change sys time if "time" in message.data: if self.model.db["ntp"]["enable"] is True: _logger.debug("NTP enabled. skipping time setup.") else: rc = SysTime.set_system_time(message.data["time"]) if rc is False: raise RuntimeWarning("Change system time failed.") if rc is None: return response(code=400, data={"message": "No input paramters."}) else: self.model.save_db() except Exception as e: _logger.debug(e, exc_info=True) code = 400 if not isinstance(e, RuntimeWarning) else 500 return response(code=code, data={"message": str(e)}) realtime_data = { "time": SysTime.get_system_time() } # operation successed return response( data=dict(self.model.db.items() + realtime_data.items()))
class TestModelInitiatorClass(unittest.TestCase): """ " Test class """ model_name = "test_myself" model_path = "/tmp/sanji-sdk/tests/test_myself" model_db_folder = "/tmp/sanji-sdk/tests/test_myself/data" model_factory_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json.factory" model_backup_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json.backup" model_db = "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json" def setUp(self): """ " Prepare """ factory_data = {"name": "factory"} if not os.path.exists(self.model_db_folder): os.makedirs(self.model_db_folder) with open(self.model_factory_db, "w") as fp: json.dump(factory_data, fp, indent=4) self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=-1) def tearDown(self): """ " Clean up """ self.model_initiator.stop_backup() self.model_initiator = None if os.path.exists(self.model_path): removeall(self.model_db_folder) def test_init(self): """ " Test __init__() """ # case 1: check name self.assertEquals(self.model_initiator.model_name, self.model_name) # case 2: thread self.model_initiator = None self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=1) self.assertEquals(self.model_initiator.model_name, self.model_name) def test_db_manager(self): # case 1: existing self.model_initiator.save_db() self.model_initiator.db_manager() self.assertEqual(self.model_initiator.db_status, "existing") # case 2: factory with patch( "sanji.model_initiator.ModelInitiator.create_db")as create_db: create_db.return_value = 1 if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator = ModelInitiator( self.model_name, self.model_path, backup_interval=-1) self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "factory") # case 3: backup with patch( "sanji.model_initiator.ModelInitiator.create_db")as create_db: create_db.return_value = 1 self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.model_initiator.db_manager() self.assertTrue(os.path.exists(self.model_db)) self.assertEqual(self.model_initiator.db_status, "backup") def test_create_db(self): """ " It should generate a factory db if there is no db. """ if os.path.exists(self.model_db_folder): shutil.rmtree(self.model_db_folder) os.makedirs(self.model_db_folder) try: with open(self.model_factory_db, "a"): os.utime(self.model_factory_db, None) except Exception: self.fail("Maybe there is no folder to create file.") # case 1: general case result = self.model_initiator.create_db() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db)) # case 2: no factory db if os.path.exists(self.model_db): os.remove(self.model_db) if os.path.exists(self.model_factory_db): os.remove(self.model_factory_db) with self.assertRaises(RuntimeError): result = self.model_initiator.create_db() # case 3: sql type self.db_type = "sql" with self.assertRaises(RuntimeError): result = self.model_initiator.create_db() def test_recover_db(self): # case 1: Check file which restore from backup db self.model_initiator.db = {} self.model_initiator.db["name"] = "backup" self.model_initiator.db["type"] = "json" self.model_initiator.save_db() self.model_initiator.backup_db() if os.path.exists(self.model_db): os.remove(self.model_db) self.assertFalse(os.path.exists(self.model_db)) self.model_initiator.recover_db(self.model_backup_db) self.assertTrue(os.path.exists(self.model_db)) # case 2: Check data which restore from backup db with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "backup", "type": "json"}) # case 3: Check file which restore from factory db if os.path.exists(self.model_db): os.remove(self.model_db) self.assertFalse(os.path.exists(self.model_db)) self.model_initiator.recover_db(self.model_factory_db) self.assertTrue(os.path.exists(self.model_db)) # case 4: Check data which restore from factory db with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "factory"}) # case 5: no file try: self.assertRaises( self.model_initiator.recover_db("/tmp/1234555555.txt")) except IOError: pass else: self.fail("No file to load but pass.") def test_backup_db(self): """ " Test backup db """ if os.path.exists(self.model_backup_db): os.remove(self.model_backup_db) # case 1: Check file exist self.model_initiator.backup_db() self.assertTrue(os.path.exists(self.model_backup_db)) # case 2: Check data with open(self.model_backup_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "factory"}) def test_load_db(self): """ " It should load json db as a dictionary. """ # case 1: No folder self.model_initiator.db = None self.assertEqual(type(self.model_initiator.db), type(None)) self.model_initiator.create_db() try: with open(self.model_factory_db, "a"): os.utime(self.model_factory_db, None) except Exception: self.fail("Maybe there is no folder to create file.") # case 2: data data = {"enable": 1} with open(self.model_factory_db, "w") as fp: json.dump(data, fp, indent=4) shutil.copyfile(self.model_factory_db, self.model_db) self.model_initiator.load_db() self.assertEqual(self.model_initiator.db, data) # case 3: if os.path.exists(self.model_db): os.remove(self.model_db) try: self.assertRaises(self.model_initiator.load_db()) except Exception: pass def test_save_db(self): """ " Test save db """ # case 1: data of saving self.model_initiator.db = {} self.model_initiator.db["name"] = "John" self.model_initiator.db["age"] = 33 self.model_initiator.save_db() db_data = None with open(self.model_db) as fp: db_data = json.load(fp) self.assertEqual(db_data, {"name": "John", "age": 33}) # case 2: non dictionary or array type. self.model_initiator.db = "string type" rc = self.model_initiator.save_db() self.assertFalse(rc) # case 3: open with no file. (coverage) if os.path.exists(self.model_db_folder): shutil.rmtree(self.model_db_folder) try: self.assertRaises(self.model_initiator.save_db()) except Exception: pass @patch("sanji.model_initiator.Thread") def test_start_backup_thread_is_not_alive(self, Thread): self.model_initiator._backup_thread.is_alive = Mock(return_value=False) Thread.return_value = MagicMock(daemon=False, start=Mock()) self.model_initiator.start_backup() self.assertTrue(self.model_initiator._backup_thread.daemon) self.model_initiator._backup_thread.start.assert_called_once_with() def test_start_backup_thread_is_alive(self): with self.assertRaises(RuntimeError): self.model_initiator._backup_thread.is_alive =\ Mock(return_value=True) self.model_initiator.start_backup() self.model_initiator.stop_backup = Mock() def test_stop_backup(self): rc = self.model_initiator.stop_backup() self.assertFalse(rc) self.model_initiator.start_backup() rc = self.model_initiator.stop_backup() self.assertTrue(rc) self.model_initaitor.db_type = "json" self.model_initaitor.factory_json_db_path = "/1231231235566" result = self.model_initaitor.create_db() self.assertFalse(result)
def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("ntp", path_root) self.ntp = Ntp(self.model)
class Index(Sanji): CONF_SCHEMA = Schema( { "id": int, Required("enable"): bool, Required("pdpContext"): { Required("static"): bool, Required("id"): int, Required("retryTimeout", default=120): All(int, Any(0, Range(min=10, max=86400 - 1))), Required("primary"): { Required("apn", default="internet"): All(Any(unicode, str), Length(0, 100)), Optional("type", default="ipv4v6"): In(frozenset(["ipv4", "ipv6", "ipv4v6"])) }, Required("secondary", default={}): { Optional("apn"): All(Any(unicode, str), Length(0, 100)), Optional("type", default="ipv4v6"): In(frozenset(["ipv4", "ipv6", "ipv4v6"])) } }, Required("pinCode", default=""): Any(Match(r"[0-9]{4,4}"), ""), Required("keepalive"): { Required("enable"): bool, Required("targetHost"): str, Required("intervalSec"): All(int, Any(0, Range(min=60, max=86400 - 1))), Required("reboot", default={ "enable": False, "cycles": 1 }): { Required("enable", default=False): bool, Required("cycles", default=1): All(int, Any(0, Range(min=1, max=48))), } } }, extra=REMOVE_EXTRA) def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("cellular", path_root) self.model.db[0] = Index.CONF_SCHEMA(self.model.db[0]) self._dev_name = None self._mgr = None self._vnstat = None self.__init_monit_config( enable=(self.model.db[0]["enable"] and self.model.db[0]["keepalive"]["enable"] and True and self.model.db[0]["keepalive"]["reboot"]["enable"] and True), target_host=self.model.db[0]["keepalive"]["targetHost"], iface=self._dev_name, cycles=self.model.db[0]["keepalive"]["reboot"]["cycles"]) self._init_thread = Thread(name="sanji.cellular.init_thread", target=self.__initial_procedure) self._init_thread.daemon = True self._init_thread.start() def __initial_procedure(self): """ Continuously check Cellular modem existence. Set self._dev_name, self._mgr, self._vnstat properly. """ cell_mgmt = CellMgmt() wwan_node = None for retry in xrange(0, 4): if retry == 3: return try: wwan_node = cell_mgmt.m_info().wwan_node break except CellMgmtError: _logger.warning("get wwan_node failure: " + format_exc()) cell_mgmt.power_cycle(timeout_sec=60) self._dev_name = wwan_node self.__init_monit_config( enable=(self.model.db[0]["enable"] and self.model.db[0]["keepalive"]["enable"] and True and self.model.db[0]["keepalive"]["reboot"]["enable"] and True), target_host=self.model.db[0]["keepalive"]["targetHost"], iface=self._dev_name, cycles=self.model.db[0]["keepalive"]["reboot"]["cycles"]) self.__create_manager() self._vnstat = VnStat(self._dev_name) def __create_manager(self): pin = self.model.db[0]["pinCode"] if "primary" in self.model.db[0]["pdpContext"]: pdpc_primary_apn = \ self.model.db[0]["pdpContext"]["primary"].get( "apn", "internet") pdpc_primary_type = \ self.model.db[0]["pdpContext"]["primary"].get("type", "ipv4v6") else: pdpc_primary_apn = "internet" pdpc_primary_type = "ipv4v6" if "secondary" in self.model.db[0]["pdpContext"]: pdpc_secondary_apn = \ self.model.db[0]["pdpContext"]["secondary"].get("apn", "") pdpc_secondary_type = \ self.model.db[0]["pdpContext"]["secondary"].get( "type", "ipv4v6") else: pdpc_secondary_apn = "" pdpc_secondary_type = "ipv4v6" pdpc_retry_timeout = self.model.db[0]["pdpContext"]["retryTimeout"] self._mgr = Manager( dev_name=self._dev_name, enabled=self.model.db[0]["enable"], pin=None if pin == "" else pin, pdp_context_static=self.model.db[0]["pdpContext"]["static"], pdp_context_id=self.model.db[0]["pdpContext"]["id"], pdp_context_primary_apn=pdpc_primary_apn, pdp_context_primary_type=pdpc_primary_type, pdp_context_secondary_apn=pdpc_secondary_apn, pdp_context_secondary_type=pdpc_secondary_type, pdp_context_retry_timeout=pdpc_retry_timeout, keepalive_enabled=self.model.db[0]["keepalive"]["enable"], keepalive_host=self.model.db[0]["keepalive"]["targetHost"], keepalive_period_sec=self.model.db[0]["keepalive"]["intervalSec"], log_period_sec=60) # clear PIN code if pin error if self._mgr.status() == Manager.Status.pin_error and pin != "": self.model.db[0]["pinCode"] = "" self.model.save_db() self._mgr.set_update_network_information_callback( self._publish_network_info) self._mgr.start() def __init_completed(self): if self._init_thread is None: return True self._init_thread.join(0) if self._init_thread.is_alive(): return False self._init_thread = None return True def __init_monit_config(self, enable=False, target_host="8.8.8.8", iface="", cycles=1): if enable is False: rm("-rf", "/etc/monit/conf.d/keepalive") service("monit", "restart") return ifacecmd = "" if iface == "" or iface is None \ else "-I {}".format(iface) config = """check program ping-test with path "/bin/ping {target_host} {ifacecmd} -c 3 -W 20" if status != 0 then exec "/bin/bash -c '/usr/sbin/cell_mgmt power_off force && /bin/sleep 5 && /sbin/reboot -i -f -d'" every {cycles} cycles """ # noqa with open("/etc/monit/conf.d/keepalive", "w") as f: f.write( config.format(target_host=target_host, ifacecmd=ifacecmd, cycles=cycles)) service("monit", "restart") @Route(methods="get", resource="/network/cellulars") def get_list(self, message, response): if not self.__init_completed(): return response(code=200, data=[]) if (self._dev_name is None or self._mgr is None or self._vnstat is None): return response(code=200, data=[]) return response(code=200, data=[self._get()]) @Route(methods="get", resource="/network/cellulars/:id") def get(self, message, response): if not self.__init_completed(): return response(code=400, data={"message": "resource not exist"}) id_ = int(message.param["id"]) if id_ != 1: return response(code=400, data={"message": "resource not exist"}) return response(code=200, data=self._get()) PUT_SCHEMA = CONF_SCHEMA @Route(methods="put", resource="/network/cellulars/:id", schema=PUT_SCHEMA) def put(self, message, response): if not self.__init_completed(): return response(code=400, data={"message": "resource not exist"}) id_ = int(message.param["id"]) if id_ != 1: return response(code=400, data={"message": "resource not exist"}) _logger.info(str(message.data)) data = Index.PUT_SCHEMA(message.data) data["id"] = id_ _logger.info(str(data)) # always use the 1st PDP context for static if data["pdpContext"]["static"] is True: data["pdpContext"]["id"] = 1 # since all items are required in PUT, # its schema is identical to cellular.json self.model.db[0] = data self.model.save_db() if self._mgr is not None: self._mgr.stop() self._mgr = None self.__create_manager() self.__init_monit_config( enable=(self.model.db[0]["enable"] and self.model.db[0]["keepalive"]["enable"] and True and self.model.db[0]["keepalive"]["reboot"]["enable"] and True), target_host=self.model.db[0]["keepalive"]["targetHost"], iface=self._dev_name, cycles=self.model.db[0]["keepalive"]["reboot"]["cycles"]) # self._get() may wait until start/stop finished return response(code=200, data=self.model.db[0]) def _get(self): name = self._dev_name if name is None: name = "n/a" config = self.model.db[0] status = self._mgr.status() sinfo = self._mgr.static_information() cinfo = self._mgr.cellular_information() ninfo = self._mgr.network_information() try: pdpc_list = self._mgr.pdp_context_list() except CellMgmtError: pdpc_list = [] try: self._vnstat.update() usage = self._vnstat.get_usage() except VnStatError: usage = {"txkbyte": -1, "rxkbyte": -1} # clear PIN code if pin error if (config["pinCode"] != "" and status == Manager.Status.pin): config["pinCode"] = "" self.model.db[0] = config self.model.save_db() config["pdpContext"]["list"] = pdpc_list return { "id": config["id"], "name": name, "mode": "n/a" if cinfo is None else cinfo.mode, "signal": { "csq": 0, "rssi": 0, "ecio": 0.0 } if cinfo is None else { "csq": cinfo.signal_csq, "rssi": cinfo.signal_rssi_dbm, "ecio": cinfo.signal_ecio_dbm }, "operatorName": "n/a" if cinfo is None else cinfo.operator, "lac": "n/a" if cinfo is None else cinfo.lac, "tac": "n/a" if cinfo is None else cinfo.tac, "nid": "n/a" if cinfo is None else cinfo.nid, "cellId": "n/a" if cinfo is None else cinfo.cell_id, "bid": "n/a" if cinfo is None else cinfo.bid, "imsi": "n/a" if sinfo is None else sinfo.imsi, "iccId": "n/a" if sinfo is None else sinfo.iccid, "imei": "n/a" if sinfo is None else sinfo.imei, "pinRetryRemain": (-1 if sinfo is None else sinfo.pin_retry_remain), "status": status.name, "ip": "n/a" if ninfo is None else ninfo.ip, "netmask": "n/a" if ninfo is None else ninfo.netmask, "gateway": "n/a" if ninfo is None else ninfo.gateway, "dns": [] if ninfo is None else ninfo.dns_list, "usage": { "txkbyte": usage["txkbyte"], "rxkbyte": usage["rxkbyte"] }, "enable": config["enable"], "pdpContext": config["pdpContext"], "pinCode": config["pinCode"], "keepalive": { "enable": config["keepalive"]["enable"], "targetHost": config["keepalive"]["targetHost"], "intervalSec": config["keepalive"]["intervalSec"], "reboot": { "enable": config["keepalive"]["reboot"]["enable"], "cycles": config["keepalive"]["reboot"]["cycles"] } } } def _publish_network_info(self, nwk_info): name = self._dev_name if name is None: _logger.error("device name not available") return data = { "name": name, "wan": True, "type": "cellular", "mode": "dhcp", "status": nwk_info.status, "ip": nwk_info.ip, "netmask": nwk_info.netmask, "gateway": nwk_info.gateway, "dns": nwk_info.dns_list } _logger.info("publish network info: " + str(data)) self.publish.event.put("/network/interfaces/{}".format(name), data=data)
class Index(Sanji): HOSTNAME_SCHEMA = Schema({ Required("hostname"): All(Any(unicode, str), Length(1, 255)) }, extra=REMOVE_EXTRA) PASSWORD_SCHEMA = Schema({ Required("password"): All(Any(unicode, str), Length(1, 255)) }, extra=REMOVE_EXTRA) GPS_SCHEMA = Schema({ "lat": Any(int, float), "lng": Any(int, float), }, extra=REMOVE_EXTRA) ALIASNAME_SCHEMA = Schema(All(Any(unicode, str), Length(0, 255))) PROPERTIES_SCHEMA = { "aliasName": ALIASNAME_SCHEMA, "gps": GPS_SCHEMA } UPDATE_PROPERTY_SCHEMA = Schema({ "data": Any(list, dict, str, unicode, int, float) }, extra=REMOVE_EXTRA) def init(self, *args, **kwargs): path_root = os.path.abspath(os.path.dirname(__file__)) self.status = status.Status(name="status", path=path_root) self.properties = ModelInitiator( model_name="properties", model_path=path_root) # Check aliasName if self.properties.db.get("aliasName", "$ModelName") == "$ModelName": self.set_alias() def set_alias(self): try: version = sh.pversion() self.properties.db["aliasName"] = version.split()[0] except Exception: self.properties.db["aliasName"] = "ThingsPro" self.properties.save_db() @Route(methods="get", resource="/system/status") def get_status(self, message, response): if message.query.get("fields") is None: return response( data={ "hostname": self.status.get_hostname(), "version": self.status.get_product_version(), "uptimeSec": self.status.get_uptime(), "cpuUsage": self.status.get_cpu_usage(), "memoryUsage": self.status.get_memory_usage(), "memory": self.status.get_memory(), "disks": self.status.get_disks() } ) fields = [_.strip() for _ in message.query.get("fields").split(',')] data = {} if "hostname" in fields: data["hostname"] = self.status.get_hostname() if "version" in fields: data["version"] = self.status.get_product_version() if "uptimeSec" in fields: data["uptimeSec"] = self.status.get_uptime() if "cpuUsage" in fields: data["cpuUsage"] = self.status.get_cpu_usage() if "memoryUsage" in fields: data["memoryUsage"] = self.status.get_memory_usage() if "memory" in fields: data["memory"] = self.status.get_memory() if "disks" in fields: data["disks"] = self.status.get_disks() return response(data=data) @Route(methods="put", resource="/system/status") def put_status(self, message, response, schema=HOSTNAME_SCHEMA): self.status.set_hostname(message.data['hostname']) return response(data=message.data) @Route(methods="get", resource="/network/interfaces") def get_net_interface(self, message, response): ifaces = self.status.get_net_interfaces() return response(data=ifaces) @Route(methods="post", resource="/system/syslog") def post_syslog(self, message, response): output = status.tar_syslog_files( "/run/shm/syslog-%s.tar.gz" % (datetime.datetime.now().strftime("%Y%m%d%H%M"))) headers = message.data.get("headers", {}) r = requests.post( message.data["url"], files={output: open(output, "rb")}, headers=headers, verify=False ) if r.status_code != requests.codes.ok: return response( code=r.status_code, data={"message": "Can't upload config."} ) sh.rm("-rf", sh.glob("/run/shm/syslog-*.tar.gz")) resp = r.json() if "url" not in resp: return response( code=500, data={"message": "Can't get file link."}) return response(data={"url": resp["url"]}) @Route(methods="post", resource="/system/reboot") def post_reboot(self, message, response): response() sleep(3) self.status.reboot() @Route(methods="put", resource="/system/password", schema=PASSWORD_SCHEMA) def post_passwd(self, message, response): set_password(message.data["password"]) return response() @Route(methods="get", resource="/system/properties") def get_properties(self, message, response): return response(data=self.properties.db) @Route(methods="get", resource="/system/properties/:key") def get_property(self, message, response): val = self.properties.db.get(message.param["key"], None) if val is None: return response(code=404) return response(data=val) @Route(methods="put", resource="/system/properties/:key", schema=UPDATE_PROPERTY_SCHEMA) def put_property(self, message, response): key = message.param["key"] if key not in Index.PROPERTIES_SCHEMA: return response(code=400, data={"message": "wrong key."}) data = Index.PROPERTIES_SCHEMA.get(key)(message.data["data"]) self.properties.db[key] = data self.properties.save_db() return response(data=self.properties.db[key]) @Route(methods="get", resource="/mxc/system/equipments") def get_system_equipments(self, message, response): equs = [ { "equipmentName": "SYSTEM", "equipmentTags": [ { "name": "cpu_usage", "dataType": "float64", "access": "ro", "size": 8, "description": "CPU Usage" }, { "name": "memory_usage", "dataType": "float64", "access": "ro", "size": 8, "description": "Memory Usage" }, { "name": "disk_usage", "dataType": "float64", "access": "ro", "size": 8, "description": "Disk Usage" } ] } ] return response(data=equs)
class IPRoute(Sanji): """ A model to handle IP Route configuration. Attributes: model: database with json format. """ update_interval = 60 def init(self, *args, **kwargs): try: # pragma: no cover self.bundle_env = kwargs["bundle_env"] except KeyError: self.bundle_env = os.getenv("BUNDLE_ENV", "debug") self._path_root = os.path.abspath(os.path.dirname(__file__)) if self.bundle_env == "debug": # pragma: no cover self._path_root = "%s/tests" % self._path_root self.interfaces = {} try: self.load(self._path_root) except: self.stop() raise IOError("Cannot load any configuration.") # find correct interface if shell command is required self._cmd_regex = re.compile(r"\$\(([\S\s]+)\)") self._routes = self._get_routes() def _get_routes(self): routes = [] for iface in self.model.db: match = self._cmd_regex.match(iface) if not match: routes.append(iface) continue try: with open("{}/iface_cmd.sh".format(self._path_root), "w") as f: f.write(match.group(1)) _iface = sh.sh("{}/iface_cmd.sh".format(self._path_root)) routes.append(str(_iface).rstrip()) except Exception as e: _logger.debug(e) return routes def run(self): while True: sleep(self.update_interval) try: self.try_update_default(self._routes) except Exception as e: _logger.debug(e) def load(self, path): """ Load the configuration. If configuration is not installed yet, initialise them with default value. Args: path: Path for the bundle, the configuration should be located under "data" directory. """ self.model = ModelInitiator("route", path, backup_interval=-1) if self.model.db is None: raise IOError("Cannot load any configuration.") self.save() def save(self): """ Save and backup the configuration. """ self.model.save_db() self.model.backup_db() def list_interfaces(self): """ List available interfaces. """ # retrieve all interfaces try: ifaces = ip.addr.interfaces() except: return {} # list connected interfaces data = [] for iface in ifaces: try: iface_info = ip.addr.ifaddresses(iface) except: continue if iface_info["link"] is True: inet_ip = [inet["ip"] for inet in iface_info["inet"] if "" != inet["ip"]] if len(inet_ip) and \ (iface in self.interfaces and self.interfaces[iface]["status"] is True and self.interfaces[iface]["wan"] is True): data.append(iface) return data def get_default(self): """ Retrieve the default gateway Return: default: dict format with "interface" and/or "gateway" """ gws = netifaces.gateways() default = {} if gws['default'] != {} and netifaces.AF_INET in gws['default']: gw = gws['default'][netifaces.AF_INET] else: return default default["wan"] = True default["status"] = True default["gateway"] = gw[0] default["interface"] = gw[1] return default def update_wan_info(self, interface): """ Update WAN interface to default gateway's interface. Args: default: interface name """ self.publish.event.put("/network/wan", data={"interface": interface}) def update_default(self, default): """ Update default gateway. If updated failed, should recover to previous one. Args: default: dict format with "interface" required and "gateway" optional. """ # delete the default gateway if not default or ("interface" not in default and "gateway" not in default): ip.route.delete("default") # change the default gateway # FIXME: only "gateway" without interface is also available # FIXME: add "secondary" default route rule else: ip.route.delete("default") if "gateway" in default and "interface" in default: ip.route.add("default", default["interface"], default["gateway"]) elif "interface" in default: ip.route.add("default", default["interface"]) elif "gateway" in default: ip.route.add("default", "", default["gateway"]) else: raise IPRouteError("Invalid default route.") # update DNS if "interface" in default: self.update_wan_info(default["interface"]) def _try_update_default(self, routes): """ Try to update the default gateway. Args: routes: array format of default gateway list with priority. For example: ["wwan0", "eth0"] """ ifaces = self.list_interfaces() if not ifaces: # FIXME: keep or clean? # self.update_default({}) raise IPRouteError("Interfaces should be UP.") default = {} for iface in routes: if iface in ifaces: default["interface"] = iface break else: self.update_default({}) return # find gateway by interface default.update(self.interfaces[default["interface"]]) current = self.get_default() if current.get("interface", "") != default.get("interface", "") or \ current.get("gateway", "") != default.get("gateway", ""): self.update_default(default) def try_update_default(self, routes): with _update_default_lock: try: self._try_update_default(routes) except IPRouteError as e: _logger.debug(e) def update_router(self, iface): """ Save the interface name with its gateway and update the default gateway if needed. If gateway is not specified, use the previous value. Only delete the gateway when gateway attribute is empty. Args: interface: dict format with interface "name" and/or "gateway". """ if "status" not in iface: iface["status"] = True if "wan" not in iface: iface["wan"] = True # update the router information if iface["name"] not in self.interfaces: self.interfaces[iface["name"]] = {} self.interfaces[iface["name"]]["status"] = iface["status"] self.interfaces[iface["name"]]["wan"] = iface["wan"] if "gateway" in iface: self.interfaces[iface["name"]]["gateway"] = iface["gateway"] # check if the default gateway need to be modified self.try_update_default(self._routes) def get_default_routes(self): """ Get default gateway list. """ return self._routes def set_default_routes(self, defaults): """ Update default gateway list. """ # save the setting # if no interface but has gateway, do not update anything self.model.db = defaults self.save() self._routes = self._get_routes() try: self.update_default(defaults) except Exception as e: # try database if failed try: self.try_update_default(self._routes) except IPRouteError as e2: _logger.debug( "Failed to recover the default gateway: {}".format(e2)) error = "Update default gateway failed: {}".format(e) _logger.error(error) raise IPRouteError(error) @Route(methods="get", resource="/network/routes/default") def _get_default(self, message, response): """ Get default gateway and priority list. """ data = self.get_default() if data is None: data = {} data["priorityList"] = self.get_default_routes() return response(data=data) put_default_schema = Schema({ Required("priorityList"): [Any(str, unicode, Length(1, 255))] }, extra=REMOVE_EXTRA) @Route(methods="put", resource="/network/routes/default") def _put_default_routes(self, message, response, schema=put_default_schema): """ Update the default gateway, delete default gateway if data is None or empty. """ try: self.set_default_routes(message.data["priorityList"]) except Exception as e: return response(code=404, data={"message": e}) data = {} data["priorityList"] = self.get_default_routes() return response(data=data) def set_router_db(self, message, response): """ Update router database batch or by interface. """ if type(message.data) is list: for iface in message.data: self.update_router(iface) return response(data=self.interfaces) elif type(message.data) is dict: self.update_router(message.data) return response(data=message.data) return response(code=400, data={"message": "Wrong type of router database."}) @Route(methods="put", resource="/network/routes/db") def _set_router_db(self, message, response): return self.set_router_db(message, response) @Route(methods="get", resource="/network/routes/db") def _get_router_db(self, message, response): return response(data=self.interfaces) @Route(methods="put", resource="/network/interfaces/:name") def _event_router_db(self, message): message.data["name"] = message.param["name"] self.update_router(message.data)
class TestModelInitiatorClass(unittest.TestCase): """ " Test class """ model_name = "test_myself" model_path = "/tmp/sanji-sdk/tests/test_myself" model_db_folder = "/tmp/sanji-sdk/tests/test_myself/data" model_factory_db = \ "/tmp/sanji-sdk/tests/test_myself/data/test_myself.factory.json" model_db = "/tmp/sanji-sdk/tests/test_myself/data/test_myself.json" def setUp(self): """ " Prepare """ os.makedirs(self.model_path) self.model_initaitor = ModelInitiator(self.model_name, self.model_path) def tearDown(self): """ " Clean up """ if os.path.exists(self.model_path): shutil.rmtree(self.model_path) self.model_initaitor = None def test_init(self): """ " Test __init__() """ self.assertEquals(self.model_initaitor.model_name, self.model_name) def test_mkdir(self): """ " It Should generate a data folder. """ result = self.model_initaitor.mkdir() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db_folder)) def test_create_db(self): """ " It should generate a factory db. """ self.model_initaitor.mkdir() try: with open(self.model_initaitor.factory_json_db_path, 'a'): os.utime(self.model_initaitor.factory_json_db_path, None) except Exception: self.fail("Maybe there is no folder to create file.") result = self.model_initaitor.create_db() self.assertTrue(result) self.assertTrue(os.path.exists(self.model_db)) self.model_initaitor.db_type = "sql" result = self.model_initaitor.create_db() self.assertFalse(result) self.model_initaitor.db_type = "json" self.model_initaitor.factory_json_db_path = "/1231231235566" result = self.model_initaitor.create_db() self.assertFalse(result)
class Model(object): def __init__(self, name, path, schema=None, model_cls=dict): self.model_cls = model_cls self.schema = schema if schema is not None and not isinstance(schema, Schema): raise TypeError("schema should be instance of voluptuous.Schema") if not issubclass(model_cls, dict): raise TypeError("model_cls should be derivative dict class") self.model = ModelInitiator( model_name=name, model_path=path ) self._batch = ModelBatch(self.model) def batch(self): return self._batch def _cast_model(self, obj): return self.model_cls(obj) @property def maxId(self): """int: current max id of objects""" if len(self.model.db) == 0: return 0 return max(map(lambda obj: obj["id"], self.model.db)) def validation(self, instance): """Valid input instance is vaild or not Args: Object: input instance Returns: Object: Instance after vaildation or original instance if schema is None Raises: Error: If vaildation failed """ if self.schema is None: return instance return self.schema(instance) def add(self, obj): """Add a object Args: Object: Object will be added Returns: Object: Object with id Raises: TypeError: If add object is not a dict MultipleInvalid: If input object is invaild """ if not isinstance(obj, dict): raise TypeError("Add object should be a dict object") obj = self.validation(obj) obj["id"] = self.maxId + 1 obj = self._cast_model(obj) self.model.db.append(obj) if not self._batch.enable.is_set(): self.model.save_db() return obj def get(self, id): """Get a object by id Args: id (int): Object id Returns: Object: Object with specified id None: If object not found """ for obj in self.model.db: if obj["id"] == id: return self._cast_model(obj) return None def remove(self, id): """Remove a object by id Args: id (int): Object's id should be deleted Returns: len(int): affected rows """ before_len = len(self.model.db) self.model.db = [t for t in self.model.db if t["id"] != id] if not self._batch.enable.is_set(): self.model.save_db() return before_len - len(self.model.db) def removeAll(self): """Remove all objects Returns: len(int): affected rows """ before_len = len(self.model.db) self.model.db = [] if not self._batch.enable.is_set(): self.model.save_db() return before_len - len(self.model.db) def update(self, id, newObj): """Update a object Args: id (int): Target Object ID newObj (object): New object will be merged into original object Returns: Object: Updated object None: If specified object id is not found MultipleInvalid: If input object is invaild """ newObj = self.validation(newObj) for obj in self.model.db: if obj["id"] != id: continue newObj.pop("id", None) obj.update(newObj) obj = self._cast_model(obj) if not self._batch.enable.is_set(): self.model.save_db() return obj return None def set(self, id, newObj): """Set a object Args: id (int): Target Object ID newObj (object): New object will be set Returns: Object: New object None: If specified object id is not found MultipleInvalid: If input object is invaild """ newObj = self.validation(newObj) for index in xrange(0, len(self.model.db)): if self.model.db[index]["id"] != id: continue newObj["id"] = id self.model.db[index] = self._cast_model(newObj) if not self._batch.enable.is_set(): self.model.save_db() return self.model.db[index] return None def getAll(self): """Get all objects Returns: List: list of all objects """ objs = [] for obj in self.model.db: objs.append(self._cast_model(obj)) return objs
class Index(Sanji): def init(self, *args, **kwargs): self.path_root = os.path.abspath(os.path.dirname(__file__)) self.model = ModelInitiator("bootstrap", self.path_root) self.modes_config = load_mode_config(self.path_root) self.keeper = SanjiKeeper() def start_keeper(self): bundles_home = os.getenv( "BUNDLES_HOME", os.path.dirname(__file__) + "/tests/mock_bundles/") _logger.info("enableMode: %s" % self.model.db["enableMode"]) watchdog_thread = Thread(target=watchdog, args=[self.keeper]) watchdog_thread.daemon = True watchdog_thread.start() self.keeper.start( bundles_home, self.modes_config[self.model.db["enableMode"]].get( "omittedBundleNames", [])) def run(self): if self.model.db["enableMode"] not in self.modes_config: _logger.info("enableMode is not set. Waitting...") return self.start_keeper() def before_stop(self): if self.keeper: self.keeper.stop() @Route(resource="/system/sanjikeeper", methods="get") def get(self, message, response): response( data=[meta.instance.bundle.profile for meta in self.keeper.running_bundles.itervalues()]) @Route(resource="/system/mode", methods="get") def get_system_mode(self, message, response): response(data=self.model.db) @Route(resource="/system/mode", methods="put") def put_system_mode( self, message, response, schema=_SYSTEM_MODE_SCHEMA): mode_name = message.data["enableMode"] if mode_name not in self.modes_config: response(code=400, data={"message": "Mode is not exist."}) return if self.model.db["enableMode"] != "none": response( code=400, data={"message": "Can't change system mode. Please reset to default."}) return self.model.db["enableMode"] = mode_name self.model.save_db() self.start_keeper() response(data=self.model.db)