pull/478/head
Piero Toffanin 2018-06-25 13:31:42 -04:00
rodzic b5f98ae5ae
commit 8a88d9f4ab
8 zmienionych plików z 166 dodań i 10 usunięć

Wyświetl plik

@ -4,6 +4,6 @@ from guardian.admin import GuardedModelAdmin
from .models import ProcessingNode from .models import ProcessingNode
class ProcessingNodeAdmin(GuardedModelAdmin): class ProcessingNodeAdmin(GuardedModelAdmin):
fields = ('hostname', 'port') fields = ('hostname', 'port', 'token')
admin.site.register(ProcessingNode, ProcessingNodeAdmin) admin.site.register(ProcessingNode, ProcessingNodeAdmin)

Wyświetl plik

@ -6,20 +6,24 @@ import requests
import mimetypes import mimetypes
import json import json
import os import os
from urllib.parse import urlunparse from urllib.parse import urlunparse, urlencode
from app.testwatch import TestWatch from app.testwatch import TestWatch
class ApiClient: class ApiClient:
def __init__(self, host, port, timeout=30): def __init__(self, host, port, token = "", timeout=30):
self.host = host self.host = host
self.port = port self.port = port
self.token = token
self.timeout = timeout self.timeout = timeout
def url(self, url): def url(self, url, query = {}):
netloc = self.host if self.port == 80 else "{}:{}".format(self.host, self.port) netloc = self.host if self.port == 80 else "{}:{}".format(self.host, self.port)
if len(self.token) > 0:
query['token'] = self.token
# TODO: https support # TODO: https support
return urlunparse(('http', netloc, url, '', '', '')) return urlunparse(('http', netloc, url, '', urlencode(query), ''))
def info(self): def info(self):
return requests.get(self.url('/info'), timeout=self.timeout).json() return requests.get(self.url('/info'), timeout=self.timeout).json()
@ -32,7 +36,7 @@ class ApiClient:
@TestWatch.watch() @TestWatch.watch()
def task_output(self, uuid, line = 0): def task_output(self, uuid, line = 0):
return requests.get(self.url('/task/{}/output?line={}').format(uuid, line), timeout=self.timeout).json() return requests.get(self.url('/task/{}/output', {'line': line}).format(uuid), timeout=self.timeout).json()
def task_cancel(self, uuid): def task_cancel(self, uuid):
return requests.post(self.url('/task/cancel'), data={'uuid': uuid}, timeout=self.timeout).json() return requests.post(self.url('/task/cancel'), data={'uuid': uuid}, timeout=self.timeout).json()

@ -1 +1 @@
Subproject commit 1b3b33df63ce1423988dfb68969e827d27de9035 Subproject commit bcdbc51f48a3da87d63b77d07434f9c51960b39f

Wyświetl plik

@ -22,5 +22,18 @@
}, },
"model": "nodeodm.processingnode", "model": "nodeodm.processingnode",
"pk": 2 "pk": 2
},
{
"fields": {
"api_version": "",
"available_options": {},
"hostname": "localhost",
"last_refreshed": null,
"port": 11224,
"queue_count": 0,
"token": "test_token"
},
"model": "nodeodm.processingnode",
"pk": 3
} }
] ]

Wyświetl plik

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-25 16:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nodeodm', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='processingnode',
name='token',
field=models.CharField(default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, null=True),
),
]

Wyświetl plik

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-25 16:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nodeodm', '0002_processingnode_token'),
]
operations = [
migrations.AlterField(
model_name='processingnode',
name='token',
field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024),
),
]

Wyświetl plik

