Commit 23530b0b by Szabolcs Gelencser

Merge usernetworks

parents 132dd4a5 cc5b0edb
# 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.core.exceptions import PermissionDenied
class CheckedObjectMixin(object):
read_level = 'user'
def get_object(self, **kwargs):
obj = super(CheckedObjectMixin, self).get_object()
if not obj.has_level(self.request.user, self.read_level):
raise PermissionDenied()
return obj
......@@ -22,6 +22,7 @@
"favico.js": "~0.3.5",
"datatables": "~1.10.4",
"chart.js": "2.3.0",
"clipboard": "~1.6.1"
"clipboard": "~1.6.1",
"jsPlumb": "2.5.7"
}
}
No preview for this file type
......@@ -548,4 +548,8 @@ POLICY_FILES = {
POLICY_DIRS = {
'compute': ['nova_policy.d'],
'volume': ['cinder_policy.d'],
}
\ No newline at end of file
}
DEFAULT_USERNET_VLAN_NAME = (
get_env_variable("DEFAULT_USERNET_VLAN_NAME", "usernet"))
USERNET_MAX = 2 ** 12
......@@ -110,6 +110,7 @@ if DEBUG:
PIPELINE["COMPILERS"] = (
'dashboard.compilers.DummyLessCompiler',
'pipeline.compilers.es6.ES6Compiler',
)
ADMIN_ENABLED = True
......
# Copyright 2017 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/>.
""" Static files and pipeline configuration. """
# flake8: noqa
from os.path import abspath, dirname, join, normpath, isfile, exists
from util import get_env_variable
########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
BASE_DIR = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(BASE_DIR)
########## MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, 'media'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = get_env_variable('DJANGO_MEDIA_URL', default='/media/')
########## END MEDIA CONFIGURATION
########## STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = normpath(join(SITE_ROOT, 'static_collected'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = get_env_variable('DJANGO_STATIC_URL', default='/static/')
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'pipeline.finders.PipelineFinder',
)
########## END STATIC FILE CONFIGURATION
STATICFILES_DIRS = [normpath(join(SITE_ROOT, 'bower_components'))]
p = normpath(join(SITE_ROOT, '../../site-circle/static'))
if exists(p):
STATICFILES_DIRS.append(p)
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
PIPELINE = {
'COMPILERS' : ('pipeline.compilers.less.LessCompiler',
'pipeline.compilers.es6.ES6Compiler', ),
'LESS_ARGUMENTS': u'--include-path={}'.format(':'.join(STATICFILES_DIRS)),
'CSS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor',
'BABEL_ARGUMENTS': u'--presets env',
'JS_COMPRESSOR': None,
'DISABLE_WRAPPER': True,
'STYLESHEETS': {
"all": {
"source_filenames": (
"compile_bootstrap.less",
"bootstrap/dist/css/bootstrap-theme.css",
"fontawesome/css/font-awesome.css",
"jquery-simple-slider/css/simple-slider.css",
"intro.js/introjs.css",
"template.less",
"dashboard/dashboard.less",
"network/network.less",
"autocomplete_light/vendor/select2/dist/css/select2.css",
"autocomplete_light/select2.css",
),
"output_filename": "all.css",
},
"network-editor": {
"source_filenames": (
"network/editor.less",
),
"output_filename": "network-editor.css",
},
},
'JAVASCRIPT': {
"all": {
"source_filenames": (
# "jquery/dist/jquery.js", # included separately
"bootbox/bootbox.js",
"bootstrap/dist/js/bootstrap.js",
"intro.js/intro.js",
"jquery-knob/dist/jquery.knob.min.js",
"jquery-simple-slider/js/simple-slider.js",
"favico.js/favico.js",
"datatables/media/js/jquery.dataTables.js",
"autocomplete_light/jquery.init.js",
"autocomplete_light/autocomplete.init.js",
"autocomplete_light/vendor/select2/dist/js/select2.js",
"autocomplete_light/select2.js",
"jsPlumb/dist/js/dom.jsPlumb-1.7.5-min.js",
"dashboard/dashboard.js",
"dashboard/activity.js",
"dashboard/group-details.js",
"dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file
"dashboard/node-create.js",
"dashboard/node-details.js",
"dashboard/node-list.js",
"dashboard/profile.js",
"dashboard/store.js",
"dashboard/template-list.js",
"dashboard/vm-common.js",
"dashboard/vm-create.js",
"dashboard/vm-list.js",
"dashboard/help.js",
"js/host.js",
"js/network.js",
"js/switch-port.js",
"js/host-list.js",
),
"output_filename": "all.js",
},
"vm-detail": {
"source_filenames": (
"clipboard/dist/clipboard.min.js",
"dashboard/vm-details.js",
"no-vnc/include/util.js",
"no-vnc/include/webutil.js",
"no-vnc/include/base64.js",
"no-vnc/include/websock.js",
"no-vnc/include/des.js",
"no-vnc/include/keysym.js",
"no-vnc/include/keysymdef.js",
"no-vnc/include/keyboard.js",
"no-vnc/include/input.js",
"no-vnc/include/display.js",
"no-vnc/include/jsunzip.js",
"no-vnc/include/rfb.js",
"dashboard/vm-console.js",
"dashboard/vm-tour.js",
),
"output_filename": "vm-detail.js",
},
"datastore": {
"source_filenames": (
"chart.js/dist/Chart.min.js",
"dashboard/datastore-details.js"
),
"output_filename": "datastore.js",
},
"network-editor": {
"source_filenames": (
"jsPlumb/dist/js/jsplumb.min.js",
"network/editor.es6",
),
"output_filename": "network-editor.js",
},
},
}
# Copyright 2017 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 os import environ
from django.core.exceptions import ImproperlyConfigured
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
def get_env_variable(var_name, default=None):
""" Get the environment variable or return exception/default """
try:
return environ[var_name]
except KeyError:
if default is None:
error_msg = "Set the %s environment variable" % var_name
raise ImproperlyConfigured(error_msg)
else:
return default
......@@ -30,7 +30,7 @@ utils.patch_middleware_get_user()
urlpatterns = [
url(r'^$', lambda x: redirect(reverse("dashboard.index"))),
# url(r'^network/', include('network.urls')),
url(r'^network/', include('network.urls')),
# url(r'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')),
......
......@@ -19,8 +19,11 @@ from sys import exc_info
import logging
from django.shortcuts import render_to_response
from django.shortcuts import render_to_response, redirect
from django.contrib import messages
from django.template import RequestContext
from django.http import JsonResponse
from django.utils.translation import ugettext_lazy as _
from .models import HumanReadableException
......@@ -59,3 +62,29 @@ def handler403(request):
resp = render_to_response("403.html", ctx)
resp.status_code = 403
return resp
class CreateLimitedResourceMixin(object):
resource_name = None
model = None
profile_attribute = None
def post(self, *args, **kwargs):
user = self.request.user
try:
limit = getattr(user.profile, self.profile_attribute)
except Exception as e:
logger.debug('No profile or %s: %s', self.profile_attribute, e)
else:
current = self.model.objects.filter(owner=user).count()
logger.debug('%s current use: %d, limit: %d',
self.resource_name, current, limit)
if current > limit:
messages.error(self.request,
_('%s limit (%d) exceeded.')
% (self.resource_name, limit))
if self.request.is_ajax():
return JsonResponse({'redirect': '/'})
else:
return redirect('/')
return super(CreateLimitedResourceMixin, self).post(*args, **kwargs)
......@@ -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):
......@@ -1298,6 +1309,9 @@ class UserEditForm(forms.ModelForm):
instance_limit = forms.IntegerField(
label=_('Instance limit'),
min_value=0, widget=NumberInput)
network_limit = forms.IntegerField(
label=_('Virtual network limit'),
min_value=0, widget=NumberInput)
two_factor_secret = forms.CharField(
label=_('Two-factor authentication secret'),
help_text=_("Remove the secret key to disable two-factor "
......@@ -1307,18 +1321,22 @@ class UserEditForm(forms.ModelForm):
super(UserEditForm, self).__init__(*args, **kwargs)
self.fields["instance_limit"].initial = (
self.instance.profile.instance_limit)
self.fields["network_limit"].initial = (
self.instance.profile.network_limit)
self.fields["two_factor_secret"].initial = (
self.instance.profile.two_factor_secret)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'instance_limit',
'is_active', "two_factor_secret", )
'network_limit', 'is_active', 'two_factor_secret', )
def save(self, commit=True):
user = super(UserEditForm, self).save()
user.profile.instance_limit = (
self.cleaned_data['instance_limit'] or None)
user.profile.network_limit = (
self.cleaned_data['network_limit'] or None)
user.profile.two_factor_secret = (
self.cleaned_data['two_factor_secret'] or None)
user.profile.save()
......
......@@ -125,6 +125,11 @@ class Command(BaseCommand):
vm.snat_to.add(net)
vm.snat_to.add(vm)
# Add unmanged vlan for user networks
self.create(Vlan, 'vid', name='usernet', vid=5,
network4='0.0.0.0/0', domain=vm_domain,
managed=False)
# default vlan groups
vg_all = self.create(VlanGroup, 'name', name='all')
vg_all.vlans.add(vm, man, net)
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-10 00:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0006_auto_20170707_1909'),
]
operations = [
migrations.AddField(
model_name='profile',
name='network_limit',
field=models.IntegerField(default=2, verbose_name=b'Virtual network limit'),
),
]
......@@ -178,6 +178,8 @@ class Profile(Model):
unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5)
network_limit = IntegerField(default=2,
verbose_name="Virtual network limit")
use_gravatar = BooleanField(
verbose_name=_("Use Gravatar"), default=True,
help_text=_("Whether to use email address as Gravatar profile image"))
......
......@@ -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({
......
......@@ -223,6 +223,7 @@ $(function () {
register_search($("#dashboard-group-search-form"), $("#dashboard-group-list"), generateGroupHTML);
register_search($("#dashboard-user-search-form"), $("#dashboard-user-list"), generateUserHTML);
register_search($("#dashboard-template-search-form"), $("#dashboard-template-list"), generateTemplateHTML);
register_search($("#dashboard-vxlan-search-form"), $("#dashboard-vxlan-list"), generateVxlanHTML);
/* notification message toggle */
$(document).on('click', ".notification-message-subject", function() {
......@@ -331,6 +332,18 @@ function generateNodeHTML(data, is_last) {
'</a>';
}
function generateVxlanHTML(data, is_last) {
html = '<a href="' + data.url + '" class="list-group-item real-link' + (is_last ? ' list-group-item-last' : '') + '">' +
'<span class="index-vxlan-list-name">' +
'<i class="fa ' + data.icon + '" title="' + data.name + '"></i> ' + safe_tags_replace(data.name);
if(data.vni !== null && data.vni !== undefined)
html += ' <small class="text-muted"> vni: ' + data.vni + '</small>';
html += '</span>' +
'<div style="clear: both;"></div>' +
'</a>';
return html;
}
/* copare vm-s by fav, pk order */
function compareVmByFav(a, b) {
if(a.fav && b.fav) {
......
......@@ -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;
}
......
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<span class="btn btn-default btn-xs infobtn pull-right" data-container="body" title="{% trans "List of virtual networks that are available for you." %}">
<i class="fa fa-info-circle"></i>
</span>
<a href="{% url "network.editor" %}" class="btn btn-default btn-xs pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Editor
</a>
<h3 class="no-margin"><i class="fa fa-globe"></i> {% trans "Virtual networks" %}
</h3>
</div>
<div class="list-group" id="vxlan-list-view">
<div id="dashboard-vxlan-list">
{% for vxlan in vxlans %}
<a href="{% url "network.vxlan" vni=vxlan.vni %}" class="list-group-item
{% if forloop.last and vxlan|length < 5 %} list-group-item-last{% endif %}">
<span class="index-vxlan-list-name">
<i class="fa fa-sitemap"></i> {{ vxlan.name }}
{% if user.is_superuser %}
<small class="text-muted"> vni: {{ vxlan.vni }} </small>
{% endif %}
</span>
</a>
{% endfor %}
</div>
<div class="list-group-item list-group-footer">
<div class="row">
<div class="col-xs-5 col-sm-6">
<form action="{% url "network.vxlan-list" %}" method="GET" id="dashboard-vxlan-search-form">
<div class="input-group input-group-sm">
<input name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
</div>
<div class="col-xs-7 col-sm-6 text-right">
<a href="{% url "network.vxlan-list" %}" class="btn btn-primary btn-xs">
<i class="fa fa-chevron-circle-right"></i> {% trans "show all" %}
</a>
<a href="{% url "network.vxlan-create" %}" class="btn btn-success btn-xs">
<i class="fa fa-plus-circle"></i> {% trans "new" %}
</a>
</div>
</div>
</div>
</div>
</div>
......@@ -48,6 +48,11 @@
{# {% include "dashboard/index-users.html" %}#}
{# </div>#}
{# {% endif %}#}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vxlans.html" %}
</div>
</div>
</div>
{% endblock %}
......@@ -8,6 +8,15 @@
<i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a>
{% endif %}{% endwith %}
</div>
<br />
<br />
<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 +25,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 +55,7 @@
</a>
</span>
{% endif %}{% endwith %}
{% endif %}
</h3>
{% if i.host %}
<div class="row">
......
......@@ -15,6 +15,9 @@
<!--<i class="fa fa-refresh fa-spin fa-2x"></i>-->
</div>
<a class="pull-right btn btn-success btn-xs vm-create" href="{% url "dashboard.views.vm-create" %}"><i class="fa fa-plus-circle"></i> {% trans "new virtual machine" %}</a>
<a href="{% url "network.editor" %}" class="btn btn-primary btn-xs pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Edit topology
</a>
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Virtual machines" %}</h3>
</div>
<div class="panel-body">
......
......@@ -25,6 +25,8 @@ from django.contrib.auth.models import Group, User
from django.core.cache import cache
from django.views.generic import TemplateView
from vm.models import Instance, Node, InstanceTemplate
from dashboard.views.vm import vm_ops
from network.models import Vxlan
from ..store_api import Store
......
......@@ -20,13 +20,14 @@ from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput, Field
from crispy_forms.bootstrap import FormActions, FieldWithButtons, StrictButton
from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, Firewall
)
from network.models import Vxlan
class LinkButton(BaseInput):
......@@ -348,3 +349,53 @@ class VlanGroupForm(ModelForm):
class Meta:
model = VlanGroup
fields = ("name", "vlans", "description", "owner", )
class VxlanSuperUserForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
Div(
Fieldset(
'',
'name',
'vni',
'vlan',
'description',
'comment',
'owner',
)
),
FormActions(
Submit('submit', _('Save')),
LinkButton('back', _('Back'), reverse_lazy(
'network.vxlan-list'))
)
)
class Meta:
model = Vxlan
fields = ('name', 'vni', 'vlan', 'description', 'comment', 'owner', )
class VxlanForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
Div(
Fieldset(
'',
'name',
'description',
'comment',
Field('vni', type='hidden'),
)
),
FormActions(
Submit('submit', _('Save')),
LinkButton('back', _('Back'), reverse_lazy(
'network.vxlan-list'))
)
)
class Meta:
model = Vxlan
fields = ('name', 'description', 'comment', 'vni', )
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-25 18:56
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import firewall.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('firewall', '0006_auto_20170707_1909'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Vxlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vni', models.IntegerField(help_text='VXLAN Network Identifier.', unique=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(16777215)], verbose_name='VNI')),
('name', models.CharField(help_text='The short name of the virtual network.', max_length=20, unique=True, validators=[firewall.fields.val_alfanum], verbose_name='Name')),
('description', models.TextField(blank=True, help_text='Description of the goals and elements of the virtual network.', verbose_name='description')),
('comment', models.TextField(blank=True, help_text='Notes, comments about the network', verbose_name='comment')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('modified_at', models.DateTimeField(auto_now=True, verbose_name='modified at')),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='owner')),
('vlan', models.ForeignKey(help_text='The server vlan.', on_delete=django.db.models.deletion.CASCADE, to='firewall.Vlan', verbose_name='vlan')),
],
options={
'ordering': ('vni',),
'verbose_name': 'vxlan',
'verbose_name_plural': 'vxlans',
},
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-10 00:41
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import firewall.fields
class Migration(migrations.Migration):
dependencies = [
('network', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='vxlan',
options={'ordering': ('vni',), 'permissions': (('create_vxlan', 'Can create a Vxlan network.'),), 'verbose_name': 'vxlan', 'verbose_name_plural': 'vxlans'},
),
migrations.AlterField(
model_name='vxlan',
name='name',
field=models.CharField(help_text='The short name of the virtual network.', max_length=20, validators=[firewall.fields.val_alfanum], verbose_name='Name'),
),
migrations.AlterField(
model_name='vxlan',
name='vni',
field=models.IntegerField(help_text='VXLAN Network Identifier.', unique=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(4095)], verbose_name='VNI'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-30 01:01
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('network', '0002_auto_20171110_0041'),
]
operations = [
migrations.CreateModel(
name='EditorElement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('x', models.IntegerField()),
('y', models.IntegerField()),
('free_port_num', models.IntegerField()),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
......@@ -15,4 +15,103 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# Create your models here.
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.fields import (
GenericRelation, GenericForeignKey
)
from django.contrib.contenttypes.models import ContentType
from firewall.models import Vlan
from firewall.fields import val_alfanum
from openstack_auth.user import User
class EditorElement(models.Model):
x = models.IntegerField()
y = models.IntegerField()
free_port_num = models.IntegerField()
owner = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
def as_data(self):
type = 'network' if isinstance(self.content_object, Vxlan) else 'vm'
if type == 'network':
id = "net-%s" % self.content_object.vni
else:
id = "vm-%s" % self.content_object.pk
return {
'name': unicode(self.content_object),
'id': id,
'x': self.x,
'y': self.y,
'free_port_num': self.free_port_num,
'type': type,
'description': self.content_object.description,
}
class Vxlan(models.Model):
"""
A virtual L2 network,
These networks are isolated by the vxlan (virtual extensible lan)
technology, which is commonly used by managed network switches
to partition the network, with a more scalable way opposite vlan
technology. Usually, it used over vlan networks.
Each vxlan network has a unique identifier (VNI), a name, and
a server vlan network.
"""
# NOTE: VXLAN VNI's maximal value is 2^24-1, but MAC address generator
# only supports 2^12-1 maximal value.
vni = models.IntegerField(unique=True,
verbose_name=_('VNI'),
help_text=_('VXLAN Network Identifier.'),
validators=[MinValueValidator(0),
MaxValueValidator(2 ** 12 - 1)])
vlan = models.ForeignKey(Vlan,
verbose_name=_('vlan'),
help_text=_('The server vlan.'))
name = models.CharField(max_length=20,
verbose_name=_('Name'),
help_text=_('The short name of the '
'virtual network.'),
validators=[val_alfanum])
description = models.TextField(blank=True, verbose_name=_('description'),
help_text=_(
'Description of the goals and elements '
'of the virtual network.'))
comment = models.TextField(blank=True,
verbose_name=_('comment'),
help_text=_(
'Notes, comments about the network'))
created_at = models.DateTimeField(auto_now_add=True,
verbose_name=_('created at'))
owner = models.ForeignKey(User, blank=True, null=True,
verbose_name=_('owner'))
modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at'))
editor_elements = GenericRelation(EditorElement)
class Meta:
app_label = 'network'
verbose_name = _("vxlan")
verbose_name_plural = _("vxlans")
ordering = ('vni', )
permissions = (
('create_vxlan', _('Can create a Vxlan network.')),
)
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('network.vxlan', kwargs={'vni': self.vni})
.flex-container {
display: flex;
flex-direction: row;
}
#filterConatiner {
margin-left: 10px;
margin-right: 10px;
}
#dragPanel {
flex: 250px;
width: 250px;
margin: 0px;
overflow: hidden;
height: 700px;
}
#dragContainer{
overflow-y: scroll;
height: 574px;
}
#dropContainer{
position: relative !important;
text-align: justify !important;
flex: auto;
width: 90%;
height: auto;
background: grey;
resize: none;
}
.unused-element {
cursor: pointer;
}
.element {
position: absolute;
left: 10px;
display: inline;
line-height: 75px;
text-align: center;
vertical-align: middle;
color: black;
font-size: 20px;
height: 75px;
padding-left: 10px;
padding-right: 10px;
border-radius: 8px;
background: white;
-webkit-border-radius: 8px;
border-style: solid;
border-width: 1px;
z-index: 40;
cursor: pointer;
}
.container {
margin: 10px;
width: 100%;
padding: 0px;
}
body {
height: 100%;
}
......@@ -24,6 +24,8 @@ from django_tables2.columns import (LinkColumn, TemplateColumn, Column,
BooleanColumn)
from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort
from network.models import Vxlan
from vm.models import Interface
class MACColumn(Column):
......@@ -233,6 +235,16 @@ class VlanGroupTable(Table):
order_by = 'name'
class VxlanTable(Table):
name = LinkColumn('network.vxlan', args=[A('vni')])
class Meta:
model = Vxlan
attrs = {'class': 'table table-striped table-condensed'}
fields = ('vni', 'name', )
order_by = 'vni'
class HostRecordsTable(Table):
fqdn = LinkColumn(
"network.record", args=[A("pk")],
......@@ -282,3 +294,16 @@ class FirewallRuleTable(Table):
'action', 'proto', 'actions')
order_by = '-pk'
empty_text = _("No related rules found.")
class SmallVmTable(Table):
instance = Column(accessor=A('instance.name'), verbose_name='VM')
instance_id = LinkColumn('dashboard.views.detail', args=[A('instance.pk')],
accessor=A('instance.pk'), verbose_name='ID')
class Meta:
model = Interface
attrs = {'class': 'table table-striped'}
fields = ('instance', )
sequence = ('instance_id', 'instance', )
order_by = 'instance.pk'
......@@ -29,7 +29,22 @@
</div>
{% trans "Vlans" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "802.1Q virtual networks" %}</small>
</div>
</h3>
<h3>
<div class="pull-right">
<a href="{% url "network.vxlan-list" %}" class="btn btn-xs btn-default">
<i class="fa fa-list"></i> {% trans "list" %}
</a>
<a href="{% url "network.vxlan-create" %}" class="btn btn-xs btn-success">
<i class="fa fa-plus-circle"></i> {% trans "new" %}
</a>
</div>
{% trans "Vxlans" %}
<div>
<small>{% trans "VXLAN virtual networks" %}</small>
</div>
</h3>
......@@ -44,7 +59,7 @@
</div>
{% trans "Domains" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Domain names" %}</small>
</div>
</h3>
......@@ -59,7 +74,7 @@
</div>
{% trans "Records" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "DNS records" %}</small>
</div>
</h3>
......@@ -74,7 +89,7 @@
</div>
{% trans "Blacklist items" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "List of denied IPs and exceptions" %}</small>
</div>
</h3>
......@@ -89,7 +104,7 @@
</div>
{% trans "Rules" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Firewall rules" %}</small>
</div>
</h3>
......@@ -104,7 +119,7 @@
</div>
{% trans "Switch ports" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Switch ports" %}</small>
</div>
</h3>
......@@ -119,7 +134,7 @@
</div>
{% trans "Vlan groups" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Groups of 802.1Q virtual networks" %}</small>
</div>
</h3>
......@@ -134,7 +149,7 @@
</div>
{% trans "Host groups" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Groups of hosts are machines on the network" %}</small>
</div>
</h3>
</div>
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load pipeline %}
{% block title-page %}{% trans 'Network Editor' %}{% endblock %}
{% block extra_css %}
{% stylesheet "network-editor" %}
{% endblock %}
{% block content %}
<div class="flex-container" id="workspace">
<div class="panel panel-default text-center" id="dragPanel">
<div class="panel-heading">
<div class="row">
<div class="col-md-9 text-left">
<h3 class="no-margin"><i class="fa fa-sitemap"></i> {% trans 'Editor' %}</h3>
</div>
<div class="col-md-3 text-left">
<button class="btn btn-success btn-xs" id="saveButton"><i class="fa fa-floppy-o"></i></button>
</div>
</div>
</div>
<div class="panel-heading text-center">
<div id="filterConatiner">
<div class="row">
<input type="text" class="form-control" id="searchField" placeholder="{% trans 'Search' %}"/><br />
</div>
<div class="row">
<div class="col-md-6">
<i id="vm-filter"></i> <i class="fa fa-desktop"></i> vm
</div>
<div class="col-md-6">
<i id="net-filter"></i> <i class="fa fa-sitemap"></i> net
</div>
</div>
</div>
</div>
<div class="panel-body" id="dragContainer">
</div>
</div>
<div class="" id="dropContainer" oncontextmenu="return false;"></div>
</div>
{% endblock %}
{% block extra_js %}
{% javascript "network-editor" %}
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create" %} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<h2>{% trans "Create a new vxlan" %}</h2>
</div>
<div class="row">
<div class="col-sm-8">
{% crispy form %}
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{{ form.name.value }} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-delete" vni=vxlan.vni %}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this vxlan" %}</a>
<h2>{{ form.name.value }} <small>{% trans "details of vxlan" %}</small></h2>
</div>
<div class="row">
<div class="col-sm-6">
{% crispy form %}
</div>
<div class="col-sm-6">
<div class="page-header">
<h3>{% trans "Connected virtual machines" %}</h3>
</div>
{% render_table vm_list %}
<div class="page-header">
<h3>{% trans "Manage access" %}</h3>
</div>
{% include "dashboard/_manage_access.html" with table_id="vxlan-access-table" %}
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% block title-page %}{% trans "Vxlans" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-create" %}" class="btn btn-success pull-right"><i class="fa fa-plus-circle"></i> {% trans "Create a new vxlan" %}</a>
<a href="{% url "network.editor" %}" class="btn btn-primary pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Edit topology
</a>
<h1>Vxlans <small>{% trans "list of all vxlans" %}</small></h1>
</div>
<div class="table-responsive">
{% render_table table %}
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create" %} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<h2>{% trans "Create a new vxlan" %}</h2>
</div>
<div class="row">
<div class="col-sm-8">
{% crispy form %}
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{{ form.name.value }} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-delete" vni=vxlan.vni %}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this vxlan" %}</a>
<h2>{{ form.name.value }} <small>{% trans "details of vxlan" %}</small></h2>
</div>
<div class="row">
<div class="col-sm-6">
{% crispy form %}
</div>
<div class="col-sm-6">
<div class="page-header">
<h3>{% trans "Connected virtual machines" %}</h3>
</div>
{% render_table vm_list %}
<div class="page-header">
<h3>{% trans "Manage access" %}</h3>
</div>
{% include "dashboard/_manage_access.html" with table_id="vxlan-access-table" %}
</div>
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% block title-page %}{% trans "Vxlans" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-create" %}" class="btn btn-success pull-right"><i class="fa fa-plus-circle"></i> {% trans "Create a new vxlan" %}</a>
<h1>Vxlans <small>{% trans "list of all vxlans" %}</small></h1>
</div>
<div class="table-responsive">
{% render_table table %}
</div>
{% endblock %}
......@@ -20,6 +20,7 @@ from .views import (
IndexView,
HostList, HostDetail, HostCreate, HostDelete,
VlanList, VlanDetail, VlanDelete, VlanCreate,
VxlanList, VxlanDetail, VxlanDelete, VxlanCreate, VxlanAclUpdateView,
DomainList, DomainDetail, DomainDelete, DomainCreate,
GroupList, GroupDetail, GroupDelete, GroupCreate,
RecordList, RecordDetail, RecordCreate, RecordDelete,
......@@ -30,7 +31,7 @@ from .views import (
FirewallList, FirewallDetail, FirewallCreate, FirewallDelete,
remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device,
VlanAclUpdateView
VlanAclUpdateView, NetworkEditorView
)
urlpatterns = [
......@@ -125,6 +126,19 @@ urlpatterns = [
url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(),
name="network.rule_delete"),
# vxlan
url('^vxlans/$', VxlanList.as_view(), name='network.vxlan-list'),
url('^vxlans/create$', VxlanCreate.as_view(), name='network.vxlan-create'),
url('^vxlans/(?P<vni>\d+)/$', VxlanDetail.as_view(), name='network.vxlan'),
url('^vxlans/(?P<pk>\d+)/acl/$', VxlanAclUpdateView.as_view(),
name='network.vxlan-acl'),
url('^vxlans/delete/(?P<vni>\d+)/$', VxlanDelete.as_view(),
name="network.vxlan-delete"),
# editor
url('^editor/$', NetworkEditorView.as_view(),
name="network.editor"),
# non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'),
......
# -*- 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'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-26 11:59
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('vm', '0003_auto_20171105_2011'),
('vm', '0007_auto_20180226_1239'),
]
operations = [
]
......@@ -34,6 +34,7 @@ from django.db import IntegrityError
from django.dispatch import Signal
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.contrib.contenttypes.fields import GenericRelation
from model_utils import Choices
from model_utils.managers import QueryManager
......@@ -51,6 +52,8 @@ from .activity import (ActivityInProgressError, InstanceActivity)
from .common import BaseResourceConfigModel, Lease
from .network import Interface
from .node import Node, Trait
from network.models import EditorElement
from openstack_auth.user import User
......@@ -247,6 +250,7 @@ class Instance(OperatedMixin, TimeStampedModel):
destroyed_at = DateTimeField(blank=True, null=True,
help_text=_("The virtual machine's time of "
"destruction."))
editor_elements = GenericRelation(EditorElement)
objects = Manager()
active = QueryManager(destroyed_at=None)
......
......@@ -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 'cloudx-%s-%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
......@@ -189,6 +189,8 @@ class AddInterfaceOperation(InstanceOperation):
"the VM.")
required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'ACTIVE')
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
network_type = None
def rollback(self, net, activity):
with activity.sub_activity(
......
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