Commit dc4d4e55 by Karsa Zoltán István

Merge branch 'iac-api'

parents 796da6b9 42092a44
...@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = ( ...@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = (
'statici18n', 'statici18n',
'simplesshkey', 'simplesshkey',
'pipeline', 'pipeline',
'rest_framework',
'rest_framework.authtoken',
) )
...@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS ...@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION ########## END APP CONFIGURATION
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser'
),
}
########## LOGGING CONFIGURATION ########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging # A sample logging configuration. The only tangible logging
......
...@@ -20,7 +20,7 @@ from django.views.generic import TemplateView ...@@ -20,7 +20,7 @@ from django.views.generic import TemplateView
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse, path
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.auth.views import ( from django.contrib.auth.views import (
PasswordResetView, PasswordResetConfirmView PasswordResetView, PasswordResetConfirmView
...@@ -33,6 +33,8 @@ from dashboard.views import ( ...@@ -33,6 +33,8 @@ from dashboard.views import (
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item from firewall.views import add_blacklist_item
from rest_framework.authtoken.views import obtain_auth_token
admin.autodiscover() admin.autodiscover()
urlpatterns = [ urlpatterns = [
...@@ -71,6 +73,9 @@ urlpatterns = [ ...@@ -71,6 +73,9 @@ urlpatterns = [
name="info.support"), name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(), url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"), name="info.resize"),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-token-auth/', obtain_auth_token)
] ]
if 'rosetta' in settings.INSTALLED_APPS: if 'rosetta' in settings.INSTALLED_APPS:
......
...@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel): ...@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel):
return 'failed' return 'failed'
@celery.task() @celery.task()
def compute_cached(method, instance, memcached_seconds, def compute_cached(method, instance, memcached_seconds,
key, start, *args, **kwargs): key, start, *args, **kwargs):
......
...@@ -61,6 +61,15 @@ except NameError: ...@@ -61,6 +61,15 @@ except NameError:
logger = getLogger(__name__) logger = getLogger(__name__)
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
def pwgen(): def pwgen():
return User.objects.make_random_password() return User.objects.make_random_password()
......
from rest_framework.renderers import JSONRenderer
from rest_framework import serializers
from django.contrib.auth.models import Group, User
from vm.models import Instance, InstanceTemplate, Lease, Interface, Node, InstanceActivity
from firewall.models import Vlan, Rule
from storage.models import Disk, StorageActivity
class RuleSerializer(serializers.ModelSerializer):
class Meta:
model = Rule
fields = '__all__'
class InstanceActivitySerializer(serializers.ModelSerializer):
get_percentage = serializers.IntegerField()
result_data = serializers.JSONField()
class Meta:
model = InstanceActivity
fields = ('id', 'instance', 'resultant_state', 'interruptible', 'activity_code', 'parent',
'task_uuid', 'user', 'started', 'finished', 'succeeded', 'result_data', 'created', 'modified', 'get_percentage')
class StorageActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StorageActivity
fields = ('id', 'parent', 'task_uuid', 'user', 'started', 'finished', 'succeeded', 'result_data', 'created', 'modified', 'disk')
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'user_set')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'is_staff', 'groups', 'is_superuser', 'first_name', 'last_name')
class NodeSerializer(serializers.ModelSerializer):
class Meta:
model = Node
fields = [ 'id', 'name', 'priority', 'host', 'enabled', 'schedule_enabled',
'traits', 'overcommit', 'ram_weight', 'cpu_weight', 'time_stamp' ]
class InstanceTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = InstanceTemplate
fields = [ 'id', 'name', 'description', 'parent', 'owner', 'access_method', 'boot_menu',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks']
class LeaseSerializer(serializers.ModelSerializer):
class Meta:
model = Lease
fields = [ 'id', 'name', 'suspend_interval_seconds', 'delete_interval_seconds']
class DiskSerializer(serializers.ModelSerializer):
class Meta:
model = Disk
fields = ['id', 'name', 'filename', 'datastore', 'type', 'bus', 'size', 'base',
'dev_num', 'destroyed', 'ci_disk', 'is_ready']
class InstanceSerializer(serializers.ModelSerializer):
ipv4addr = serializers.SerializerMethodField('get_ipv4')
ipv6addr = serializers.SerializerMethodField('get_ipv6')
vlans = serializers.SerializerMethodField('get_vlans')
#interfaces = serializers.SerializerMethodField('get_interfaces')
def get_ipv4(self, i):
return str(i.ipv4)
def get_ipv6(self, i):
return str(i.ipv6)
def get_vlans(self, i):
return list(net.vlan.id for net in i.interface_set.all() if net.host)
def get_interfaces(self, i):
return i.interface_set.all()
class Meta:
model = Instance
fields = ['id', 'name', 'description', 'status', 'owner', 'access_method', 'boot_menu', 'pw', 'is_base',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system', 'req_traits', 'interface_set',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks', 'node', 'ipv4addr', 'ipv6addr', 'vlans']
extra_kwargs = {
'disks': {'required': False, 'allow_empty': True,},
'req_traits': {'required': False, 'allow_empty': True,}
}
class InterfaceSerializer(serializers.ModelSerializer):
mac = serializers.SerializerMethodField('get_mac')
ipv4 = serializers.SerializerMethodField('get_ipv4')
ipv6 = serializers.SerializerMethodField('get_ipv6')
def get_mac(self, i):
return str(i.mac)
def get_ipv4(self, i):
return str(i.ipv4)
def get_ipv6(self, i):
return str(i.ipv6)
class Meta:
model = Interface
fields = ['id', 'vlan', 'host', 'instance', 'model', 'host', 'mac', 'ipv4', 'ipv6']
class VlanSerializer(serializers.ModelSerializer):
class Meta:
model = Vlan
fields = ['id', 'vid', 'name', 'description', 'comment', 'domain']
class CreateDiskSerializer(serializers.Serializer):
size = serializers.CharField(max_length=50)
name = serializers.CharField(max_length=100)
class ResizeDiskSerializer(serializers.Serializer):
size = serializers.CharField(max_length=50)
disk = serializers.IntegerField()
class DownloadDiskSerializer(serializers.Serializer):
url = serializers.CharField(max_length=500)
name = serializers.CharField(max_length=100)
class CreateTemplateSerializer(serializers.Serializer):
name = serializers.CharField(max_length=100)
class DestroyDiskSerializer(serializers.Serializer):
disk = serializers.IntegerField()
instance = serializers.IntegerField(required=False)
class Meta:
extra_kwargs = {'instance': {'required': False, 'allow_null': True}}
class AddPortSerializer(serializers.Serializer):
type = serializers.CharField(max_length=5)
port_destination = serializers.IntegerField()
port_source = serializers.IntegerField(required=False)
forwarding = serializers.BooleanField(required=False)
class VMDeploySerializer(serializers.Serializer):
node = serializers.IntegerField(required=False)
class Meta:
extra_kwargs = {'node': {'required': False, 'allow_null': True}}
\ No newline at end of file
...@@ -42,6 +42,14 @@ ...@@ -42,6 +42,14 @@
{% trans "Email address" %}: {{ profile.email }} {% trans "Email address" %}: {{ profile.email }}
{% endif %} {% endif %}
</p> </p>
<p>
IAC-TOKEN:
{% if profile.is_superuser %}
<i>{{ iac_token }}</i>
{% else %}
for admin users only
{% endif %}
</p>
<p>{% trans "Last login" %}: <span title="{{ profile.last_login }}">{{ profile.last_login|arrowfilter:LANGUAGE_CODE}}</span></p> <p>{% trans "Last login" %}: <span title="{{ profile.last_login }}">{{ profile.last_login|arrowfilter:LANGUAGE_CODE}}</span></p>
{% if request.user == profile %} {% if request.user == profile %}
<p> <p>
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
from django.conf.urls import url from django.conf.urls import url
from django.urls import path
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
...@@ -53,17 +54,62 @@ from .views import ( ...@@ -53,17 +54,62 @@ from .views import (
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView, NodeActivityView,
UserList, UserList, TemplateREST, LeaseREST, DiskRest, InstanceREST,
StorageDetail, DiskDetail, InterfaceREST, InstanceFromTemplateREST, InstanceFTforUsersREST,
DownloadDiskREST, GetInstanceREST, GetInterfaceREST, ShutdownInstanceREST,
GetLeaseREST, GetDiskRest, DeployInstanceREST, CreateDiskREST,
VlanREST, ResizeDiskREST, GetVlanREST, DestroyDiskREST, InstanceFTforUsersIdREST,
StorageDetail, DiskDetail, UserREST, GroupREST, CreateTemplateREST,
InstanceActivityREST, GetInstanceActivityREST, GetGroupREST, GetUserREST,
SleepInstanceREST, WakeUpInstanceREST, DownloadPersistentDiskREST,
CreatePersistentDiskREST, GetStorageActivityREST, GetTemplateREST,
MessageList, MessageDetail, MessageCreate, MessageDelete, MessageList, MessageDetail, MessageCreate, MessageDelete,
SetupPortREST, RulesREST,
EnableTwoFactorView, DisableTwoFactorView, EnableTwoFactorView, DisableTwoFactorView,
AclUserGroupAutocomplete, AclUserAutocomplete, AclUserGroupAutocomplete, AclUserAutocomplete,
RescheduleView, GroupImportView, GroupExportView RescheduleView, GroupImportView, GroupExportView,
) )
from .views.node import node_ops from .views.node import node_ops, NodeREST, GetNodeREST
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
urlpatterns = [ urlpatterns = [
path('acpi/stact/<int:pk>/', GetStorageActivityREST.as_view()),
path('acpi/pddisk/', DownloadPersistentDiskREST.as_view()),
path('acpi/pcdisk/', CreatePersistentDiskREST.as_view()),
path('acpi/vmact/', InstanceActivityREST.as_view()),
path('acpi/vmact/<int:pk>/', GetInstanceActivityREST.as_view()),
path('acpi/user/', UserREST.as_view()),
path('acpi/user/<int:pk>/', GetUserREST.as_view()),
path('acpi/group/', GroupREST.as_view()),
path('acpi/group/<int:pk>/', GetGroupREST.as_view()),
path('acpi/vm/', InstanceREST.as_view()),
path('acpi/node/', NodeREST.as_view()),
path('acpi/node/<int:pk>/', GetNodeREST.as_view()),
path('acpi/vm/<int:pk>/', GetInstanceREST.as_view()),
path('acpi/template/<int:pk>/', GetTemplateREST.as_view()),
path('acpi/template/', TemplateREST.as_view()),
path('acpi/ft/', InstanceFromTemplateREST.as_view()),
path('acpi/lease/', LeaseREST.as_view()),
path('acpi/lease/<int:pk>/', GetLeaseREST.as_view()),
path('acpi/disk/', DiskRest.as_view()),
path('acpi/disk/<int:pk>/', GetDiskRest.as_view()),
path('acpi/interface/', InterfaceREST.as_view()),
path('acpi/vlan/', VlanREST.as_view()),
path('acpi/vlan/<int:pk>/', GetVlanREST.as_view()),
path('acpi/interface/<int:pk>/', GetInterfaceREST.as_view()),
path('acpi/ftusers/', InstanceFTforUsersREST.as_view()),
path('acpi/ftusersid/', InstanceFTforUsersIdREST.as_view()),
path('acpi/vm/<int:pk>/downloaddisk/', DownloadDiskREST.as_view()),
path('acpi/vm/<int:vm_id>/port/<int:vlan_id>/', SetupPortREST.as_view()),
path('acpi/vm/<int:vm_id>/rules/<int:vlan_id>/', RulesREST.as_view()),
path('acpi/vm/<int:pk>/createdisk/', CreateDiskREST.as_view()),
path('acpi/vm/<int:pk>/deploy/', DeployInstanceREST.as_view()),
path('acpi/vm/<int:pk>/shutdown/', ShutdownInstanceREST.as_view()),
path('acpi/vm/<int:pk>/sleep/', SleepInstanceREST.as_view()),
path('acpi/vm/<int:pk>/wakeup/', WakeUpInstanceREST.as_view()),
path('acpi/vm/<int:pk>/resizedisk/', ResizeDiskREST.as_view()),
path('acpi/vm/<int:pk>/destroydisk/', DestroyDiskREST.as_view()),
path('acpi/vm/<int:pk>/saveastemplate/', CreateTemplateREST.as_view()),
url(r'^$', IndexView.as_view(), name="dashboard.index"), url(r'^$', IndexView.as_view(), name="dashboard.index"),
url(r"^profile/list/$", UserList.as_view(), url(r"^profile/list/$", UserList.as_view(),
name="dashboard.views.user-list"), name="dashboard.views.user-list"),
......
...@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group ...@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import UpdateView, TemplateView from django.views.generic import UpdateView, TemplateView
...@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin ...@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from itertools import chain from itertools import chain
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from vm.models import Instance, InstanceTemplate from vm.models import Instance, InstanceTemplate
from .util import (CheckedDetailView, AclUpdateView, search_user, from .util import (CheckedDetailView, AclUpdateView, search_user,
saml_available, DeleteViewBase) saml_available, DeleteViewBase)
...@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile ...@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException from ..store_api import Store, NoStoreException
from ..tables import GroupListTable from ..tables import GroupListTable
from dashboard.serializers import GroupSerializer
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
unicode unicode
...@@ -55,6 +63,33 @@ except NameError: ...@@ -55,6 +63,33 @@ except NameError:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GroupREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
group = Group.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = GroupSerializer(group, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
groups = Group.objects.all()
serializer = GroupSerializer(groups, many=True)
return JsonResponse(serializer.data, safe=False)
class GetGroupREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
groups = Group.objects.get(pk=pk)
serializer = GroupSerializer(groups, many=False)
return JsonResponse(serializer.data, safe=False)
class GroupCodeMixin(object): class GroupCodeMixin(object):
@classmethod @classmethod
......
...@@ -31,6 +31,12 @@ from django.template.loader import render_to_string ...@@ -31,6 +31,12 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import DetailView, TemplateView, View from django.views.generic import DetailView, TemplateView, View
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase ...@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase
from manager.mancelery import crontab_parser from manager.mancelery import crontab_parser
from dashboard.serializers import NodeSerializer
class NodeREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
template = Node.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = NodeSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Node.objects.all()
serializer = NodeSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetNodeREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = Node.objects.get(pk=pk)
serializer = NodeSerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
def get_operations(instance, user): def get_operations(instance, user):
ops = [] ops = []
......
...@@ -21,14 +21,20 @@ from django.urls import reverse ...@@ -21,14 +21,20 @@ from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin from braces.views import SuperuserRequiredMixin
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.views import APIView
from common.models import WorkerNotFound from common.models import WorkerNotFound
from storage.models import DataStore, Disk from storage.models import DataStore, Disk
from ..tables import DiskListTable from ..tables import DiskListTable
from ..forms import DataStoreForm, DiskForm from ..forms import DataStoreForm, DiskForm
from dashboard.serializers import DiskSerializer
class StorageDetail(SuperuserRequiredMixin, UpdateView): class StorageDetail(SuperuserRequiredMixin, UpdateView):
...@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView): ...@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView):
def form_valid(self, form): def form_valid(self, form):
pass pass
class DiskRest(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = Disk.objects.all()
serializer = DiskSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetDiskRest(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
disk = Disk.objects.get(pk=pk)
serializer = DiskSerializer(disk, many=False)
return JsonResponse(serializer.data, safe=False)
def delete(self, request, pk, format=None):
return JsonResponse(status=204)
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
from datetime import timedelta from datetime import timedelta
import json import json
import logging import logging
from string import Template
from xml.dom import NotFoundErr
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.db.models import Count from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop from django.utils.translation import ugettext as _, ugettext_noop
...@@ -56,6 +58,13 @@ from .util import ( ...@@ -56,6 +58,13 @@ from .util import (
GraphMixin GraphMixin
) )
from dashboard.serializers import InstanceTemplateSerializer, LeaseSerializer, InstanceSerializer
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
unicode unicode
...@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return reverse_lazy("dashboard.views.template-list") return reverse_lazy("dashboard.views.template-list")
class TemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get("name"):
try:
template = InstanceTemplate.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = InstanceTemplateSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse(state=411)
templates = InstanceTemplate.objects.all()
serializer = InstanceTemplateSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = InstanceTemplateSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
templates = InstanceTemplate.objects.get(pk=pk)
serializer = InstanceTemplateSerializer(templates, many=False)
return JsonResponse(serializer.data, safe=False)
def delete(self, request, pk, format=None):
return JsonResponse(status=400)
class InstanceFromTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
user = User.objects.get(pk=request.user.pk)
template = InstanceTemplate.objects.get(pk=data['template'])
ikwargs = {
'name': data['name'],
'template': template,
'owner': user,
}
amount = data.get("amount", 1)
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
instances = Instance.mass_create_from_template(amount=amount,
**ikwargs)
for i in instances:
i.deploy._async(user=user)
if amount == 1:
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
class InstanceFTforUsersREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
template = InstanceTemplate.objects.get(pk=data['template'])
ikwargs = {
'name': data['name'],
'users': data['users'],
'template': template,
'operator': data.get('operator', None),
'admin': data.get('admin', None)
}
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
missing_users, instances = Instance.mass_create_for_users(**ikwargs)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201)
class InstanceFTforUsersIdREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
template = InstanceTemplate.objects.get(pk=int(data['template']))
ikwargs = {
'name': data['name'],
'users': data['users'],
'template': template,
'operator': data.get('operator', None),
'admin': data.get('admin', None)
}
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
missing_users, instances = Instance.mass_create_for_users_id(**ikwargs)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
class LeaseREST(APIView):
authentication_classes = [TokenAuthentication, BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
template = Lease.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = LeaseSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Lease.objects.all()
serializer = LeaseSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = LeaseSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetLeaseREST(APIView):
authentication_classes = [TokenAuthentication, BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
lease = Lease.objects.get(pk=pk)
serializer = LeaseSerializer(lease, many=False)
return JsonResponse(serializer.data, safe=False)
class TemplateAclUpdateView(AclUpdateView): class TemplateAclUpdateView(AclUpdateView):
model = InstanceTemplate model = InstanceTemplate
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
import json import json
import logging import logging
from multiprocessing import context
import pyotp import pyotp
...@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation ...@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.core.paginator import Paginator, InvalidPage from django.core.paginator import Paginator, InvalidPage
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
...@@ -41,6 +42,12 @@ from django.views.generic import ( ...@@ -41,6 +42,12 @@ from django.views.generic import (
) )
from simplesshkey.models import UserKey from simplesshkey.models import UserKey
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from braces.views import LoginRequiredMixin, PermissionRequiredMixin from braces.views import LoginRequiredMixin, PermissionRequiredMixin
from django_tables2 import SingleTableView, LazyPaginator from django_tables2 import SingleTableView, LazyPaginator
...@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand ...@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand
from ..tables import ( from ..tables import (
UserKeyListTable, ConnectCommandListTable, UserListTable, UserKeyListTable, ConnectCommandListTable, UserListTable,
) )
from rest_framework.authtoken.models import Token
from .util import saml_available, DeleteViewBase, LoginView from .util import saml_available, DeleteViewBase, LoginView
from dashboard.serializers import UserSerializer
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
...@@ -67,6 +75,30 @@ except NameError: ...@@ -67,6 +75,30 @@ except NameError:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class UserREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('username'):
try:
user = User.objects.filter(username__istartswith=request.query_params.get('username')).get()
serializer = UserSerializer(user, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return JsonResponse(serializer.data, safe=False)
class GetUserREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
users = User.objects.get(pk=pk)
serializer = UserSerializer(users, many=False)
return JsonResponse(serializer.data, safe=False)
def set_session_expiry(request, user): def set_session_expiry(request, user):
if user.is_superuser: if user.is_superuser:
...@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['perm_email'] = ( context['perm_email'] = (
context['perm_group_list'] or self.request.user == user) context['perm_group_list'] or self.request.user == user)
context['iac_token'] = Token.objects.filter(user=user).get()
# filter the virtual machine list # filter the virtual machine list
# if the logged in user is not superuser or not the user itself # if the logged in user is not superuser or not the user itself
# filter the list so only those virtual machines are shown that are # filter the list so only those virtual machines are shown that are
......
...@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin ...@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin
from braces.views._access import AccessMixin from braces.views._access import AccessMixin
from celery.exceptions import TimeoutError from celery.exceptions import TimeoutError
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from storage.models import StorageActivity
from dashboard.serializers import InstanceActivitySerializer, StorageActivitySerializer
from common.models import HumanReadableException, HumanReadableObject from common.models import HumanReadableException, HumanReadableObject
from ..models import GroupProfile, Profile from ..models import GroupProfile, Profile
from vm.models import InstanceActivity
from ..forms import TransferOwnershipForm from ..forms import TransferOwnershipForm
...@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__) ...@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__)
saml_available = hasattr(settings, "SAML_CONFIG") saml_available = hasattr(settings, "SAML_CONFIG")
class InstanceActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = InstanceActivity.objects.all()
serializer = InstanceActivitySerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetInstanceActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = InstanceActivity.objects.get(pk=pk)
serializer = InstanceActivitySerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
class GetStorageActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
act = StorageActivity.objects.get(pk=pk)
serializer = StorageActivitySerializer(act, many=False)
return JsonResponse(serializer.data, safe=False)
class RedirectToLoginMixin(AccessMixin): class RedirectToLoginMixin(AccessMixin):
redirect_exception_classes = (PermissionDenied, ) redirect_exception_classes = (PermissionDenied, )
......
...@@ -17,9 +17,13 @@ ...@@ -17,9 +17,13 @@
import json import json
import queue
import string
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from os import getenv from os import getenv
from urllib import response
from xml.dom import ValidationErr
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
...@@ -42,6 +46,11 @@ from django.views.generic import ( ...@@ -42,6 +46,11 @@ from django.views.generic import (
) )
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from storage.tasks import storage_tasks
from vm.tasks.local_tasks import abortable_async_downloaddisk_operation
from vm.operations import (DeployOperation, DestroyOperation, DownloadDiskOperation, RemovePortOperation, ShutdownOperation, RenewOperation,
ResizeDiskOperation, RemoveDiskOperation, SleepOperation, WakeUpOperation, AddPortOperation, SaveAsTemplateOperation,
)
from common.models import ( from common.models import (
create_readable, HumanReadableException, fetch_human_exception, create_readable, HumanReadableException, fetch_human_exception,
...@@ -49,7 +58,7 @@ from common.models import ( ...@@ -49,7 +58,7 @@ from common.models import (
) )
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from manager.scheduler import SchedulerError from manager.scheduler import SchedulerError
from storage.models import Disk from storage.models import Disk, StorageActivity
from vm.models import ( from vm.models import (
Instance, InstanceActivity, Node, Lease, Instance, InstanceActivity, Node, Lease,
InstanceTemplate, InterfaceTemplate, Interface, InstanceTemplate, InterfaceTemplate, Interface,
...@@ -73,9 +82,10 @@ from ..forms import ( ...@@ -73,9 +82,10 @@ from ..forms import (
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from request.models import TemplateAccessType, LeaseType from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm from request.forms import LeaseRequestForm, TemplateRequestForm
from ..models import Favourite from ..models import Favourite, pwgen
from manager.scheduler import has_traits from manager.scheduler import has_traits
import re
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
...@@ -86,6 +96,404 @@ except NameError: ...@@ -86,6 +96,404 @@ except NameError:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from dashboard.serializers import (
CreateTemplateSerializer, DiskSerializer, InstanceSerializer, InterfaceSerializer, CreateDiskSerializer, DownloadDiskSerializer,
VMDeploySerializer, VlanSerializer, ResizeDiskSerializer, InstanceActivitySerializer, DestroyDiskSerializer, StorageActivitySerializer,
AddPortSerializer, RuleSerializer,
)
def size_util(size: str):
size_dict = {
"GB": 1000000000,
"Gi": 1073741824,
"MB": 1000000,
"Mi": 1048576,
"KB": 1000,
"Ki": 1024,
}
res = re.search(r"(\d*)\s*(\w*)", size)
if res and res.group(1) and res.group(2):
return int(res.group(1)) * size_dict[str(res.group(2))]
raise ValidationErr()
class CreatePersistentDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = CreateDiskSerializer(data=data)
if serializer.is_valid():
disk_size = str(size_util(str(data['size'])))
disk_name = str(data['name'])
disk = Disk.create(size=disk_size, name=disk_name, type="qcow2-norm")
disk.full_clean()
disk.dev_num = 'f'
disk.save()
ret = DiskSerializer(disk, many=False)
return JsonResponse(ret.data, status=201)
return JsonResponse(serializer.errors, status=400)
class DownloadPersistentDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = DownloadDiskSerializer(data=data)
if serializer.is_valid():
disk_url = str(data['url'])
disk_name = str(data['name'])
store_act = StorageActivity.create(code_suffix="download_disk", user=request.user)
abortable_async_downloaddisk_operation.apply_async(args=(store_act.id, disk_url, disk_name), queue='localhost.man.slow')
serializer = StorageActivitySerializer(store_act, many=False)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class VlanREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
template = Vlan.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = VlanSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Vlan.objects.all()
serializer = VlanSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetVlanREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = Vlan.objects.get(pk=pk)
serializer = VlanSerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
class InterfaceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = Interface.objects.all()
serializer = InterfaceSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetInterfaceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = Interface.objects.get(pk=pk)
serializer = InterfaceSerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
class InstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = Instance.objects.exclude(status='DESTROYED').all()
serializer = InstanceSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
data['pw'] = pwgen()
req_deploy = False
if 'status' in data:
logger.debug(' vm-status: ' + data['status'])
req_deploy = data['status'] == "RUNNING"
data['status'] = 'STOPPED'
if 'owner' not in data:
data['owner'] = request.user.id
serializer = InstanceSerializer(data=data)
if serializer.is_valid():
inst = serializer.save()
networks = []
if 'vlans' in data:
for v in data['vlans']:
v = Vlan.objects.filter(vid=v).get()
if not v.has_level(request.user, "user"):
raise PermissionDenied()
networks.append(InterfaceTemplate(vlan=v, managed=v.managed))
def __on_commit(activity):
activity.resultant_state = 'PENDING'
with inst.activity(code_suffix='create',
readable_name=ugettext_noop("create instance (REST)"),
on_commit=__on_commit, user=inst.owner) as act:
lease = Lease.objects.get(pk=str(data['lease']))
RenewOperation(inst).call(user=request.user, lease=lease, force=True, save=True)
for net in networks:
Interface.create(instance=inst, vlan=net.vlan,
owner=inst.owner, managed=net.managed,
base_activity=act)
if 'disks' in data:
import string
devnums = list(string.ascii_lowercase)
for l in data['disks']:
disk = Disk.objects.get(pk=int(l))
disk.dev_num = devnums.pop(0)
disk.save()
if req_deploy:
DeployOperation(inst).call(node=None, user=inst.owner)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetInstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, safe=False)
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
data = JSONParser().parse(request)
deploy = VMDeploySerializer(data=data)
if deploy.is_valid():
if 'node' in data:
selected = Node.objects.get(pk=int(data['node']))
DeployOperation(instance).call(node=selected, user=instance.owner)
else:
DeployOperation(instance).call(node=None, user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, safe=False)
return JsonResponse(deploy.errors, status=400)
def delete(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
DestroyOperation(instance).call(user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, status=204)
class DeployInstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
data = JSONParser().parse(request)
deploy = VMDeploySerializer(data=data)
if deploy.is_valid():
if 'node' in data:
selected = Node.objects.get(pk=int(data['node']))
DeployOperation(instance).call(node=selected, user=instance.owner)
else:
DeployOperation(instance).call(node=None, user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, safe=False)
return JsonResponse(deploy.errors, status=400)
class AddPortREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
data = JSONParser().parse(request)
addport = AddPortSerializer(data=data)
if addport.is_valid():
AddPortOperation(instance).call(port=int(data['port_number']), proto=str(data['port_type']))
return JsonResponse({}, status=201)
return JsonResponse(addport.errors, status=400)
class SleepInstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
SleepOperation(instance).call(user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, safe=False, status=201)
class RulesREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, vm_id, vlan_id, format=None):
instance = Instance.objects.get(pk=vm_id)
vlan=Vlan.objects.get(pk=vlan_id)
interface = Interface.objects.filter(instance=instance).filter(vlan=vlan).get()
rules = Rule.objects.filter(host=interface.host)
serializer = RuleSerializer(rules, many=True)
return JsonResponse(serializer.data, safe=False, status=200)
class SetupPortREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, vm_id, vlan_id, format=None):
instance = Instance.objects.get(pk=vm_id)
data = JSONParser().parse(request)
addport = AddPortSerializer(data=data)
vlan=Vlan.objects.get(pk=vlan_id)
interface = Interface.objects.filter(instance=instance).filter(vlan=vlan).get()
if addport.is_valid():
AddPortOperation(instance).call(port=int(data['port_destination']), proto=str(data['type']), host=interface.host, user=request.user)
rules = Rule.objects.filter(host=interface.host)
serializer = RuleSerializer(rules, many=True)
return JsonResponse(serializer.data, safe=False, status=201)
return JsonResponse({}, status=400)
def delete(self, request, vm_id, vlan_id, format=None):
instance = Instance.objects.get(pk=vm_id)
data = JSONParser().parse(request)
addport = AddPortSerializer(data=data)
vlan=Vlan.objects.get(pk=vlan_id)
interface = Interface.objects.filter(instance=instance).filter(vlan=vlan).get()
if addport.is_valid():
rule = Rule.objects.filter(host=interface.host).filter(dport=int(data['port_destination'])).filter(proto=str(data['type'])).get()
RemovePortOperation(instance).call(rule=rule, user=request.user)
return JsonResponse({}, safe=False, status=204)
return JsonResponse({}, status=400)
class WakeUpInstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
WakeUpOperation(instance).call(user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, safe=False, status=201)
class ShutdownInstanceREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
instance = Instance.objects.get(pk=pk)
ShutdownOperation(instance).call(user=instance.owner)
serializer = InstanceSerializer(instance, many=False)
return JsonResponse(serializer.data, status=201)
class DownloadDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
data = JSONParser().parse(request)
serializer = DownloadDiskSerializer(data=data)
if serializer.is_valid():
vm_id = pk
disk_url = str(data['url'])
disk_name = str(data['name'])
instance = Instance.objects.get(pk=vm_id)
DownloadDiskOperation(instance)._async(name=disk_name, url=disk_url, user=instance.owner)
act = instance.get_latest_activity_in_progress()
serializer = InstanceActivitySerializer(act, many=False)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class CreateTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
data = JSONParser().parse(request)
serializer = CreateTemplateSerializer(data=data)
if serializer.is_valid():
vm_id = pk
template_name = str(data['name'])
instance = Instance.objects.get(pk=vm_id)
SaveAsTemplateOperation(instance)._async(name=template_name, user=instance.owner)
act = instance.get_latest_activity_in_progress()
serializer = InstanceActivitySerializer(act, many=False)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class CreateDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
data = JSONParser().parse(request)
serializer = CreateDiskSerializer(data=data)
if serializer.is_valid():
vm_id = pk
disk_size = str(size_util(str(data['size'])))
disk_name = str(data['name'])
instance = Instance.objects.get(pk=vm_id)
old_disks = list(instance.disks.all())
instance.create_disk(size=disk_size, user=instance.owner,
name=disk_name, activity=None)
new_disks = instance.disks.all()
for d in new_disks:
if d not in old_disks:
ret = DiskSerializer(d, many=False)
return JsonResponse(ret.data, status=201)
return JsonResponse(serializer.errors, status=400)
class ResizeDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, pk, format=None):
data = JSONParser().parse(request)
serializer = ResizeDiskSerializer(data=data)
if serializer.is_valid():
vm_id = pk
disk_size = str(size_util(str(data['size'])))
disk_id = str(data['disk'])
instance = Instance.objects.get(pk=vm_id)
disk = Disk.objects.get(pk=disk_id)
ResizeDiskOperation(instance).call(disk=disk, size=disk_size, user=instance.owner)
ret = DiskSerializer(disk, many=False)
return JsonResponse(ret.data, status=201)
return JsonResponse(serializer.errors, status=400)
class DestroyDiskREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def delete(self, request, pk, format=None):
data = JSONParser().parse(request)
serializer = DestroyDiskSerializer(data=data)
if serializer.is_valid():
instance = Instance.objects.get(pk=pk)
disk = Disk.objects.get(pk=int(data['disk']))
RemoveDiskOperation(instance).call(disk=disk, user=instance.owner)
return JsonResponse({}, status=204)
return JsonResponse(serializer.errors, status=400)
class VmDetailVncTokenView(CheckedDetailView): class VmDetailVncTokenView(CheckedDetailView):
template_name = "dashboard/vm-detail.html" template_name = "dashboard/vm-detail.html"
model = Instance model = Instance
......
...@@ -6,7 +6,7 @@ msgid "" ...@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-24 12:32+0000\n" "POT-Creation-Date: 2022-09-13 15:14+0000\n"
"PO-Revision-Date: 2015-09-04 11:15+0116\n" "PO-Revision-Date: 2015-09-04 11:15+0116\n"
"Last-Translator: <>\n" "Last-Translator: <>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n" "Language-Team: Hungarian <cloud@ik.bme.hu>\n"
...@@ -463,7 +463,7 @@ msgstr "Választott nyelv" ...@@ -463,7 +463,7 @@ msgstr "Választott nyelv"
#: dashboard/forms.py:1358 dashboard/templates/dashboard/group-list.html:14 #: dashboard/forms.py:1358 dashboard/templates/dashboard/group-list.html:14
#: dashboard/templates/dashboard/index-groups.html:7 #: dashboard/templates/dashboard/index-groups.html:7
#: dashboard/templates/dashboard/profile.html:61 #: dashboard/templates/dashboard/profile.html:69
#: dashboard/templates/dashboard/vm-detail/network.html:44 #: dashboard/templates/dashboard/vm-detail/network.html:44
#: network/templates/network/host-edit.html:32 #: network/templates/network/host-edit.html:32
#: templates/info/help/overview.html:404 #: templates/info/help/overview.html:404
...@@ -585,39 +585,39 @@ msgstr "" ...@@ -585,39 +585,39 @@ msgstr ""
msgid "Invalid confirmation code." msgid "Invalid confirmation code."
msgstr "Fájl törlésének megerősítése" msgstr "Fájl törlésének megerősítése"
#: dashboard/models.py:70 dashboard/models.py:79 #: dashboard/models.py:79 dashboard/models.py:88
msgid "message" msgid "message"
msgstr "üzenet" msgstr "üzenet"
#: dashboard/models.py:72 #: dashboard/models.py:81
msgid "effect" msgid "effect"
msgstr "hatás" msgstr "hatás"
#: dashboard/models.py:73 #: dashboard/models.py:82
msgid "success" msgid "success"
msgstr "siker" msgstr "siker"
#: dashboard/models.py:73 #: dashboard/models.py:82
msgid "info" msgid "info"
msgstr "infó" msgstr "infó"
#: dashboard/models.py:74 #: dashboard/models.py:83
msgid "warning" msgid "warning"
msgstr "figyelmeztetés" msgstr "figyelmeztetés"
#: dashboard/models.py:74 #: dashboard/models.py:83
msgid "danger" msgid "danger"
msgstr "veszély" msgstr "veszély"
#: dashboard/models.py:75 vm/models/node.py:129 #: dashboard/models.py:84 vm/models/node.py:129
msgid "enabled" msgid "enabled"
msgstr "engedélyezve" msgstr "engedélyezve"
#: dashboard/models.py:80 #: dashboard/models.py:89
msgid "messages" msgid "messages"
msgstr "üzenetek" msgstr "üzenetek"
#: dashboard/models.py:96 dashboard/templates/dashboard/index-groups.html:43 #: dashboard/models.py:105 dashboard/templates/dashboard/index-groups.html:43
#: dashboard/templates/dashboard/index-nodes.html:60 #: dashboard/templates/dashboard/index-nodes.html:60
#: dashboard/templates/dashboard/index-templates.html:52 #: dashboard/templates/dashboard/index-templates.html:52
#: dashboard/templates/dashboard/index-users.html:48 #: dashboard/templates/dashboard/index-users.html:48
...@@ -635,23 +635,23 @@ msgstr "üzenetek" ...@@ -635,23 +635,23 @@ msgstr "üzenetek"
msgid "new" msgid "new"
msgstr "új" msgstr "új"
#: dashboard/models.py:97 #: dashboard/models.py:106
msgid "delivered" msgid "delivered"
msgstr "kézbesített" msgstr "kézbesített"
#: dashboard/models.py:98 #: dashboard/models.py:107
msgid "read" msgid "read"
msgstr "olvasott" msgstr "olvasott"
#: dashboard/models.py:158 vm/models/instance.py:137 #: dashboard/models.py:167 vm/models/instance.py:137
msgid "access method" msgid "access method"
msgstr "elérés módja" msgstr "elérés módja"
#: dashboard/models.py:159 #: dashboard/models.py:168
msgid "Type of the remote access method." msgid "Type of the remote access method."
msgstr "Távoli elérési mód típusa." msgstr "Távoli elérési mód típusa."
#: dashboard/models.py:160 firewall/models.py:549 firewall/models.py:581 #: dashboard/models.py:169 firewall/models.py:549 firewall/models.py:581
#: firewall/models.py:970 firewall/models.py:1015 firewall/models.py:1041 #: firewall/models.py:970 firewall/models.py:1015 firewall/models.py:1041
#: storage/models.py:52 storage/models.py:133 vm/models/common.py:66 #: storage/models.py:52 storage/models.py:133 vm/models/common.py:66
#: vm/models/common.py:90 vm/models/common.py:165 vm/models/instance.py:180 #: vm/models/common.py:90 vm/models/common.py:165 vm/models/instance.py:180
...@@ -659,15 +659,15 @@ msgstr "Távoli elérési mód típusa." ...@@ -659,15 +659,15 @@ msgstr "Távoli elérési mód típusa."
msgid "name" msgid "name"
msgstr "név" msgstr "név"
#: dashboard/models.py:161 #: dashboard/models.py:170
msgid "Name of your custom command." msgid "Name of your custom command."
msgstr "Egyedi parancs neve" msgstr "Egyedi parancs neve"
#: dashboard/models.py:163 #: dashboard/models.py:172
msgid "command template" msgid "command template"
msgstr "parancssablon" msgstr "parancssablon"
#: dashboard/models.py:164 #: dashboard/models.py:173
msgid "" msgid ""
"Template for connection command string. Available parameters are: username, " "Template for connection command string. Available parameters are: username, "
"password, host, port." "password, host, port."
...@@ -675,79 +675,79 @@ msgstr "" ...@@ -675,79 +675,79 @@ msgstr ""
"Sablon a csatlakozási parancshoz. Elérhető paraméterek: username, password, " "Sablon a csatlakozási parancshoz. Elérhető paraméterek: username, password, "
"host, port." "host, port."
#: dashboard/models.py:179 #: dashboard/models.py:188
msgid "preferred language" msgid "preferred language"
msgstr "választott nyelv" msgstr "választott nyelv"
#: dashboard/models.py:185 dashboard/models.py:281 #: dashboard/models.py:194 dashboard/models.py:290
msgid "Unique identifier of the person, e.g. a student number." msgid "Unique identifier of the person, e.g. a student number."
msgstr "A személy egyedi azonosítója, például hallgatói azonosító." msgstr "A személy egyedi azonosítója, például hallgatói azonosító."
#: dashboard/models.py:189 #: dashboard/models.py:198
msgid "Use Gravatar" msgid "Use Gravatar"
msgstr "Gravatar használata" msgstr "Gravatar használata"
#: dashboard/models.py:190 #: dashboard/models.py:199
msgid "Whether to use email address as Gravatar profile image" msgid "Whether to use email address as Gravatar profile image"
msgstr "Használható-e az e-mail cím a Gravatar profilkép betöltésére" msgstr "Használható-e az e-mail cím a Gravatar profilkép betöltésére"
#: dashboard/models.py:192 #: dashboard/models.py:201
msgid "Email notifications" msgid "Email notifications"
msgstr "E-mail értesítések" msgstr "E-mail értesítések"
#: dashboard/models.py:193 #: dashboard/models.py:202
msgid "Whether user wants to get digested email notifications." msgid "Whether user wants to get digested email notifications."
msgstr "A felhasználó kéri-e tömbösített e-mail értesítések küldését." msgstr "A felhasználó kéri-e tömbösített e-mail értesítések küldését."
#: dashboard/models.py:195 #: dashboard/models.py:204
#, fuzzy #, fuzzy
#| msgid "Latest modifications" #| msgid "Latest modifications"
msgid "Desktop notifications" msgid "Desktop notifications"
msgstr "Legutóbbi változások" msgstr "Legutóbbi változások"
#: dashboard/models.py:196 #: dashboard/models.py:205
msgid "" msgid ""
"Whether user wants to get desktop notification when an activity has finished " "Whether user wants to get desktop notification when an activity has finished "
"and the window is not in focus." "and the window is not in focus."
msgstr "" msgstr ""
#: dashboard/models.py:200 #: dashboard/models.py:209
msgid "Samba password" msgid "Samba password"
msgstr "Samba jelszó" msgstr "Samba jelszó"
#: dashboard/models.py:202 #: dashboard/models.py:211
msgid "Generated password for accessing store from virtual machines." msgid "Generated password for accessing store from virtual machines."
msgstr "A tárhely virtuális gépekről való eléréséhez generált jelszó." msgstr "A tárhely virtuális gépekről való eléréséhez generált jelszó."
#: dashboard/models.py:207 dashboard/models.py:306 #: dashboard/models.py:216 dashboard/models.py:315
msgid "disk quota" msgid "disk quota"
msgstr "lemezkvóta" msgstr "lemezkvóta"
#: dashboard/models.py:209 dashboard/models.py:308 #: dashboard/models.py:218 dashboard/models.py:317
msgid "Disk quota in mebibytes." msgid "Disk quota in mebibytes."
msgstr "Lemezkvóta mebibyte-okban." msgstr "Lemezkvóta mebibyte-okban."
#: dashboard/models.py:211 #: dashboard/models.py:220
msgid "two factor secret key" msgid "two factor secret key"
msgstr "" msgstr ""
#: dashboard/models.py:275 #: dashboard/models.py:284
msgid "Can use autocomplete." msgid "Can use autocomplete."
msgstr "Használhat automatikus kiegészítést." msgstr "Használhat automatikus kiegészítést."
#: dashboard/models.py:294 firewall/models.py:296 request/models.py:266 #: dashboard/models.py:303 firewall/models.py:296 request/models.py:266
#: vm/models/common.py:86 vm/models/instance.py:177 vm/models/instance.py:314 #: vm/models/common.py:86 vm/models/instance.py:177 vm/models/instance.py:314
msgid "operator" msgid "operator"
msgstr "operátor" msgstr "operátor"
#: dashboard/models.py:295 firewall/models.py:111 firewall/models.py:401 #: dashboard/models.py:304 firewall/models.py:111 firewall/models.py:401
#: firewall/models.py:558 firewall/models.py:586 firewall/models.py:656 #: firewall/models.py:558 firewall/models.py:586 firewall/models.py:656
#: firewall/models.py:1016 firewall/models.py:1050 vm/models/common.py:87 #: firewall/models.py:1016 firewall/models.py:1050 vm/models/common.py:87
#: vm/models/instance.py:178 vm/models/instance.py:315 #: vm/models/instance.py:178 vm/models/instance.py:315
msgid "owner" msgid "owner"
msgstr "tulajdonos" msgstr "tulajdonos"
#: dashboard/models.py:301 #: dashboard/models.py:310
msgid "Unique identifier of the group at the organization." msgid "Unique identifier of the group at the organization."
msgstr "A csoport egyedi szervezeti azonosítója." msgstr "A csoport egyedi szervezeti azonosítója."
...@@ -973,11 +973,14 @@ msgstr "alapértelmezett ipv6 cím" ...@@ -973,11 +973,14 @@ msgstr "alapértelmezett ipv6 cím"
msgid "" msgid ""
"associated vlans: list of objects (name: vlan name, ipv4/ipv6: host ip in " "associated vlans: list of objects (name: vlan name, ipv4/ipv6: host ip in "
"vlan)" "vlan)"
msgstr "csatolt vlan-ok (objektumok listája): vlan neve (.name), ipv4/ipv6 (.ipv4/.ipv6) címek" msgstr ""
"csatolt vlan-ok (objektumok listája): vlan neve (.name), ipv4/ipv6 (.ipv4/."
"ipv6) címek"
#: dashboard/templates/dashboard/_ci-data-help.html:22 #: dashboard/templates/dashboard/_ci-data-help.html:22
msgid "owner's ssh-keys dictionary: the key is the ssh-key's name" msgid "owner's ssh-keys dictionary: the key is the ssh-key's name"
msgstr "tulajdonos ssh-kulcsainak dictionary-je, a kulcsot a ssh kulcs neve adja" msgstr ""
"tulajdonos ssh-kulcsainak dictionary-je, a kulcsot a ssh kulcs neve adja"
#: dashboard/templates/dashboard/_ci-data-help.html:23 #: dashboard/templates/dashboard/_ci-data-help.html:23
msgid "function: make random string with 'len' charachters lenght" msgid "function: make random string with 'len' charachters lenght"
...@@ -994,7 +997,9 @@ msgstr "Filterek: hash - sha512 hash" ...@@ -994,7 +997,9 @@ msgstr "Filterek: hash - sha512 hash"
#: dashboard/templates/dashboard/_ci-data-help.html:27 #: dashboard/templates/dashboard/_ci-data-help.html:27
msgid "" msgid ""
"Use this commands, to clean cloud-init setup (for save as template example):" "Use this commands, to clean cloud-init setup (for save as template example):"
msgstr "Használja az alábbi parancsokat a cloud-init beállítások törlésére (pl. hogy template-nek elmentsük)" msgstr ""
"Használja az alábbi parancsokat a cloud-init beállítások törlésére (pl. hogy "
"template-nek elmentsük)"
#: dashboard/templates/dashboard/_client-check.html:4 #: dashboard/templates/dashboard/_client-check.html:4
msgid "" msgid ""
...@@ -1989,7 +1994,7 @@ msgstr "meghiúsult" ...@@ -1989,7 +1994,7 @@ msgstr "meghiúsult"
#: dashboard/templates/dashboard/instanceactivity_detail.html:78 #: dashboard/templates/dashboard/instanceactivity_detail.html:78
#: dashboard/templates/dashboard/nodeactivity_detail.html:74 #: dashboard/templates/dashboard/nodeactivity_detail.html:74
#: dashboard/views/storage.py:59 #: dashboard/views/storage.py:66
msgid "none" msgid "none"
msgstr "nincs" msgstr "nincs"
...@@ -2251,43 +2256,43 @@ msgstr "Keresztnév" ...@@ -2251,43 +2256,43 @@ msgstr "Keresztnév"
msgid "Last name" msgid "Last name"
msgstr "Vezetéknév" msgstr "Vezetéknév"
#: dashboard/templates/dashboard/profile.html:45 #: dashboard/templates/dashboard/profile.html:53
msgid "Last login" msgid "Last login"
msgstr "Utolsó belépés" msgstr "Utolsó belépés"
#: dashboard/templates/dashboard/profile.html:48 #: dashboard/templates/dashboard/profile.html:56
msgid "Use email address as Gravatar profile image" msgid "Use email address as Gravatar profile image"
msgstr "E-mail cím használata a Gravatar profilkép betöltésére" msgstr "E-mail cím használata a Gravatar profilkép betöltésére"
#: dashboard/templates/dashboard/profile.html:51 #: dashboard/templates/dashboard/profile.html:59
msgid "What's Gravatar?" msgid "What's Gravatar?"
msgstr "Mi az a Gravatar?" msgstr "Mi az a Gravatar?"
#: dashboard/templates/dashboard/profile.html:53 #: dashboard/templates/dashboard/profile.html:61
msgid "Change my preferences" msgid "Change my preferences"
msgstr "Személyes beállítások" msgstr "Személyes beállítások"
#: dashboard/templates/dashboard/profile.html:67 #: dashboard/templates/dashboard/profile.html:75
msgid "This user is not in any group." msgid "This user is not in any group."
msgstr "A felhasználó nem tagja csoportnak." msgstr "A felhasználó nem tagja csoportnak."
#: dashboard/templates/dashboard/profile.html:76 #: dashboard/templates/dashboard/profile.html:84
msgid "Virtual machines owned by the user" msgid "Virtual machines owned by the user"
msgstr "A felhasználó virtuális gépei" msgstr "A felhasználó virtuális gépei"
#: dashboard/templates/dashboard/profile.html:88 #: dashboard/templates/dashboard/profile.html:96
msgid "This user have no virtual machines." msgid "This user have no virtual machines."
msgstr "A felhasználónak nincs virtuális gépe." msgstr "A felhasználónak nincs virtuális gépe."
#: dashboard/templates/dashboard/profile.html:97 #: dashboard/templates/dashboard/profile.html:105
msgid "Virtual machines with access" msgid "Virtual machines with access"
msgstr "Elérhető virtuális gépek" msgstr "Elérhető virtuális gépek"
#: dashboard/templates/dashboard/profile.html:109 #: dashboard/templates/dashboard/profile.html:117
msgid "This user have no access to any virtual machine." msgid "This user have no access to any virtual machine."
msgstr "A felhasználónak egy géphez sincs hozzáférése." msgstr "A felhasználónak egy géphez sincs hozzáférése."
#: dashboard/templates/dashboard/profile.html:123 #: dashboard/templates/dashboard/profile.html:131
msgid "Edit user" msgid "Edit user"
msgstr "Felhasználó szerkesztése" msgstr "Felhasználó szerkesztése"
...@@ -2582,7 +2587,7 @@ msgid "Currently uploading to" ...@@ -2582,7 +2587,7 @@ msgid "Currently uploading to"
msgstr "Feltöltés helye:" msgstr "Feltöltés helye:"
#: dashboard/templates/dashboard/template-edit.html:7 #: dashboard/templates/dashboard/template-edit.html:7
#: dashboard/views/storage.py:58 vm/models/instance.py:203 #: dashboard/views/storage.py:65 vm/models/instance.py:203
#: vm/models/instance.py:332 vm/models/network.py:45 #: vm/models/instance.py:332 vm/models/network.py:45
msgid "template" msgid "template"
msgstr "sablon" msgstr "sablon"
...@@ -3308,72 +3313,72 @@ msgstr "CPU-használat (%)" ...@@ -3308,72 +3313,72 @@ msgstr "CPU-használat (%)"
msgid "Allocated memory (bytes)" msgid "Allocated memory (bytes)"
msgstr "Foglalt memória (byte)" msgstr "Foglalt memória (byte)"
#: dashboard/views/group.py:160 #: dashboard/views/group.py:185
#, python-format #, python-format
msgid "User \"%s\" not found." msgid "User \"%s\" not found."
msgstr "Nem található „%s” felhasználó." msgstr "Nem található „%s” felhasználó."
#: dashboard/views/group.py:174 #: dashboard/views/group.py:199
msgid "Group successfully renamed." msgid "Group successfully renamed."
msgstr "A csoport átnevezésre került." msgstr "A csoport átnevezésre került."
#: dashboard/views/group.py:242 #: dashboard/views/group.py:267
msgid "Member successfully removed from group." msgid "Member successfully removed from group."
msgstr "A csoporttag eltávolításra került." msgstr "A csoporttag eltávolításra került."
#: dashboard/views/group.py:276 #: dashboard/views/group.py:301
msgid "Future user successfully removed from group." msgid "Future user successfully removed from group."
msgstr "A leendő csoporttag eltávolításra került." msgstr "A leendő csoporttag eltávolításra került."
#: dashboard/views/group.py:301 #: dashboard/views/group.py:326
#, fuzzy #, fuzzy
#| msgid "Future user successfully removed from group." #| msgid "Future user successfully removed from group."
msgid "All users successfully removed from group." msgid "All users successfully removed from group."
msgstr "A leendő csoporttag eltávolításra került." msgstr "A leendő csoporttag eltávolításra került."
#: dashboard/views/group.py:310 #: dashboard/views/group.py:335
#, fuzzy #, fuzzy
#| msgid "user" #| msgid "user"
msgid "all users" msgid "all users"
msgstr "felhasználó" msgstr "felhasználó"
#: dashboard/views/group.py:325 #: dashboard/views/group.py:350
msgid "Group successfully deleted." msgid "Group successfully deleted."
msgstr "A csoport törlésre került." msgstr "A csoport törlésre került."
#: dashboard/views/group.py:353 #: dashboard/views/group.py:378
msgid "Create a Group" msgid "Create a Group"
msgstr "Csoport létrehozása" msgstr "Csoport létrehozása"
#: dashboard/views/group.py:369 #: dashboard/views/group.py:394
msgid "Group successfully created." msgid "Group successfully created."
msgstr "A csoport létrehozásra került." msgstr "A csoport létrehozásra került."
#: dashboard/views/group.py:403 #: dashboard/views/group.py:428
#, fuzzy #, fuzzy
#| msgid "Create a Group" #| msgid "Create a Group"
msgid "Import a Group" msgid "Import a Group"
msgstr "Csoport létrehozása" msgstr "Csoport létrehozása"
#: dashboard/views/group.py:426 #: dashboard/views/group.py:451
#, fuzzy #, fuzzy
#| msgid "Group successfully created." #| msgid "Group successfully created."
msgid "Group successfully imported." msgid "Group successfully imported."
msgstr "A csoport létrehozásra került." msgstr "A csoport létrehozásra került."
#: dashboard/views/group.py:473 #: dashboard/views/group.py:498
#, fuzzy #, fuzzy
#| msgid "host group" #| msgid "host group"
msgid "Export Group" msgid "Export Group"
msgstr "gépcsoport" msgstr "gépcsoport"
#: dashboard/views/group.py:499 #: dashboard/views/group.py:524
#, fuzzy #, fuzzy
#| msgid "Group successfully deleted." #| msgid "Group successfully deleted."
msgid "Group successfully exported." msgid "Group successfully exported."
msgstr "A csoport törlésre került." msgstr "A csoport törlésre került."
#: dashboard/views/group.py:520 #: dashboard/views/group.py:545
msgid "Group is successfully updated." msgid "Group is successfully updated."
msgstr "A csoport frissítésre került." msgstr "A csoport frissítésre került."
...@@ -3385,37 +3390,37 @@ msgstr "Az üzenet frissítésre került." ...@@ -3385,37 +3390,37 @@ msgstr "Az üzenet frissítésre került."
msgid "New broadcast message successfully created." msgid "New broadcast message successfully created."
msgstr "Az üzenet létrehozása került." msgstr "Az üzenet létrehozása került."
#: dashboard/views/node.py:152 #: dashboard/views/node.py:187
msgid "Node successfully renamed." msgid "Node successfully renamed."
msgstr "A csomópont átnevezésre került." msgstr "A csomópont átnevezésre került."
#: dashboard/views/node.py:261 #: dashboard/views/node.py:296
msgid "Create a node" msgid "Create a node"
msgstr "Új csomópont hozzáadása" msgstr "Új csomópont hozzáadása"
#: dashboard/views/node.py:285 #: dashboard/views/node.py:320
msgid "Node successfully created." msgid "Node successfully created."
msgstr "A csomópont létrehozásra került." msgstr "A csomópont létrehozásra került."
#: dashboard/views/node.py:296 #: dashboard/views/node.py:331
msgid "Node successfully deleted." msgid "Node successfully deleted."
msgstr "A csomópont törlésre került." msgstr "A csomópont törlésre került."
#: dashboard/views/node.py:332 #: dashboard/views/node.py:367
msgid "Trait successfully added to node." msgid "Trait successfully added to node."
msgstr "A csomópontjellemző hozzáadásra került." msgstr "A csomópontjellemző hozzáadásra került."
#: dashboard/views/node.py:392 #: dashboard/views/node.py:427
#, fuzzy #, fuzzy
#| msgid "schedule enabled" #| msgid "schedule enabled"
msgid "Reschedule has started." msgid "Reschedule has started."
msgstr "ütemezés engedélyezve" msgstr "ütemezés engedélyezve"
#: dashboard/views/storage.py:51 #: dashboard/views/storage.py:58
msgid "The DataStore is offline." msgid "The DataStore is offline."
msgstr "Az adattár nem elérhető." msgstr "Az adattár nem elérhető."
#: dashboard/views/storage.py:57 #: dashboard/views/storage.py:64
msgid "virtual machine" msgid "virtual machine"
msgstr "virtuális gép" msgstr "virtuális gép"
...@@ -3448,39 +3453,39 @@ msgstr "%s törlése sikertelen." ...@@ -3448,39 +3453,39 @@ msgstr "%s törlése sikertelen."
msgid "Unable to create folder." msgid "Unable to create folder."
msgstr "Mappa létrehozása sikertelen." msgstr "Mappa létrehozása sikertelen."
#: dashboard/views/template.py:87 #: dashboard/views/template.py:96
msgid "Choose template" msgid "Choose template"
msgstr "Válasszon sablont" msgstr "Válasszon sablont"
#: dashboard/views/template.py:102 #: dashboard/views/template.py:111
msgid "Select an option to proceed." msgid "Select an option to proceed."
msgstr "Válasszon a folytatáshoz." msgstr "Válasszon a folytatáshoz."
#: dashboard/views/template.py:133 #: dashboard/views/template.py:142
msgid "Create a new base VM" msgid "Create a new base VM"
msgstr "Alap VM létrehozása" msgstr "Alap VM létrehozása"
#: dashboard/views/template.py:296 dashboard/views/vm.py:1081 #: dashboard/views/template.py:426 dashboard/views/vm.py:1334
msgid "Error during filtering." msgid "Error during filtering."
msgstr "A szűrés sikertelen." msgstr "A szűrés sikertelen."
#: dashboard/views/template.py:303 #: dashboard/views/template.py:433
msgid "Template successfully deleted." msgid "Template successfully deleted."
msgstr "A sablon törlésre került." msgstr "A sablon törlésre került."
#: dashboard/views/template.py:319 #: dashboard/views/template.py:449
msgid "Successfully modified template." msgid "Successfully modified template."
msgstr "A sablon módosításra került." msgstr "A sablon módosításra került."
#: dashboard/views/template.py:378 #: dashboard/views/template.py:508
msgid "Disk successfully removed." msgid "Disk successfully removed."
msgstr "A lemez eltávolításra került." msgstr "A lemez eltávolításra került."
#: dashboard/views/template.py:394 #: dashboard/views/template.py:524
msgid "Disk remove confirmation" msgid "Disk remove confirmation"
msgstr "Lemez törlésének megerősítése" msgstr "Lemez törlésének megerősítése"
#: dashboard/views/template.py:395 #: dashboard/views/template.py:525
#, python-format #, python-format
msgid "" msgid ""
"Are you sure you want to remove <strong>%(disk)s</strong> from <strong>" "Are you sure you want to remove <strong>%(disk)s</strong> from <strong>"
...@@ -3489,23 +3494,23 @@ msgstr "" ...@@ -3489,23 +3494,23 @@ msgstr ""
"Biztosan eltávolítja a(z) <strong>%(disk)s</strong> lemezt a következőből: " "Biztosan eltávolítja a(z) <strong>%(disk)s</strong> lemezt a következőből: "
"%(app)s?" "%(app)s?"
#: dashboard/views/template.py:418 #: dashboard/views/template.py:548
msgid "Successfully created a new lease." msgid "Successfully created a new lease."
msgstr "Új bérlési mód létrehozásra került." msgstr "Új bérlési mód létrehozásra került."
#: dashboard/views/template.py:437 #: dashboard/views/template.py:567
msgid "Successfully modified lease." msgid "Successfully modified lease."
msgstr "A bérlési mód megváltoztatásra került." msgstr "A bérlési mód megváltoztatásra került."
#: dashboard/views/template.py:451 #: dashboard/views/template.py:581
msgid "Only the owners can modify the selected lease." msgid "Only the owners can modify the selected lease."
msgstr "Csak a tulajdonosai törölhetik a kiválasztott bérleti módot." msgstr "Csak a tulajdonosai törölhetik a kiválasztott bérleti módot."
#: dashboard/views/template.py:465 #: dashboard/views/template.py:595
msgid "Lease successfully deleted." msgid "Lease successfully deleted."
msgstr "A bérlési mód törlésre került." msgstr "A bérlési mód törlésre került."
#: dashboard/views/template.py:475 #: dashboard/views/template.py:605
msgid "" msgid ""
"You can't delete this lease because some templates are still using it, " "You can't delete this lease because some templates are still using it, "
"modify these to proceed: " "modify these to proceed: "
...@@ -3513,7 +3518,7 @@ msgstr "" ...@@ -3513,7 +3518,7 @@ msgstr ""
"Nem törölhető a bérleti mód, mivel az alábbi sablonok még használják. A " "Nem törölhető a bérleti mód, mivel az alábbi sablonok még használják. A "
"folytatáshoz módosítsa őket: " "folytatáshoz módosítsa őket: "
#: dashboard/views/template.py:500 #: dashboard/views/template.py:630
#, python-format #, python-format
msgid "" msgid ""
"%(owner)s offered you to take the ownership of his/her template called " "%(owner)s offered you to take the ownership of his/her template called "
...@@ -3523,251 +3528,257 @@ msgstr "" ...@@ -3523,251 +3528,257 @@ msgstr ""
"%(owner)s át kívánja ruházni %(instance)s nevű sablonját Önre. <a href=" "%(owner)s át kívánja ruházni %(instance)s nevű sablonját Önre. <a href="
"\"%(token)s\" class=\"btn btn-success btn-small\">Elfogadás</a>" "\"%(token)s\" class=\"btn btn-success btn-small\">Elfogadás</a>"
#: dashboard/views/user.py:73 #: dashboard/views/user.py:96
msgid "" msgid ""
"You've logged in with an administrator account, your session will expire " "You've logged in with an administrator account, your session will expire "
"when the web browser is closed." "when the web browser is closed."
msgstr "" msgstr ""
#: dashboard/views/user.py:178 #: dashboard/views/user.py:201
#, python-format #, python-format
msgid "Logged in as user %s." msgid "Logged in as user %s."
msgstr "Bejelentkezve mint %s." msgstr "Bejelentkezve mint %s."
#: dashboard/views/user.py:211 #: dashboard/views/user.py:234
msgid "You don't have a profile." msgid "You don't have a profile."
msgstr "Nincs profilja." msgstr "Nincs profilja."
#: dashboard/views/user.py:229 #: dashboard/views/user.py:252
#, fuzzy #, fuzzy
#| msgid "Node successfully changed status." #| msgid "Node successfully changed status."
msgid "Password successfully changed." msgid "Password successfully changed."
msgstr "A csomópont állapota megváltoztatásra került." msgstr "A csomópont állapota megváltoztatásra került."
#: dashboard/views/user.py:251 #: dashboard/views/user.py:274
msgid "Successfully modified subscription." msgid "Successfully modified subscription."
msgstr "A feliratkozás módosításra került." msgstr "A feliratkozás módosításra került."
#: dashboard/views/user.py:314 #: dashboard/views/user.py:337
msgid "Create a User" msgid "Create a User"
msgstr "Felhasználó létrehozása" msgstr "Felhasználó létrehozása"
#: dashboard/views/user.py:345 #: dashboard/views/user.py:368
msgid "Successfully modified user." msgid "Successfully modified user."
msgstr "A felhasználó megváltoztatásra került." msgstr "A felhasználó megváltoztatásra került."
#: dashboard/views/user.py:437 #: dashboard/views/user.py:462
msgid "Successfully modified SSH key." msgid "Successfully modified SSH key."
msgstr "Az SSH kulcs módosításra került." msgstr "Az SSH kulcs módosításra került."
#: dashboard/views/user.py:458 #: dashboard/views/user.py:483
msgid "SSH key successfully deleted." msgid "SSH key successfully deleted."
msgstr "Az SSH kulcs törlésre került." msgstr "Az SSH kulcs törlésre került."
#: dashboard/views/user.py:472 #: dashboard/views/user.py:497
msgid "Successfully created a new SSH key." msgid "Successfully created a new SSH key."
msgstr "Az új SSH kulcs hozzáadásra került." msgstr "Az új SSH kulcs hozzáadásra került."
#: dashboard/views/user.py:488 #: dashboard/views/user.py:513
msgid "Successfully modified command template." msgid "Successfully modified command template."
msgstr "A parancssablon módosításra került." msgstr "A parancssablon módosításra került."
#: dashboard/views/user.py:514 #: dashboard/views/user.py:539
msgid "Command template successfully deleted." msgid "Command template successfully deleted."
msgstr "A parancssablon törlésre került." msgstr "A parancssablon törlésre került."
#: dashboard/views/user.py:529 #: dashboard/views/user.py:554
msgid "Successfully created a new command template." msgid "Successfully created a new command template."
msgstr "A parancssablon létrehozásra került." msgstr "A parancssablon létrehozásra került."
#: dashboard/views/user.py:591 #: dashboard/views/user.py:616
msgid "Two-factor authentication is already enabled for your account." msgid "Two-factor authentication is already enabled for your account."
msgstr "" msgstr ""
#: dashboard/views/util.py:321 #: dashboard/views/util.py:350
msgid "Could not start operation." msgid "Could not start operation."
msgstr "A művelet megkezdése meghiúsult." msgstr "A művelet megkezdése meghiúsult."
#: dashboard/views/util.py:338 #: dashboard/views/util.py:367
msgid "Operation failed." msgid "Operation failed."
msgstr "A művelet meghiúsult." msgstr "A művelet meghiúsult."
#: dashboard/views/util.py:343 #: dashboard/views/util.py:372
msgid "Operation succeeded." msgid "Operation succeeded."
msgstr "A művelet sikeresen végrehajtásra került." msgstr "A művelet sikeresen végrehajtásra került."
#: dashboard/views/util.py:345 #: dashboard/views/util.py:374
msgid "Operation is started." msgid "Operation is started."
msgstr "A művelet megkezdődött." msgstr "A művelet megkezdődött."
#: dashboard/views/util.py:433 #: dashboard/views/util.py:462
#, python-format #, python-format
msgid "Acl user/group %(w)s successfully modified." msgid "Acl user/group %(w)s successfully modified."
msgstr "A(z) %(w)s ACL felhasználó/csoport módosításra került." msgstr "A(z) %(w)s ACL felhasználó/csoport módosításra került."
#: dashboard/views/util.py:435 #: dashboard/views/util.py:464
#, python-format #, python-format
msgid "Acl user/group %(w)s successfully added." msgid "Acl user/group %(w)s successfully added."
msgstr "A(z) %(w)s ACL felhasználó/csoport hozzáadásra került." msgstr "A(z) %(w)s ACL felhasználó/csoport hozzáadásra került."
#: dashboard/views/util.py:437 #: dashboard/views/util.py:466
#, python-format #, python-format
msgid "Acl user/group %(w)s successfully removed." msgid "Acl user/group %(w)s successfully removed."
msgstr "A(z) %(w)s ACL felhasználó/csoport törlésre került." msgstr "A(z) %(w)s ACL felhasználó/csoport törlésre került."
#: dashboard/views/util.py:522 #: dashboard/views/util.py:551
msgid "" msgid ""
"The original owner cannot be removed, however you can transfer ownership." "The original owner cannot be removed, however you can transfer ownership."
msgstr "Az eredeti tulajdonos nem törölhető, azonban a tulajdon átruházható." msgstr "Az eredeti tulajdonos nem törölhető, azonban a tulajdon átruházható."
#: dashboard/views/util.py:558 #: dashboard/views/util.py:587
#, python-format #, python-format
msgid "User \"%s\" has already access to this object." msgid "User \"%s\" has already access to this object."
msgstr "„%s” felhasználó már hozzáfér az objektumhoz." msgstr "„%s” felhasználó már hozzáfér az objektumhoz."
#: dashboard/views/util.py:567 #: dashboard/views/util.py:596
#, python-format #, python-format
msgid "Group \"%s\" has already access to this object." msgid "Group \"%s\" has already access to this object."
msgstr "„%s” csoport már hozzáfér az objektumhoz." msgstr "„%s” csoport már hozzáfér az objektumhoz."
#: dashboard/views/util.py:572 #: dashboard/views/util.py:601
#, python-format #, python-format
msgid "User or group \"%s\" not found." msgid "User or group \"%s\" not found."
msgstr "Nem található „%s” felhasználó vagy csoport." msgstr "Nem található „%s” felhasználó vagy csoport."
#: dashboard/views/util.py:588 #: dashboard/views/util.py:617
msgid "1 hour" msgid "1 hour"
msgstr "1 óra" msgstr "1 óra"
#: dashboard/views/util.py:589 #: dashboard/views/util.py:618
msgid "6 hours" msgid "6 hours"
msgstr "6 óra" msgstr "6 óra"
#: dashboard/views/util.py:590 #: dashboard/views/util.py:619
msgid "1 day" msgid "1 day"
msgstr "1 nap" msgstr "1 nap"
#: dashboard/views/util.py:591 #: dashboard/views/util.py:620
msgid "1 week" msgid "1 week"
msgstr "1 hét" msgstr "1 hét"
#: dashboard/views/util.py:592 #: dashboard/views/util.py:621
msgid "1 month" msgid "1 month"
msgstr "1 hónap" msgstr "1 hónap"
#: dashboard/views/util.py:593 #: dashboard/views/util.py:622
msgid "6 months" msgid "6 months"
msgstr "6 hónap" msgstr "6 hónap"
#: dashboard/views/util.py:602 #: dashboard/views/util.py:631
msgid "Bad graph time format, available periods are: h, d, w, and y." msgid "Bad graph time format, available periods are: h, d, w, and y."
msgstr "Hibás grafikon időformátum. Lehetséges egységek: h, d, w és y." msgstr "Hibás grafikon időformátum. Lehetséges egységek: h, d, w és y."
#: dashboard/views/util.py:627 #: dashboard/views/util.py:656
msgid "Transfer ownership" msgid "Transfer ownership"
msgstr "Tulajdon átruházása" msgstr "Tulajdon átruházása"
#: dashboard/views/util.py:640 #: dashboard/views/util.py:669
msgid "Can not find specified user." msgid "Can not find specified user."
msgstr "Nem található a megadott felhasználó." msgstr "Nem található a megadott felhasználó."
#: dashboard/views/util.py:656 #: dashboard/views/util.py:685
msgid "Ownership offer" msgid "Ownership offer"
msgstr "Átruházási ajánlat" msgstr "Átruházási ajánlat"
#: dashboard/views/util.py:660 #: dashboard/views/util.py:689
msgid "Can not notify selected user." msgid "Can not notify selected user."
msgstr "A kiválaszott felhasználó értesítése sikertelen." msgstr "A kiválaszott felhasználó értesítése sikertelen."
#: dashboard/views/util.py:663 #: dashboard/views/util.py:692
#, python-format #, python-format
msgid "User %s is notified about the offer." msgid "User %s is notified about the offer."
msgstr "%s felhasználó értesítésre került az ajánlatról." msgstr "%s felhasználó értesítésre került az ajánlatról."
#: dashboard/views/util.py:673 #: dashboard/views/util.py:702
msgid "Ownership successfully transferred to you." msgid "Ownership successfully transferred to you."
msgstr "A tulajdon átruházásra került." msgstr "A tulajdon átruházásra került."
#: dashboard/views/util.py:686 #: dashboard/views/util.py:715
msgid "This token is for an other user." msgid "This token is for an other user."
msgstr "A token más felhasználó nevére szól." msgstr "A token más felhasználó nevére szól."
#: dashboard/views/util.py:689 #: dashboard/views/util.py:718
msgid "This token is invalid or has expired." msgid "This token is invalid or has expired."
msgstr "A token érvénytelen vagy lejárt." msgstr "A token érvénytelen vagy lejárt."
#: dashboard/views/util.py:711 #: dashboard/views/util.py:740
msgid "Ownership accepted" msgid "Ownership accepted"
msgstr "Átruházás elfogadva" msgstr "Átruházás elfogadva"
#: dashboard/views/util.py:712 #: dashboard/views/util.py:741
#, python-format #, python-format
msgid "Your ownership offer of %(instance)s has been accepted by %(owner)s." msgid "Your ownership offer of %(instance)s has been accepted by %(owner)s."
msgstr "%(instance)s gépre vonatkozó átruházási ajánlatát elfogadta %(owner)s." msgstr "%(instance)s gépre vonatkozó átruházási ajánlatát elfogadta %(owner)s."
#: dashboard/views/util.py:761 #: dashboard/views/util.py:790
msgid "Only the owners can delete the selected object." msgid "Only the owners can delete the selected object."
msgstr "Csak a tulajdonos törölheti a kiválasztott objektumot." msgstr "Csak a tulajdonos törölheti a kiválasztott objektumot."
#: dashboard/views/vm.py:102 #: dashboard/views/vm.py:181
#, fuzzy
#| msgid "create instance"
msgid "create instance (REST)"
msgstr "példány létrehozása"
#: dashboard/views/vm.py:355
msgid "console access" msgid "console access"
msgstr "konzolhozzáférés" msgstr "konzolhozzáférés"
#: dashboard/views/vm.py:235 #: dashboard/views/vm.py:488
msgid "VM description successfully updated." msgid "VM description successfully updated."
msgstr "A VM leírása megváltoztatásra került." msgstr "A VM leírása megváltoztatásra került."
#: dashboard/views/vm.py:654 #: dashboard/views/vm.py:907
msgid "The token has expired." msgid "The token has expired."
msgstr "A token lejárt." msgstr "A token lejárt."
#: dashboard/views/vm.py:787 #: dashboard/views/vm.py:1040
msgid "VM successfully renamed." msgid "VM successfully renamed."
msgstr "A virtuális gép átnevezésre került." msgstr "A virtuális gép átnevezésre került."
#: dashboard/views/vm.py:916 #: dashboard/views/vm.py:1169
#, python-format #, python-format
msgid "Failed to execute %(op)s operation on instance %(instance)s." msgid "Failed to execute %(op)s operation on instance %(instance)s."
msgstr "%(op)s végrehajtása meghiúsult a következőn: %(instance)s." msgstr "%(op)s végrehajtása meghiúsult a következőn: %(instance)s."
#: dashboard/views/vm.py:932 #: dashboard/views/vm.py:1185
#, python-format #, python-format
msgid "You are not permitted to execute %(op)s on instance %(instance)s." msgid "You are not permitted to execute %(op)s on instance %(instance)s."
msgstr "Nem engedélyezett a(z) %(op)s végrehajtása a(z) %(instance)s gépen." msgstr "Nem engedélyezett a(z) %(op)s végrehajtása a(z) %(instance)s gépen."
#: dashboard/views/vm.py:1131 #: dashboard/views/vm.py:1384
msgid "Customize VM" msgid "Customize VM"
msgstr "VM testreszabása" msgstr "VM testreszabása"
#: dashboard/views/vm.py:1139 #: dashboard/views/vm.py:1392
msgid "Create a VM" msgid "Create a VM"
msgstr "VM létrehozása" msgstr "VM létrehozása"
#: dashboard/views/vm.py:1196 #: dashboard/views/vm.py:1449
#, python-format #, python-format
msgid "Successfully created %(count)d VM." msgid "Successfully created %(count)d VM."
msgid_plural "Successfully created %(count)d VMs." msgid_plural "Successfully created %(count)d VMs."
msgstr[0] "%(count)d VM létrehozásra került." msgstr[0] "%(count)d VM létrehozásra került."
msgstr[1] "%(count)d VM létrehozásra került." msgstr[1] "%(count)d VM létrehozásra került."
#: dashboard/views/vm.py:1201 #: dashboard/views/vm.py:1454
msgid "VM successfully created." msgid "VM successfully created."
msgstr "VM létrehozásra került." msgstr "VM létrehozásra került."
#: dashboard/views/vm.py:1263 #: dashboard/views/vm.py:1516
#, python-format #, python-format
msgid "Instance limit (%d) exceeded." msgid "Instance limit (%d) exceeded."
msgstr "A példányok létrehozási korlátját (%d) túllépte." msgstr "A példányok létrehozási korlátját (%d) túllépte."
#: dashboard/views/vm.py:1326 #: dashboard/views/vm.py:1579
msgid "About CIRCLE Client" msgid "About CIRCLE Client"
msgstr "A CIRCLE kliensről" msgstr "A CIRCLE kliensről"
#: dashboard/views/vm.py:1417 #: dashboard/views/vm.py:1670
msgid "transfer ownership" msgid "transfer ownership"
msgstr "tulajdon átruházása" msgstr "tulajdon átruházása"
#: dashboard/views/vm.py:1427 #: dashboard/views/vm.py:1680
#, python-format #, python-format
msgid "" msgid ""
"%(owner)s offered you to take the ownership of his/her virtual machine " "%(owner)s offered you to take the ownership of his/her virtual machine "
...@@ -7203,7 +7214,8 @@ msgstr "Használjon cloud-init-et" ...@@ -7203,7 +7214,8 @@ msgstr "Használjon cloud-init-et"
#: vm/models/instance.py:148 #: vm/models/instance.py:148
msgid "VM use cloud-init, set user- and meta-data below" msgid "VM use cloud-init, set user- and meta-data below"
msgstr "A vm cloud-init-et használ, állítsa be a user- és meta-data configurációt" msgstr ""
"A vm cloud-init-et használ, állítsa be a user- és meta-data configurációt"
#: vm/models/instance.py:149 #: vm/models/instance.py:149
msgid "CI Meta Data" msgid "CI Meta Data"
...@@ -7418,18 +7430,18 @@ msgstr "" ...@@ -7418,18 +7430,18 @@ msgstr ""
msgid "create instance" msgid "create instance"
msgstr "példány létrehozása" msgstr "példány létrehozása"
#: vm/models/instance.py:609 #: vm/models/instance.py:614
#, python-format #, python-format
msgid "vm state changed to %(state)s on %(node)s" msgid "vm state changed to %(state)s on %(node)s"
msgstr "VM állapota erre változott: %(state)s (ezen: %(node)s)" msgstr "VM állapota erre változott: %(state)s (ezen: %(node)s)"
#: vm/models/instance.py:611 #: vm/models/instance.py:616
#, fuzzy, python-format #, fuzzy, python-format
#| msgid "vm state changed to %(state)s on %(node)s" #| msgid "vm state changed to %(state)s on %(node)s"
msgid "vm state changed to %(state)s" msgid "vm state changed to %(state)s"
msgstr "VM állapota erre változott: %(state)s (ezen: %(node)s)" msgstr "VM állapota erre változott: %(state)s (ezen: %(node)s)"
#: vm/models/instance.py:811 #: vm/models/instance.py:816
#, python-format #, python-format
msgid "" msgid ""
"Your instance <a href=\"%(url)s\">%(instance)s</a> is going to expire. It " "Your instance <a href=\"%(url)s\">%(instance)s</a> is going to expire. It "
...@@ -7441,7 +7453,7 @@ msgstr "" ...@@ -7441,7 +7453,7 @@ msgstr ""
"kerül. Kérjük, <a href=\"%(token)s\">újítsa meg</a> vagy <a href=\"%(url)s" "kerül. Kérjük, <a href=\"%(token)s\">újítsa meg</a> vagy <a href=\"%(url)s"
"\">törölje</a> most." "\">törölje</a> most."
#: vm/models/instance.py:823 #: vm/models/instance.py:828
#, python-format #, python-format
msgid "" msgid ""
"%(failed)s notifications failed and %(success) succeeded. Failed ones are: " "%(failed)s notifications failed and %(success) succeeded. Failed ones are: "
...@@ -7450,7 +7462,7 @@ msgstr "" ...@@ -7450,7 +7462,7 @@ msgstr ""
"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: " "%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: "
"%(faileds)s." "%(faileds)s."
#: vm/models/instance.py:825 #: vm/models/instance.py:830
#, python-format #, python-format
msgid "" msgid ""
"%(failed)s notifications failed and %(success) succeeded. Failed ones are: " "%(failed)s notifications failed and %(success) succeeded. Failed ones are: "
...@@ -7459,16 +7471,16 @@ msgstr "" ...@@ -7459,16 +7471,16 @@ msgstr ""
"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: " "%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: "
"%(faileds_ex)s." "%(faileds_ex)s."
#: vm/models/instance.py:833 #: vm/models/instance.py:838
#, python-format #, python-format
msgid "%(success)s notifications succeeded." msgid "%(success)s notifications succeeded."
msgstr "%(success)s sikeres értesítés." msgstr "%(success)s sikeres értesítés."
#: vm/models/instance.py:838 #: vm/models/instance.py:843
msgid "notify owner about expiration" msgid "notify owner about expiration"
msgstr "tulaj értesítése a lejáratról" msgstr "tulaj értesítése a lejáratról"
#: vm/models/instance.py:846 #: vm/models/instance.py:851
#, python-format #, python-format
msgid "%(instance)s expiring soon" msgid "%(instance)s expiring soon"
msgstr "%(instance)s hamarosan lejár" msgstr "%(instance)s hamarosan lejár"
...@@ -7683,13 +7695,17 @@ msgstr "" ...@@ -7683,13 +7695,17 @@ msgstr ""
#: vm/operations.py:344 #: vm/operations.py:344
#, python-format #, python-format
msgid "download %(name)s" msgid "download %(name)s (id: %(disk_id)s)"
msgstr "%(name)s letöltése" msgstr "%(name)s letöltése (id: %(disk_id)s)"
#: vm/operations.py:347 #: vm/operations.py:347
#, python-format #, fuzzy, python-format
msgid "Downloading %(url)s is finished. The file md5sum is: '%(checksum)s'." #| msgid "Downloading %(url)s is finished. The file md5sum is: '%(checksum)s'."
msgstr "%(url)s letöltése sikeres. A fájl md5sum összege: '%(checksum)s'." msgid ""
"Downloading %(url)s is finished. The file md5sum is: '%(checksum)s' (id: "
"%(disk_id)s)."
msgstr "%(url)s letöltése sikeres. A fájl md5sum összege: '%(checksum)s'(id: "
"%(disk_id)s)."
#: vm/operations.py:358 #: vm/operations.py:358
#, fuzzy #, fuzzy
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
from django import contrib from django import contrib
# from django.utils.translation import ugettext_lazy as _ # from django.utils.translation import ugettext_lazy as _
from .models import Disk, DataStore from .models import Disk, DataStore, StorageActivity
class DiskAdmin(contrib.admin.ModelAdmin): class DiskAdmin(contrib.admin.ModelAdmin):
...@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin): ...@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin):
contrib.admin.site.register(Disk, DiskAdmin) contrib.admin.site.register(Disk, DiskAdmin)
contrib.admin.site.register(DataStore, DataStoreAdmin) contrib.admin.site.register(DataStore, DataStoreAdmin)
contrib.admin.site.register(StorageActivity)
\ No newline at end of file
# Generated by Django 3.2.3 on 2022-09-14 15:32
import common.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('storage', '0004_disk_ci_disk'),
]
operations = [
migrations.CreateModel(
name='StorageActivity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('activity_code', models.CharField(max_length=100, verbose_name='activity code')),
('readable_name_data', jsonfield.fields.JSONField(blank=True, dump_kwargs={'cls': common.models.Encoder}, help_text='Human readable name of activity.', null=True, verbose_name='human readable name')),
('task_uuid', models.CharField(blank=True, help_text='Celery task unique identifier.', max_length=50, null=True, unique=True, verbose_name='task_uuid')),
('started', models.DateTimeField(blank=True, help_text='Time of activity initiation.', null=True, verbose_name='started at')),
('finished', models.DateTimeField(blank=True, help_text='Time of activity finalization.', null=True, verbose_name='finished at')),
('succeeded', models.BooleanField(blank=True, help_text='True, if the activity has finished successfully.', null=True)),
('result_data', jsonfield.fields.JSONField(blank=True, dump_kwargs={'cls': common.models.Encoder}, help_text='Human readable result of activity.', null=True, verbose_name='result')),
('disk', models.ForeignKey(blank=True, help_text='Disks which are to be mounted.', null=True, on_delete=django.db.models.deletion.CASCADE, to='storage.disk', verbose_name='disk')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='storage.storageactivity')),
('user', models.ForeignKey(blank=True, help_text='The person who started this activity.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'db_table': 'st_act',
'ordering': ['-finished', '-started', '-id'],
},
),
]
...@@ -39,7 +39,7 @@ from os.path import join ...@@ -39,7 +39,7 @@ from os.path import join
from sizefield.models import FileSizeField from sizefield.models import FileSizeField
from common.models import ( from common.models import (
WorkerNotFound, HumanReadableException, humanize_exception, method_cache WorkerNotFound, HumanReadableException, humanize_exception, join_activity_code, method_cache
) )
from .tasks import local_tasks, storage_tasks from .tasks import local_tasks, storage_tasks
...@@ -622,3 +622,23 @@ class Disk(TimeStampedModel): ...@@ -622,3 +622,23 @@ class Disk(TimeStampedModel):
@property @property
def is_exportable(self): def is_exportable(self):
return self.type in ('qcow2-norm', 'qcow2-snap', 'raw-rw', 'raw-ro') return self.type in ('qcow2-norm', 'qcow2-snap', 'raw-rw', 'raw-ro')
from common.models import ActivityModel
class StorageActivity(ActivityModel):
disk = ForeignKey('storage.Disk', blank=True, null=True, verbose_name=_('disk'), on_delete=models.CASCADE,
help_text=_('Disks which are to be mounted.'))
ACTIVITY_CODE_BASE = join_activity_code('st', 'Storage')
class Meta:
db_table = 'st_act'
ordering = ['-finished', '-started', '-id']
@classmethod
def create(cls, code_suffix, task_uuid=None, user=None):
activity_code = cls.construct_activity_code(code_suffix)
act = cls(activity_code=activity_code, parent=None,
started=timezone.now(), task_uuid=task_uuid, user=user)
act.save()
return act
\ No newline at end of file
# Generated by Django 3.2.3 on 2022-09-14 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0009_auto_20220721_1118'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='ci_meta_data',
field=models.TextField(blank=True, default='instance-id: {{ hostname }} \nlocal-hostname: {{ hostname }} \ncloud-name: circle3\nplatform: circle3', help_text='When cloud-init is active, set meta-data (YAML format)', verbose_name='CI Meta Data'),
),
migrations.AlterField(
model_name='instance',
name='ci_user_data',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User Data'),
),
migrations.AlterField(
model_name='instance',
name='has_agent',
field=models.BooleanField(default=False, help_text='If the machine has agent installed, and the manager should wait for its start.', verbose_name='has agent'),
),
migrations.AlterField(
model_name='instancetemplate',
name='ci_meta_data',
field=models.TextField(blank=True, default='instance-id: {{ hostname }} \nlocal-hostname: {{ hostname }} \ncloud-name: circle3\nplatform: circle3', help_text='When cloud-init is active, set meta-data (YAML format)', verbose_name='CI Meta Data'),
),
migrations.AlterField(
model_name='instancetemplate',
name='ci_user_data',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User Data'),
),
migrations.AlterField(
model_name='instancetemplate',
name='has_agent',
field=models.BooleanField(default=False, help_text='If the machine has agent installed, and the manager should wait for its start.', verbose_name='has agent'),
),
]
...@@ -580,10 +580,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -580,10 +580,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
""" """
user_instances = [] user_instances = []
missing_users = [] missing_users = []
instances = []
for user_id in users: for user_id in users:
try: try:
user_instances.append(User.objects.get(profile__org_id=user_id)) user_instances.append(User.objects.get(profile__org_id=user_id))
except User.DoesNotExist: except User.DoesNotExist:
try:
user_instances.append(User.objects.get(username=user_id))
except User.DoesNotExist:
missing_users.append(user_id) missing_users.append(user_id)
for user in user_instances: for user in user_instances:
...@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
instance.set_level(User.objects.get(username=admin), 'owner') instance.set_level(User.objects.get(username=admin), 'owner')
if operator: if operator:
instance.set_level(User.objects.get(username=operator), 'operator') instance.set_level(User.objects.get(username=operator), 'operator')
instance.deploy(user=user) instance.deploy._async(user=user)
instances.append(instance)
return missing_users, instances
@classmethod
def mass_create_for_users_id(cls, template, users, admin=None, operator=None, **kwargs):
"""
Create and deploy an instance of a template for each user
in a list of users. Returns the user IDs of missing users.
"""
user_instances = []
missing_users = []
instances = []
for user_id in users:
try:
user_instances.append(User.objects.get(pk=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
for user in user_instances:
instance = cls.create_from_template(template, user, **kwargs)
if admin:
instance.set_level(User.objects.get(pk=admin), 'owner')
if operator:
instance.set_level(User.objects.get(pk=operator), 'operator')
instance.deploy._async(user=user)
instances.append(instance)
return missing_users return missing_users, instances
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
self.time_of_suspend, self.time_of_delete = self.get_renew_times() self.time_of_suspend, self.time_of_delete = self.get_renew_times()
......
...@@ -87,6 +87,14 @@ class Interface(Model): ...@@ -87,6 +87,14 @@ class Interface(Model):
except: except:
return Interface.generate_mac(self.instance, self.vlan) return Interface.generate_mac(self.instance, self.vlan)
@property
def ipv4(self):
return self.host.ipv4
@property
def ipv6(self):
return self.host.ipv6
@classmethod @classmethod
def generate_mac(cls, instance, vlan): def generate_mac(cls, instance, vlan):
"""Generate MAC address for a VM instance on a VLAN. """Generate MAC address for a VM instance on a VLAN.
......
...@@ -36,11 +36,11 @@ from django.urls import reverse ...@@ -36,11 +36,11 @@ from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop from django.utils.translation import ugettext as _, ugettext_noop
from re import search from re import search, template
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from common.models import ( from common.models import (
create_readable, humanize_exception, HumanReadableException ActivityModel, create_readable, humanize_exception, HumanReadableException
) )
from common.operations import Operation, register_operation, SubOperationMixin from common.operations import Operation, register_operation, SubOperationMixin
from dashboard.store_api import Store, NoStoreException from dashboard.store_api import Store, NoStoreException
...@@ -172,6 +172,38 @@ class InstanceOperation(Operation): ...@@ -172,6 +172,38 @@ class InstanceOperation(Operation):
return False return False
class StorageOperation(Operation):
acl_level = 'owner'
async_operation = abortable_async_instance_operation
host_cls = Disk
concurrency_check = False
accept_states = None
deny_states = None
resultant_state = None
def __init__(self):
super(InstanceOperation, self).__init__(subject=None)
def check_precond(self):
pass
def check_auth(self, user):
if not user.is_superuser:
raise Exception()
def create_activity(self, parent, user, kwargs):
name = self.get_activity_name(kwargs)
return ActivityModel.create(
readable_name=name, user=user,
concurrency_check=self.concurrency_check,
resultant_state=self.resultant_state)
def is_preferred(self):
"""If this is the recommended op in the current state of the instance.
"""
return False
class RemoteInstanceOperation(RemoteOperationMixin, InstanceOperation): class RemoteInstanceOperation(RemoteOperationMixin, InstanceOperation):
remote_queue = ('vm', 'fast') remote_queue = ('vm', 'fast')
...@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation): ...@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation):
description = _("Create and attach empty disk to the virtual machine.") description = _("Create and attach empty disk to the virtual machine.")
required_perms = ('storage.create_empty_disk',) required_perms = ('storage.create_empty_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
concurrency_check = False
def _operation(self, user, size, activity, name=None): def _operation(self, user, size, activity, name=None):
from storage.models import Disk from storage.models import Disk
...@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation):
required_perms = ('storage.download_disk',) required_perms = ('storage.download_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow" async_queue = "localhost.man.slow"
concurrency_check = False # warning!!!
def _operation(self, user, url, task, activity, name=None): def _operation(self, user, url, task, activity, name=None):
disk = Disk.download(url=url, name=name, task=task) disk = Disk.download(url=url, name=name, task=task)
...@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation):
disk.save() disk.save()
self.instance.disks.add(disk) self.instance.disks.add(disk)
activity.readable_name = create_readable( activity.readable_name = create_readable(
ugettext_noop("download %(name)s"), name=disk.name) ugettext_noop("download %(name)s (id: %(disk_id)d)"), name=disk.name, disk_id=disk.id)
activity.result = create_readable(ugettext_noop( activity.result = create_readable(ugettext_noop(
"Downloading %(url)s is finished. The file md5sum " "Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s'."), "is: '%(checksum)s' (id: %(disk_id)d)."),
url=url, checksum=disk.checksum) url=url, checksum=disk.checksum, disk_id=disk.id, disk_size=disk.size)
# TODO iso (cd) hot-plug is not supported by kvm/guests # TODO iso (cd) hot-plug is not supported by kvm/guests
if self.instance.is_running and disk.type not in ["iso"]: if self.instance.is_running and disk.type not in ["iso"]:
self.instance._attach_disk(parent_activity=activity, disk=disk) self.instance._attach_disk(parent_activity=activity, disk=disk)
return create_readable(ugettext_noop("Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s' (id: %(disk_id)s)."), url=url, checksum=disk.checksum, disk_id=str(disk.id))
@register_operation @register_operation
...@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation):
raise raise
else: else:
return create_readable( return create_readable(
ugettext_noop("New template: %(template)s"), ugettext_noop("New template: %(template)s (%(template_id)s)"),
template=reverse('dashboard.views.template-detail', template_id=tmpl.id, template=reverse('dashboard.views.template-detail',
kwargs={'pk': tmpl.pk})) kwargs={'pk': tmpl.pk}))
...@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin, ...@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
remote_queue = ("vm", "slow") remote_queue = ("vm", "slow")
remote_timeout = 180 remote_timeout = 180
def _operation(self, task): def _operation(self, task=vm_tasks.shutdown):
super(ShutdownOperation, self)._operation(task=task) super(ShutdownOperation, self)._operation(task=task)
self.instance.yield_node() self.instance.yield_node()
......
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import timezone
from celery.contrib.abortable import AbortableTask from celery.contrib.abortable import AbortableTask
from common.models import ActivityModel
from storage.models import Disk, StorageActivity
from manager.mancelery import celery from manager.mancelery import celery
...@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk, ...@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk,
allargs['task'] = task allargs['task'] = task
return operation._exec_op(allargs, auxargs) return operation._exec_op(allargs, auxargs)
@celery.task(base=AbortableTask, bind=True)
def abortable_async_downloaddisk_operation(task, activity_pk, url, name):
activity = StorageActivity.objects.get(pk=activity_pk)
activity.task_uuid = task.request.id
activity.save()
disk = Disk.download(url=url, name=name, task=task)
disk.dev_num = 'g'
disk.full_clean()
disk.save()
activity.disk = disk
activity.succeeded = True
#activity.finished = timezone.now()
activity.save()
return
\ No newline at end of file
...@@ -55,3 +55,4 @@ django-nose==1.4.7 ...@@ -55,3 +55,4 @@ django-nose==1.4.7
nose-exclude==0.5.0 nose-exclude==0.5.0
factory_boy==3.2.1 factory_boy==3.2.1
passlib==1.7.4 passlib==1.7.4
djangorestframework==3.13.1
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment