Commit 8355c1d3 by Kálmán Viktor

Merge branch 'master' into fix-graphs

Conflicts:
	circle/dashboard/views.py
parents 0892583d 0c2709fa
...@@ -20,7 +20,6 @@ from django.conf.urls import patterns, url, include ...@@ -20,7 +20,6 @@ from django.conf.urls import patterns, url, include
import autocomplete_light import autocomplete_light
from vm.models import Instance from vm.models import Instance
from .graph import VmGraphView, NodeGraphView, NodeListGraphView
from .views import ( from .views import (
AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete, AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
GroupDetailView, GroupList, IndexView, GroupDetailView, GroupList, IndexView,
...@@ -47,6 +46,7 @@ from .views import ( ...@@ -47,6 +46,7 @@ from .views import (
GroupPermissionsView, GroupPermissionsView,
LeaseAclUpdateView, LeaseAclUpdateView,
ClientCheck, TokenLogin, ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView,
) )
autocomplete_light.autodiscover() autocomplete_light.autodiscover()
......
This source diff could not be displayed because it is too large. You can view the blob instead.
# flake8: noqa
# from .node import Node
# __all__ = [ ]
from group import *
from index import *
from node import *
from store import *
from template import *
from user import *
from util import *
from vm import *
from graph import *
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
import logging
from django.core.cache import get_cache
from django.conf import settings
from django.contrib.auth.models import Group
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile
from vm.models import Instance, Node, InstanceTemplate
from ..store_api import Store
logger = logging.getLogger(__name__)
class IndexView(LoginRequiredMixin, TemplateView):
template_name = "dashboard/index.html"
def get_context_data(self, **kwargs):
user = self.request.user
context = super(IndexView, self).get_context_data(**kwargs)
# instances
favs = Instance.objects.filter(favourite__user=self.request.user)
instances = Instance.get_objects_with_level(
'user', user, disregard_superuser=True).filter(destroyed_at=None)
display = list(favs) + list(set(instances) - set(favs))
for d in display:
d.fav = True if d in favs else False
context.update({
'instances': display[:5],
'more_instances': instances.count() - len(instances[:5])
})
running = instances.filter(status='RUNNING')
stopped = instances.exclude(status__in=('RUNNING', 'NOSTATE'))
context.update({
'running_vms': running[:20],
'running_vm_num': running.count(),
'stopped_vm_num': stopped.count()
})
# nodes
if user.is_superuser:
nodes = Node.objects.all()
context.update({
'nodes': nodes[:5],
'more_nodes': nodes.count() - len(nodes[:5]),
'sum_node_num': nodes.count(),
'node_num': {
'running': Node.get_state_count(True, True),
'missing': Node.get_state_count(False, True),
'disabled': Node.get_state_count(True, False),
'offline': Node.get_state_count(False, False)
}
})
# groups
if user.has_module_perms('auth'):
profiles = GroupProfile.get_objects_with_level('operator', user)
groups = Group.objects.filter(groupprofile__in=profiles)
context.update({
'groups': groups[:5],
'more_groups': groups.count() - len(groups[:5]),
})
# template
if user.has_perm('vm.create_template'):
context['templates'] = InstanceTemplate.get_objects_with_level(
'operator', user, disregard_superuser=True).all()[:5]
# toplist
if settings.STORE_URL:
cache_key = "files-%d" % self.request.user.pk
cache = get_cache("default")
files = cache.get(cache_key)
if not files:
try:
store = Store(self.request.user)
toplist = store.toplist()
quota = store.get_quota()
files = {'toplist': toplist, 'quota': quota}
except Exception:
logger.exception("Unable to get tolist for %s",
unicode(self.request.user))
files = {'toplist': []}
cache.set(cache_key, files, 300)
context['files'] = files
else:
context['no_store'] = True
return context
class HelpView(TemplateView):
def get_context_data(self, *args, **kwargs):
ctx = super(HelpView, self).get_context_data(*args, **kwargs)
ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
"store": settings.STORE_URL})
return ctx
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
import json
import logging
from os.path import join, normpath, dirname, basename
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.cache import get_cache
from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import redirect, render_to_response, render
from django.template import RequestContext
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_GET, require_POST
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
from ..store_api import Store, NoStoreException, NotOkException
logger = logging.getLogger(__name__)
class StoreList(LoginRequiredMixin, TemplateView):
template_name = "dashboard/store/list.html"
def get_context_data(self, **kwargs):
context = super(StoreList, self).get_context_data(**kwargs)
directory = self.request.GET.get("directory", "/")
directory = "/" if not len(directory) else directory
store = Store(self.request.user)
context['root'] = store.list(directory)
context['quota'] = store.get_quota()
context['up_url'] = self.create_up_directory(directory)
context['current'] = directory
context['next_url'] = "%s%s?directory=%s" % (
settings.DJANGO_URL.rstrip("/"),
reverse("dashboard.views.store-list"), directory)
return context
def get(self, *args, **kwargs):
try:
if self.request.is_ajax():
context = self.get_context_data(**kwargs)
return render_to_response(
"dashboard/store/_list-box.html",
RequestContext(self.request, context),
)
else:
return super(StoreList, self).get(*args, **kwargs)
except NoStoreException:
messages.warning(self.request, _("No store."))
except NotOkException:
messages.warning(self.request, _("Store has some problems now."
" Try again later."))
except Exception as e:
logger.critical("Something is wrong with store: %s", unicode(e))
messages.warning(self.request, _("Unknown store error."))
return redirect("/")
def create_up_directory(self, directory):
path = normpath(join('/', directory, '..'))
if not path.endswith("/"):
path += "/"
return path
@require_GET
@login_required
def store_download(request):
path = request.GET.get("path")
try:
url = Store(request.user).request_download(path)
except Exception:
messages.error(request, _("Something went wrong during download."))
logger.exception("Unable to download, "
"maybe it is already deleted")
return redirect(reverse("dashboard.views.store-list"))
return redirect(url)
@require_GET
@login_required
def store_upload(request):
directory = request.GET.get("directory", "/")
try:
action = Store(request.user).request_upload(directory)
except Exception:
logger.exception("Unable to upload")
messages.error(request, _("Unable to upload file."))
return redirect("/")
next_url = "%s%s?directory=%s" % (
settings.DJANGO_URL.rstrip("/"),
reverse("dashboard.views.store-list"), directory)
return render(request, "dashboard/store/upload.html",
{'directory': directory, 'action': action,
'next_url': next_url})
@require_GET
@login_required
def store_get_upload_url(request):
current_dir = request.GET.get("current_dir")
try:
url = Store(request.user).request_upload(current_dir)
except Exception:
logger.exception("Unable to upload")
messages.error(request, _("Unable to upload file."))
return redirect("/")
return HttpResponse(
json.dumps({'url': url}), content_type="application/json")
class StoreRemove(LoginRequiredMixin, TemplateView):
template_name = "dashboard/store/remove.html"
def get_context_data(self, *args, **kwargs):
context = super(StoreRemove, self).get_context_data(*args, **kwargs)
path = self.request.GET.get("path", "/")
if path == "/":
SuspiciousOperation()
context['path'] = path
context['is_dir'] = path.endswith("/")
if context['is_dir']:
context['directory'] = path
else:
context['directory'] = dirname(path)
context['name'] = basename(path)
return context
def get(self, *args, **kwargs):
try:
return super(StoreRemove, self).get(*args, **kwargs)
except NoStoreException:
return redirect("/")
def post(self, *args, **kwargs):
path = self.request.POST.get("path")
try:
Store(self.request.user).remove(path)
except Exception:
logger.exception("Unable to remove %s", path)
messages.error(self.request, _("Unable to remove %s.") % path)
return redirect("%s?directory=%s" % (
reverse("dashboard.views.store-list"),
dirname(dirname(path)),
))
@require_POST
@login_required
def store_new_directory(request):
path = request.POST.get("path")
name = request.POST.get("name")
try:
Store(request.user).new_folder(join(path, name))
except Exception:
logger.exception("Unable to create folder %s in %s for %s",
name, path, unicode(request.user))
messages.error(request, _("Unable to create folder."))
return redirect("%s?directory=%s" % (
reverse("dashboard.views.store-list"), path))
@require_POST
@login_required
def store_refresh_toplist(request):
cache_key = "files-%d" % request.user.pk
cache = get_cache("default")
try:
store = Store(request.user)
toplist = store.toplist()
quota = store.get_quota()
files = {'toplist': toplist, 'quota': quota}
except Exception:
logger.exception("Can't get toplist of %s", unicode(request.user))
files = {'toplist': []}
cache.set(cache_key, files, 300)
return redirect(reverse("dashboard.index"))
...@@ -447,21 +447,22 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -447,21 +447,22 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
self.time_of_suspend, self.time_of_delete = self.get_renew_times() self.time_of_suspend, self.time_of_delete = self.get_renew_times()
super(Instance, self).clean(*args, **kwargs) super(Instance, self).clean(*args, **kwargs)
def vm_state_changed(self, new_state): def vm_state_changed(self, new_state, new_node=False):
if new_node is False: # None would be a valid value
new_node = self.node
# log state change # log state change
try: try:
act = InstanceActivity.create( act = InstanceActivity.create(
code_suffix='vm_state_changed', code_suffix='vm_state_changed',
readable_name=create_readable( readable_name=create_readable(
ugettext_noop("vm state changed to %(state)s"), ugettext_noop("vm state changed to %(state)s on %(node)s"),
state=new_state), state=new_state, node=new_node),
instance=self) instance=self)
except ActivityInProgressError: except ActivityInProgressError:
pass # discard state change if another activity is in progress. pass # discard state change if another activity is in progress.
else: else:
if new_state == 'STOPPED': if self.node != new_node:
self.vnc_port = None self.node = new_node
self.node = None
self.save() self.save()
act.finished = act.started act.finished = act.started
act.resultant_state = new_state act.resultant_state = new_state
......
...@@ -359,7 +359,8 @@ class Node(OperatedMixin, TimeStampedModel): ...@@ -359,7 +359,8 @@ class Node(OperatedMixin, TimeStampedModel):
logger.info('Node %s update: instance %s missing from ' logger.info('Node %s update: instance %s missing from '
'libvirt', self, i['id']) 'libvirt', self, i['id'])
# Set state to STOPPED when instance is missing # Set state to STOPPED when instance is missing
self.instance_set.get(id=i['id']).vm_state_changed('STOPPED') self.instance_set.get(id=i['id']).vm_state_changed(
'STOPPED', None)
else: else:
if d != i['state']: if d != i['state']:
logger.info('Node %s update: instance %s state changed ' logger.info('Node %s update: instance %s state changed '
...@@ -368,9 +369,11 @@ class Node(OperatedMixin, TimeStampedModel): ...@@ -368,9 +369,11 @@ class Node(OperatedMixin, TimeStampedModel):
self.instance_set.get(id=i['id']).vm_state_changed(d) self.instance_set.get(id=i['id']).vm_state_changed(d)
del domains[i['id']] del domains[i['id']]
for i in domains.keys(): for id, state in domains.iteritems():
logger.info('Node %s update: domain %s in libvirt but not in db.', from .instance import Instance
self, i) logger.error('Node %s update: domain %s in libvirt but not in db.',
self, id)
Instance.objects.get(id=id).vm_state_changed(state, self)
@classmethod @classmethod
def get_state_count(cls, online, enabled): def get_state_count(cls, online, enabled):
......
...@@ -629,7 +629,6 @@ class ShutdownOperation(InstanceOperation): ...@@ -629,7 +629,6 @@ class ShutdownOperation(InstanceOperation):
def _operation(self, task=None): def _operation(self, task=None):
self.instance.shutdown_vm(task=task) self.instance.shutdown_vm(task=task)
self.instance.yield_node() self.instance.yield_node()
self.instance.yield_vnc_port()
def on_abort(self, activity, error): def on_abort(self, activity, error):
if isinstance(error, TimeLimitExceeded): if isinstance(error, TimeLimitExceeded):
...@@ -670,9 +669,7 @@ class ShutOffOperation(InstanceOperation): ...@@ -670,9 +669,7 @@ class ShutOffOperation(InstanceOperation):
with activity.sub_activity('delete_vm'): with activity.sub_activity('delete_vm'):
self.instance.delete_vm() self.instance.delete_vm()
# Clear node and VNC port association
self.instance.yield_node() self.instance.yield_node()
self.instance.yield_vnc_port()
register_operation(ShutOffOperation) register_operation(ShutOffOperation)
......
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