Commit 79a46294 by Bach Dániel

Merge remote-tracking branch 'origin/master' into feature-fix-acls

Conflicts:
	circle/dashboard/views.py
parents 0c7119c8 cce7935c
...@@ -126,6 +126,18 @@ class Profile(Model): ...@@ -126,6 +126,18 @@ class Profile(Model):
return self.get_display_name() return self.get_display_name()
class FutureMember(Model):
org_id = CharField(max_length=64, help_text=_(
'Unique identifier of the person, e.g. a student number.'))
group = ForeignKey(Group)
class Meta:
unique_together = ('org_id', 'group')
def __unicode__(self):
return u"%s (%s)" % (self.org_id, self.group)
class GroupProfile(AclBase): class GroupProfile(AclBase):
ACL_LEVELS = ( ACL_LEVELS = (
('operator', _('operator')), ('operator', _('operator')),
...@@ -210,6 +222,10 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'): ...@@ -210,6 +222,10 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
group, unicode(g)) group, unicode(g))
g.user_set.add(sender) g.user_set.add(sender)
for i in FutureMember.objects.filter(org_id=value):
i.group.user_set.add(sender)
i.delete()
owneratrs = getattr(settings, 'SAML_GROUP_OWNER_ATTRIBUTES', []) owneratrs = getattr(settings, 'SAML_GROUP_OWNER_ATTRIBUTES', [])
for group in chain(*[attributes[i] for group in chain(*[attributes[i]
for i in owneratrs if i in attributes]): for i in owneratrs if i in attributes]):
......
{% extends "base.html" %}
{% load i18n %}
{% block title-site %}Dashboard | CIRCLE{% endblock %}
{% block content %}
{% blocktrans with group=object member=member %}
Do you really want to remove {{member}} from {{group}}?
{% endblocktrans %}
<form action="" method="POST">{% csrf_token %}
<input type="submit" value="{% trans "Remove" %}" />
</form>
{% endblock %}
...@@ -71,16 +71,30 @@ ...@@ -71,16 +71,30 @@
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% for i in future_users %}
<tr>
<td>
<i class="icon-user text-muted"></i>
</td>
<td> {{ i.org_id }} </td>
<td>
<a href="{% url "dashboard.views.remove-future-user" member_org_id=i.org_id group_pk=group.pk %}"
class="real-link btn-link btn-xs">
<i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a>
</td>
</tr>
{% endfor %}
<tr> <tr>
<td><i class="icon-plus"></i></td> <td><i class="icon-plus"></i></td>
<td colspan="2"> <td colspan="2">
<input type="text" class="form-control" name="list-new-name"placeholder="{% trans "Name of user" %}"> <input type="text" class="form-control" name="list-new-name"
placeholder="{% trans "Name of user" %}">
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<textarea name="list-new-namelist" class="form-control" <textarea name="list-new-namelist" class="form-control"
placeholder="{% trans "List of usernames (one per line)." %}"></textarea> placeholder="{% trans "Add multiple users at once (one identifier per line)." %}"></textarea>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button> <button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div> </div>
......
...@@ -80,9 +80,9 @@ ...@@ -80,9 +80,9 @@
{% endif %} {% endif %}
</dd> </dd>
{% if instance.ipv6 %} {% if instance.ipv6 and instance.get_connect_port %}
<dt>{% trans "Host (IPv6)" %}</dt> <dt>{% trans "Host (IPv6)" %}</dt>
<dd>{{ ipv6_host }}:<strong>{{ instance.ipv6_port }}</strong></dd> <dd>{{ ipv6_host }}:<strong>{{ ipv6_port }}</strong></dd>
{% endif %} {% endif %}
<dt>{% trans "Username" %}</dt> <dt>{% trans "Username" %}</dt>
......
...@@ -2,10 +2,20 @@ ...@@ -2,10 +2,20 @@
{% for op in ops %} {% for op in ops %}
{% if op.show_in_toolbar %} {% if op.show_in_toolbar %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn btn-default btn-xs"
title="{{op.name}}: {{op.description}}"> {% if op.disabled %}
<span class="operation operation-{{op.op}} btn btn-{{op.effect}} disabled btn-xs">
{% else %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn
btn-{{op.effect}} btn-xs" title="{{op.name}}: {{op.description}}">
{% endif %}
<i class="icon-{{op.icon}}"></i> <i class="icon-{{op.icon}}"></i>
<span class="sr-only">{{op.name}}</span> <span{% if not op.is_preferred %} class="sr-only"{% endif %}>{{op.name}}</span>
{% if op.disabled %}
</span>
{% else %}
</a> </a>
{% endif %} {% endif %}
{% endif %}
{% endfor %} {% endfor %}
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
{% if l.ipv4 %} {% if l.ipv4 %}
<tr> <tr>
<td> <td>
{% display_portforward l %} {% display_portforward4 l %}
</td> </td>
<td><i class="icon-long-arrow-right"></i></td> <td><i class="icon-long-arrow-right"></i></td>
<td> <td>
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
{% if l.ipv6 %} {% if l.ipv6 %}
<tr> <tr>
<td> <td>
{% display_portforward l %} {% display_portforward6 l %}
</td> </td>
<td><i class="icon-long-arrow-right"></i></td> <td><i class="icon-long-arrow-right"></i></td>
<td> <td>
......
...@@ -6,10 +6,18 @@ register = template.Library() ...@@ -6,10 +6,18 @@ register = template.Library()
LINKABLE_PORTS = {80: "http", 8080: "http", 443: "https", 21: "ftp"} LINKABLE_PORTS = {80: "http", 8080: "http", 443: "https", 21: "ftp"}
@register.simple_tag(name="display_portforward") @register.simple_tag(name="display_portforward4")
def display_pf(ports): def display_pf4(ports):
is_ipv6 = "ipv6" in ports return display_pf(ports, 'ipv4')
data = ports["ipv6" if is_ipv6 else "ipv4"]
@register.simple_tag(name="display_portforward6")
def display_pf6(ports):
return display_pf(ports, 'ipv6')
def display_pf(ports, proto):
data = ports[proto]
if ports['private'] in LINKABLE_PORTS.keys(): if ports['private'] in LINKABLE_PORTS.keys():
href = "%s:%d" % (data['host'], data['port']) href = "%s:%d" % (data['host'], data['port'])
......
...@@ -31,6 +31,7 @@ from .views import ( ...@@ -31,6 +31,7 @@ from .views import (
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView, VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView, GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView,
GroupRemoveFutureUserView,
GroupCreate, GroupProfileUpdate, GroupCreate, GroupProfileUpdate,
TemplateChoose, TemplateChoose,
UserCreationView, UserCreationView,
...@@ -157,6 +158,9 @@ urlpatterns = patterns( ...@@ -157,6 +158,9 @@ urlpatterns = patterns(
url(r'^group/(?P<group_pk>\d+)/remove/user/(?P<member_pk>\d+)/$', url(r'^group/(?P<group_pk>\d+)/remove/user/(?P<member_pk>\d+)/$',
GroupRemoveUserView.as_view(), GroupRemoveUserView.as_view(),
name="dashboard.views.remove-user"), name="dashboard.views.remove-user"),
url(r'^group/(?P<group_pk>\d+)/remove/futureuser/(?P<member_org_id>.+)/$',
GroupRemoveFutureUserView.as_view(),
name="dashboard.views.remove-future-user"),
url(r'^group/create/$', GroupCreate.as_view(), url(r'^group/create/$', GroupCreate.as_view(),
name='dashboard.views.group-create'), name='dashboard.views.group-create'),
url(r'^group/(?P<group_pk>\d+)/create/$', url(r'^group/(?P<group_pk>\d+)/create/$',
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from collections import OrderedDict
from itertools import chain from itertools import chain
from os import getenv from os import getenv
import json import json
...@@ -74,7 +75,7 @@ from vm.models import ( ...@@ -74,7 +75,7 @@ from vm.models import (
) )
from storage.models import Disk from storage.models import Disk
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from .models import Favourite, Profile, GroupProfile from .models import Favourite, Profile, GroupProfile, FutureMember
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
saml_available = hasattr(settings, "SAML_CONFIG") saml_available = hasattr(settings, "SAML_CONFIG")
...@@ -501,6 +502,7 @@ class OperationView(DetailView): ...@@ -501,6 +502,7 @@ class OperationView(DetailView):
template_name = 'dashboard/operate.html' template_name = 'dashboard/operate.html'
show_in_toolbar = True show_in_toolbar = True
effect = None
@property @property
def name(self): def name(self):
...@@ -510,6 +512,9 @@ class OperationView(DetailView): ...@@ -510,6 +512,9 @@ class OperationView(DetailView):
def description(self): def description(self):
return self.get_op().description return self.get_op().description
def is_preferred(self):
return self.get_op().is_preferred()
@classmethod @classmethod
def get_urlname(cls): def get_urlname(cls):
return 'dashboard.vm.op.%s' % cls.op return 'dashboard.vm.op.%s' % cls.op
...@@ -559,14 +564,16 @@ class OperationView(DetailView): ...@@ -559,14 +564,16 @@ class OperationView(DetailView):
return redirect("%s#activity" % self.object.get_absolute_url()) return redirect("%s#activity" % self.object.get_absolute_url())
@classmethod @classmethod
def factory(cls, op, icon='cog'): def factory(cls, op, icon='cog', effect='info'):
return type(str(cls.__name__ + op), return type(str(cls.__name__ + op),
(cls, ), {'op': op, 'icon': icon}) (cls, ), {'op': op, 'icon': icon, 'effect': effect})
@classmethod @classmethod
def bind_to_object(cls, instance): def bind_to_object(cls, instance, **kwargs):
v = cls() v = cls()
v.get_object = lambda: instance v.get_object = lambda: instance
for key, value in kwargs.iteritems():
setattr(v, key, value)
return v return v
...@@ -643,6 +650,7 @@ class VmMigrateView(VmOperationView): ...@@ -643,6 +650,7 @@ class VmMigrateView(VmOperationView):
op = 'migrate' op = 'migrate'
icon = 'truck' icon = 'truck'
effect = 'info'
template_name = 'dashboard/_vm-migrate.html' template_name = 'dashboard/_vm-migrate.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
...@@ -665,6 +673,7 @@ class VmSaveView(FormOperationMixin, VmOperationView): ...@@ -665,6 +673,7 @@ class VmSaveView(FormOperationMixin, VmOperationView):
op = 'save_as_template' op = 'save_as_template'
icon = 'save' icon = 'save'
effect = 'info'
form_class = VmSaveForm form_class = VmSaveForm
...@@ -690,21 +699,28 @@ class VmResourcesChangeView(VmOperationView): ...@@ -690,21 +699,28 @@ class VmResourcesChangeView(VmOperationView):
*args, **kwargs) *args, **kwargs)
vm_ops = { vm_ops = OrderedDict([
'reset': VmOperationView.factory(op='reset', icon='bolt'), ('deploy', VmOperationView.factory(
'deploy': VmOperationView.factory(op='deploy', icon='play'), op='deploy', icon='play', effect='success')),
'migrate': VmMigrateView, ('wake_up', VmOperationView.factory(
'reboot': VmOperationView.factory(op='reboot', icon='refresh'), op='wake_up', icon='sun', effect='success')),
'shut_off': VmOperationView.factory(op='shut_off', icon='ban-circle'), ('sleep', VmOperationView.factory(
'shutdown': VmOperationView.factory(op='shutdown', icon='off'), op='sleep', icon='moon', effect='info')),
'save_as_template': VmSaveView, ('migrate', VmMigrateView),
'destroy': VmOperationView.factory(op='destroy', icon='remove'), ('save_as_template', VmSaveView),
'sleep': VmOperationView.factory(op='sleep', icon='moon'), ('reboot', VmOperationView.factory(
'wake_up': VmOperationView.factory(op='wake_up', icon='sun'), op='reboot', icon='refresh', effect='warning')),
'create_disk': VmCreateDiskView, ('reset', VmOperationView.factory(
'download_disk': VmDownloadDiskView, op='reset', icon='bolt', effect='warning')),
'resources_change': VmResourcesChangeView, ('shutdown', VmOperationView.factory(
} op='shutdown', icon='off', effect='warning')),
('shut_off', VmOperationView.factory(
op='shut_off', icon='ban-circle', effect='warning')),
('destroy', VmOperationView.factory(
op='destroy', icon='remove', effect='danger')),
('create_disk', VmCreateDiskView),
('download_disk', VmDownloadDiskView),
])
def get_operations(instance, user): def get_operations(instance, user):
...@@ -714,9 +730,11 @@ def get_operations(instance, user): ...@@ -714,9 +730,11 @@ def get_operations(instance, user):
op = v.get_op_by_object(instance) op = v.get_op_by_object(instance)
op.check_auth(user) op.check_auth(user)
op.check_precond() op.check_precond()
except Exception as e: except PermissionDenied as e:
logger.debug('Not showing operation %s for %s: %s', logger.debug('Not showing operation %s for %s: %s',
k, instance, unicode(e)) k, instance, unicode(e))
except Exception:
ops.append(v.bind_to_object(instance, disabled=True))
else: else:
ops.append(v.bind_to_object(instance)) ops.append(v.bind_to_object(instance))
return ops return ops
...@@ -803,6 +821,8 @@ class GroupDetailView(CheckedDetailView): ...@@ -803,6 +821,8 @@ class GroupDetailView(CheckedDetailView):
context = super(GroupDetailView, self).get_context_data(**kwargs) context = super(GroupDetailView, self).get_context_data(**kwargs)
context['group'] = self.object context['group'] = self.object
context['users'] = self.object.user_set.all() context['users'] = self.object.user_set.all()
context['future_users'] = FutureMember.objects.filter(
group=self.object)
context['acl'] = get_group_acl_data(self.object) context['acl'] = get_group_acl_data(self.object)
context['group_profile_form'] = GroupProfileUpdate.get_form_object( context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile) self.request, self.object.profile)
...@@ -836,7 +856,11 @@ class GroupDetailView(CheckedDetailView): ...@@ -836,7 +856,11 @@ class GroupDetailView(CheckedDetailView):
entity = User.objects.get(username=name) entity = User.objects.get(username=name)
self.object.user_set.add(entity) self.object.user_set.add(entity)
except User.DoesNotExist: except User.DoesNotExist:
warning(request, _('User "%s" not found.') % name) if saml_available:
FutureMember.objects.get_or_create(org_id=name,
group=self.object)
else:
warning(request, _('User "%s" not found.') % name)
def __add_list(self, request): def __add_list(self, request):
if not self.get_has_level()(request.user, 'operator'): if not self.get_has_level()(request.user, 'operator'):
...@@ -1350,6 +1374,7 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -1350,6 +1374,7 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
slug_field = 'pk' slug_field = 'pk'
slug_url_kwarg = 'group_pk' slug_url_kwarg = 'group_pk'
read_level = 'operator' read_level = 'operator'
member_key = 'member_pk'
def get_has_level(self): def get_has_level(self):
return self.object.profile.has_level return self.object.profile.has_level
...@@ -1391,7 +1416,7 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -1391,7 +1416,7 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
object = self.get_object() object = self.get_object()
if not object.profile.has_level(request.user, 'operator'): if not object.profile.has_level(request.user, 'operator'):
raise PermissionDenied() raise PermissionDenied()
self.remove_member(kwargs["member_pk"]) self.remove_member(kwargs[self.member_key])
success_url = self.get_success_url() success_url = self.get_success_url()
success_message = self.get_success_message() success_message = self.get_success_message()
if request.is_ajax(): if request.is_ajax():
...@@ -1404,6 +1429,31 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -1404,6 +1429,31 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
class GroupRemoveFutureUserView(GroupRemoveUserView):
member_key = 'member_org_id'
def get(self, request, member_org_id, *args, **kwargs):
self.member_org_id = member_org_id
return super(GroupRemoveUserView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(GroupRemoveUserView, self).get_context_data(**kwargs)
try:
context['member'] = FutureMember.objects.get(
org_id=self.member_org_id, group=self.get_object())
except FutureMember.DoesNotExist:
raise Http404()
return context
def remove_member(self, org_id):
FutureMember.objects.filter(org_id=org_id,
group=self.get_object()).delete()
def get_success_message(self):
return _("Future user successfully removed from group.")
class GroupRemoveAclUserView(GroupRemoveUserView): class GroupRemoveAclUserView(GroupRemoveUserView):
def remove_member(self, pk): def remove_member(self, pk):
......
...@@ -28,6 +28,7 @@ import re ...@@ -28,6 +28,7 @@ import re
alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$') alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$')
domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$') domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
domain_wildcard_re = re.compile(r'^(\*\.)?([A-Za-z0-9_-]\.?)+$')
ipv4_re = re.compile('^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$') ipv4_re = re.compile('^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$') reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
ipv6_template_re = re.compile(r'^(%\([abcd]\)[dxX]|[A-Za-z0-9:-])+$') ipv6_template_re = re.compile(r'^(%\([abcd]\)[dxX]|[A-Za-z0-9:-])+$')
...@@ -216,12 +217,23 @@ def is_valid_domain(value): ...@@ -216,12 +217,23 @@ def is_valid_domain(value):
return domain_re.match(value) is not None return domain_re.match(value) is not None
def is_valid_domain_wildcard(value):
"""Check whether the parameter is a valid domain name."""
return domain_wildcard_re.match(value) is not None
def val_domain(value): def val_domain(value):
"""Validate whether the parameter is a valid domin name.""" """Validate whether the parameter is a valid domin name."""
if not is_valid_domain(value): if not is_valid_domain(value):
raise ValidationError(_(u'%s - invalid domain name') % value) raise ValidationError(_(u'%s - invalid domain name') % value)
def val_domain_wildcard(value):
"""Validate whether the parameter is a valid domin name."""
if not is_valid_domain_wildcard(value):
raise ValidationError(_(u'%s - invalid domain name') % value)
def is_valid_reverse_domain(value): def is_valid_reverse_domain(value):
"""Check whether the parameter is a valid reverse domain name.""" """Check whether the parameter is a valid reverse domain name."""
return reverse_domain_re.match(value) is not None return reverse_domain_re.match(value) is not None
......
...@@ -27,6 +27,7 @@ from django.forms import ValidationError ...@@ -27,6 +27,7 @@ from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain, from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain,
val_ipv6_template, val_domain, val_ipv4, val_ipv6_template, val_domain, val_ipv4,
val_domain_wildcard,
val_ipv6, val_mx, convert_ipv4_to_ipv6, val_ipv6, val_mx, convert_ipv4_to_ipv6,
IPNetworkField, IPAddressField) IPNetworkField, IPAddressField)
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
...@@ -695,8 +696,7 @@ class Host(models.Model): ...@@ -695,8 +696,7 @@ class Host(models.Model):
:param private: Port number of host in subject. :param private: Port number of host in subject.
""" """
self.rules.filter(owner=self.owner, proto=proto, host=self, self.rules.filter(proto=proto, dport=private).delete()
dport=private).delete()
def get_hostname(self, proto, public=True): def get_hostname(self, proto, public=True):
""" """
...@@ -728,7 +728,7 @@ class Host(models.Model): ...@@ -728,7 +728,7 @@ class Host(models.Model):
Return a list of ports with forwarding rules set. Return a list of ports with forwarding rules set.
""" """
retval = [] retval = []
for rule in self.rules.filter(owner=self.owner): for rule in self.rules.all():
forward = { forward = {
'proto': rule.proto, 'proto': rule.proto,
'private': rule.dport, 'private': rule.dport,
...@@ -770,9 +770,7 @@ class Host(models.Model): ...@@ -770,9 +770,7 @@ class Host(models.Model):
if public_port else if public_port else
None) None)
# IPv6 # IPv6
blocked = self.incoming_rules.exclude( endpoints['ipv6'] = (self.ipv6, port) if public_port else None
action='accept').filter(dport=port, proto=protocol).exists()
endpoints['ipv6'] = (self.ipv6, port) if not blocked else None
return endpoints return endpoints
@models.permalink @models.permalink
...@@ -821,7 +819,7 @@ class Domain(models.Model): ...@@ -821,7 +819,7 @@ class Domain(models.Model):
class Record(models.Model): class Record(models.Model):
CHOICES_type = (('A', 'A'), ('CNAME', 'CNAME'), ('AAAA', 'AAAA'), CHOICES_type = (('A', 'A'), ('CNAME', 'CNAME'), ('AAAA', 'AAAA'),
('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT')) ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT'))
name = models.CharField(max_length=40, validators=[val_domain], name = models.CharField(max_length=40, validators=[val_domain_wildcard],
blank=True, null=True, verbose_name=_('name')) blank=True, null=True, verbose_name=_('name'))
domain = models.ForeignKey('Domain', verbose_name=_('domain')) domain = models.ForeignKey('Domain', verbose_name=_('domain'))
host = models.ForeignKey('Host', blank=True, null=True, host = models.ForeignKey('Host', blank=True, null=True,
......
...@@ -579,11 +579,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -579,11 +579,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def get_connect_host(self, use_ipv6=False): def get_connect_host(self, use_ipv6=False):
"""Get public hostname. """Get public hostname.
""" """
if not self.interface_set.exclude(host=None): if not self.primary_host:
return _('None') return None
proto = 'ipv6' if use_ipv6 else 'ipv4' proto = 'ipv6' if use_ipv6 else 'ipv4'
return self.interface_set.exclude(host=None)[0].host.get_hostname( return self.primary_host.get_hostname(proto=proto)
proto=proto)
def get_connect_command(self, use_ipv6=False): def get_connect_command(self, use_ipv6=False):
"""Returns a formatted connect string. """Returns a formatted connect string.
......
...@@ -76,6 +76,11 @@ class InstanceOperation(Operation): ...@@ -76,6 +76,11 @@ class InstanceOperation(Operation):
code_suffix=self.activity_code_suffix, instance=self.instance, code_suffix=self.activity_code_suffix, instance=self.instance,
user=user, concurrency_check=self.concurrency_check) user=user, concurrency_check=self.concurrency_check)
def is_preferred(self):
"""If this is the recommended op in the current state of the instance.
"""
return False
class AddInterfaceOperation(InstanceOperation): class AddInterfaceOperation(InstanceOperation):
activity_code_suffix = 'add_interface' activity_code_suffix = 'add_interface'
...@@ -166,6 +171,10 @@ class DeployOperation(InstanceOperation): ...@@ -166,6 +171,10 @@ class DeployOperation(InstanceOperation):
if self.instance.status in ['RUNNING', 'SUSPENDED']: if self.instance.status in ['RUNNING', 'SUSPENDED']:
raise self.instance.WrongStateError(self.instance) raise self.instance.WrongStateError(self.instance)
def is_preferred(self):
return self.instance.status in (self.instance.STATUS.STOPPED,
self.instance.STATUS.ERROR)
def on_commit(self, activity): def on_commit(self, activity):
activity.resultant_state = 'RUNNING' activity.resultant_state = 'RUNNING'
...@@ -377,6 +386,10 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -377,6 +386,10 @@ class SaveAsTemplateOperation(InstanceOperation):
abortable = True abortable = True
required_perms = ('vm.create_template', ) required_perms = ('vm.create_template', )
def is_preferred(self):
return (self.instance.is_base and
self.instance.status == self.instance.STATUS.RUNNING)
@staticmethod @staticmethod
def _rename(name): def _rename(name):
m = search(r" v(\d+)$", name) m = search(r" v(\d+)$", name)
...@@ -525,6 +538,10 @@ class SleepOperation(InstanceOperation): ...@@ -525,6 +538,10 @@ class SleepOperation(InstanceOperation):
description = _("Suspend virtual machine with memory dump.") description = _("Suspend virtual machine with memory dump.")
required_perms = () required_perms = ()
def is_preferred(self):
return (not self.instance.is_base and
self.instance.status == self.instance.STATUS.RUNNING)
def check_precond(self): def check_precond(self):
super(SleepOperation, self).check_precond() super(SleepOperation, self).check_precond()
if self.instance.status not in ['RUNNING']: if self.instance.status not in ['RUNNING']:
...@@ -565,6 +582,10 @@ class WakeUpOperation(InstanceOperation): ...@@ -565,6 +582,10 @@ class WakeUpOperation(InstanceOperation):
""") """)
required_perms = () required_perms = ()
def is_preferred(self):
return (self.instance.is_base and
self.instance.status == self.instance.STATUS.SUSPENDED)
def check_precond(self): def check_precond(self):
super(WakeUpOperation, self).check_precond() super(WakeUpOperation, self).check_precond()
if self.instance.status not in ['SUSPENDED']: if self.instance.status not in ['SUSPENDED']:
......
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