Commit 154dd694 by Czémán Arnold

dashboard, vm: rework Interface forms, views and operations, add UserInterface…

dashboard, vm: rework Interface forms, views and operations, add UserInterface forms, views and operations; extends Interface models and views with Vxlan support
parent 5f0d3eba
...@@ -947,16 +947,27 @@ class VmRemoveInterfaceForm(OperationForm): ...@@ -947,16 +947,27 @@ class VmRemoveInterfaceForm(OperationForm):
class VmAddInterfaceForm(OperationForm): class VmAddInterfaceForm(OperationForm):
network_type = 'vlan'
label = _('Vlan')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices') choices = kwargs.pop('choices')
super(VmAddInterfaceForm, self).__init__(*args, **kwargs) super(VmAddInterfaceForm, self).__init__(*args, **kwargs)
field = forms.ModelChoiceField( field = forms.ModelChoiceField(
queryset=choices, required=True, label=_('Vlan')) queryset=choices, required=False,
label=self.label)
if not choices: if not choices:
field.widget.attrs['disabled'] = 'disabled' field.widget.attrs['disabled'] = 'disabled'
field.empty_label = _('No more networks.') field.empty_label = _('No more networks.')
self.fields['vlan'] = field self.fields[self.network_type] = field
class VmAddUserInterfaceForm(VmAddInterfaceForm):
network_type = 'vxlan'
label = _('Vxlan')
class DeployChoiceField(forms.ModelChoiceField): class DeployChoiceField(forms.ModelChoiceField):
......
...@@ -28,7 +28,7 @@ $(function() { ...@@ -28,7 +28,7 @@ $(function() {
}); });
/* operations */ /* operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) { $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, #vm-details-add-user-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({ $.ajax({
......
...@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited { ...@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited {
} }
#dashboard-vm-list, #dashboard-node-list, #dashboard-group-list, #dashboard-vm-list, #dashboard-node-list, #dashboard-group-list,
#dashboard-template-list, #dashboard-files-toplist, #dashboard-user-list { #dashboard-template-list, #dashboard-files-toplist, #dashboard-user-list,
#dashboard-vxlan-list {
min-height: 200px; min-height: 200px;
} }
......
...@@ -8,6 +8,13 @@ ...@@ -8,6 +8,13 @@
<i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a> <i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a>
{% endif %}{% endwith %} {% endif %}{% endwith %}
</div> </div>
<div id="vm-details-add-user-interface">
{% with op=op.add_user_interface %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-{{op.effect}} operation pull-right"
{% if op.disabled %}disabled{% endif %}>
<i class="fa fa-{{op.icon}}"></i> {% trans "add user interface" %}</a>
{% endif %}{% endwith %}
</div>
<h2> <h2>
{% trans "Interfaces" %} {% trans "Interfaces" %}
</h2> </h2>
...@@ -16,12 +23,28 @@ ...@@ -16,12 +23,28 @@
{% for i in instance.interface_set.all %} {% for i in instance.interface_set.all %}
<div> <div>
<h3 class="list-group-item-heading dashboard-vm-details-network-h3"> <h3 class="list-group-item-heading dashboard-vm-details-network-h3">
<i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i> {{ i.vlan.name }} <i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i>
{% if not i.host%}({% trans "unmanaged" %}){% endif %} {% if i.vxlan %}
{{ i.vxlan.name }} (user)
{% else %}
{{ i.vlan.name }}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
{% endif %}
{% if user.is_superuser and i.host %} {% if user.is_superuser and i.host %}
<a href="{{ i.host.get_absolute_url }}" <a href="{{ i.host.get_absolute_url }}"
class="btn btn-default btn-xs">{% trans "edit" %}</a> class="btn btn-default btn-xs">{% trans "edit" %}</a>
{% endif %} {% endif %}
{% if i.vxlan %}
{% with op=op.remove_user_interface %}{% if op %}
<span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}"
class="btn btn-{{op.effect}} btn-xs operation interface-remove"
{% if op.disabled %}disabled{% endif %}>{% trans "remove" %}
</a>
</span>
{% endif %}{% endwith %}
{% else %}
{% with op=op.remove_interface %}{% if op %} {% with op=op.remove_interface %}{% if op %}
<span class="operation-wrapper"> <span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}" <a href="{{op.get_url}}?interface={{ i.pk }}"
...@@ -30,6 +53,7 @@ ...@@ -30,6 +53,7 @@
</a> </a>
</span> </span>
{% endif %}{% endwith %} {% endif %}{% endwith %}
{% endif %}
</h3> </h3>
{% if i.host %} {% if i.host %}
<div class="row"> <div class="row">
......
...@@ -48,6 +48,7 @@ from common.models import ( ...@@ -48,6 +48,7 @@ from common.models import (
split_activity_code, split_activity_code,
) )
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from network.models import Vxlan
from manager.scheduler import SchedulerError from manager.scheduler import SchedulerError
from storage.models import Disk from storage.models import Disk
from vm.models import ( from vm.models import (
...@@ -67,7 +68,7 @@ from ..forms import ( ...@@ -67,7 +68,7 @@ from ..forms import (
VmMigrateForm, VmDeployForm, VmMigrateForm, VmDeployForm,
VmPortRemoveForm, VmPortAddForm, VmPortRemoveForm, VmPortAddForm,
VmRemoveInterfaceForm, VmRemoveInterfaceForm,
VmRenameForm, VmRenameForm, VmAddUserInterfaceForm,
) )
from request.models import TemplateAccessType, LeaseType from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm from request.forms import LeaseRequestForm, TemplateRequestForm
...@@ -349,6 +350,32 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView): ...@@ -349,6 +350,32 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView):
return val return val
class VmRemoveUserInterfaceView(FormOperationMixin, VmOperationView):
op = 'remove_user_interface'
form_class = VmRemoveInterfaceForm
show_in_toolbar = False
wait_for_result = 0.5
icon = 'times'
effect = "danger"
with_reload = True
def get_form_kwargs(self):
instance = self.get_op().instance
choices = instance.interface_set.all()
interface_pk = self.request.GET.get('interface')
if interface_pk:
try:
default = choices.get(pk=interface_pk)
except (ValueError, Interface.DoesNotExist):
raise Http404()
else:
default = None
val = super(VmRemoveUserInterfaceView, self).get_form_kwargs()
val.update({'choices': choices, 'default': default})
return val
class VmAddInterfaceView(FormOperationMixin, VmOperationView): class VmAddInterfaceView(FormOperationMixin, VmOperationView):
op = 'add_interface' op = 'add_interface'
...@@ -357,10 +384,11 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView): ...@@ -357,10 +384,11 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
icon = 'globe' icon = 'globe'
effect = 'success' effect = 'success'
with_reload = True with_reload = True
network_model = Vlan
def get_form_kwargs(self): def get_form_kwargs(self):
inst = self.get_op().instance inst = self.get_op().instance
choices = Vlan.get_objects_with_level( choices = self.network_model.get_objects_with_level(
"user", self.request.user).exclude( "user", self.request.user).exclude(
vm_interface__instance__in=[inst]) vm_interface__instance__in=[inst])
val = super(VmAddInterfaceView, self).get_form_kwargs() val = super(VmAddInterfaceView, self).get_form_kwargs()
...@@ -368,6 +396,17 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView): ...@@ -368,6 +396,17 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
return val return val
class VmAddUserInterfaceView(VmAddInterfaceView):
op = 'add_user_interface'
form_class = VmAddUserInterfaceForm
show_in_toolbar = False
icon = 'link'
effect = 'success'
with_reload = True
network_model = Vxlan
class VmDiskModifyView(FormOperationMixin, VmOperationView): class VmDiskModifyView(FormOperationMixin, VmOperationView):
show_in_toolbar = False show_in_toolbar = False
with_reload = True with_reload = True
...@@ -778,6 +817,8 @@ vm_ops = OrderedDict([ ...@@ -778,6 +817,8 @@ vm_ops = OrderedDict([
icon='times', effect="danger")), icon='times', effect="danger")),
('add_interface', VmAddInterfaceView), ('add_interface', VmAddInterfaceView),
('remove_interface', VmRemoveInterfaceView), ('remove_interface', VmRemoveInterfaceView),
('add_user_interface', VmAddUserInterfaceView),
('remove_user_interface', VmRemoveUserInterfaceView),
('remove_port', VmPortRemoveView), ('remove_port', VmPortRemoveView),
('add_port', VmPortAddView), ('add_port', VmPortAddView),
('renew', VmRenewView), ('renew', VmRenewView),
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-05 20:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('network', '0001_initial'),
('vm', '0002_interface_model'),
]
operations = [
migrations.AddField(
model_name='interface',
name='vxlan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vm_interface', to='network.Vxlan', verbose_name='vxlan'),
),
migrations.AddField(
model_name='interfacetemplate',
name='vxlan',
field=models.ForeignKey(blank=True, help_text='Virtual network the interface belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='network.Vxlan', verbose_name='vxlan'),
),
migrations.AlterField(
model_name='interface',
name='vlan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vm_interface', to='firewall.Vlan', verbose_name='vlan'),
),
migrations.AlterField(
model_name='interfacetemplate',
name='vlan',
field=models.ForeignKey(blank=True, help_text='Network the interface belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='firewall.Vlan', verbose_name='vlan'),
),
]
...@@ -25,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop ...@@ -25,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from common.models import create_readable from common.models import create_readable
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from network.models import Vxlan
from ..tasks import net_tasks from ..tasks import net_tasks
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -35,9 +36,15 @@ class InterfaceTemplate(Model): ...@@ -35,9 +36,15 @@ class InterfaceTemplate(Model):
"""Network interface template for an instance template. """Network interface template for an instance template.
If the interface is managed, a host will be created for it. If the interface is managed, a host will be created for it.
Use with Vxlan is never managed.
""" """
vlan = ForeignKey(Vlan, verbose_name=_('vlan'), vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
help_text=_('Network the interface belongs to.')) help_text=_('Network the interface belongs to.'))
vxlan = ForeignKey(Vxlan, blank=True, null=True,
verbose_name=_('vxlan'),
help_text=_('Virtual network the interface '
'belongs to.'))
managed = BooleanField(verbose_name=_('managed'), default=True, managed = BooleanField(verbose_name=_('managed'), default=True,
help_text=_('If a firewall host (i.e. IP address ' help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.')) 'association) should be generated.'))
...@@ -54,7 +61,10 @@ class InterfaceTemplate(Model): ...@@ -54,7 +61,10 @@ class InterfaceTemplate(Model):
verbose_name_plural = _('interface templates') verbose_name_plural = _('interface templates')
def __unicode__(self): def __unicode__(self):
return "%s - %s - %s" % (self.template, self.vlan, self.managed) if self.vlan:
return "%s - %s - %s" % (self.template, self.vlan, self.managed)
else: # vxlan
return "%s - %s - %s" % (self.template, self.vxlan, False)
class Interface(Model): class Interface(Model):
...@@ -64,8 +74,12 @@ class Interface(Model): ...@@ -64,8 +74,12 @@ class Interface(Model):
MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'), MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'),
('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'), ('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'),
('e1000', 'e1000')) ('e1000', 'e1000'))
vlan = ForeignKey(Vlan, verbose_name=_('vlan'), vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
related_name="vm_interface") related_name="vm_interface")
vxlan = ForeignKey(Vxlan, blank=True, null=True,
verbose_name=_('vxlan'),
related_name="vm_interface")
host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True) host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True)
instance = ForeignKey('Instance', verbose_name=_('instance'), instance = ForeignKey('Instance', verbose_name=_('instance'),
related_name='interface_set') related_name='interface_set')
...@@ -77,50 +91,66 @@ class Interface(Model): ...@@ -77,50 +91,66 @@ class Interface(Model):
ordering = ("-vlan__managed", ) ordering = ("-vlan__managed", )
def __unicode__(self): def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid) if self.vxlan is None:
return 'cloud-%s-%s' % (str(self.instance.id),
str(self.vlan.vid))
else: # vxlan
return 'cloud-%s-x%s' % (str(self.instance.id),
str(self.vxlan.vni))
@property @property
def mac(self): def mac(self):
try: try:
return self.host.mac return self.host.mac
except: except:
return Interface.generate_mac(self.instance, self.vlan) return Interface.generate_mac(
self.instance,
self.vlan.vid if self.vxlan is None else self.vxlan.vni,
self.vxlan is not None
)
@classmethod @classmethod
def generate_mac(cls, instance, vlan): def generate_mac(cls, instance, vid, is_vxlan):
"""Generate MAC address for a VM instance on a VLAN. """Generate MAC address for a VM instance on a VLAN.
""" """
# MAC 02:XX:XX:XX:XX:XX # MAC 02:XX:XX:XX:XX:XX
# \________/\__/ # \______/ |\__/
# VM ID VLAN ID # VM ID | V(X)LAN ID
# __________|_____
# / \
# VXLAN: 1, VLAN: 0
class mac_custom(mac_unix): class mac_custom(mac_unix):
word_fmt = '%.2X' word_fmt = '%.2X'
i = instance.id & 0xfffffff i = instance.id & 0xffffff
v = vlan.vid & 0xfff v = vid & 0xfff
m = (0x02 << 40) | (i << 12) | v vx = 1 if is_vxlan else 0
m = (0x02 << 40) | (i << 16) | (vx << 12) | v
return EUI(m, dialect=mac_custom) return EUI(m, dialect=mac_custom)
def get_vmnetwork_desc(self): def get_vmnetwork_desc(self):
return { return {
'name': self.__unicode__(), 'name': self.__unicode__(),
'bridge': 'cloud', 'bridge': ('cloud' if self.vxlan is None
else 'cloudx-%s' % self.vxlan.vni),
'mac': str(self.mac), 'mac': str(self.mac),
'ipv4': str(self.host.ipv4) if self.host is not None else None, 'ipv4': str(self.host.ipv4) if self.host is not None else None,
'ipv6': str(self.host.ipv6) if self.host is not None else None, 'ipv6': str(self.host.ipv6) if self.host is not None else None,
'vlan': self.vlan.vid, 'vlan': self.vlan.vid,
'vxlan': self.vxlan.vni if self.vxlan is not None else None,
'model': self.model, 'model': self.model,
'managed': self.host is not None 'managed': self.host is not None
} }
@classmethod @classmethod
def create(cls, instance, vlan, managed, owner=None, base_activity=None): def create(cls, instance, vlan, managed, vxlan=None,
owner=None, base_activity=None):
"""Create a new interface for a VM instance to the specified VLAN. """Create a new interface for a VM instance to the specified VLAN.
""" """
if managed: if managed and vxlan is None:
host = Host() host = Host()
host.vlan = vlan host.vlan = vlan
# TODO change Host's mac field's type to EUI in firewall # TODO change Host's mac field's type to EUI in firewall
host.mac = str(cls.generate_mac(instance, vlan)) host.mac = str(cls.generate_mac(instance, vlan.vid, False))
host.hostname = instance.vm_name host.hostname = instance.vm_name
# Get addresses from firewall # Get addresses from firewall
if base_activity is None: if base_activity is None:
...@@ -159,7 +189,7 @@ class Interface(Model): ...@@ -159,7 +189,7 @@ class Interface(Model):
else: else:
host = None host = None
iface = cls(vlan=vlan, host=host, instance=instance) iface = cls(vlan=vlan, vxlan=vxlan, host=host, instance=instance)
iface.save() iface.save()
return iface return iface
...@@ -180,7 +210,10 @@ class Interface(Model): ...@@ -180,7 +210,10 @@ class Interface(Model):
def save_as_template(self, instance_template): def save_as_template(self, instance_template):
"""Create a template based on this interface. """Create a template based on this interface.
""" """
i = InterfaceTemplate(vlan=self.vlan, managed=self.host is not None, i = InterfaceTemplate(vlan=self.vlan,
managed=(
self.host is not None or
self.vxlan or not None),
template=instance_template) template=instance_template)
i.save() i.save()
return i return i
...@@ -202,32 +202,26 @@ class RemoteAgentOperation(EnsureAgentMixin, RemoteInstanceOperation): ...@@ -202,32 +202,26 @@ class RemoteAgentOperation(EnsureAgentMixin, RemoteInstanceOperation):
concurrency_check = False concurrency_check = False
@register_operation class AddInterfaceOperationBase(InstanceOperation):
class AddInterfaceOperation(InstanceOperation): id = None
id = 'add_interface' name = None
name = _("add interface") description = None
description = _("Add a new network interface for the specified VLAN to "
"the VM.")
required_perms = () required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
network_type = None
def rollback(self, net, activity): def rollback(self, net, activity):
with activity.sub_activity( with activity.sub_activity(
'destroying_net', 'destroying_net',
readable_name=ugettext_noop("destroy network (rollback)")): readable_name=ugettext_noop('destroy network (rollback)')):
net.destroy() net.destroy()
net.delete() net.delete()
def _operation(self, activity, user, system, vlan, managed=None): def _operation(self, activity, user, system, vlan, vxlan, managed):
if not vlan.has_level(user, 'user'): net = Interface.create(base_activity=activity,
raise humanize_exception(ugettext_noop( instance=self.instance,
"User acces to vlan %(vlan)s is required."), managed=managed, owner=user,
PermissionDenied(), vlan=vlan) vlan=vlan, vxlan=vxlan)
if managed is None:
managed = vlan.managed
net = Interface.create(base_activity=activity, instance=self.instance,
managed=managed, owner=user, vlan=vlan)
if self.instance.is_running: if self.instance.is_running:
try: try:
...@@ -242,8 +236,46 @@ class AddInterfaceOperation(InstanceOperation): ...@@ -242,8 +236,46 @@ class AddInterfaceOperation(InstanceOperation):
self.instance._restart_networking(parent_activity=activity) self.instance._restart_networking(parent_activity=activity)
def get_activity_name(self, kwargs): def get_activity_name(self, kwargs):
return create_readable(ugettext_noop("add %(vlan)s interface"), return create_readable(ugettext_noop('add %(vlan)s interface'),
vlan=kwargs['vlan']) vlan=kwargs[self.network_type])
@register_operation
class AddInterfaceOperation(AddInterfaceOperationBase):
id = 'add_interface'
name = _('add interface')
description = _('Add a new network interface for the specified VLAN to '
'the VM.')
network_type = 'vlan'
def _operation(self, activity, user, system, vlan, managed=None):
if not vlan.has_level(user, 'user'):
raise humanize_exception(ugettext_noop(
'User acces to vlan %(vlan)s is required.'),
PermissionDenied(), vlan=vlan)
if managed is None:
managed = vlan.managed
super(AddInterfaceOperation, self)._operation(
activity=activity, user=user, system=system,
vlan=vlan, vxlan=None, managed=managed)
@register_operation
class AddUserInterfaceOperation(AddInterfaceOperationBase):
id = 'add_user_interface'
name = _('add user interface')
description = _('Add a new user network interface for the specified to '
'the VM.')
network_type = 'vxlan'
def _operation(self, activity, user, system, vxlan, managed=None):
if not vxlan.has_level(user, 'user'):
raise humanize_exception(ugettext_noop(
'User acces to vxlan %(vxlan)s is required.'),
PermissionDenied(), vxlan=vxlan)
super(AddUserInterfaceOperation, self)._operation(
activity=activity, user=user, system=system,
vlan=vxlan.vlan, vxlan=vxlan, managed=managed)
@register_operation @register_operation
...@@ -624,6 +656,15 @@ class RemoveInterfaceOperation(InstanceOperation): ...@@ -624,6 +656,15 @@ class RemoveInterfaceOperation(InstanceOperation):
@register_operation @register_operation
class RemoveUserInterfaceOperation(RemoveInterfaceOperation):
id = 'remove_user_interface'
name = _("remove user interface")
description = _("Remove the specified user network interface.")
required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
@register_operation
class RemovePortOperation(InstanceOperation): class RemovePortOperation(InstanceOperation):
id = 'remove_port' id = 'remove_port'
name = _("close port") name = _("close port")
......
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