Commit c08afb10 by Szeberényi Imre

Form batman:

  two-factor fix
  vm-details fix
  storage model (duisk types?)
parent 2a3a6018
...@@ -333,7 +333,7 @@ DJANGO_APPS = ( ...@@ -333,7 +333,7 @@ DJANGO_APPS = (
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.sites', # 'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
...@@ -357,6 +357,7 @@ THIRD_PARTY_APPS = ( ...@@ -357,6 +357,7 @@ THIRD_PARTY_APPS = (
'statici18n', 'statici18n',
'django_sshkey', 'django_sshkey',
'pipeline', 'pipeline',
'qrcode2',
) )
...@@ -413,7 +414,7 @@ LOGGING = { ...@@ -413,7 +414,7 @@ LOGGING = {
'address': '/dev/log', 'address': '/dev/log',
# 'socktype': SOCK_STREAM, # 'socktype': SOCK_STREAM,
# 'address': ('host', '514'), # 'address': ('host', '514'),
} },
}, },
'loggers': { 'loggers': {
'django.request': { 'django.request': {
...@@ -531,6 +532,7 @@ LOGIN_REDIRECT_URL = "/" ...@@ -531,6 +532,7 @@ LOGIN_REDIRECT_URL = "/"
AGENT_DIR = get_env_variable( AGENT_DIR = get_env_variable(
'DJANGO_AGENT_DIR', join(unicode(expanduser("~")), 'agent')) 'DJANGO_AGENT_DIR', join(unicode(expanduser("~")), 'agent'))
# AGENT_DIR is the root directory for the agent. # AGENT_DIR is the root directory for the agent.
# The directory structure SHOULD be: # The directory structure SHOULD be:
# /home/username/agent # /home/username/agent
...@@ -542,12 +544,45 @@ AGENT_DIR = get_env_variable( ...@@ -542,12 +544,45 @@ AGENT_DIR = get_env_variable(
# #
try: try:
git_env = {'GIT_DIR': join(join(AGENT_DIR, "agent-linux"), '.git')} git_env = {'GIT_DIR': join(join(AGENT_DIR, "agent"), ".git")}
AGENT_VERSION = check_output( AGENT_VERSION = check_output(
('git', 'log', '-1', r'--pretty=format:%h', 'HEAD'), env=git_env) ('git', 'log', '-1', r'--pretty=format:%h', 'HEAD'), env=git_env)
except: except:
AGENT_VERSION = None AGENT_VERSION = None
print("LEGACY VERSION: %s ------" % AGENT_VERSION)
#### NEW ####
####
# AGENT_VERSIONS is a dict eg: { "Linux" : "vers1" , "Windows" : "vers2", .... }
# Normally it is fetched from Jason fromatted AGENT_DIR/wersions.txt file
#
# The dir namings ha changed:
# The same, but the dir names are lowercase and generated like this:
# agent-(agent_system)-(version). Eg: agent-linux-vers1, agent-window-vers2
try:
with open("%s/versions.txt" % AGENT_DIR, "r") as f:
AGENT_VERSIONS = loads(f.read())
print("-----KONWN VERSIONS-----")
print(AGENT_VERSIONS)
except:
print("Format ERROR in versions.txt !!!! ") # TODO more error reposrting
AGENT_VERSIONS = None
## PUBLIC function for getting the latest version ad the DIR
def GET_AGENT_VERSION_BY_SYSTEM(agent_system):
if agent_system is None:
return None, None
if type(AGENT_VERSIONS) is not dict:
# legacy naming
return AGENT_VERSION, AGENT_DIR + "/agent-" + agent_system.lower()
ret = AGENT_VERSIONS.get(agent_system)
if ret is None:
return None, None
return ret, AGENT_DIR + "/agent-" + agent_system.lower() + "-" + ret
LOCALE_PATHS = (join(SITE_ROOT, 'locale'), ) LOCALE_PATHS = (join(SITE_ROOT, 'locale'), )
COMPANY_NAME = get_env_variable("COMPANY_NAME", "BME IK 2015") COMPANY_NAME = get_env_variable("COMPANY_NAME", "BME IK 2015")
......
...@@ -543,7 +543,7 @@ class TemplateForm(forms.ModelForm): ...@@ -543,7 +543,7 @@ class TemplateForm(forms.ModelForm):
else: else:
self.allowed_fields = ( self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags', 'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease', 'has_agent') 'arch', 'lease', 'has_agent',)
if (self.user.has_perm('vm.change_template_resources') or if (self.user.has_perm('vm.change_template_resources') or
not self.instance.pk): not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) - self.allowed_fields += tuple(set(self.fields.keys()) -
...@@ -972,11 +972,14 @@ class VmImportDiskForm(OperationForm): ...@@ -972,11 +972,14 @@ class VmImportDiskForm(OperationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') self.user = kwargs.pop('user')
super(VmImportDiskForm, self).__init__(*args, **kwargs) super(VmImportDiskForm, self).__init__(*args, **kwargs)
allowed = ()
allowed = allowed + Disk.EXPORT_FORMATS
print(allowed)
disk_paths = Store(self.user).get_files_with_exts( disk_paths = Store(self.user).get_files_with_exts(
[f[0] for f in Disk.EXPORT_FORMATS] [f[0] for f in allowed]
) )
disk_filenames = [os.path.basename(item) for item in disk_paths] disk_filenames = [os.path.basename(item) for item in disk_paths]
#raise Error(disk_filenames)
self.choices = zip(disk_paths, disk_filenames) self.choices = zip(disk_paths, disk_filenames)
self.fields['name'] = forms.CharField(max_length=100, label=_('Name')) self.fields['name'] = forms.CharField(max_length=100, label=_('Name'))
......
{% extends "dashboard/base.html" %} {% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% load qrcode2 %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
...@@ -27,8 +29,9 @@ ...@@ -27,8 +29,9 @@
Your secret key is: <strong>{{ secret }}</strong> Your secret key is: <strong>{{ secret }}</strong>
{% endblocktrans %} {% endblocktrans %}
</span> </span>
<img src="//chart.googleapis.com/chart?chs=255x255&chld=L|0&cht=qr&chl={{ uri }}"/> {# <img src="//chart.googleapis.com/chart?chs=255x255&chld=L|0&cht=qr&chl={{ uri }}"/> #}
<small><a href="{{ uri }}">{{ uri }}</a></small> <img src="{{ uri | qrcode_src }}" with="300"i height=300 alt="{{ uri }}">
{# <small><a href="{{ uri }}">{{ uri }}</a></small> #}
</div> </div>
<hr /> <hr />
<div id="two-factor-confirm"> <div id="two-factor-confirm">
......
...@@ -112,15 +112,13 @@ ...@@ -112,15 +112,13 @@
<dd>{{ instance.access_method|upper }}</dd> <dd>{{ instance.access_method|upper }}</dd>
<dt>{% trans "Host" %}</dt> <dt>{% trans "Host" %}</dt>
<dd> <dd>
{% if instance.get_connect_port %} {% if instance.get_connect_port %}{{ instance.get_connect_host }}:<strong>{{ instance.get_connect_port }}</strong>
{{ instance.get_connect_host }}:<strong>{{ instance.get_connect_port }}</strong>
{% elif instance.interface_set.count < 1%} {% elif instance.interface_set.count < 1%}
<strong>{% trans "The VM doesn't have any network interface." %}</strong> <strong>{% trans "The VM doesn't have any network interface." %}</strong>
{% else %} {% else %}
<strong>{% trans "The required port for this protocol is not forwarded." %}</strong> <strong>{% trans "The required port for this protocol is not forwarded." %}</strong>
{% endif %} {% endif %}
</dd> </dd>
{% if instance.ipv6 and instance.get_connect_port %} {% if instance.ipv6 and instance.get_connect_port %}
<dt>{% trans "Host (IPv6)" %}</dt> <dt>{% trans "Host (IPv6)" %}</dt>
<dd>{{ ipv6_host }}:<strong>{{ ipv6_port }}</strong></dd> <dd>{{ ipv6_host }}:<strong>{{ ipv6_port }}</strong></dd>
......
...@@ -58,7 +58,8 @@ from .views import ( ...@@ -58,7 +58,8 @@ from .views import (
MessageList, MessageDetail, MessageCreate, MessageDelete, MessageList, MessageDetail, MessageCreate, MessageDelete,
EnableTwoFactorView, DisableTwoFactorView, EnableTwoFactorView, DisableTwoFactorView,
AclUserGroupAutocomplete, AclUserAutocomplete, AclUserGroupAutocomplete, AclUserAutocomplete,
RescheduleView, GroupImportView, GroupExportView RescheduleView, GroupImportView, GroupExportView,
start_instance, shutdown_instance, get_instance
) )
from .views.node import node_ops from .views.node import node_ops
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
...@@ -78,7 +79,9 @@ urlpatterns = [ ...@@ -78,7 +79,9 @@ urlpatterns = [
name="dashboard.views.lease-delete"), name="dashboard.views.lease-delete"),
url(r'^lease/(?P<pk>\d+)/acl/$', LeaseAclUpdateView.as_view(), url(r'^lease/(?P<pk>\d+)/acl/$', LeaseAclUpdateView.as_view(),
name="dashboard.views.lease-acl"), name="dashboard.views.lease-acl"),
url(r'^vmstart/(?P<pk>\d+)', start_instance),
url(r'^vmshutdown/(?P<pk>\d+)', shutdown_instance),
url(r'^vmget/(?P<pk>\d+)', get_instance),
url(r'^template/create/$', TemplateCreate.as_view(), url(r'^template/create/$', TemplateCreate.as_view(),
name="dashboard.views.template-create"), name="dashboard.views.template-create"),
url(r'^template/choose/$', TemplateChoose.as_view(), url(r'^template/choose/$', TemplateChoose.as_view(),
......
...@@ -723,7 +723,6 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'): ...@@ -723,7 +723,6 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
# authenticate the remote user # authenticate the remote user
session_info = response.session_info() session_info = response.session_info()
if callable(attribute_mapping): if callable(attribute_mapping):
attribute_mapping = attribute_mapping() attribute_mapping = attribute_mapping()
if callable(create_unknown_user): if callable(create_unknown_user):
......
...@@ -36,7 +36,7 @@ from django.template.loader import render_to_string ...@@ -36,7 +36,7 @@ from django.template.loader import render_to_string
from django.utils.translation import ( from django.utils.translation import (
ugettext as _, ugettext_noop, ungettext_lazy, ugettext as _, ugettext_noop, ungettext_lazy,
) )
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET, require_POST
from django.views.generic import ( from django.views.generic import (
UpdateView, ListView, TemplateView UpdateView, ListView, TemplateView
) )
...@@ -70,6 +70,7 @@ from ..forms import ( ...@@ -70,6 +70,7 @@ from ..forms import (
VmRemoveInterfaceForm, VmRemoveInterfaceForm,
VmRenameForm, VmRenameForm,
) )
from vm.operations import DeployOperation, ShutdownOperation
from request.models import TemplateAccessType, LeaseType from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm from request.forms import LeaseRequestForm, TemplateRequestForm
from ..models import Favourite from ..models import Favourite
...@@ -1266,6 +1267,51 @@ def get_disk_download_status(request, pk): ...@@ -1266,6 +1267,51 @@ def get_disk_download_status(request, pk):
content_type="application/json", content_type="application/json",
) )
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
@require_POST
def start_instance(request, pk):
instance = Instance.objects.get(pk=pk)
DeployOperation(instance).call(node=None, user=request.user)
return HttpResponse(
json.dumps({
'id': pk,
'op': 'deploy'
}),
content_type="application/json",
)
@csrf_exempt
@require_POST
def shutdown_instance(request, pk):
instance = Instance.objects.get(pk=pk)
ShutdownOperation(instance).call(user=request.user)
return HttpResponse(
json.dumps({
'id': pk,
'op': 'shutdown'
}),
content_type="application/json",
)
@require_GET
def get_instance(request, pk):
instance = Instance.objects.get(pk=pk)
if instance.owner != request.user:
raise PermissionDenied()
return HttpResponse(
json.dumps({
'id': pk,
'name': instance.name,
'status': instance.status,
'pw': instance.pw,
'ipv4': str(instance.ipv4),
'hostipv4': instance.get_connect_host(use_ipv6=False),
'sshport': instance.get_connect_port(use_ipv6=False)
}),
content_type="application/json",
)
class ClientCheck(LoginRequiredMixin, TemplateView): class ClientCheck(LoginRequiredMixin, TemplateView):
......
...@@ -126,7 +126,8 @@ class Disk(TimeStampedModel): ...@@ -126,7 +126,8 @@ class Disk(TimeStampedModel):
EXPORT_FORMATS = (('qcow2', _('QEMU disk image')), EXPORT_FORMATS = (('qcow2', _('QEMU disk image')),
('vmdk', _('VMware disk image')), ('vmdk', _('VMware disk image')),
('vdi', _('VirtualBox disk image')), ('vdi', _('VirtualBox disk image')),
('vpc', _('HyperV disk image'))) ('vpc', _('HyperV disk image')),
('iso', 'ISO image'))
name = CharField(blank=True, max_length=100, verbose_name=_("name")) name = CharField(blank=True, max_length=100, verbose_name=_("name"))
filename = CharField(max_length=256, unique=True, filename = CharField(max_length=256, unique=True,
verbose_name=_("filename")) verbose_name=_("filename"))
......
...@@ -873,7 +873,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin, ...@@ -873,7 +873,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
remote_queue = ("vm", "slow") remote_queue = ("vm", "slow")
remote_timeout = 180 remote_timeout = 180
def _operation(self, task): def _operation(self, task=vm_tasks.shutdown):
super(ShutdownOperation, self)._operation(task=task) super(ShutdownOperation, self)._operation(task=task)
self.instance.yield_node() self.instance.yield_node()
...@@ -1575,19 +1575,6 @@ class AgentStartedOperation(InstanceOperation): ...@@ -1575,19 +1575,6 @@ class AgentStartedOperation(InstanceOperation):
self.instance._change_ip(parent_activity=activity) self.instance._change_ip(parent_activity=activity)
self.instance._restart_networking(parent_activity=activity) self.instance._restart_networking(parent_activity=activity)
new_version = settings.AGENT_VERSION
if new_version and old_version and new_version != old_version:
try:
self.instance.update_agent(
parent_activity=activity, agent_system=agent_system)
except TimeoutError:
pass
else:
activity.sub_activity(
'agent_wait', readable_name=ugettext_noop(
"wait agent restarting"), interruptible=True)
return # agent is going to restart
if not self.initialized: if not self.initialized:
try: try:
self.measure_boot_time() self.measure_boot_time()
...@@ -1600,6 +1587,20 @@ class AgentStartedOperation(InstanceOperation): ...@@ -1600,6 +1587,20 @@ class AgentStartedOperation(InstanceOperation):
self.instance._set_time(parent_activity=activity) self.instance._set_time(parent_activity=activity)
self.instance._set_hostname(parent_activity=activity) self.instance._set_hostname(parent_activity=activity)
new_version = settings.GET_AGENT_VERSION_BY_SYSTEM(agent_system)[0]
# if agent_system and new_version and (old_version is None or ("NOAGENTUPDATE" not in old_version and new_version != old_version)) :
if agent_system and new_version and old_version and "NOAGENTUPDATE" not in old_version and new_version != old_version :
try:
self.instance.update_agent(
parent_activity=activity, agent_system=agent_system)
except TimeoutError:
pass
else:
activity.sub_activity(
'agent_wait', readable_name=ugettext_noop(
"wait agent restarting"), interruptible=True)
return # agent is going to restart
@register_operation @register_operation
class CleanupOperation(SubOperationMixin, RemoteAgentOperation): class CleanupOperation(SubOperationMixin, RemoteAgentOperation):
id = '_cleanup' id = '_cleanup'
...@@ -1658,11 +1659,12 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1658,11 +1659,12 @@ class UpdateAgentOperation(RemoteAgentOperation):
def get_activity_name(self, kwargs): def get_activity_name(self, kwargs):
return create_readable( return create_readable(
ugettext_noop('update agent to %(version)s'), ugettext_noop('update agent'))
version=settings.AGENT_VERSION) # ugettext_noop('update agent to %(version)s'),
# version=settings.GET_AGENT_VERSION_BY_SYSTEM(agent_system)[0])
@staticmethod @staticmethod
def create_linux_tar(): def create_linux_tar(agent_system):
def exclude(tarinfo): def exclude(tarinfo):
ignored = ('./.', './misc', './windows') ignored = ('./.', './misc', './windows')
if any(tarinfo.name.startswith(x) for x in ignored): if any(tarinfo.name.startswith(x) for x in ignored):
...@@ -1670,13 +1672,14 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1670,13 +1672,14 @@ class UpdateAgentOperation(RemoteAgentOperation):
else: else:
return tarinfo return tarinfo
_vers, _dir = settings.GET_AGENT_VERSION_BY_SYSTEM(agent_system)
f = StringIO() f = StringIO()
with TarFile.open(fileobj=f, mode='w:gz') as tar: with TarFile.open(fileobj=f, mode='w:gz') as tar:
agent_path = os.path.join(settings.AGENT_DIR, "agent-linux") agent_path = _dir
tar.add(agent_path, arcname='.', filter=exclude) tar.add(agent_path, arcname='.', filter=exclude)
version_fileobj = StringIO(settings.AGENT_VERSION) version_fileobj = StringIO(_vers)
version_info = TarInfo(name='version.txt') version_info = TarInfo(name='version.txt')
version_info.size = len(version_fileobj.buf) version_info.size = len(version_fileobj.buf)
tar.addfile(version_info, version_fileobj) tar.addfile(version_info, version_fileobj)
...@@ -1684,14 +1687,15 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1684,14 +1687,15 @@ class UpdateAgentOperation(RemoteAgentOperation):
return encodestring(f.getvalue()).replace('\n', '') return encodestring(f.getvalue()).replace('\n', '')
@staticmethod @staticmethod
def create_windows_tar(): def create_windows_tar(agent_system):
_vers, _dir = settings.GET_AGENT_VERSION_BY_SYSTEM(agent_system)
f = StringIO() f = StringIO()
agent_path = os.path.join(settings.AGENT_DIR, "agent-win") agent_path = _dir
with TarFile.open(fileobj=f, mode='w|gz') as tar: with TarFile.open(fileobj=f, mode='w|gz') as tar:
tar.add(agent_path, arcname='.') tar.add(agent_path, arcname='.')
version_fileobj = StringIO(settings.AGENT_VERSION) version_fileobj = StringIO(_vers)
version_info = TarInfo(name='version.txt') version_info = TarInfo(name='version.txt')
version_info.size = len(version_fileobj.buf) version_info.size = len(version_fileobj.buf)
tar.addfile(version_info, version_fileobj) tar.addfile(version_info, version_fileobj)
...@@ -1699,15 +1703,15 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1699,15 +1703,15 @@ class UpdateAgentOperation(RemoteAgentOperation):
return encodestring(f.getvalue()).replace('\n', '') return encodestring(f.getvalue()).replace('\n', '')
def _operation(self, user, activity, agent_system): def _operation(self, user, activity, agent_system):
_vers, _dir = settings.GET_AGENT_VERSION_BY_SYSTEM(agent_system)
queue = self._get_remote_queue() queue = self._get_remote_queue()
instance = self.instance instance = self.instance
if agent_system == "Windows": if agent_system == "Windows":
executable = os.listdir( executable = sorted(os.listdir(_dir))[0]
os.path.join(settings.AGENT_DIR, "agent-win"))[0] data = self.create_windows_tar(agent_system)
data = self.create_windows_tar()
elif agent_system == "Linux": elif agent_system == "Linux":
executable = "" executable = ""
data = self.create_linux_tar() data = self.create_linux_tar(agent_system)
else: else:
# Legacy update method # Legacy update method
executable = "" executable = ""
...@@ -1720,7 +1724,7 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1720,7 +1724,7 @@ class UpdateAgentOperation(RemoteAgentOperation):
chunk_size = 1024 * 1024 chunk_size = 1024 * 1024
chunk_number = 0 chunk_number = 0
index = 0 index = 0
filename = settings.AGENT_VERSION + ".tar" filename = _vers + ".tar"
while True: while True:
chunk = data[index:index + chunk_size] chunk = data[index:index + chunk_size]
if chunk: if chunk:
...@@ -1734,7 +1738,7 @@ class UpdateAgentOperation(RemoteAgentOperation): ...@@ -1734,7 +1738,7 @@ class UpdateAgentOperation(RemoteAgentOperation):
agent_tasks.update.apply_async( agent_tasks.update.apply_async(
queue=queue, queue=queue,
args=(instance.vm_name, filename, executable, checksum) args=(instance.vm_name, filename, executable, checksum)
).get(timeout=60) ).get(timeout=120)
break break
......
...@@ -50,17 +50,19 @@ def garbage_collector(offset=timezone.timedelta(seconds=20)): ...@@ -50,17 +50,19 @@ def garbage_collector(offset=timezone.timedelta(seconds=20)):
for i in Instance.objects.filter(destroyed_at=None).all(): for i in Instance.objects.filter(destroyed_at=None).all():
logger.debug("Garbage_collector work_package:%d %s: %s:", work_package, i.pk, now > i.time_of_delete) logger.debug("Garbage_collector work_package:%d %s: %s:", work_package, i.pk, now > i.time_of_delete)
if i.time_of_delete and now > i.time_of_delete + grace_period and work_package > 0: if i.time_of_delete and now > i.time_of_delete + grace_period and work_package > 0:
logger.debug("Garbage_collector delete")
work_package -= 1 work_package -= 1
i.destroy.async(system=True) logger.debug("Garbage_collector delete")
logger.info("Expired instance %d destroyed.", i.pk) logger.info("Expired instance %d destroyed.", i.pk)
try: try:
i.destroy.async(system=True)
i.owner.profile.notify( i.owner.profile.notify(
ugettext_noop('%(instance)s destroyed'), ugettext_noop('%(instance)s destroyed'),
ugettext_noop( ugettext_noop(
'Your instance <a href="%(url)s">%(instance)s</a> ' 'Your instance <a href="%(url)s">%(instance)s</a> '
'has been destroyed due to expiration.'), 'has been destroyed due to expiration.'),
instance=i.name, url=i.get_absolute_url()) instance=i.name, url=i.get_absolute_url())
except ActivityInProgressError:
logger.error("Expired instance %d can't be destroyed due the AtctivityInPorgressError.", i.pk)
except Exception as e: except Exception as e:
logger.debug('Could not notify owner of instance %d .%s', logger.debug('Could not notify owner of instance %d .%s',
i.pk, unicode(e)) i.pk, unicode(e))
...@@ -78,7 +80,7 @@ def garbage_collector(offset=timezone.timedelta(seconds=20)): ...@@ -78,7 +80,7 @@ def garbage_collector(offset=timezone.timedelta(seconds=20)):
'You can resume or destroy it.'), 'You can resume or destroy it.'),
instance=i.name, url=i.get_absolute_url()) instance=i.name, url=i.get_absolute_url())
except ActivityInProgressError: except ActivityInProgressError:
logger.error("Expired instance %d can't be destroyed due the AtctivityInPorgressError.", i.pk) logger.error("Expired instance %d can't be suspended due the AtctivityInPorgressError.", i.pk)
except Exception as e: except Exception as e:
logger.info('Could not notify owner of instance %d .%s', logger.info('Could not notify owner of instance %d .%s',
i.pk, unicode(e)) i.pk, unicode(e))
......
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