@ -41,6 +41,7 @@ class ProcessingNode(models.Model):
last_refreshed = models.DateTimeField(null=True, help_text="When was the information about this node last retrieved?") last_refreshed = models.DateTimeField(null=True, help_text="When was the information about this node last retrieved?")
queue_count = models.PositiveIntegerField(default=0, help_text="Number of tasks currently being processed by this node (as reported by the node itself)") queue_count = models.PositiveIntegerField(default=0, help_text="Number of tasks currently being processed by this node (as reported by the node itself)")
available_options = fields.JSONField(default=dict(), help_text="Description of the options that can be used for processing") available_options = fields.JSONField(default=dict(), help_text="Description of the options that can be used for processing")
token = models.CharField(max_length=1024, blank=True, default="", help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.")
def __str__(self): def __str__(self):
return '{}:{}'.format(self.hostname, self.port) return '{}:{}'.format(self.hostname, self.port)
@ -81,7 +82,7 @@ class ProcessingNode(models.Model):
return False return False
def api_client(self, timeout=30): def api_client(self, timeout=30):
return ApiClient(self.hostname, self.port, timeout) return ApiClient(self.hostname, self.port, self.token, timeout)
def get_available_options_json(self, pretty=False): def get_available_options_json(self, pretty=False):
""" """

Wyświetl plik

@ -22,7 +22,7 @@ class TestClientApi(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(TestClientApi, cls).setUpClass() super(TestClientApi, cls).setUpClass()
cls.node_odm = subprocess.Popen(['node', 'index.js', '--port', '11223', '--test'], shell=False, cwd=path.join(current_dir, "external", "node-OpenDroneMap")) cls.node_odm = subprocess.Popen(['node', 'index.js', '--port', '11223', '--test'], shell=False, cwd=path.join(current_dir, "external", "node-OpenDroneMap"))
time.sleep(5) # Wait for the server to launch time.sleep(2) # Wait for the server to launch
@classmethod @classmethod
@ -187,3 +187,105 @@ class TestClientApi(TestCase):
# Best choice now is original processing node # Best choice now is original processing node
self.assertTrue(ProcessingNode.find_best_available_node().id == pnode.id) self.assertTrue(ProcessingNode.find_best_available_node().id == pnode.id)
def test_token_auth(self):
node_odm = subprocess.Popen(
['node', 'index.js', '--port', '11224', '--token', 'test_token', '--test'], shell=False,
cwd=path.join(current_dir, "external", "node-OpenDroneMap"))
time.sleep(2)
def wait_for_status(api, uuid, status, num_retries=10, error_description="Failed to wait for status"):
retries = 0
while True:
try:
task_info = api.task_info(uuid)
if task_info['status']['code'] == status:
return True
except ProcessingError:
pass
time.sleep(0.5)
retries += 1
if retries >= num_retries:
self.assertTrue(False, error_description)
return False
api = ApiClient("localhost", 11224, "test_token")
online_node = ProcessingNode.objects.get(pk=3)
self.assertTrue(online_node.update_node_info(), "Could update info")
# Can always call info(), options() (even without valid tokens)
api.token = "invalid"
self.assertTrue(type(api.info()['version']) == str)
self.assertTrue(len(api.options()) > 0)
# Cannot call new_task() without token
import glob
res = api.new_task(
glob.glob("nodeodm/fixtures/test_images/*.JPG"))
self.assertTrue('error' in res)
# Can call new_task() with token
api.token = "test_token"
res = api.new_task(
glob.glob("nodeodm/fixtures/test_images/*.JPG"))
self.assertTrue('uuid' in res)
self.assertFalse('error' in res)
uuid = res['uuid']
# Can call task_info() with token
task_info = api.task_info(uuid)
self.assertTrue(isinstance(task_info['dateCreated'], int))
# Cannot call task_info() without token
api.token = "invalid"
res = api.task_info(uuid)
self.assertTrue('error' in res)
self.assertTrue('token does not match' in res['error'])
# Here we are waiting for the task to be completed
api.token = "test_token"
wait_for_status(api, uuid, status_codes.COMPLETED, 10, "Could not download assets")
# Cannot download assets without token
api.token = "invalid"
res = api.task_download(uuid, "all.zip")
self.assertTrue('error' in res)
api.token = "test_token"
asset = api.task_download(uuid, "all.zip")
self.assertTrue(isinstance(asset, requests.Response))
# Cannot get task output without token
api.token = "invalid"
res = api.task_output(uuid, 0)
self.assertTrue('error' in res)
api.token = "test_token"
res = api.task_output(uuid, 0)
self.assertTrue(isinstance(res, list))
# Cannot restart task without token
online_node.token = "invalid"
self.assertRaises(ProcessingError, online_node.restart_task, uuid)
online_node.token = "test_token"
self.assertTrue(online_node.restart_task(uuid))
# Cannot cancel task without token
online_node.token = "invalid"
self.assertRaises(ProcessingError, online_node.cancel_task, uuid)
online_node.token = "test_token"
self.assertTrue(online_node.cancel_task(uuid))
# Wait for task to be canceled
wait_for_status(api, uuid, status_codes.CANCELED, 5, "Could not cancel task")
# Cannot delete task without token
online_node.token = "invalid"
self.assertRaises(ProcessingError, online_node.remove_task, "wrong-uuid")
online_node.token = "test_token"
self.assertTrue(online_node.remove_task(uuid))
node_odm.terminate();