From fd50a86a664416d9455307b11199048acddbb308 Mon Sep 17 00:00:00 2001 From: shinedday <shinedday@gmail.com> Date: Wed, 8 Jun 2022 15:09:07 +0200 Subject: [PATCH] Uptodate with core-0.0.7 --- .gitignore | 1 + iotamak_ihm/settings.py | 8 ++- ping/admin.py | 3 +- ping/models.py | 18 ++++++- ping/templates/ping/play.html | 35 ++++++------ ping/views/experiment.py | 25 +++------ ping/views/experiment_checker.py | 71 ++++++++++++++++++++++++ ping/views/network.py | 15 ++++-- ping/views/play.py | 92 +++++++++++++++++++++++++++----- ping/views/tool.py | 8 ++- requirements.txt | 1 + 11 files changed, 216 insertions(+), 61 deletions(-) create mode 100644 ping/views/experiment_checker.py diff --git a/.gitignore b/.gitignore index bb2256e..3e4c418 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ db.sqlite3 uploads/tmp/* uploads/media/* uploads/current_experiment/* +dump.rdb diff --git a/iotamak_ihm/settings.py b/iotamak_ihm/settings.py index e16d92c..c80c96a 100644 --- a/iotamak_ihm/settings.py +++ b/iotamak_ihm/settings.py @@ -87,8 +87,12 @@ CHANNEL_LAYERS = { DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'iotamak', + 'USER': 'raspberry', + 'PASSWORD': 'raspberry', + 'HOST': "localhost", + 'PORT': '5432' } } diff --git a/ping/admin.py b/ping/admin.py index 4cadb75..e43d25f 100644 --- a/ping/admin.py +++ b/ping/admin.py @@ -6,4 +6,5 @@ admin.site.register(Client) admin.site.register(Agent) admin.site.register(Experiment) admin.site.register(Metrics) -admin.site.register(CurrentExperiment) \ No newline at end of file +admin.site.register(CurrentExperiment) +admin.site.register(Network) \ No newline at end of file diff --git a/ping/models.py b/ping/models.py index 7731b89..58bc5f9 100644 --- a/ping/models.py +++ b/ping/models.py @@ -22,6 +22,7 @@ class Agent(models.Model): def __str__(self): return self.command + class Experiment(models.Model): name = models.CharField(max_length=60) status = models.CharField(max_length=60, default="Not checked") @@ -34,6 +35,7 @@ class ExperimentForm(ModelForm): model = Experiment fields = ["name", "description", "media"] + class Metrics(models.Model): cycle = models.PositiveIntegerField() _metrics = models.BinaryField() @@ -48,11 +50,23 @@ class Metrics(models.Model): class CurrentExperiment(models.Model): - name = models.CharField(max_length=60) cycle = models.PositiveIntegerField(default=0) nbr_agent = models.PositiveIntegerField(default=0) amas_pid = models.PositiveIntegerField(default=0) scheduler_pid = models.PositiveIntegerField(default=0) - env_pid = models.PositiveIntegerField(default=0) \ No newline at end of file + env_pid = models.PositiveIntegerField(default=0) + + seed = models.CharField(default="0", max_length=64) + scheduling = models.CharField(default="sync", max_length=10) + + +class Network(models.Model): + broker_ip = models.CharField(max_length=60) + broker_username = models.CharField(max_length=60) + broker_password = models.CharField(max_length=60) + + iotamak_core_version = models.CharField(max_length=60) + + path_to_iotamak = models.CharField(max_length=60) diff --git a/ping/templates/ping/play.html b/ping/templates/ping/play.html index 058ca30..48d9d4b 100644 --- a/ping/templates/ping/play.html +++ b/ping/templates/ping/play.html @@ -105,21 +105,25 @@ {% endif %} -<canvas id="myChart" width="500" height="500" style="border:1px solid #000000;"></canvas> +<canvas id="line-chart" width="800" height="450"></canvas> <script> -var chart_data = { - labels: ["0", "1", "2", "3"], - type: 'line', - data: { - datasets: [{ - data: [0,0,0,0] - }] - } -}; +chart_data = { + labels: ["t-9","t-8","t-7","t-6","t-5","t-4","t-3","t-2","t-1","t"], + datasets: [{ + data: [0,0,0,0,0,0,0,0,0,0], + label: "Colored agents", + borderColor: "#3e95cd", + fill: false + } + ] + }; -const ctx = document.getElementById('myChart').getContext('2d'); -const myChart = new Chart(ctx, chart_data); +const myChart = new Chart(document.getElementById("line-chart"), { + type: 'line', + data: chart_data, + options: {} +}); const graphSocket = new WebSocket( @@ -130,14 +134,11 @@ const myChart = new Chart(ctx, chart_data); graphSocket.onmessage = function (e) { const metrics = JSON.parse(e.data).message; - console.log(metrics); - new_graph_data = chart_data.data.datasets[0].data; + new_graph_data = chart_data.datasets[0].data; new_graph_data.shift(); new_graph_data.push(metrics[0]); - console.log(new_graph_data); - chart_data.data.datasets[0].data = new_graph_data; - console.log(new_graph_data); + chart_data.datasets[0].data = new_graph_data; myChart.update(); diff --git a/ping/views/experiment.py b/ping/views/experiment.py index 4ac42b7..593169a 100644 --- a/ping/views/experiment.py +++ b/ping/views/experiment.py @@ -1,5 +1,4 @@ import os -import zipfile from django.conf import settings @@ -7,6 +6,7 @@ from django.http import HttpResponse, HttpResponseRedirect from django.template import loader from django.urls import reverse +from .experiment_checker import unzip, contains_basic_files, check_config from .tool import delete_folder from ..models import Experiment, ExperimentForm @@ -27,33 +27,20 @@ def entry(request): def check(request, experiment_id): - # get the path exp = Experiment.objects.get(pk=experiment_id) - # check if it's a zip -> wrong format : not zip - if not zipfile.is_zipfile(str(settings.MEDIA_ROOT) + str(exp.media)): - exp.status = "Wrong format : zip file expected" - exp.save() + if not unzip(str(settings.MEDIA_ROOT) + str(exp.media), str(settings.MEDIA_ROOT) + "/tmp", exp): return HttpResponseRedirect(reverse('ping:experiment')) - with zipfile.ZipFile(str(settings.MEDIA_ROOT) + str(exp.media), 'r') as zip_ref: - zip_ref.extractall(str(settings.MEDIA_ROOT) + "/tmp") - folder_path = str(settings.MEDIA_ROOT) + "/tmp/" + exp.name - if not os.path.isdir(folder_path): - exp.status = "Wrong format : zip should contain a folder" - exp.save() + if not contains_basic_files(folder_path, exp): delete_folder(str(settings.MEDIA_ROOT) + "/tmp") return HttpResponseRedirect(reverse('ping:experiment')) - required_files = ["amas.py", "agent.py", "env.py", "scheduler.py", "config.json"] - for required_file in required_files: - if not os.path.exists(folder_path + "/" + required_file): - exp.status = "Wrong format : " + required_file + " file is expected" - exp.save() - delete_folder(str(settings.MEDIA_ROOT) + "/tmp") - return HttpResponseRedirect(reverse('ping:experiment')) + if not check_config(folder_path, exp): + delete_folder(str(settings.MEDIA_ROOT) + "/tmp") + return HttpResponseRedirect(reverse('ping:experiment')) exp.status = "Checked" exp.save() diff --git a/ping/views/experiment_checker.py b/ping/views/experiment_checker.py new file mode 100644 index 0000000..0c331dd --- /dev/null +++ b/ping/views/experiment_checker.py @@ -0,0 +1,71 @@ +import json +import os +import zipfile + +from ..models import Network + + +def unzip(src_path: str, dest_path: str, exp) -> bool: + if not zipfile.is_zipfile(src_path): + exp.status = "Wrong format : zip file expected" + exp.save() + return False + + with zipfile.ZipFile(src_path, 'r') as zip_ref: + zip_ref.extractall(dest_path) + + return True + + +def contains_basic_files(path: str, exp) -> bool: + if not os.path.isdir(path): + exp.status = "Wrong format : zip should contain a folder" + exp.save() + return False + + required_files = ["amas.py", "env.py", "config.json"] + for required_file in required_files: + if not os.path.exists(path + "/" + required_file): + exp.status = "Wrong format : " + required_file + " file is expected" + exp.save() + return False + return True + + +def check_config(path: str, exp) -> bool: + with open(path + "/config.json") as json_file: + config = json.load(json_file) + + required_keys = ["iotamak_version", "scheduling_type"] + for key in required_keys: + if key not in config: + exp.status = "Wrong config : " + key + " is expected in config.json" + exp.save() + return False + + if config.get("scheduling_type") not in ["sync", "async"]: + exp.status = "Wrong config : scheduling_type is either sync or async" + exp.save() + return False + + n = Network.objects.all()[0] + version = n.iotamak_core_version + + if int(config.get("iotamak_version").replace(".", "00")) < int(version.replace(".", "00")): + exp.status = "Wrong version : experiment version is outdated" + exp.save() + return False + elif int(config.get("iotamak_version").replace(".", "00")) > int(version.replace(".", "00")): + exp.status = "Wrong version : experiment version is ahead of the server version" + exp.save() + return False + + if config.get("scheduling_type") == "sync": + if not os.path.exists(path + "/scheduler.py"): + exp.status = "Wrong format : scheduler.py file is expected" + exp.save() + return False + else: + pass + + return True diff --git a/ping/views/network.py b/ping/views/network.py index 7c669d5..e5e4997 100644 --- a/ping/views/network.py +++ b/ping/views/network.py @@ -8,25 +8,32 @@ from django.urls import reverse from iotAmak.tool.ssh_client import Cmd from .tool import get_ssh_client -from ..models import Client, Agent +from ..models import Client, Agent, Network def update(request): ssh = get_ssh_client() - version = "0.0.3" + n = Network.objects.all()[0] + version = n.iotamak_core_version commands = [ Cmd( - cmd="cd Desktop/mqtt_goyon/iotamak-core" + cmd="cd "+n.path_to_iotamak+"iotamak-core" ), Cmd( cmd="git pull" ), Cmd( - cmd="git checkout main" + cmd="git reset --hard origin/main" + ), + Cmd( + cmd="git clean -f -d" ), Cmd( cmd="git pull" ), + Cmd( + cmd="git checkout main" + ), Cmd( cmd="python3 -m pip install --force-reinstall dist/iotAmak-" + version + "-py3-none-any.whl" ) diff --git a/ping/views/play.py b/ping/views/play.py index f987cbc..046b2aa 100644 --- a/ping/views/play.py +++ b/ping/views/play.py @@ -1,5 +1,6 @@ import csv import os +import random import sys import zipfile import json @@ -16,7 +17,7 @@ from iotAmak.tool.ssh_client import Cmd from paho.mqtt.client import Client as MQTTClient from .tool import delete_folder, get_ssh_client, canvas_event_triger, graph_event_triger -from ..models import Experiment, CurrentExperiment, Metrics +from ..models import Experiment, CurrentExperiment, Metrics, Network client = MQTTClient(client_id="django-ihm") @@ -63,6 +64,7 @@ def cycle_done(client, userdata, message) -> None: def update_nbr_agent(client, userdata, message) -> None: exp = CurrentExperiment.objects.all()[0] + subscribe(client, "agent/"+str(exp.nbr_agent)+"/log", agent_log) exp.nbr_agent += 1 exp.save() @@ -81,13 +83,50 @@ def experiment_load(request): # drop current experiment table and create new instance CurrentExperiment.objects.all().delete() cur_exp = CurrentExperiment(name=Experiment.objects.get(status="Selected").name) + + with open(str(settings.MEDIA_ROOT) + "current_experiment/"+str(exp.name)+"/config.json") as json_file: + config = json.load(json_file) + + if "seed" in config: + cur_exp.seed = str(config.get("seed")) + else: + random.seed() + cur_exp.seed = str(random.randint(0,9999999)) + + cur_exp.scheduling = config.get("scheduling_type") + cur_exp.save() return HttpResponseRedirect(reverse('ping:play')) +def delete_nohup(): + ssh = get_ssh_client() + + commands = [ + Cmd( + cmd="rm nohup.out" + ) + + ] + for i_client in range(len(ssh.clients)): + print(" Ip : ", ssh.clients[i_client].hostname) + ssh.run_cmd(i_client, commands) + +def get_nohup(): + ssh = get_ssh_client() + + commands = [ + Cmd( + cmd="cat nohup.out" + ) + + ] + for i_client in range(len(ssh.clients)): + print(" Ip : ", ssh.clients[i_client].hostname) + ssh.run_cmd(i_client, commands) def experiment_start(request): - broker_ip = "192.168.193.209" + n = Network.objects.all()[0] ssh = get_ssh_client() experiment_path = str(settings.MEDIA_ROOT) + "current_experiment/" + Experiment.objects.get(status="Selected").name @@ -96,25 +135,47 @@ def experiment_start(request): global client client.disconnect() client = MQTTClient(client_id="django-ihm") - client.username_pw_set(username="goyon", password="mosquitto") - client.connect(host=broker_ip) + client.username_pw_set(username=n.broker_username, password=n.broker_password) + client.connect(host=n.broker_ip) client.loop_start() subscribe(client, "amas/agent/new", update_nbr_agent) subscribe(client, "scheduler/cycledone", cycle_done) subscribe(client, "amas/all_metric", agent_metric) - # start subprocess scheduler - p1 = Popen([sys.executable, experiment_path + '/scheduler.py', broker_ip]) - sleep(1) + delete_nohup() + + exp = CurrentExperiment.objects.all()[0] + + if exp.scheduling == "sync": + p1 = Popen( + [sys.executable, experiment_path + '/scheduler.py', n.broker_ip, n.broker_username, n.broker_password] + ) + exp.scheduler_pid = p1.pid + sleep(1) # start subprocess amas - send_client = [c.to_send() for c in ssh.clients] - p2 = Popen([sys.executable, experiment_path + '/amas.py', broker_ip, str(send_client)]) + amas_dict = { + "broker_ip": n.broker_ip, + "clients": str([c.to_send() for c in ssh.clients]), + "seed": exp.seed, + "iot_path": n.path_to_iotamak+"example/", + "broker_username": n.broker_username, + "broker_password": n.broker_password + } + if not exp.scheduling == "sync": + amas_dict["wait_delay"] = str(5) + p2 = Popen([sys.executable, experiment_path + '/amas.py', json.dumps(amas_dict)]) # start subprocess env - p3 = Popen([sys.executable, experiment_path + '/env.py', broker_ip]) + env_dict = { + "broker_ip": n.broker_ip, + "seed": exp.seed, + "broker_username": n.broker_username, + "broker_password": n.broker_password + } + if not exp.scheduling == "sync": + env_dict["wait_delay"] = str(5) + p3 = Popen([sys.executable, experiment_path + '/env.py', json.dumps(env_dict)]) - exp = CurrentExperiment.objects.all()[0] exp.amas_pid = p2.pid - exp.scheduler_pid = p1.pid exp.env_pid = p3.pid exp.save() @@ -174,6 +235,7 @@ def scheduler_start(request): def experiment_share(request): # get selected experiment exp = Experiment.objects.get(status="Selected") + n = Network.objects.all()[0] # open ssh connection ssh = get_ssh_client() @@ -181,19 +243,21 @@ def experiment_share(request): # file transfer commands = [ Cmd( - cmd="rm -r Desktop/mqtt_goyon/example/" + exp.name + cmd="rm -r " + n.path_to_iotamak + "example/" + exp.name ) ] for i_client in range(len(ssh.clients)): + print(" Ip : ", ssh.clients[i_client].hostname) ssh.run_cmd(i_client, commands) - ssh.update(exp.name, str(settings.MEDIA_ROOT) + "current_experiment/" + exp.name) + ssh.update("example/" + exp.name, str(settings.MEDIA_ROOT) + "current_experiment/" + exp.name) return HttpResponseRedirect(reverse('ping:play')) def experiment_stop(request): + get_nohup() client.publish("ihm/exit") client.publish("ihm/step") return HttpResponseRedirect(reverse('ping:play')) diff --git a/ping/views/tool.py b/ping/views/tool.py index fa93859..5eb763e 100644 --- a/ping/views/tool.py +++ b/ping/views/tool.py @@ -6,7 +6,7 @@ from channels.layers import get_channel_layer from iotAmak.tool.remote_client import RemoteClient from iotAmak.tool.ssh_client import SSHClient -from ping.models import Client +from ping.models import Client, Network def delete_folder(folder_name): @@ -20,6 +20,7 @@ def delete_folder(folder_name): except Exception as e: print('Failed to delete %s. Reason: %s' % (file_path, e)) + def get_remote_client(): res = [] for client in Client.objects.all(): @@ -28,8 +29,10 @@ def get_remote_client(): return res + def get_ssh_client(): - return SSHClient(get_remote_client()) + n = Network.objects.all()[0] + return SSHClient(get_remote_client(), n.path_to_iotamak) def canvas_event_triger(metrics): @@ -42,6 +45,7 @@ def canvas_event_triger(metrics): } ) + def graph_event_triger(metrics): channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)( diff --git a/requirements.txt b/requirements.txt index 290785c..7ef1ac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django >= 4.0.4 channels >= 3.0.4 channels-redis >= 3.4.0 +psycopg2 >= 2.9.3 \ No newline at end of file -- GitLab