Commit 113c216a by Bach Dániel

Merge branch 'issue-298' into 'master'

Issue 298

closes #298
parents 34bf3f22 0a778b39
......@@ -273,3 +273,4 @@ def register_operation(op_cls, op_id=None, target_cls=None):
setattr(target_cls, operation_registry_name, dict())
getattr(target_cls, operation_registry_name)[op_id] = op_cls
return op_cls
......@@ -19,8 +19,7 @@ from __future__ import absolute_import
from django.contrib.auth.models import Group, User
from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
LinkColumn)
from django_tables2.columns import TemplateColumn, Column, LinkColumn
from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _
......@@ -40,8 +39,10 @@ class NodeListTable(Table):
attrs={'th': {'class': 'node-list-table-thin'}},
)
enabled = BooleanColumn(
get_status_display = Column(
verbose_name=_("Status"),
attrs={'th': {'class': 'node-list-table-thin'}},
order_by=("enabled", "schedule_enabled"),
)
name = TemplateColumn(
......@@ -66,20 +67,12 @@ class NodeListTable(Table):
orderable=False,
)
actions = TemplateColumn(
verbose_name=_("Actions"),
attrs={'th': {'class': 'node-list-table-thin'}},
template_code=('{% include "dashboard/node-list/column-'
'actions.html" with btn_size="btn-xs" %}'),
orderable=False,
)
class Meta:
model = Node
attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')}
fields = ('pk', 'name', 'host', 'enabled', 'priority', 'overcommit',
'number_of_VMs', )
fields = ('pk', 'name', 'host', 'get_status_display', 'priority',
'overcommit', 'number_of_VMs', )
class GroupListTable(Table):
......
......@@ -18,6 +18,8 @@ Choose a compute node to migrate {{obj}} to.
<li class="panel panel-default"><div class="panel-body">
<label for="migrate-to-{{n.pk}}">
<strong>{{ n }}</strong>
<div class="label label-primary"><i class="fa {{n.get_status_icon}}"></i>
{{n.get_status_display}}</div>
{% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
{% if selected == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
</label>
......
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to flush <strong>{{ object }}</strong>?
{%endblocktrans%}
{% endif %}
<div class="pull-right">
<form action="{% url "dashboard.views.flush-node" pk=node.pk %}?next={{next}}" method="POST">
{% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<input type="hidden" name="flush" value=""/>
<button class="btn btn-warning">{% trans "Yes" %}</button>
</form>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
......@@ -6,13 +6,12 @@
{% block content %}
<div class="body-content">
<div class="page-header">
<div class="pull-right" id="ops">
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<div class="pull-right" style="padding-top: 15px;">
<a title="{% trans "Rename" %}" href="#" class="btn btn-default btn-xs node-details-rename-button"><i class="fa fa-pencil"></i></a>
<a title="{% trans "Flush" %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-flush" href="{% url "dashboard.views.flush-node" pk=node.pk %}"><i class="fa fa-cloud-upload"></i></a>
<a title="{% trans "Enable" %}" style="display:{% if node.enabled %}none{% else %}inline-block{% endif %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-enable" href="{% url "dashboard.views.status-node" pk=node.pk %}?next={{ request.path }}"><i class="fa fa-check"></i></a>
<a title="{% trans "Disable" %}" style="display:{% if not node.enabled %}none{% else %}inline-block{% endif %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-enable" href="{% url "dashboard.views.status-node" pk=node.pk %}?next={{ request.path }}"><i class="fa fa-ban"></i></a>
<a title="{% trans "Delete" %}" data-node-pk="{{ node.pk }}" class="btn btn-default btn-xs real-link node-delete" href="{% url "dashboard.views.delete-node" pk=node.pk %}"><i class="fa fa-trash-o"></i></a>
<a title="{% trans "Help" %}" href="#" class="btn btn-default btn-xs node-details-help-button"><i class="fa fa-question"></i></a>
</div>
<h1>
<div id="node-details-rename">
......@@ -26,42 +25,34 @@
{{ node.name }}
</div>
</h1>
<div class="node-details-help js-hidden">
<ul style="list-style: none;">
<li>
<strong>{% trans "Rename" %}:</strong>
{% trans "Change the name of the node." %}
</li>
<li>
<strong>{% trans "Flush" %}:</strong>
{% trans "Disable node and move all instances to other one." %}
</li>
<li>
<strong>{% trans "Enable" %}:</strong>
{% trans "Enables node." %}
</li>
<li>
<strong>{% trans "Disable" %}:</strong>
{% trans "Disables node." %}
</li>
<li>
<strong>{% trans "Delete" %}:</strong>
{% trans "Remove node and it's host." %}
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-md-2" id="node-info-pane">
<div id="node-info-data" class="big">
<span id="node-details-state" class="label
{% if node.state == 'ONLINE' %}label-success
{% elif node.state == 'MISSING' %}label-danger
{% elif node.state == 'DISABLED' %}label-warning
{% elif node.state == 'OFFLINE' %}label-warning{% endif %}">
{% if node.state == 'ACTIVE' %}label-success
{% elif node.state == 'PASSIVE' %}label-warning
{% else %}label-danger{% endif %}">
<i class="fa {{ node.get_status_icon }}"></i> {{ node.get_status_display|upper }}
</span>
</div>
<div>
{% if node.enabled %}
<span class="label label-success">{% trans "Enabled" %}</span>
{% if node.schedule_enabled %}
<span class="label label-success">{% trans "Schedule enabled" %}</span>
{% else %}
<span class="label label-warning">{% trans "Schedule disabled" %}</span>
{% endif %}
{% else %}
<span class="label label-warning">{% trans "Disabled" %}</span>
{% endif %}
{% if node.online %}
<span class="label label-success">{% trans "Online" %}</span>
{% else %}
<span class="label label-warning">{% trans "Offline" %}</span>
{% endif %}
</div>
</div>
<div class="col-md-10" id="node-detail-pane">
<div class="panel panel-default" id="node-detail-panel">
......
{% load i18n %}
<div class="btn-group">
<button type="button" class="btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle" data-toggle="dropdown">Action
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu nojs-dropdown-toogle" role="menu">
<li>
<a href="#" class="node-details-rename-button">
<i class="fa fa-pencil"></i> {% trans "Rename" %}
</a>
</li>
<li>
<a data-node-pk="{{ record.pk }}" class="real-link node-flush" href="{% url "dashboard.views.flush-node" pk=record.pk %}">
<i class="fa fa-cloud-upload"></i> {% trans "Flush" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:none"{% else %}"display:block"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-check"></i> {% trans "Enable" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:block"{% else %}"display:none"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-times"></i> {% trans "Disable" %}
</a>
</li>
<li>
<a data-node-pk="{{ record.pk }}" class="real-link node-delete" href="{% url "dashboard.views.delete-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i> {% trans "Delete" %}
</a>
</li>
</ul>
</div>
......@@ -25,7 +25,7 @@ from .views import (
GroupDetailView, GroupList, IndexView,
InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail,
MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete,
NodeDetailView, NodeFlushView, NodeList, NodeStatus,
NodeDetailView, NodeList, NodeStatus,
NotificationView, PortDelete, TemplateAclUpdateView, TemplateCreate,
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDetailView,
......@@ -48,6 +48,8 @@ from .views import (
ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView,
)
from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops
autocomplete_light.autodiscover()
......@@ -75,8 +77,6 @@ urlpatterns = patterns(
name="dashboard.views.template-list"),
url(r"^template/delete/(?P<pk>\d+)/$", TemplateDelete.as_view(),
name="dashboard.views.template-delete"),
url(r'^vm/', include('dashboard.vm.urls')),
url(r'^vm/(?P<pk>\d+)/remove_port/(?P<rule>\d+)/$', PortDelete.as_view(),
name='dashboard.views.remove-port'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
......@@ -111,8 +111,6 @@ urlpatterns = patterns(
name="dashboard.views.delete-node"),
url(r'^node/status/(?P<pk>\d+)/$', NodeStatus.as_view(),
name="dashboard.views.status-node"),
url(r'^node/flush/(?P<pk>\d+)/$', NodeFlushView.as_view(),
name="dashboard.views.flush-node"),
url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'),
......@@ -215,3 +213,21 @@ urlpatterns = patterns(
url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(),
name="dashboard.views.token-login"),
)
urlpatterns += patterns(
'',
*(url(r'^vm/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_ops.iteritems())
)
urlpatterns += patterns(
'',
*(url(r'^vm/mass_op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_mass_ops.iteritems())
)
urlpatterns += patterns(
'',
*(url(r'^node/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in node_ops.iteritems())
)
......@@ -17,6 +17,7 @@
from __future__ import unicode_literals, absolute_import
import json
from collections import OrderedDict
from django.conf import settings
from django.contrib import messages
......@@ -37,7 +38,39 @@ from vm.models import Node, NodeActivity, Trait
from ..forms import TraitForm, HostForm, NodeForm
from ..tables import NodeListTable
from .util import GraphMixin
from .util import AjaxOperationMixin, OperationView, GraphMixin
def get_operations(instance, user):
ops = []
for k, v in node_ops.iteritems():
try:
op = v.get_op_by_object(instance)
op.check_auth(user)
op.check_precond()
except Exception:
ops.append(v.bind_to_object(instance, disabled=True))
else:
ops.append(v.bind_to_object(instance))
return ops
class NodeOperationView(AjaxOperationMixin, OperationView):
model = Node
context_object_name = 'node' # much simpler to mock object
node_ops = OrderedDict([
('activate', NodeOperationView.factory(
op='activate', icon='play-circle', effect='success')),
('passivate', NodeOperationView.factory(
op='passivate', icon='play-circle-o', effect='info')),
('disable', NodeOperationView.factory(
op='disable', icon='times-circle-o', effect='danger')),
('flush', NodeOperationView.factory(
op='flush', icon='paint-brush', effect='danger')),
])
class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin,
......@@ -54,6 +87,8 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin,
na = NodeActivity.objects.filter(
node=self.object, parent=None
).order_by('-started').select_related()
context['ops'] = get_operations(self.object, self.request.user)
context['op'] = {i.op: i for i in context['ops']}
context['activities'] = na
context['trait_form'] = form
context['graphite_enabled'] = (
......@@ -316,39 +351,3 @@ class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
else:
messages.success(request, success_message)
return redirect(self.get_success_url())
class NodeFlushView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/confirm/node-flush.html"
model = Node
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-node-flush.html']
else:
return ['dashboard/confirm/node-flush.html']
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
else:
return reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(NodeFlushView, self).get_context_data(**kwargs)
return context
def post(self, request, *args, **kwargs):
if request.POST.get('flush') is not None:
return self.__flush(request)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.get_object().pk}))
def __flush(self, request):
self.object = self.get_object()
self.object.flush.async(user=request.user)
success_message = _("Node successfully flushed.")
messages.success(request, success_message)
return redirect(self.get_success_url())
......@@ -395,7 +395,7 @@ class VmMigrateView(VmOperationView):
def get_context_data(self, **kwargs):
ctx = super(VmMigrateView, self).get_context_data(**kwargs)
ctx['nodes'] = [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"]
if n.online]
return ctx
def post(self, request, extra=None, *args, **kwargs):
......
# 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 django.conf.urls import patterns, url
from ..views import vm_ops, vm_mass_ops
urlpatterns = patterns(
'',
*(url(r'^(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_ops.iteritems())
)
urlpatterns += patterns(
'',
*(url(r'^mass_op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_mass_ops.iteritems())
)
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-09-16 12:15+0200\n"
"POT-Creation-Date: 2014-09-24 12:19+0200\n"
"PO-Revision-Date: 2014-09-03 12:51+0200\n"
"Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......@@ -38,9 +38,9 @@ msgstr ""
msgid "Select an option to proceed!"
msgstr "Válasszon a folytatáshoz."
#: dashboard/static/dashboard/dashboard.js:258
#: dashboard/static/dashboard/dashboard.js:306
#: dashboard/static/dashboard/dashboard.js:316
#: dashboard/static/dashboard/dashboard.js:259
#: dashboard/static/dashboard/dashboard.js:307
#: dashboard/static/dashboard/dashboard.js:317
#: static_collected/all.047675ebf594.js:3633
#: static_collected/all.047675ebf594.js:3681
#: static_collected/all.047675ebf594.js:3691
......
......@@ -55,7 +55,7 @@ def select_node(instance, nodes):
'''
# check required traits
nodes = [n for n in nodes
if n.enabled and n.online
if n.schedule_enabled and n.online
and has_traits(instance.req_traits.all(), n)]
if not nodes:
logger.warning('select_node: no usable node for %s', unicode(instance))
......
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Node.schedule_enabled'
db.add_column(u'vm_node', 'schedule_enabled',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Node.schedule_enabled'
db.delete_column(u'vm_node', 'schedule_enabled')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'firewall.domain': {
'Meta': {'object_name': 'Domain'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'})
},
u'firewall.group': {
'Meta': {'object_name': 'Group'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'firewall.host': {
'Meta': {'ordering': "('normalized_hostname', 'vlan')", 'unique_together': "(('hostname', 'vlan'),)", 'object_name': 'Host'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'external_ipv4': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Group']", 'null': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ipv4': ('firewall.fields.IPAddressField', [], {'unique': 'True', 'max_length': '100'}),
'ipv6': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'normalized_hostname': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '80', 'monitor': "'hostname'", 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"})
},
u'firewall.vlan': {
'Meta': {'object_name': 'Vlan'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Domain']"}),
'host_ipv6_prefixlen': ('django.db.models.fields.IntegerField', [], {'default': '112'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ipv6_template': ('django.db.models.fields.TextField', [], {'default': "'2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0'"}),
'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'network4': ('firewall.fields.IPNetworkField', [], {'max_length': '100'}),
'network6': ('firewall.fields.IPNetworkField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'network_type': ('django.db.models.fields.CharField', [], {'default': "'portforward'", 'max_length': '20'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'reverse_domain': ('django.db.models.fields.TextField', [], {'default': "'%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa'"}),
'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}),
'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'})
},
u'storage.datastore': {
'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'},
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
u'storage.disk': {
'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}),
'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}),
'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
},
u'vm.instance': {
'Meta': {'ordering': "(u'pk',)", 'object_name': 'Instance'},
'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'destroyed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'instance_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}),
'has_agent': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_base': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'to': u"orm['vm.Node']"}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'ram_size': ('django.db.models.fields.IntegerField', [], {}),
'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}),
'status': ('model_utils.fields.StatusField', [], {'default': "u'NOSTATE'", 'max_length': '100', u'no_check_for_status': 'True'}),
'status_changed': ('model_utils.fields.MonitorField', [], {'default': 'datetime.datetime.now', u'monitor': "u'status'"}),
'system': ('django.db.models.fields.TextField', [], {}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['vm.InstanceTemplate']"}),
'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'vnc_port': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'})
},
u'vm.instanceactivity': {
'Meta': {'ordering': "[u'-finished', u'-started', u'instance', u'-id']", 'object_name': 'InstanceActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'activity_log'", 'to': u"orm['vm.Instance']"}),
'interruptible': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['vm.InstanceActivity']"}),
'readable_name_data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
'result_data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
'resultant_state': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'succeeded': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'vm.instancetemplate': {
'Meta': {'ordering': "(u'name',)", 'object_name': 'InstanceTemplate'},
'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'template_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}),
'has_agent': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.InstanceTemplate']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'ram_size': ('django.db.models.fields.IntegerField', [], {}),
'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}),
'system': ('django.db.models.fields.TextField', [], {})
},
u'vm.interface': {
'Meta': {'ordering': "(u'-vlan__managed',)", 'object_name': 'Interface'},
'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']", 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'interface_set'", 'to': u"orm['vm.Instance']"}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'vm_interface'", 'to': u"orm['firewall.Vlan']"})
},
u'vm.interfacetemplate': {
'Meta': {'object_name': 'InterfaceTemplate'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'interface_set'", 'to': u"orm['vm.InstanceTemplate']"}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"})
},
u'vm.lease': {
'Meta': {'ordering': "[u'name']", 'object_name': 'Lease'},
'delete_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'suspend_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
},
u'vm.namedbaseresourceconfig': {
'Meta': {'object_name': 'NamedBaseResourceConfig'},
'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'max_ram_size': ('django.db.models.fields.IntegerField', [], {}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
'num_cores': ('django.db.models.fields.IntegerField', [], {}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'ram_size': ('django.db.models.fields.IntegerField', [], {})
},
u'vm.node': {
'Meta': {'ordering': "(u'-enabled', u'normalized_name')", 'object_name': 'Node'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
'normalized_name': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '100', 'monitor': "u'name'", 'blank': 'True'}),
'overcommit': ('django.db.models.fields.FloatField', [], {'default': '1.0'}),
'priority': ('django.db.models.fields.IntegerField', [], {}),
'schedule_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'})
},
u'vm.nodeactivity': {
'Meta': {'object_name': 'NodeActivity'},
'activity_code': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'activity_log'", 'to': u"orm['vm.Node']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['vm.NodeActivity']"}),
'readable_name_data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
'result_data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'succeeded': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'task_uuid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'vm.trait': {
'Meta': {'object_name': 'Trait'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}
}
complete_apps = ['vm']
\ No newline at end of file
......@@ -27,7 +27,7 @@ from django.db.models import (
FloatField, permalink, Sum
)
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.utils.translation import ugettext_lazy as _
from celery.exceptions import TimeoutError
from model_utils.models import TimeStampedModel
......@@ -37,7 +37,7 @@ from common.models import method_cache, WorkerNotFound, HumanSortField
from common.operations import OperatedMixin
from firewall.models import Host
from ..tasks import vm_tasks
from .activity import node_activity, NodeActivity
from .activity import NodeActivity
from .common import Trait
......@@ -72,6 +72,11 @@ class Node(OperatedMixin, TimeStampedModel):
enabled = BooleanField(verbose_name=_('enabled'), default=False,
help_text=_('Indicates whether the node can '
'be used for hosting.'))
schedule_enabled = BooleanField(verbose_name=_('schedule enabled'),
default=False, help_text=_(
'Indicates whether a vm can be '
'automatically scheduled to this '
'node.'))
traits = ManyToManyField(Trait, blank=True,
help_text=_("Declared traits."),
verbose_name=_('traits'))
......@@ -130,46 +135,30 @@ class Node(OperatedMixin, TimeStampedModel):
warn('Use Node.info["core_num"]', DeprecationWarning)
return self.info['core_num']
STATES = {False: {False: ('OFFLINE', _('offline')),
True: ('DISABLED', _('disabled'))},
True: {False: ('MISSING', _('missing')),
True: ('ONLINE', _('online'))}}
STATES = {None: ({True: ('MISSING', _('missing')),
False: ('OFFLINE', _('offline'))}),
False: {False: ('DISABLED', _('disabled'))},
True: {False: ('PASSIVE', _('passive')),
True: ('ACTIVE', _('active'))}}
def get_state(self):
"""The state combined of online and enabled attributes.
def _get_state(self):
"""The state tuple based on online and enabled attributes.
"""
return self.STATES[self.enabled][self.online][0]
state = property(get_state)
if self.online:
return self.STATES[self.enabled][self.schedule_enabled]
else:
return self.STATES[None][self.enabled]
def get_status_display(self):
return self.STATES[self.enabled][self.online][1]
def disable(self, user=None, base_activity=None):
''' Disable the node.'''
if self.enabled:
if base_activity:
act_ctx = base_activity.sub_activity(
'disable', readable_name=ugettext_noop("disable node"))
else:
act_ctx = node_activity(
'disable', node=self, user=user,
readable_name=ugettext_noop("disable node"))
with act_ctx:
self.enabled = False
self.save()
return self._get_state()[1]
def get_state(self):
return self._get_state()[0]
state = property(get_state)
def enable(self, user=None, base_activity=None):
''' Enable the node. '''
if self.enabled is not True:
if base_activity:
act_ctx = base_activity.sub_activity('enable')
else:
act_ctx = node_activity('enable', node=self, user=user)
with act_ctx:
self.enabled = True
self.save()
self.get_info(invalidate_cache=True)
raise NotImplementedError("Use activate or passivate instead.")
@property
@node_available
......@@ -314,10 +303,11 @@ class Node(OperatedMixin, TimeStampedModel):
def get_status_icon(self):
return {
'OFFLINE': 'fa-minus-circle',
'DISABLED': 'fa-moon-o',
'DISABLED': 'fa-times-circle-o',
'OFFLINE': 'fa-times-circle',
'MISSING': 'fa-warning',
'ONLINE': 'fa-play-circle'}.get(self.get_state(),
'PASSIVE': 'fa-play-circle-o',
'ACTIVE': 'fa-play-circle'}.get(self.get_state(),
'fa-question-circle')
def get_status_label(self):
......@@ -385,6 +375,11 @@ class Node(OperatedMixin, TimeStampedModel):
def get_absolute_url(self):
return ('dashboard.views.node-detail', None, {'pk': self.id})
def save(self, *args, **kwargs):
if not self.enabled:
self.schedule_enabled = False
super(Node, self).save(*args, **kwargs)
@property
def metric_prefix(self):
return 'circle.%s' % self.host.hostname
......@@ -116,6 +116,7 @@ class InstanceOperation(Operation):
return False
@register_operation
class AddInterfaceOperation(InstanceOperation):
activity_code_suffix = 'add_interface'
id = 'add_interface'
......@@ -161,9 +162,7 @@ class AddInterfaceOperation(InstanceOperation):
vlan=kwargs['vlan'])
register_operation(AddInterfaceOperation)
@register_operation
class CreateDiskOperation(InstanceOperation):
activity_code_suffix = 'create_disk'
......@@ -205,9 +204,7 @@ class CreateDiskOperation(InstanceOperation):
size=filesizeformat(kwargs['size']), name=kwargs['name'])
register_operation(CreateDiskOperation)
@register_operation
class DownloadDiskOperation(InstanceOperation):
activity_code_suffix = 'download_disk'
id = 'download_disk'
......@@ -245,9 +242,8 @@ class DownloadDiskOperation(InstanceOperation):
):
self.instance.attach_disk(disk)
register_operation(DownloadDiskOperation)
@register_operation
class DeployOperation(InstanceOperation):
activity_code_suffix = 'deploy'
id = 'deploy'
......@@ -315,9 +311,7 @@ class DeployOperation(InstanceOperation):
"wait operating system loading"), interruptible=True)
register_operation(DeployOperation)
@register_operation
class DestroyOperation(InstanceOperation):
activity_code_suffix = 'destroy'
id = 'destroy'
......@@ -363,9 +357,7 @@ class DestroyOperation(InstanceOperation):
self.instance.save()
register_operation(DestroyOperation)
@register_operation
class MigrateOperation(InstanceOperation):
activity_code_suffix = 'migrate'
id = 'migrate'
......@@ -417,9 +409,7 @@ class MigrateOperation(InstanceOperation):
self.instance.deploy_net()
register_operation(MigrateOperation)
@register_operation
class RebootOperation(InstanceOperation):
activity_code_suffix = 'reboot'
id = 'reboot'
......@@ -436,9 +426,7 @@ class RebootOperation(InstanceOperation):
"wait operating system loading"), interruptible=True)
register_operation(RebootOperation)
@register_operation
class RemoveInterfaceOperation(InstanceOperation):
activity_code_suffix = 'remove_interface'
id = 'remove_interface'
......@@ -466,9 +454,7 @@ class RemoveInterfaceOperation(InstanceOperation):
vlan=kwargs['interface'].vlan)
register_operation(RemoveInterfaceOperation)
@register_operation
class RemoveDiskOperation(InstanceOperation):
activity_code_suffix = 'remove_disk'
id = 'remove_disk'
......@@ -495,9 +481,8 @@ class RemoveDiskOperation(InstanceOperation):
return create_readable(ugettext_noop('remove disk %(name)s'),
name=kwargs["disk"].name)
register_operation(RemoveDiskOperation)
@register_operation
class ResetOperation(InstanceOperation):
activity_code_suffix = 'reset'
id = 'reset'
......@@ -512,9 +497,8 @@ class ResetOperation(InstanceOperation):
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(ResetOperation)
@register_operation
class SaveAsTemplateOperation(InstanceOperation):
activity_code_suffix = 'save_as_template'
id = 'save_as_template'
......@@ -610,9 +594,7 @@ class SaveAsTemplateOperation(InstanceOperation):
return tmpl
register_operation(SaveAsTemplateOperation)
@register_operation
class ShutdownOperation(InstanceOperation):
activity_code_suffix = 'shutdown'
id = 'shutdown'
......@@ -642,9 +624,7 @@ class ShutdownOperation(InstanceOperation):
super(ShutdownOperation, self).on_abort(activity, error)
register_operation(ShutdownOperation)
@register_operation
class ShutOffOperation(InstanceOperation):
activity_code_suffix = 'shut_off'
id = 'shut_off'
......@@ -672,9 +652,7 @@ class ShutOffOperation(InstanceOperation):
self.instance.yield_node()
register_operation(ShutOffOperation)
@register_operation
class SleepOperation(InstanceOperation):
activity_code_suffix = 'sleep'
id = 'sleep'
......@@ -718,9 +696,7 @@ class SleepOperation(InstanceOperation):
# VNC port needs to be kept
register_operation(SleepOperation)
@register_operation
class WakeUpOperation(InstanceOperation):
activity_code_suffix = 'wake_up'
id = 'wake_up'
......@@ -764,9 +740,7 @@ class WakeUpOperation(InstanceOperation):
pass
register_operation(WakeUpOperation)
@register_operation
class RenewOperation(InstanceOperation):
activity_code_suffix = 'renew'
id = 'renew'
......@@ -801,9 +775,7 @@ class RenewOperation(InstanceOperation):
suspend=suspend, delete=delete)
register_operation(RenewOperation)
@register_operation
class ChangeStateOperation(InstanceOperation):
activity_code_suffix = 'emergency_change_state'
id = 'emergency_change_state'
......@@ -828,17 +800,23 @@ class ChangeStateOperation(InstanceOperation):
logger.error('Forced finishing activity %s', i)
register_operation(ChangeStateOperation)
class NodeOperation(Operation):
async_operation = abortable_async_node_operation
host_cls = Node
online_required = True
superuser_required = True
def __init__(self, node):
super(NodeOperation, self).__init__(subject=node)
self.node = node
def check_precond(self):
super(NodeOperation, self).check_precond()
if self.online_required and not self.node.online:
raise humanize_exception(ugettext_noop(
"You cannot call this operation on an offline node."),
Exception())
def create_activity(self, parent, user, kwargs):
name = self.get_activity_name(kwargs)
if parent:
......@@ -859,24 +837,19 @@ class NodeOperation(Operation):
readable_name=name)
@register_operation
class FlushOperation(NodeOperation):
activity_code_suffix = 'flush'
id = 'flush'
name = _("flush")
description = _("Disable node and move all instances to other ones.")
description = _("Passivate node and move all instances to other ones.")
required_perms = ()
superuser_required = True
async_queue = "localhost.man.slow"
def on_abort(self, activity, error):
from manager.scheduler import TraitsUnsatisfiableException
if isinstance(error, TraitsUnsatisfiableException):
if self.node_enabled:
self.node.enable(activity.user, activity)
def _operation(self, activity, user):
self.node_enabled = self.node.enabled
self.node.disable(user, activity)
if self.node.schedule_enabled:
PassivateOperation(self.node).call(parent_activity=activity,
user=user)
for i in self.node.instance_set.all():
name = create_readable(ugettext_noop(
"migrate %(instance)s (%(pk)s)"), instance=i.name, pk=i.pk)
......@@ -885,9 +858,75 @@ class FlushOperation(NodeOperation):
i.migrate(user=user)
register_operation(FlushOperation)
@register_operation
class ActivateOperation(NodeOperation):
activity_code_suffix = 'activate'
id = 'activate'
name = _("activate")
description = _("Make node active, i.e. scheduler is allowed to deploy "
"virtual machines to it.")
required_perms = ()
def check_precond(self):
super(ActivateOperation, self).check_precond()
if self.node.enabled and self.node.schedule_enabled:
raise humanize_exception(ugettext_noop(
"You cannot activate an active node."), Exception())
def _operation(self):
self.node.enabled = True
self.node.schedule_enabled = True
self.node.save()
@register_operation
class PassivateOperation(NodeOperation):
activity_code_suffix = 'passivate'
id = 'passivate'
name = _("passivate")
description = _("Make node passive, i.e. scheduler is denied to deploy "
"virtual machines to it, but remaining instances and "
"the ones manually migrated will continue running.")
required_perms = ()
def check_precond(self):
if self.node.enabled and not self.node.schedule_enabled:
raise humanize_exception(ugettext_noop(
"You cannot passivate a passive node."), Exception())
super(PassivateOperation, self).check_precond()
def _operation(self):
self.node.enabled = True
self.node.schedule_enabled = False
self.node.save()
@register_operation
class DisableOperation(NodeOperation):
activity_code_suffix = 'disable'
id = 'disable'
name = _("disable")
description = _("Disable node.")
required_perms = ()
online_required = False
def check_precond(self):
if not self.node.enabled:
raise humanize_exception(ugettext_noop(
"You cannot disable a disabled node."), Exception())
if self.node.instance_set.exists():
raise humanize_exception(ugettext_noop(
"You cannot disable a node which is hosting instances."),
Exception())
super(DisableOperation, self).check_precond()
def _operation(self):
self.node.enabled = False
self.node.schedule_enabled = False
self.node.save()
@register_operation
class ScreenshotOperation(InstanceOperation):
activity_code_suffix = 'screenshot'
id = 'screenshot'
......@@ -903,9 +942,7 @@ class ScreenshotOperation(InstanceOperation):
return self.instance.get_screenshot(timeout=20)
register_operation(ScreenshotOperation)
@register_operation
class RecoverOperation(InstanceOperation):
activity_code_suffix = 'recover'
id = 'recover'
......@@ -933,9 +970,7 @@ class RecoverOperation(InstanceOperation):
self.instance.save()
register_operation(RecoverOperation)
@register_operation
class ResourcesOperation(InstanceOperation):
activity_code_suffix = 'Resources change'
id = 'resources_change'
......@@ -963,9 +998,6 @@ class ResourcesOperation(InstanceOperation):
)
register_operation(ResourcesOperation)
class EnsureAgentMixin(object):
accept_states = ('RUNNING', )
......@@ -985,6 +1017,7 @@ class EnsureAgentMixin(object):
raise self.instance.NoAgentError(self.instance)
@register_operation
class PasswordResetOperation(EnsureAgentMixin, InstanceOperation):
activity_code_suffix = 'password_reset'
id = 'password_reset'
......@@ -1005,9 +1038,7 @@ class PasswordResetOperation(EnsureAgentMixin, InstanceOperation):
self.instance.save()
register_operation(PasswordResetOperation)
@register_operation
class MountStoreOperation(EnsureAgentMixin, InstanceOperation):
activity_code_suffix = 'mount_store'
id = 'mount_store'
......@@ -1034,6 +1065,3 @@ class MountStoreOperation(EnsureAgentMixin, InstanceOperation):
password = user.profile.smb_password
agent_tasks.mount_store.apply_async(
queue=queue, args=(inst.vm_name, host, username, password))
register_operation(MountStoreOperation)
......@@ -208,8 +208,10 @@ class NodeTestCase(TestCase):
node = Mock(spec=Node)
node.online = True
node.enabled = True
node.schedule_enabled = True
node.STATES = Node.STATES
self.assertEqual(Node.get_state(node), "ONLINE")
node._get_state = lambda: Node._get_state(node)
self.assertEqual(Node.get_state(node), "ACTIVE")
assert isinstance(Node.get_status_display(node), _("x").__class__)
......@@ -351,70 +353,45 @@ class InstanceActivityTestCase(TestCase):
self.assertTrue(InstanceActivity.is_abortable_for(iaobj, su))
def test_disable_enabled(self):
node = MagicMock(spec=Node, enabled=True)
with patch('vm.models.node.node_activity') as nac:
na = MagicMock()
nac.return_value = na
na.__enter__.return_value = MagicMock()
Node.disable(node)
self.assertFalse(node.enabled)
node.save.assert_called_once()
na.assert_called()
node = MagicMock(spec=Node, enabled=True, online=True)
node.instance_set.exists.return_value = False
Node._ops['disable'](node).check_precond()
def test_disable_disabled(self):
node = MagicMock(spec=Node, enabled=False)
with patch('vm.models.node.node_activity') as nac:
na = MagicMock()
na.__enter__.side_effect = AssertionError
nac.return_value = na
Node.disable(node)
self.assertFalse(node.enabled)
def test_disable_enabled_sub(self):
node = MagicMock(spec=Node, enabled=True)
act = MagicMock()
subact = MagicMock()
act.sub_activity.return_value = subact
Node.disable(node, base_activity=act)
self.assertFalse(node.enabled)
subact.__enter__.assert_called()
with self.assertRaises(Exception):
Node._ops['disable'](node).check_precond()
def test_flush(self):
insts = [MagicMock(spec=Instance, migrate=MagicMock()),
MagicMock(spec=Instance, migrate=MagicMock())]
insts[0].name = insts[1].name = "x"
node = MagicMock(spec=Node, enabled=True)
node = MagicMock(spec=Node, enabled=True, schedule_enabled=True)
node.instance_set.all.return_value = insts
user = MagicMock(spec=User)
user.is_superuser = MagicMock(return_value=True)
flush_op = FlushOperation(node)
with patch.object(FlushOperation, 'create_activity') as create_act:
act = create_act.return_value = MagicMock()
flush_op(user=user)
with patch.object(FlushOperation, 'create_activity') as create_act, \
patch.object(
Node._ops['passivate'], 'create_activity') as create_act2:
FlushOperation(node)(user=user)
node.schedule_enabled = True
create_act.assert_called()
node.disable.assert_called_with(user, act)
create_act2.assert_called()
for i in insts:
i.migrate.assert_called()
user.is_superuser.assert_called()
user.is_superuser.assert_called()
def test_flush_disabled_wo_user(self):
insts = [MagicMock(spec=Instance, migrate=MagicMock()),
MagicMock(spec=Instance, migrate=MagicMock())]
insts[0].name = insts[1].name = "x"
node = MagicMock(spec=Node, enabled=False)
node = MagicMock(spec=Node, enabled=False, schedule_enabled=False)
node.instance_set.all.return_value = insts
flush_op = FlushOperation(node)
with patch.object(FlushOperation, 'create_activity') as create_act:
act = create_act.return_value = MagicMock()
create_act.return_value = MagicMock()
flush_op(system=True)
create_act.assert_called()
node.disable.assert_called_with(None, act)
# ^ should be called, but real method no-ops if disabled
for i in insts:
i.migrate.assert_called()
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