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):
class VmAddInterfaceForm(OperationForm):
network_type = 'vlan'
label = _('Vlan')
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
super(VmAddInterfaceForm, self).__init__(*args, **kwargs)
field = forms.ModelChoiceField(
queryset=choices, required=True, label=_('Vlan'))
queryset=choices, required=False,
label=self.label)
if not choices:
field.widget.attrs['disabled'] = 'disabled'
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):
......
......@@ -28,7 +28,7 @@ $(function() {
});
/* 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');
$.ajax({
......
......@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited {
}
#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;
}
......
......@@ -8,6 +8,13 @@
<i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a>
{% endif %}{% endwith %}
</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>
{% trans "Interfaces" %}
</h2>
......@@ -16,12 +23,28 @@
{% for i in instance.interface_set.all %}
<div>
<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 }}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
<i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i>
{% 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 %}
<a href="{{ i.host.get_absolute_url }}"
class="btn btn-default btn-xs">{% trans "edit" %}</a>
{% 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 %}
<span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}"
......@@ -30,6 +53,7 @@
</a>
</span>
{% endif %}{% endwith %}
{% endif %}
</h3>
{% if i.host %}
<div class="row">
......
......@@ -48,6 +48,7 @@ from common.models import (
split_activity_code,
)
from firewall.models import Vlan, Host, Rule
from network.models import Vxlan
from manager.scheduler import SchedulerError
from storage.models import Disk
from vm.models import (
......@@ -67,7 +68,7 @@ from ..forms import (
VmMigrateForm, VmDeployForm,
VmPortRemoveForm, VmPortAddForm,
VmRemoveInterfaceForm,
VmRenameForm,
VmRenameForm, VmAddUserInterfaceForm,
)
from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm
......@@ -349,6 +350,32 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView):
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):
op = 'add_interface'
......@@ -357,10 +384,11 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
icon = 'globe'
effect = 'success'
with_reload = True
network_model = Vlan
def get_form_kwargs(self):
inst = self.get_op().instance
choices = Vlan.get_objects_with_level(
choices = self.network_model.get_objects_with_level(
"user", self.request.user).exclude(
vm_interface__instance__in=[inst])
val = super(VmAddInterfaceView, self).get_form_kwargs()
......@@ -368,6 +396,17 @@ class VmAddInterfaceView(FormOperationMixin, VmOperationView):
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):
show_in_toolbar = False
with_reload = True
......@@ -778,6 +817,8 @@ vm_ops = OrderedDict([
icon='times', effect="danger")),
('add_interface', VmAddInterfaceView),
('remove_interface', VmRemoveInterfaceView),
('add_user_interface', VmAddUserInterfaceView),
('remove_user_interface', VmRemoveUserInterfaceView),
('remove_port', VmPortRemoveView),
('add_port', VmPortAddView),
('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
from common.models import create_readable
from firewall.models import Vlan, Host
from network.models import Vxlan
from ..tasks import net_tasks
logger = getLogger(__name__)
......@@ -35,9 +36,15 @@ class InterfaceTemplate(Model):
"""Network interface template for an instance template.
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.'))
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,
help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.'))
......@@ -54,7 +61,10 @@ class InterfaceTemplate(Model):
verbose_name_plural = _('interface templates')
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):
......@@ -64,8 +74,12 @@ class Interface(Model):
MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'),
('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'),
('e1000', 'e1000'))
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
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)
instance = ForeignKey('Instance', verbose_name=_('instance'),
related_name='interface_set')
......@@ -77,50 +91,66 @@ class Interface(Model):
ordering = ("-vlan__managed", )
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
def mac(self):
try:
return self.host.mac
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
def generate_mac(cls, instance, vlan):
def generate_mac(cls, instance, vid, is_vxlan):
"""Generate MAC address for a VM instance on a VLAN.
"""
# 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):
word_fmt = '%.2X'
i = instance.id & 0xfffffff
v = vlan.vid & 0xfff
m = (0x02 << 40) | (i << 12) | v
i = instance.id & 0xffffff
v = vid & 0xfff
vx = 1 if is_vxlan else 0
m = (0x02 << 40) | (i << 16) | (vx << 12) | v
return EUI(m, dialect=mac_custom)
def get_vmnetwork_desc(self):
return {
'name': self.__unicode__(),
'bridge': 'cloud',
'bridge': ('cloud' if self.vxlan is None
else 'cloudx-%s' % self.vxlan.vni),
'mac': str(self.mac),
'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,
'vlan': self.vlan.vid,
'vxlan': self.vxlan.vni if self.vxlan is not None else None,
'model': self.model,
'managed': self.host is not None
}
@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.
"""
if managed:
if managed and vxlan is None:
host = Host()
host.vlan = vlan
# 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
# Get addresses from firewall
if base_activity is None:
......@@ -159,7 +189,7 @@ class Interface(Model):
else:
host = None
iface = cls(vlan=vlan, host=host, instance=instance)
iface = cls(vlan=vlan, vxlan=vxlan, host=host, instance=instance)
iface.save()
return iface
......@@ -180,7 +210,10 @@ class Interface(Model):
def save_as_template(self, instance_template):
"""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)
i.save()
return i
......@@ -202,32 +202,26 @@ class RemoteAgentOperation(EnsureAgentMixin, RemoteInstanceOperation):
concurrency_check = False
@register_operation
class AddInterfaceOperation(InstanceOperation):
id = 'add_interface'
name = _("add interface")
description = _("Add a new network interface for the specified VLAN to "
"the VM.")
class AddInterfaceOperationBase(InstanceOperation):
id = None
name = None
description = None
required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
network_type = None
def rollback(self, net, activity):
with activity.sub_activity(
'destroying_net',
readable_name=ugettext_noop("destroy network (rollback)")):
readable_name=ugettext_noop('destroy network (rollback)')):
net.destroy()
net.delete()
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
net = Interface.create(base_activity=activity, instance=self.instance,
managed=managed, owner=user, vlan=vlan)
def _operation(self, activity, user, system, vlan, vxlan, managed):
net = Interface.create(base_activity=activity,
instance=self.instance,
managed=managed, owner=user,
vlan=vlan, vxlan=vxlan)
if self.instance.is_running:
try:
......@@ -242,8 +236,46 @@ class AddInterfaceOperation(InstanceOperation):
self.instance._restart_networking(parent_activity=activity)
def get_activity_name(self, kwargs):
return create_readable(ugettext_noop("add %(vlan)s interface"),
vlan=kwargs['vlan'])
return create_readable(ugettext_noop('add %(vlan)s interface'),
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
......@@ -624,6 +656,15 @@ class RemoveInterfaceOperation(InstanceOperation):
@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):
id = 'remove_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