def ActionDone(self, action_report, context): db = None if self.useRealDatabase: db = database.Database(in_memory=False) # sql schema was already instantiated else: db = database.Database(in_memory=True) db.recreate_database() if self.verbose: print("Got ActionReport", action_report, context) # prepare empty response response = protobufs_pb2.Response() response.action = protobufs_pb2.Response.DO_NOTHING if action_report.authentication_token != constants.AUTHENTICATION_TOKEN: print('Invalid token provided', action_report.authentication_token, "ignoring") return response db.save_action_report(action_report) # prepare empty response response = protobufs_pb2.Response() response.action = protobufs_pb2.Response.DO_NOTHING return response
def test_command_no_data(client): data = dict() data['passphrase'] = constants.AUTHENTICATION_TOKEN data['newCommand'] = '1' # ='SHUTDOWN' data['updateConfig'] = '1' data['feedingEnabled'] = '1' data['wateringEnabled'] = '1' data['feedingCron'] = '15h 0,3,4,5,6' data['wateringCron'] = '14h 1,2' data['pump1Duration'] = '101' data['pump2Duration'] = '102' data['pump3Duration'] = '103' data['pump4Duration'] = '104' rv = client.post('/command', data=data) assert rv.status_code == 201 db = database.Database() cmd = db.get_command() assert cmd is not None assert cmd['text'] == 'SHUTDOWN' assert cmd['config'] is not None assert cmd['config']['feeding_module_activated'] == int(data['feedingEnabled']) assert cmd['config']['watering_module_activated'] == int(data['wateringEnabled']) assert cmd['config']['feeding_module_cronstring'] == data['feedingCron'] assert cmd['config']['watering_module_cronstring'] == data['wateringCron'] assert cmd['config']['watering_pump_1_duration'] == int(data['pump1Duration']) assert cmd['config']['watering_pump_2_duration'] == int(data['pump2Duration']) assert cmd['config']['watering_pump_3_duration'] == int(data['pump3Duration']) assert cmd['config']['watering_pump_4_duration'] == int(data['pump4Duration'])
def test_real_file_wrong_auth_auth(self): db = database.Database(in_memory=False) db.recreate_database() # add a dummy command to the server config = database_test.dummy_config() db.save_command("REBOOT", config) server = server_grpc.start_grpc_server(verbose=True) sleep(1) # create the gRPC stub channel = grpc.insecure_channel('127.0.0.1:' + str(constants.GRPC_SERVER_PORT)) stub = protobufs_pb2_grpc.GaiaServiceStub(channel) with pytest.raises(Exception): response = stub.Ping(dummy_status_message()) assert response is None with pytest.raises(Exception): response = stub.ActionDone(dummy_action_report()) assert response is None server.stop(0)
def test_status_creation(self): db = database.Database(in_memory=True) db.recreate_database() # initially, there should be no command self.assertEqual(db.get_all_status(), []) # save and retrieve one status1 = dummy_status_update() db.save_status(status1) all_status = db.get_all_status() self.assertNotEqual(all_status, None) self.assertEqual(len(all_status), 1) status2 = all_status[0] self.assertEqual( status2['local_timestamp'].strftime("%Y-%m-%d %H:%M:%S"), status1.local_timestamp) self.assertEqual(status2['feeding_module_activated'], status1.current_config.feeding_module_activated) self.assertEqual(status2['watering_module_activated'], status1.current_config.watering_module_activated) self.assertEqual(status2['feeding_module_cronstring'], status1.current_config.feeding_module_cronstring) self.assertEqual(status2['watering_module_cronstring'], status1.current_config.watering_module_cronstring) self.assertEqual(status2['watering_pump_1_duration'], status1.current_config.watering_pump_1_duration) self.assertEqual(status2['watering_pump_2_duration'], status1.current_config.watering_pump_2_duration) self.assertEqual(status2['watering_pump_3_duration'], status1.current_config.watering_pump_3_duration) self.assertEqual(status2['watering_pump_4_duration'], status1.current_config.watering_pump_4_duration) self.assertEqual(status2['uptime'], status1.system_status.uptime) self.assertEqual(status2['memory'], status1.system_status.memory) self.assertEqual(status2['disk_usage'], status1.system_status.disk_usage) self.assertEqual(status2['processes'], status1.system_status.processes) self.assertEqual(1, count_records('configs', db.cursor)) self.assertEqual(1, count_records('system_status', db.cursor)) self.assertEqual(1, count_records('status', db.cursor)) db.save_status(status1) self.assertEqual(2, count_records('configs', db.cursor)) self.assertEqual(2, count_records('system_status', db.cursor)) self.assertEqual(2, count_records('status', db.cursor)) # should remove all things older than X days, here with X=0 => table should be truncated db.save_status(status1, number_of_days_to_keep_status=0) self.assertEqual(1, count_records('configs', db.cursor)) self.assertEqual(1, count_records('system_status', db.cursor)) self.assertEqual(1, count_records('status', db.cursor)) db.truncate() self.assertEqual(0, count_records('configs', db.cursor)) self.assertEqual(0, count_records('system_status', db.cursor)) self.assertEqual(0, count_records('status', db.cursor))
def Ping(self, status, context): db = None if self.useRealDatabase: db = database.Database(in_memory=False) # sql schema was already instantiated else: db = database.Database(in_memory=True) db.recreate_database() if self.verbose: print("Got Ping", status, context) # prepare empty response response = protobufs_pb2.Response() response.action = protobufs_pb2.Response.DO_NOTHING if status.authentication_token != constants.AUTHENTICATION_TOKEN: print('Invalid token provided', status.authentication_token, "ignoring") return response db.save_status(status) # check if we have to transmit a command command = db.get_command() if command is not None: if command['text'] == 'SHUTDOWN': response.action = protobufs_pb2.Response.SHUTDOWN elif command['text'] == 'REBOOT': response.action = protobufs_pb2.Response.REBOOT if command['config'] is not None: response.config.feeding_module_activated = command['config']['feeding_module_activated'] response.config.watering_module_activated = command['config']['watering_module_activated'] response.config.feeding_module_cronstring = command['config']['feeding_module_cronstring'] response.config.watering_module_cronstring = command['config']['watering_module_cronstring'] response.config.watering_pump_1_duration = command['config']['watering_pump_1_duration'] response.config.watering_pump_2_duration = command['config']['watering_pump_2_duration'] response.config.watering_pump_3_duration = command['config']['watering_pump_3_duration'] response.config.watering_pump_4_duration = command['config']['watering_pump_4_duration'] db.delete_all_commands() if self.verbose: print("Answering with", response) return response
def insert_dummy_pings(): db = database.Database() db.recreate_database() for day in range(0, 7): for hour in range(0, 24): for min in range(0, 6): db.save_status(dummy_status_update(day, hour, min))
def client(): server_web.webserver.config['TESTING'] = True web_client = server_web.webserver.test_client() db = database.Database() db.recreate_database() db.delete_all_commands() yield web_client
def test_real_file_valid_auth(self): db = database.Database(in_memory=False) db.recreate_database() # add a dummy command to the server config = database_test.dummy_config() db.save_command("REBOOT", config) server = server_grpc.start_grpc_server(verbose=True) sleep(1) # create the gRPC stub channel = grpc.insecure_channel('127.0.0.1:' + str(constants.GRPC_SERVER_PORT)) stub = protobufs_pb2_grpc.GaiaServiceStub(channel) # send message status = dummy_status_message() status.authentication_token = constants.AUTHENTICATION_TOKEN response = stub.Ping(status) # this should be the default self.assertEqual(response.action, protobufs_pb2.Response.REBOOT) self.assertEqual(response.HasField('config'), True) self.assertEqual(response.config.feeding_module_activated, config.feeding_module_activated) self.assertEqual(response.config.watering_module_activated, config.feeding_module_activated) self.assertEqual(response.config.feeding_module_cronstring, config.feeding_module_cronstring) self.assertEqual(response.config.watering_module_cronstring, config.watering_module_cronstring) self.assertEqual(response.config.watering_pump_1_duration, config.watering_pump_1_duration) self.assertEqual(response.config.watering_pump_2_duration, config.watering_pump_2_duration) self.assertEqual(response.config.watering_pump_3_duration, config.watering_pump_3_duration) self.assertEqual(response.config.watering_pump_4_duration, config.watering_pump_4_duration) # send message report = dummy_action_report() report.authentication_token = constants.AUTHENTICATION_TOKEN response = stub.ActionDone(report) # this should be the default self.assertEqual(response.action, protobufs_pb2.Response.DO_NOTHING) self.assertEqual(response.HasField('config'), False) server.stop(0)
def save_new_config_to_db(cmd_text='UNKNOWN_COMMAND'): new_config = None if request.form.get('updateConfig') == '1': new_config = protobufs_pb2.Config() # manually parse those new_config.feeding_module_activated = False if request.form.get('feedingEnabled') == '1': new_config.feeding_module_activated = True new_config.watering_module_activated = False if request.form.get('wateringEnabled') == '1': new_config.watering_module_activated = True # regex-validate those feeding_cron = request.form.get('feedingCron') watering_cron = request.form.get('wateringCron') pattern = re.compile(constants.CRON_REGEX_TESTER) if not pattern.match(feeding_cron): raise ValueError('Feeding cron is invalid:', feeding_cron) if not pattern.match(watering_cron): raise ValueError('Watering cron is invalid:', feeding_cron) new_config.feeding_module_cronstring = feeding_cron new_config.watering_module_cronstring = watering_cron # manual sanity check on those values d1 = int(request.form.get('pump1Duration')) d2 = int(request.form.get('pump2Duration')) d3 = int(request.form.get('pump3Duration')) d4 = int(request.form.get('pump4Duration')) if d1 < 0 or d2 < 0 or d3 < 0 or d4 < 0: raise ValueError('A watering duration is below zero:', d1, d2, d3, d4) m = constants.WATERING_DURATION_MAX_VALUE if d1 > m or d2 > m or d3 > m or d4 > m: raise ValueError('A watering duration exceeds the max value:', d1, d2, d3, d4) # protobuf raise exception if we're not actually inputting numbers new_config.watering_pump_1_duration = d1 new_config.watering_pump_2_duration = d2 new_config.watering_pump_3_duration = d3 new_config.watering_pump_4_duration = d4 db = database.Database() db.save_command(cmd_text, new_config)
def test_command_data_but_no_update(client): data = dict() data['passphrase'] = constants.AUTHENTICATION_TOKEN data['newCommand'] = '2' # ='REBOOT' # no update config rv = client.post('/command', data=data) assert rv.status_code == 201 db = database.Database() cmd = db.get_command() assert cmd is not None assert cmd['text'] == 'REBOOT' assert cmd['config'] is None
def update_command(): try: # very weak protection against bruteforce time.sleep(constants.WEB_SERVER_NEW_COMMAND_SLEEP_TIME) if request.form is None: return Response(status=400) if request.form.get('passphrase') != constants.COMMAND_PASSPHRASE: return Response(status=401) cmd_id = request.form.get('newCommand') if cmd_id == '0': save_new_config_to_db('DO_NOTHING') elif cmd_id == '1': save_new_config_to_db('SHUTDOWN') elif cmd_id == '2': save_new_config_to_db('REBOOT') elif cmd_id == '3': # empty server db db = database.Database() db.recreate_database() db.truncate() elif cmd_id == '4': # reset server db to dummy config db = database.Database() db.recreate_database() db.truncate() import tests.dummy_data as dummy_data dummy_data.insert_dummy_action_report() dummy_data.insert_dummy_pings() else: return Response(status=406) return Response(status=201) except Exception as e: print("Exception: ", e) return Response(status=400)
def test_action_report(self): db = database.Database(in_memory=True) db.recreate_database() # initially, there should be no command self.assertEqual(db.get_all_action_reports(), []) # save and retrieve one report1 = dummy_action_report() db.save_action_report(report1) all_reports = db.get_all_action_reports() self.assertNotEqual(all_reports, None) self.assertEqual(len(all_reports), 1) report2 = all_reports[0] self.assertEqual( report2['local_timestamp'].strftime("%Y-%m-%d %H:%M:%S"), report1.local_timestamp) self.assertEqual(report2['action_type'], 1) self.assertEqual(report2['action_details'], report1.action_details) # save and retrieve two report3 = dummy_action_report() report3.action = protobufs_pb2.ActionReport.WATERING report3.action_details = 'test' db.save_action_report(report3) all_reports = db.get_all_action_reports() self.assertNotEqual(all_reports, None) self.assertEqual(len(all_reports), 2) self.assertEqual( all_reports[0]['local_timestamp'].strftime("%Y-%m-%d %H:%M:%S"), report3.local_timestamp) self.assertEqual(all_reports[0]['action_type'], 2) self.assertEqual(all_reports[0]['action_details'], report3.action_details) self.assertEqual( all_reports[1]['local_timestamp'].strftime("%Y-%m-%d %H:%M:%S"), report1.local_timestamp) self.assertEqual(all_reports[1]['action_type'], 1) self.assertEqual(all_reports[1]['action_details'], report1.action_details) # should remove all things older than X days, here with X=0 => table should be truncated db.save_action_report(report3, number_of_days_to_keep_status=0) self.assertEqual(1, count_records('action_reports', db.cursor))
def test_db_creation(self): db = database.Database(in_memory=True) self.assertNotEqual(db.db, None) self.assertNotEqual(db.cursor, None) db.recreate_database() self.assertNotEqual(db.db, None) self.assertNotEqual(db.cursor, None) tables = db.cursor.execute( 'SELECT name FROM sqlite_master WHERE type=\'table\'').fetchall() tables = [x[0] for x in tables] for expectedTable in [ "status", "configs", "system_status", "commands" ]: if expectedTable not in tables: self.fail("Table " + expectedTable + " wasn't created")
def main(): template_source = '' template_file = os.path.join(os.getcwd(), "templates", constants.TEMPLATE_FILE) with open(template_file, 'r') as file: template_source = file.read() # query the database db = database.Database() next_command = db.get_command() all_status = db.get_all_status() all_reports = db.get_all_action_reports() next_cmd_string = '⊥' if next_command is not None: next_cmd_string = next_command['text'] # prepare the array to replace in the template tokens = dict() tokens['STATUS'] = helpers.build_status_data_html(all_status) tokens['REPORTS'] = helpers.build_reports_data_html(all_reports) if len(all_status) > 0: tokens['CURRENT_CONFIG'] = helpers.build_current_config(all_status[0]) else: tokens[ 'CURRENT_CONFIG'] = '<div id="current_config"><h3>Current Config</h3>No config</div>' tokens['JS_ARRAYS'] = helpers.build_js_arrays(all_status, all_reports) tokens['NEXT_COMMAND'] = next_cmd_string tokens['PORT_VIDEO'] = str(constants.PORT_VIDEO) helpers.build_water_levels_dict(None, None, 100, None, tokens) # build the template html = template_source for key in tokens: html = html.replace('{{' + key + '}}', tokens[key]) return Response(html, 200)
def test_command_creation(self): db = database.Database(in_memory=True) db.recreate_database() # initially, there should be no command self.assertEqual(db.get_command(), None) db.save_command("SOME_COMMAND") # save a command without config cmd = db.get_command() self.assertNotEqual(cmd, None) self.assertEqual(cmd["text"], "SOME_COMMAND") self.assertEqual(cmd["config"], None) # save a new command, should only keep the most recent db.save_command("SOME_COMMAND2") cmd = db.get_command() self.assertNotEqual(cmd, None) self.assertEqual(cmd["text"], "SOME_COMMAND2") self.assertEqual(cmd["config"], None) self.assertEqual(1, count_records('commands', db.cursor)) # save a command with config config1 = dummy_config() db.save_command("SOME_COMMAND3", config1) cmd = db.get_command() self.assertNotEqual(cmd, None) self.assertEqual(cmd["text"], "SOME_COMMAND3") config2 = cmd["config"] self.assertEqual(config2['feeding_module_activated'], config1.feeding_module_activated) self.assertEqual(config2['watering_module_activated'], config1.watering_module_activated) self.assertEqual(config2['feeding_module_cronstring'], config1.feeding_module_cronstring) self.assertEqual(config2['watering_module_cronstring'], config1.watering_module_cronstring) self.assertEqual(config2['watering_pump_1_duration'], config1.watering_pump_1_duration) self.assertEqual(config2['watering_pump_2_duration'], config1.watering_pump_2_duration) self.assertEqual(config2['watering_pump_3_duration'], config1.watering_pump_3_duration) self.assertEqual(config2['watering_pump_4_duration'], config1.watering_pump_4_duration) # saving a new command should overwrite the previous, and hence delete the config # save a command with config db.save_command("SOME_COMMAND4", None) cmd = db.get_command() self.assertNotEqual(cmd, None) self.assertEqual(cmd["text"], "SOME_COMMAND4") self.assertEqual(1, count_records('commands', db.cursor)) self.assertEqual(0, count_records('configs', db.cursor)) # delete should empty the table db.delete_all_commands() self.assertEqual(0, count_records('commands', db.cursor))
def insert_dummy_action_report(): db = database.Database() db.recreate_database() for i in range(0, 14): db.save_action_report(dummy_action_report(i))
def start_grpc_server(override_servicer=None, verbose=False): server = grpc.server(futures.ThreadPoolExecutor(max_workers=constants.GRPC_MAX_WORKERS)) servicer = None if override_servicer == None: servicer = GaiaServiceServicer(real_database=True, verbose=verbose) else: servicer = override_servicer protobufs_pb2_grpc.add_GaiaServiceServicer_to_server(servicer, server) server.add_insecure_port('[::]:' + str(constants.GRPC_SERVER_PORT)) print("Starting gRPC server on port", constants.GRPC_SERVER_PORT) server.start() return server if __name__ == '__main__': # try recreating the DB db = database.Database() db.recreate_database() gprcServer = start_grpc_server(verbose=True) try: while True: sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: print("Quitting") gprcServer.stop(0)