Commit dc4d4e55 by Karsa Zoltán István

Merge branch 'iac-api'

parents 796da6b9 42092a44
......@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = (
'statici18n',
'simplesshkey',
'pipeline',
'rest_framework',
'rest_framework.authtoken',
)
......@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser'
),
}
########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging
......
......@@ -20,7 +20,7 @@ from django.views.generic import TemplateView
from django.conf import settings
from django.contrib import admin
from django.urls import reverse
from django.urls import reverse, path
from django.shortcuts import redirect
from django.contrib.auth.views import (
PasswordResetView, PasswordResetConfirmView
......@@ -33,6 +33,8 @@ from dashboard.views import (
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
from rest_framework.authtoken.views import obtain_auth_token
admin.autodiscover()
urlpatterns = [
......@@ -71,6 +73,9 @@ urlpatterns = [
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
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:
......
......@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel):
return 'failed'
@celery.task()
def compute_cached(method, instance, memcached_seconds,
key, start, *args, **kwargs):
......
......@@ -61,6 +61,15 @@ except NameError:
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():
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 @@
{% trans "Email address" %}: {{ profile.email }}
{% endif %}
</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>
{% if request.user == profile %}
<p>
......
......@@ -18,6 +18,7 @@
from django.conf.urls import url
from django.urls import path
from vm.models import Instance
from .views import (
......@@ -52,18 +53,63 @@ from .views import (
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView,
NodeActivityView,
UserList,
StorageDetail, DiskDetail,
NodeActivityView,
UserList, TemplateREST, LeaseREST, DiskRest, InstanceREST,
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,
SetupPortREST, RulesREST,
EnableTwoFactorView, DisableTwoFactorView,
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
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"^profile/list/$", UserList.as_view(),
name="dashboard.views.user-list"),
......
......@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation
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.utils.translation import ugettext as _
from django.views.generic import UpdateView, TemplateView
......@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView
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 .util import (CheckedDetailView, AclUpdateView, search_user,
saml_available, DeleteViewBase)
......@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException
from ..tables import GroupListTable
from dashboard.serializers import GroupSerializer
try:
# Python 2: "unicode" is built-in
unicode
......@@ -55,6 +63,33 @@ except NameError:
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):
@classmethod
......
......@@ -31,6 +31,12 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
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 django_tables2 import SingleTableView
......@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase
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):
ops = []
......
......@@ -21,14 +21,20 @@ from django.urls import reverse
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin
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 storage.models import DataStore, Disk
from ..tables import DiskListTable
from ..forms import DataStoreForm, DiskForm
from dashboard.serializers import DiskSerializer
class StorageDetail(SuperuserRequiredMixin, UpdateView):
......@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView):
def form_valid(self, form):
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 @@
from datetime import timedelta
import json
import logging
from string import Template
from xml.dom import NotFoundErr
from django.contrib import messages
from django.contrib.auth.models import User
......@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy
from django.core.exceptions import PermissionDenied, SuspiciousOperation
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.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop
......@@ -56,6 +58,13 @@ from .util import (
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:
# Python 2: "unicode" is built-in
unicode
......@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
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):
model = InstanceTemplate
......
......@@ -18,6 +18,7 @@
import json
import logging
from multiprocessing import context
import pyotp
......@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy
from django.core.paginator import Paginator, InvalidPage
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.templatetags.static import static
from django.utils.translation import ugettext as _
......@@ -41,6 +42,12 @@ from django.views.generic import (
)
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 django_tables2 import SingleTableView, LazyPaginator
......@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand
from ..tables import (
UserKeyListTable, ConnectCommandListTable, UserListTable,
)
from rest_framework.authtoken.models import Token
from .util import saml_available, DeleteViewBase, LoginView
from dashboard.serializers import UserSerializer
try:
# Python 2: "unicode" is built-in
......@@ -67,6 +75,30 @@ except NameError:
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):
if user.is_superuser:
......@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['perm_email'] = (
context['perm_group_list'] or self.request.user == user)
context['iac_token'] = Token.objects.filter(user=user).get()
# filter the virtual machine list
# 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
......
......@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin
from braces.views._access import AccessMixin
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 ..models import GroupProfile, Profile
from vm.models import InstanceActivity
from ..forms import TransferOwnershipForm
......@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__)
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):
redirect_exception_classes = (PermissionDenied, )
......
......@@ -17,9 +17,13 @@
import json
import queue
import string
import logging
from collections import OrderedDict
from os import getenv
from urllib import response
from xml.dom import ValidationErr
from django.conf import settings
from django.contrib import messages
......@@ -42,6 +46,11 @@ from django.views.generic import (
)
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 (
create_readable, HumanReadableException, fetch_human_exception,
......@@ -49,7 +58,7 @@ from common.models import (
)
from firewall.models import Vlan, Host, Rule
from manager.scheduler import SchedulerError
from storage.models import Disk
from storage.models import Disk, StorageActivity
from vm.models import (
Instance, InstanceActivity, Node, Lease,
InstanceTemplate, InterfaceTemplate, Interface,
......@@ -73,9 +82,10 @@ from ..forms import (
from django.views.generic.edit import FormMixin
from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm
from ..models import Favourite
from ..models import Favourite, pwgen
from manager.scheduler import has_traits
import re
try:
# Python 2: "unicode" is built-in
......@@ -86,6 +96,404 @@ except NameError:
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):
template_name = "dashboard/vm-detail.html"
model = Instance
......
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \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"
"Last-Translator: <>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......@@ -463,7 +463,7 @@ msgstr "Választott nyelv"
#: dashboard/forms.py:1358 dashboard/templates/dashboard/group-list.html:14
#: 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
#: network/templates/network/host-edit.html:32
#: templates/info/help/overview.html:404
......@@ -585,39 +585,39 @@ msgstr ""
msgid "Invalid confirmation code."
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"
msgstr "üzenet"
#: dashboard/models.py:72
#: dashboard/models.py:81
msgid "effect"
msgstr "hatás"
#: dashboard/models.py:73
#: dashboard/models.py:82
msgid "success"
msgstr "siker"
#: dashboard/models.py:73
#: dashboard/models.py:82
msgid "info"
msgstr "infó"
#: dashboard/models.py:74
#: dashboard/models.py:83
msgid "warning"
msgstr "figyelmeztetés"
#: dashboard/models.py:74
#: dashboard/models.py:83
msgid "danger"
msgstr "veszély"
#: dashboard/models.py:75 vm/models/node.py:129
#: dashboard/models.py:84 vm/models/node.py:129
msgid "enabled"
msgstr "engedélyezve"
#: dashboard/models.py:80
#: dashboard/models.py:89
msgid "messages"
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-templates.html:52
#: dashboard/templates/dashboard/index-users.html:48
......@@ -635,23 +635,23 @@ msgstr "üzenetek"
msgid "new"
msgstr "új"
#: dashboard/models.py:97
#: dashboard/models.py:106
msgid "delivered"
msgstr "kézbesített"
#: dashboard/models.py:98
#: dashboard/models.py:107
msgid "read"
msgstr "olvasott"
#: dashboard/models.py:158 vm/models/instance.py:137
#: dashboard/models.py:167 vm/models/instance.py:137
msgid "access method"
msgstr "elérés módja"
#: dashboard/models.py:159
#: dashboard/models.py:168
msgid "Type of the remote access method."
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
#: 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
......@@ -659,15 +659,15 @@ msgstr "Távoli elérési mód típusa."
msgid "name"
msgstr "név"
#: dashboard/models.py:161
#: dashboard/models.py:170
msgid "Name of your custom command."
msgstr "Egyedi parancs neve"
#: dashboard/models.py:163
#: dashboard/models.py:172
msgid "command template"
msgstr "parancssablon"
#: dashboard/models.py:164
#: dashboard/models.py:173
msgid ""
"Template for connection command string. Available parameters are: username, "
"password, host, port."
......@@ -675,79 +675,79 @@ msgstr ""
"Sablon a csatlakozási parancshoz. Elérhető paraméterek: username, password, "
"host, port."
#: dashboard/models.py:179
#: dashboard/models.py:188
msgid "preferred language"
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."
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"
msgstr "Gravatar használata"
#: dashboard/models.py:190
#: dashboard/models.py:199
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"
#: dashboard/models.py:192
#: dashboard/models.py:201
msgid "Email notifications"
msgstr "E-mail értesítések"
#: dashboard/models.py:193
#: dashboard/models.py:202
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."
#: dashboard/models.py:195
#: dashboard/models.py:204
#, fuzzy
#| msgid "Latest modifications"
msgid "Desktop notifications"
msgstr "Legutóbbi változások"
#: dashboard/models.py:196
#: dashboard/models.py:205
msgid ""
"Whether user wants to get desktop notification when an activity has finished "
"and the window is not in focus."
msgstr ""
#: dashboard/models.py:200
#: dashboard/models.py:209
msgid "Samba password"
msgstr "Samba jelszó"
#: dashboard/models.py:202
#: dashboard/models.py:211
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ó."
#: dashboard/models.py:207 dashboard/models.py:306
#: dashboard/models.py:216 dashboard/models.py:315
msgid "disk quota"
msgstr "lemezkvóta"
#: dashboard/models.py:209 dashboard/models.py:308
#: dashboard/models.py:218 dashboard/models.py:317
msgid "Disk quota in mebibytes."
msgstr "Lemezkvóta mebibyte-okban."
#: dashboard/models.py:211
#: dashboard/models.py:220
msgid "two factor secret key"
msgstr ""
#: dashboard/models.py:275
#: dashboard/models.py:284
msgid "Can use autocomplete."
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
msgid "operator"
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:1016 firewall/models.py:1050 vm/models/common.py:87
#: vm/models/instance.py:178 vm/models/instance.py:315
msgid "owner"
msgstr "tulajdonos"
#: dashboard/models.py:301
#: dashboard/models.py:310
msgid "Unique identifier of the group at the organization."
msgstr "A csoport egyedi szervezeti azonosítója."
......@@ -973,11 +973,14 @@ msgstr "alapértelmezett ipv6 cím"
msgid ""
"associated vlans: list of objects (name: vlan name, ipv4/ipv6: host ip in "
"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
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
msgid "function: make random string with 'len' charachters lenght"
......@@ -994,7 +997,9 @@ msgstr "Filterek: hash - sha512 hash"
#: dashboard/templates/dashboard/_ci-data-help.html:27
msgid ""
"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
msgid ""
......@@ -1989,7 +1994,7 @@ msgstr "meghiúsult"
#: dashboard/templates/dashboard/instanceactivity_detail.html:78
#: dashboard/templates/dashboard/nodeactivity_detail.html:74
#: dashboard/views/storage.py:59
#: dashboard/views/storage.py:66
msgid "none"
msgstr "nincs"
......@@ -2251,43 +2256,43 @@ msgstr "Keresztnév"
msgid "Last name"
msgstr "Vezetéknév"
#: dashboard/templates/dashboard/profile.html:45
#: dashboard/templates/dashboard/profile.html:53
msgid "Last login"
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"
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?"
msgstr "Mi az a Gravatar?"
#: dashboard/templates/dashboard/profile.html:53
#: dashboard/templates/dashboard/profile.html:61
msgid "Change my preferences"
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."
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"
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."
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"
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."
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"
msgstr "Felhasználó szerkesztése"
......@@ -2582,7 +2587,7 @@ msgid "Currently uploading to"
msgstr "Feltöltés helye:"
#: 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
msgid "template"
msgstr "sablon"
......@@ -3308,72 +3313,72 @@ msgstr "CPU-használat (%)"
msgid "Allocated memory (bytes)"
msgstr "Foglalt memória (byte)"
#: dashboard/views/group.py:160
#: dashboard/views/group.py:185
#, python-format
msgid "User \"%s\" not found."
msgstr "Nem található „%s” felhasználó."
#: dashboard/views/group.py:174
#: dashboard/views/group.py:199
msgid "Group successfully renamed."
msgstr "A csoport átnevezésre került."
#: dashboard/views/group.py:242
#: dashboard/views/group.py:267
msgid "Member successfully removed from group."
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."
msgstr "A leendő csoporttag eltávolításra került."
#: dashboard/views/group.py:301
#: dashboard/views/group.py:326
#, fuzzy
#| msgid "Future user successfully removed from group."
msgid "All users successfully removed from group."
msgstr "A leendő csoporttag eltávolításra került."
#: dashboard/views/group.py:310
#: dashboard/views/group.py:335
#, fuzzy
#| msgid "user"
msgid "all users"
msgstr "felhasználó"
#: dashboard/views/group.py:325
#: dashboard/views/group.py:350
msgid "Group successfully deleted."
msgstr "A csoport törlésre került."
#: dashboard/views/group.py:353
#: dashboard/views/group.py:378
msgid "Create a Group"
msgstr "Csoport létrehozása"
#: dashboard/views/group.py:369
#: dashboard/views/group.py:394
msgid "Group successfully created."
msgstr "A csoport létrehozásra került."
#: dashboard/views/group.py:403
#: dashboard/views/group.py:428
#, fuzzy
#| msgid "Create a Group"
msgid "Import a Group"
msgstr "Csoport létrehozása"
#: dashboard/views/group.py:426
#: dashboard/views/group.py:451
#, fuzzy
#| msgid "Group successfully created."
msgid "Group successfully imported."
msgstr "A csoport létrehozásra került."
#: dashboard/views/group.py:473
#: dashboard/views/group.py:498
#, fuzzy
#| msgid "host group"
msgid "Export Group"
msgstr "gépcsoport"
#: dashboard/views/group.py:499
#: dashboard/views/group.py:524
#, fuzzy
#| msgid "Group successfully deleted."
msgid "Group successfully exported."
msgstr "A csoport törlésre került."
#: dashboard/views/group.py:520
#: dashboard/views/group.py:545
msgid "Group is successfully updated."
msgstr "A csoport frissítésre került."
......@@ -3385,37 +3390,37 @@ msgstr "Az üzenet frissítésre került."
msgid "New broadcast message successfully created."
msgstr "Az üzenet létrehozása került."
#: dashboard/views/node.py:152
#: dashboard/views/node.py:187
msgid "Node successfully renamed."
msgstr "A csomópont átnevezésre került."
#: dashboard/views/node.py:261
#: dashboard/views/node.py:296
msgid "Create a node"
msgstr "Új csomópont hozzáadása"
#: dashboard/views/node.py:285
#: dashboard/views/node.py:320
msgid "Node successfully created."
msgstr "A csomópont létrehozásra került."
#: dashboard/views/node.py:296
#: dashboard/views/node.py:331
msgid "Node successfully deleted."
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."
msgstr "A csomópontjellemző hozzáadásra került."
#: dashboard/views/node.py:392
#: dashboard/views/node.py:427
#, fuzzy
#| msgid "schedule enabled"
msgid "Reschedule has started."
msgstr "ütemezés engedélyezve"
#: dashboard/views/storage.py:51
#: dashboard/views/storage.py:58
msgid "The DataStore is offline."
msgstr "Az adattár nem elérhető."
#: dashboard/views/storage.py:57
#: dashboard/views/storage.py:64
msgid "virtual machine"
msgstr "virtuális gép"
......@@ -3448,39 +3453,39 @@ msgstr "%s törlése sikertelen."
msgid "Unable to create folder."
msgstr "Mappa létrehozása sikertelen."
#: dashboard/views/template.py:87
#: dashboard/views/template.py:96
msgid "Choose template"
msgstr "Válasszon sablont"
#: dashboard/views/template.py:102
#: dashboard/views/template.py:111
msgid "Select an option to proceed."
msgstr "Válasszon a folytatáshoz."
#: dashboard/views/template.py:133
#: dashboard/views/template.py:142
msgid "Create a new base VM"
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."
msgstr "A szűrés sikertelen."
#: dashboard/views/template.py:303
#: dashboard/views/template.py:433
msgid "Template successfully deleted."
msgstr "A sablon törlésre került."
#: dashboard/views/template.py:319
#: dashboard/views/template.py:449
msgid "Successfully modified template."
msgstr "A sablon módosításra került."
#: dashboard/views/template.py:378
#: dashboard/views/template.py:508
msgid "Disk successfully removed."
msgstr "A lemez eltávolításra került."
#: dashboard/views/template.py:394
#: dashboard/views/template.py:524
msgid "Disk remove confirmation"
msgstr "Lemez törlésének megerősítése"
#: dashboard/views/template.py:395
#: dashboard/views/template.py:525
#, python-format
msgid ""
"Are you sure you want to remove <strong>%(disk)s</strong> from <strong>"
......@@ -3489,23 +3494,23 @@ msgstr ""
"Biztosan eltávolítja a(z) <strong>%(disk)s</strong> lemezt a következőből: "
"%(app)s?"
#: dashboard/views/template.py:418
#: dashboard/views/template.py:548
msgid "Successfully created a new lease."
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."
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."
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."
msgstr "A bérlési mód törlésre került."
#: dashboard/views/template.py:475
#: dashboard/views/template.py:605
msgid ""
"You can't delete this lease because some templates are still using it, "
"modify these to proceed: "
......@@ -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 "
"folytatáshoz módosítsa őket: "
#: dashboard/views/template.py:500
#: dashboard/views/template.py:630
#, python-format
msgid ""
"%(owner)s offered you to take the ownership of his/her template called "
......@@ -3523,251 +3528,257 @@ msgstr ""
"%(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>"
#: dashboard/views/user.py:73
#: dashboard/views/user.py:96
msgid ""
"You've logged in with an administrator account, your session will expire "
"when the web browser is closed."
msgstr ""
#: dashboard/views/user.py:178
#: dashboard/views/user.py:201
#, python-format
msgid "Logged in as user %s."
msgstr "Bejelentkezve mint %s."
#: dashboard/views/user.py:211
#: dashboard/views/user.py:234
msgid "You don't have a profile."
msgstr "Nincs profilja."
#: dashboard/views/user.py:229
#: dashboard/views/user.py:252
#, fuzzy
#| msgid "Node successfully changed status."
msgid "Password successfully changed."
msgstr "A csomópont állapota megváltoztatásra került."
#: dashboard/views/user.py:251
#: dashboard/views/user.py:274
msgid "Successfully modified subscription."
msgstr "A feliratkozás módosításra került."
#: dashboard/views/user.py:314
#: dashboard/views/user.py:337
msgid "Create a User"
msgstr "Felhasználó létrehozása"
#: dashboard/views/user.py:345
#: dashboard/views/user.py:368
msgid "Successfully modified user."
msgstr "A felhasználó megváltoztatásra került."
#: dashboard/views/user.py:437
#: dashboard/views/user.py:462
msgid "Successfully modified SSH key."
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."
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."
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."
msgstr "A parancssablon módosításra került."
#: dashboard/views/user.py:514
#: dashboard/views/user.py:539
msgid "Command template successfully deleted."
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."
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."
msgstr ""
#: dashboard/views/util.py:321
#: dashboard/views/util.py:350
msgid "Could not start operation."
msgstr "A művelet megkezdése meghiúsult."
#: dashboard/views/util.py:338
#: dashboard/views/util.py:367
msgid "Operation failed."
msgstr "A művelet meghiúsult."
#: dashboard/views/util.py:343
#: dashboard/views/util.py:372
msgid "Operation succeeded."
msgstr "A művelet sikeresen végrehajtásra került."
#: dashboard/views/util.py:345
#: dashboard/views/util.py:374
msgid "Operation is started."
msgstr "A művelet megkezdődött."
#: dashboard/views/util.py:433
#: dashboard/views/util.py:462
#, python-format
msgid "Acl user/group %(w)s successfully modified."
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
msgid "Acl user/group %(w)s successfully added."
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
msgid "Acl user/group %(w)s successfully removed."
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 ""
"The original owner cannot be removed, however you can transfer ownership."
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
msgid "User \"%s\" has already access to this object."
msgstr "„%s” felhasználó már hozzáfér az objektumhoz."
#: dashboard/views/util.py:567
#: dashboard/views/util.py:596
#, python-format
msgid "Group \"%s\" has already access to this object."
msgstr "„%s” csoport már hozzáfér az objektumhoz."
#: dashboard/views/util.py:572
#: dashboard/views/util.py:601
#, python-format
msgid "User or group \"%s\" not found."
msgstr "Nem található „%s” felhasználó vagy csoport."
#: dashboard/views/util.py:588
#: dashboard/views/util.py:617
msgid "1 hour"
msgstr "1 óra"
#: dashboard/views/util.py:589
#: dashboard/views/util.py:618
msgid "6 hours"
msgstr "6 óra"
#: dashboard/views/util.py:590
#: dashboard/views/util.py:619
msgid "1 day"
msgstr "1 nap"
#: dashboard/views/util.py:591
#: dashboard/views/util.py:620
msgid "1 week"
msgstr "1 hét"
#: dashboard/views/util.py:592
#: dashboard/views/util.py:621
msgid "1 month"
msgstr "1 hónap"
#: dashboard/views/util.py:593
#: dashboard/views/util.py:622
msgid "6 months"
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."
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"
msgstr "Tulajdon átruházása"
#: dashboard/views/util.py:640
#: dashboard/views/util.py:669
msgid "Can not find specified user."
msgstr "Nem található a megadott felhasználó."
#: dashboard/views/util.py:656
#: dashboard/views/util.py:685
msgid "Ownership offer"
msgstr "Átruházási ajánlat"
#: dashboard/views/util.py:660
#: dashboard/views/util.py:689
msgid "Can not notify selected user."
msgstr "A kiválaszott felhasználó értesítése sikertelen."
#: dashboard/views/util.py:663
#: dashboard/views/util.py:692
#, python-format
msgid "User %s is notified about the offer."
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."
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."
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."
msgstr "A token érvénytelen vagy lejárt."
#: dashboard/views/util.py:711
#: dashboard/views/util.py:740
msgid "Ownership accepted"
msgstr "Átruházás elfogadva"
#: dashboard/views/util.py:712
#: dashboard/views/util.py:741
#, python-format
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."
#: dashboard/views/util.py:761
#: dashboard/views/util.py:790
msgid "Only the owners can delete the selected object."
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"
msgstr "konzolhozzáférés"
#: dashboard/views/vm.py:235
#: dashboard/views/vm.py:488
msgid "VM description successfully updated."
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."
msgstr "A token lejárt."
#: dashboard/views/vm.py:787
#: dashboard/views/vm.py:1040
msgid "VM successfully renamed."
msgstr "A virtuális gép átnevezésre került."
#: dashboard/views/vm.py:916
#: dashboard/views/vm.py:1169
#, python-format
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."
#: dashboard/views/vm.py:932
#: dashboard/views/vm.py:1185
#, python-format
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."
#: dashboard/views/vm.py:1131
#: dashboard/views/vm.py:1384
msgid "Customize VM"
msgstr "VM testreszabása"
#: dashboard/views/vm.py:1139
#: dashboard/views/vm.py:1392
msgid "Create a VM"
msgstr "VM létrehozása"
#: dashboard/views/vm.py:1196
#: dashboard/views/vm.py:1449
#, python-format
msgid "Successfully created %(count)d VM."
msgid_plural "Successfully created %(count)d VMs."
msgstr[0] "%(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."
msgstr "VM létrehozásra került."
#: dashboard/views/vm.py:1263
#: dashboard/views/vm.py:1516
#, python-format
msgid "Instance limit (%d) exceeded."
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"
msgstr "A CIRCLE kliensről"
#: dashboard/views/vm.py:1417
#: dashboard/views/vm.py:1670
msgid "transfer ownership"
msgstr "tulajdon átruházása"
#: dashboard/views/vm.py:1427
#: dashboard/views/vm.py:1680
#, python-format
msgid ""
"%(owner)s offered you to take the ownership of his/her virtual machine "
......@@ -7203,7 +7214,8 @@ msgstr "Használjon cloud-init-et"
#: vm/models/instance.py:148
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
msgid "CI Meta Data"
......@@ -7418,18 +7430,18 @@ msgstr ""
msgid "create instance"
msgstr "példány létrehozása"
#: vm/models/instance.py:609
#: vm/models/instance.py:614
#, python-format
msgid "vm state changed to %(state)s on %(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
#| msgid "vm state changed to %(state)s on %(node)s"
msgid "vm state changed to %(state)s"
msgstr "VM állapota erre változott: %(state)s (ezen: %(node)s)"
#: vm/models/instance.py:811
#: vm/models/instance.py:816
#, python-format
msgid ""
"Your instance <a href=\"%(url)s\">%(instance)s</a> is going to expire. It "
......@@ -7441,7 +7453,7 @@ msgstr ""
"kerül. Kérjük, <a href=\"%(token)s\">újítsa meg</a> vagy <a href=\"%(url)s"
"\">törölje</a> most."
#: vm/models/instance.py:823
#: vm/models/instance.py:828
#, python-format
msgid ""
"%(failed)s notifications failed and %(success) succeeded. Failed ones are: "
......@@ -7450,7 +7462,7 @@ msgstr ""
"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: "
"%(faileds)s."
#: vm/models/instance.py:825
#: vm/models/instance.py:830
#, python-format
msgid ""
"%(failed)s notifications failed and %(success) succeeded. Failed ones are: "
......@@ -7459,16 +7471,16 @@ msgstr ""
"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: "
"%(faileds_ex)s."
#: vm/models/instance.py:833
#: vm/models/instance.py:838
#, python-format
msgid "%(success)s notifications succeeded."
msgstr "%(success)s sikeres értesítés."
#: vm/models/instance.py:838
#: vm/models/instance.py:843
msgid "notify owner about expiration"
msgstr "tulaj értesítése a lejáratról"
#: vm/models/instance.py:846
#: vm/models/instance.py:851
#, python-format
msgid "%(instance)s expiring soon"
msgstr "%(instance)s hamarosan lejár"
......@@ -7683,13 +7695,17 @@ msgstr ""
#: vm/operations.py:344
#, python-format
msgid "download %(name)s"
msgstr "%(name)s letöltése"
msgid "download %(name)s (id: %(disk_id)s)"
msgstr "%(name)s letöltése (id: %(disk_id)s)"
#: vm/operations.py:347
#, python-format
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'."
#, 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' (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
#, fuzzy
......
......@@ -18,7 +18,7 @@
from django import contrib
# from django.utils.translation import ugettext_lazy as _
from .models import Disk, DataStore
from .models import Disk, DataStore, StorageActivity
class DiskAdmin(contrib.admin.ModelAdmin):
......@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin):
contrib.admin.site.register(Disk, DiskAdmin)
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
from sizefield.models import FileSizeField
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
......@@ -622,3 +622,23 @@ class Disk(TimeStampedModel):
@property
def is_exportable(self):
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,11 +580,15 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"""
user_instances = []
missing_users = []
instances = []
for user_id in users:
try:
user_instances.append(User.objects.get(profile__org_id=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
try:
user_instances.append(User.objects.get(username=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
for user in user_instances:
instance = cls.create_from_template(template, user, **kwargs)
......@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
instance.set_level(User.objects.get(username=admin), 'owner')
if 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):
self.time_of_suspend, self.time_of_delete = self.get_renew_times()
......
......@@ -86,6 +86,14 @@ class Interface(Model):
return self.host.mac
except:
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
def generate_mac(cls, instance, vlan):
......
......@@ -36,11 +36,11 @@ from django.urls import reverse
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop
from re import search
from re import search, template
from sizefield.utils import filesizeformat
from common.models import (
create_readable, humanize_exception, HumanReadableException
ActivityModel, create_readable, humanize_exception, HumanReadableException
)
from common.operations import Operation, register_operation, SubOperationMixin
from dashboard.store_api import Store, NoStoreException
......@@ -172,6 +172,38 @@ class InstanceOperation(Operation):
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):
remote_queue = ('vm', 'fast')
......@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation):
description = _("Create and attach empty disk to the virtual machine.")
required_perms = ('storage.create_empty_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
concurrency_check = False
def _operation(self, user, size, activity, name=None):
from storage.models import Disk
......@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation):
required_perms = ('storage.download_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow"
concurrency_check = False # warning!!!
def _operation(self, user, url, task, activity, name=None):
disk = Disk.download(url=url, name=name, task=task)
......@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation):
disk.save()
self.instance.disks.add(disk)
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(
"Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s'."),
url=url, checksum=disk.checksum)
"is: '%(checksum)s' (id: %(disk_id)d)."),
url=url, checksum=disk.checksum, disk_id=disk.id, disk_size=disk.size)
# TODO iso (cd) hot-plug is not supported by kvm/guests
if self.instance.is_running and disk.type not in ["iso"]:
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
......@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation):
raise
else:
return create_readable(
ugettext_noop("New template: %(template)s"),
template=reverse('dashboard.views.template-detail',
ugettext_noop("New template: %(template)s (%(template_id)s)"),
template_id=tmpl.id, template=reverse('dashboard.views.template-detail',
kwargs={'pk': tmpl.pk}))
......@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
remote_queue = ("vm", "slow")
remote_timeout = 180
def _operation(self, task):
def _operation(self, task=vm_tasks.shutdown):
super(ShutdownOperation, self)._operation(task=task)
self.instance.yield_node()
......
......@@ -15,7 +15,10 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import timezone
from celery.contrib.abortable import AbortableTask
from common.models import ActivityModel
from storage.models import Disk, StorageActivity
from manager.mancelery import celery
......@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk,
allargs['task'] = task
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
nose-exclude==0.5.0
factory_boy==3.2.1
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