Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Processing node handling improvements #1395

Merged
merged 5 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
**/.git
.secret_key
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,5 @@ package-lock.json

# Debian builds
dpkg/build
dpkg/deb
dpkg/deb
.secret_key
14 changes: 13 additions & 1 deletion app/api/processingnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.views import APIView

from nodeodm.models import ProcessingNode

from webodm import settings

class ProcessingNodeSerializer(serializers.ModelSerializer):
online = serializers.SerializerMethodField()
Expand Down Expand Up @@ -49,6 +49,18 @@ class ProcessingNodeViewSet(viewsets.ModelViewSet):
serializer_class = ProcessingNodeSerializer
queryset = ProcessingNode.objects.all()

def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())

if settings.UI_MAX_PROCESSING_NODES is not None:
queryset = queryset[:settings.UI_MAX_PROCESSING_NODES]

if settings.NODE_OPTIMISTIC_MODE:
for pn in queryset:
pn.update_node_info()

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

class ProcessingNodeOptionsView(APIView):
"""
Expand Down
2 changes: 1 addition & 1 deletion app/static/app/js/components/EditTaskForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class EditTaskForm extends React.Component {
}

checkFilesCount(filesCount){
if (!this.state.selectedNode) return false;
if (!this.state.selectedNode) return true;
if (filesCount === 0) return true;
if (this.state.selectedNode.max_images === null) return true;
return this.state.selectedNode.max_images >= filesCount;
Expand Down
154 changes: 77 additions & 77 deletions app/static/app/js/translations/odm_autogenerated.js

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion app/templatetags/processingnode_extras.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import template
from guardian.shortcuts import get_objects_for_user
from webodm import settings

from nodeodm.models import ProcessingNode

Expand All @@ -8,7 +9,11 @@

@register.simple_tag(takes_context=True)
def get_visible_processing_nodes(context):
return get_objects_for_user(context['request'].user, "nodeodm.view_processingnode", ProcessingNode, accept_global_perms=False)
queryset = get_objects_for_user(context['request'].user, "nodeodm.view_processingnode", ProcessingNode, accept_global_perms=False)
if settings.UI_MAX_PROCESSING_NODES is not None:
return queryset[:settings.UI_MAX_PROCESSING_NODES]
else:
return queryset


@register.simple_tag(takes_context=True)
Expand Down
12 changes: 12 additions & 0 deletions app/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,18 @@ def test_processingnodes(self):
self.assertTrue(len(res.data) == 1)
self.assertTrue(res.data[0]['name'] == 'a')

# Test optimistic mode
self.assertFalse(p4.is_online())

settings.NODE_OPTIMISTIC_MODE = True

self.assertTrue(p4.is_online())
res = client.get('/api/processingnodes/')
self.assertEqual(len(res.data), 3)
for nodes in res.data:
self.assertTrue(nodes['online'])

settings.NODE_OPTIMISTIC_MODE = False

def test_token_auth(self):
client = APIClient()
Expand Down
28 changes: 28 additions & 0 deletions app/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.contrib.auth.models import User, Group
from django.test import Client
from rest_framework import status
from guardian.shortcuts import assign_perm
from nodeodm.models import ProcessingNode

from app.models import Project, Task
from app.models import Setting
Expand All @@ -24,6 +26,11 @@ def setUp(self):
# Add user to test Group
User.objects.get(pk=1).groups.add(my_group)

# Add view permissions
user = User.objects.get(username=self.credentials['username'])
pns = ProcessingNode.objects.all()
for pn in pns:
assign_perm('view_processingnode', user, pn)

def test_user_login(self):
c = Client()
Expand Down Expand Up @@ -89,6 +96,27 @@ def test_views(self):
self.assertEqual(message.tags, 'warning')
self.assertTrue("offline" in message.message)

# The menu should have 3 processing nodes
res = c.get('/dashboard/', follow=True)
self.assertEqual(res.content.decode("utf-8").count('href="/processingnode/'), 3)
self.assertTemplateUsed(res, 'app/dashboard.html')

# The API should return 3 nodes
res = c.get('/api/processingnodes/')
self.assertEqual(len(res.data), 3)

# We can change that with a setting
settings.UI_MAX_PROCESSING_NODES = 1

res = c.get('/dashboard/', follow=True)
self.assertEqual(res.content.decode("utf-8").count('href="/processingnode/'), 1)
self.assertTemplateUsed(res, 'app/dashboard.html')

res = c.get('/api/processingnodes/')
self.assertEqual(len(res.data), 1)

settings.UI_MAX_PROCESSING_NODES = None

res = c.get('/processingnode/9999/')
self.assertTrue(res.status_code == 404)

Expand Down
2 changes: 1 addition & 1 deletion app/views/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def about(request):
def processing_node(request, processing_node_id):
pn = get_object_or_404(ProcessingNode, pk=processing_node_id)
if not pn.update_node_info():
messages.add_message(request, messages.constants.WARNING, '{} seems to be offline.'.format(pn))
messages.add_message(request, messages.constants.WARNING, _('%(node)s seems to be offline.') % {'node': pn})

return render(request, 'app/processing_node.html',
{
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ services:
- WO_BROKER
- WO_DEV
- WO_DEV_WATCH_PLUGINS
- WO_SECRET_KEY
restart: unless-stopped
oom_score_adj: 0
broker:
Expand All @@ -52,5 +53,6 @@ services:
environment:
- WO_BROKER
- WO_DEBUG
- WO_SECRET_KEY
restart: unless-stopped
oom_score_adj: 250
2 changes: 1 addition & 1 deletion locale
Submodule locale updated 48 files
+6 −1 az/LC_MESSAGES/django.po
+298 −298 az/LC_MESSAGES/djangojs.po
+6 −1 cs/LC_MESSAGES/django.po
+565 −565 cs/LC_MESSAGES/djangojs.po
+6 −1 de/LC_MESSAGES/django.po
+612 −612 de/LC_MESSAGES/djangojs.po
+6 −1 django.pot
+272 −272 djangojs.pot
+6 −1 es/LC_MESSAGES/django.po
+586 −586 es/LC_MESSAGES/djangojs.po
+6 −1 fr/LC_MESSAGES/django.po
+425 −425 fr/LC_MESSAGES/djangojs.po
+6 −1 hu/LC_MESSAGES/django.po
+590 −590 hu/LC_MESSAGES/djangojs.po
+6 −1 id/LC_MESSAGES/django.po
+289 −289 id/LC_MESSAGES/djangojs.po
+6 −1 it/LC_MESSAGES/django.po
+568 −568 it/LC_MESSAGES/djangojs.po
+6 −1 ja/LC_MESSAGES/django.po
+575 −575 ja/LC_MESSAGES/djangojs.po
+6 −1 kn/LC_MESSAGES/django.po
+272 −272 kn/LC_MESSAGES/djangojs.po
+6 −1 ko/LC_MESSAGES/django.po
+542 −542 ko/LC_MESSAGES/djangojs.po
+6 −1 lt/LC_MESSAGES/django.po
+272 −272 lt/LC_MESSAGES/djangojs.po
+6 −1 mn/LC_MESSAGES/django.po
+272 −272 mn/LC_MESSAGES/djangojs.po
+6 −1 nb_NO/LC_MESSAGES/django.po
+272 −272 nb_NO/LC_MESSAGES/djangojs.po
+6 −1 nl/LC_MESSAGES/django.po
+272 −272 nl/LC_MESSAGES/djangojs.po
+6 −1 pl/LC_MESSAGES/django.po
+640 −640 pl/LC_MESSAGES/djangojs.po
+6 −1 pt/LC_MESSAGES/django.po
+567 −567 pt/LC_MESSAGES/djangojs.po
+6 −1 pt_BR/LC_MESSAGES/django.po
+568 −568 pt_BR/LC_MESSAGES/djangojs.po
+6 −1 ru/LC_MESSAGES/django.po
+561 −561 ru/LC_MESSAGES/djangojs.po
+6 −1 th/LC_MESSAGES/django.po
+557 −557 th/LC_MESSAGES/djangojs.po
+6 −1 tr/LC_MESSAGES/django.po
+281 −281 tr/LC_MESSAGES/djangojs.po
+6 −1 zh_Hans/LC_MESSAGES/django.po
+478 −478 zh_Hans/LC_MESSAGES/djangojs.po
+6 −1 zh_Hant/LC_MESSAGES/django.po
+372 −372 zh_Hant/LC_MESSAGES/djangojs.po
3 changes: 3 additions & 0 deletions nodeodm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def find_best_available_node():
.order_by('queue_count').first()

def is_online(self):
if settings.NODE_OPTIMISTIC_MODE:
return True

return self.last_refreshed is not None and \
self.last_refreshed >= timezone.now() - timedelta(minutes=settings.NODE_OFFLINE_MINUTES)

Expand Down
26 changes: 20 additions & 6 deletions webodm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,27 @@ run(){
eval "$1"
}

get_secret(){
if [ ! -e ./.secret_key ] && [ -e /dev/random ]; then
echo "Generating secret in ./.secret_key"
export WO_SECRET_KEY=$(head -c50 < /dev/random | base64)
echo $WO_SECRET_KEY > ./.secret_key
elif [ -e ./.secret_key ]; then
export WO_SECRET_KEY=$(cat ./.secret_key)
else
export WO_SECRET_KEY=""
fi
}

start(){
if [[ $dev_mode = true ]]; then
echo "Starting WebODM in development mode..."
down
else
echo "Starting WebODM..."
fi
get_secret

if [[ $dev_mode = true ]]; then
echo "Starting WebODM in development mode..."
down
else
echo "Starting WebODM..."
fi
echo ""
echo "Using the following environment:"
echo "================================"
Expand Down
38 changes: 25 additions & 13 deletions webodm/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@
try:
from .secret_key import SECRET_KEY
except ImportError:
# This will be executed the first time Django runs
# It generates a secret_key.py file that contains the SECRET_KEY
from django.utils.crypto import get_random_string

current_dir = os.path.abspath(os.path.dirname(__file__))
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
secret = get_random_string(50, chars)
with open(os.path.join(current_dir, 'secret_key.py'), 'w') as f:
f.write("SECRET_KEY='{}'".format(secret))
SECRET_KEY=secret

print("Generated secret key")
if os.environ.get("WO_SECRET_KEY", "") != "":
SECRET_KEY = os.environ.get("WO_SECRET_KEY")
else:
# This will be executed the first time Django runs
# It generates a secret_key.py file that contains the SECRET_KEY
from django.utils.crypto import get_random_string

current_dir = os.path.abspath(os.path.dirname(__file__))
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
secret = get_random_string(50, chars)
with open(os.path.join(current_dir, 'secret_key.py'), 'w') as f:
f.write("SECRET_KEY='{}'".format(secret))
SECRET_KEY=secret

print("Generated secret key")

with open(os.path.join(BASE_DIR, 'package.json')) as package_file:
data = json.load(package_file)
Expand Down Expand Up @@ -389,15 +392,24 @@ def scalebyiv(color, n):

# Number of minutes a processing node hasn't been seen
# before it should be considered offline
NODE_OFFLINE_MINUTES = 5
NODE_OFFLINE_MINUTES = 5

# When turned on, updates nodes information only when necessary
# and assumes that all nodes are always online, avoiding polling
NODE_OPTIMISTIC_MODE = False

# URL to external auth endpoint
EXTERNAL_AUTH_ENDPOINT = ''

# URL to a page where a user can reset the password
RESET_PASSWORD_LINK = ''

# Number of hours before tasks are automatically deleted
# from an account that is exceeding a disk quota
QUOTA_EXCEEDED_GRACE_PERIOD = 8

# Maximum number of processing nodes to show in "Processing Nodes" menus/dropdowns
UI_MAX_PROCESSING_NODES = None

if TESTING or FLUSHING:
CELERY_TASK_ALWAYS_EAGER = True
Expand Down
3 changes: 3 additions & 0 deletions worker/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@

@app.task
def update_nodes_info():
if settings.NODE_OPTIMISTIC_MODE:
return

processing_nodes = ProcessingNode.objects.all()
for processing_node in processing_nodes:
processing_node.update_node_info()
Expand Down