Commit 327e7892 by Szabolcs Gelencser

Implement start vm

parent 55430047
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
\ No newline at end of file
No preview for this file type
......@@ -20,6 +20,7 @@ from __future__ import absolute_import
from datetime import timedelta
from urlparse import urlparse
import openstack_api
import pyotp
from django.forms import ModelForm
......@@ -107,6 +108,47 @@ class VmSaveForm(OperationForm):
help_text=_("Clone the access list of parent template. Useful "
"for updating a template."))
class VmFromPlainImageForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={
'class': "form-control",
'required': "",
}))
image = forms.ChoiceField([], widget=forms.Select(attrs={
'class': "form-control input-tags",
}))
flavor = forms.ChoiceField([], widget=forms.Select(attrs={
'class': "form-control input-tags",
}))
network = forms.ChoiceField([], widget=forms.Select(attrs={
'class': "form-control input-tags",
}))
def __init__(self, request, *args, **kwargs):
super(VmFromPlainImageForm, self).__init__(*args, **kwargs)
images = openstack_api.glance.image_list_detailed(request)[0] #TODO: flatten?
def sizeof_fmt(num, suffix='B'):
for unit in ['', 'K', 'M', 'G', 'T']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
self.fields['image'].choices = (
(image.id, '%s - %s' % (image.name, sizeof_fmt(image.size))) for image in images
)
flavors = openstack_api.nova.flavor_list(request) #TODO: flattent
self.fields['flavor'].choices = (
(flavor.id, '%s - %s CPUs, %s MB RAM' % (flavor.name, flavor.vcpus, flavor.ram)) for flavor in flavors
)
networks = openstack_api.neutron.network_list_for_tenant(request, request.user.tenant_id)
self.fields['network'].choices = (
(network.id, '%s' % (network.name)) for network in networks
)
class VmCustomizeForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={
......
{% load sizefieldtags %}
{% load i18n %}
{% load crispy_forms_tags %}
<div class="vm-create-template-list">
{% for t in templates %}
......@@ -87,17 +88,28 @@
{% include "request/_request-template-form.html" %}
{% endif %}
{% endfor %}
<div class="vm-create-template">
<a href="{% url "dashboard.views.vm-plain-image-create" %}" style="text-decoration: none; color: #333333">
<div class="vm-create-template-summary">
<span class="vm-create-list-name">
New VM from plain OS image
</span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
{% if templates and template_access_types %}
{% url "request.views.request-template" as request_url %}
<hr />
<p class="text-right">
{% blocktrans with url=request_url %}
Need other templates? Submit a new <a href="{{ url }}">request</a>.
{% endblocktrans %}
</p>
{% endif %}
{% if templates and template_access_types %}
{% url "request.views.request-template" as request_url %}
<hr />
<p class="text-right">
{% blocktrans with url=request_url %}
Need other templates? Submit a new <a href="{{ url }}">request</a>.
{% endblocktrans %}
</p>
{% endif %}
<style>
.progress {
......
{% extends "dashboard/base.html" %}
{% load sizefieldtags %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load staticfiles %}
{% block content %}
<form method="POST" action="{% url "dashboard.views.vm-plain-image-create" %} ">
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.image|as_crispy_field }}
{{ form.flavor|as_crispy_field }}
{# {{ form.network|as_crispy_field }}#}
<button class="btn btn-success btn-xs vm-create-start pull-right text-right" type="submit">
<i class="fa fa-play"></i> {% trans "Start" %}
</button>
</form>
{% endblock %}
\ No newline at end of file
......@@ -18,7 +18,7 @@
from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView
from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView, VmPlainImageCreate
from django.conf.urls import url
from .views import (
......@@ -55,7 +55,7 @@ urlpatterns = [
# name="dashboard.views.template-delete"),
# url(r'^template/(?P<pk>\d+)/tx/$', TransferTemplateOwnershipView.as_view(),
# name='dashboard.views.template-transfer-ownership'),
url(r'^vm/(?P<pk>\d+)/$', VmDetailView.as_view(),
url(r'^vm/(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', VmDetailView.as_view(),
name='dashboard.views.detail'),
# url(r'^vm/(?P<pk>\d+)/vnctoken/$', VmDetailVncTokenView.as_view(),
# name='dashboard.views.detail-vnc'),
......@@ -64,9 +64,9 @@ urlpatterns = [
# url(r'^vm/(?P<pk>\d+)/tx/$', TransferInstanceOwnershipView.as_view(),
# name='dashboard.views.vm-transfer-ownership'),
url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'),
url(r'^vm/create/$', VmCreate.as_view(),
name='dashboard.views.vm-create'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity,
url(r'^vm/create/$', VmCreate.as_view(), name='dashboard.views.vm-create'),
url(r'^vm-plain-image-create$', VmPlainImageCreate.as_view(), name='dashboard.views.vm-plain-image-create'),
url(r'^vm/(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/activity/$', vm_activity,
name='dashboard.views.vm-activity-list'),
# url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(),
# name='dashboard.views.vm-activity'),
......@@ -223,7 +223,7 @@ urlpatterns = [
]
urlpatterns += [
url(r'^vm/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname())
url(r'^vm/(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/op/%s/$' % op, v.as_view(), name=v.get_urlname())
for op, v in vm_ops.iteritems()
]
#
......
......@@ -105,6 +105,9 @@ class IndexView(LoginRequiredMixin, TemplateView):
context['templates'] = InstanceTemplate.get_objects_with_level(
'operator', user, disregard_superuser=True).all()[:5]
# vxlan
#context['vxlans'] = Instance.list_from_os(self.request)[:5]
# toplist
if settings.STORE_URL:
cache_key = "files-%d" % self.request.user.pk
......
......@@ -42,6 +42,7 @@ from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import DetailView, View, DeleteView, FormView
from django.views.generic.detail import SingleObjectMixin
from vm.models import Instance
from ..models import GroupProfile
......@@ -238,6 +239,9 @@ class OperationView(RedirectToLoginMixin, DetailView):
else:
return ['dashboard/_base.html']
def get_object(self):
return Instance(os_server_id=self.kwargs['pk']).get_from_os(self.request)
@classmethod
def get_op_by_object(cls, obj):
return getattr(obj, cls.op)
......
......@@ -48,17 +48,19 @@ from common.models import (
)
from firewall.models import Vlan, Host, Rule
from manager.scheduler import SchedulerError
from request.forms import TemplateRequestForm
from request.models import TemplateAccessType
from storage.models import Disk
from vm.models import (
Instance, InstanceActivity, Interface,
)
InstanceTemplate)
from .util import (
CheckedDetailView, AjaxOperationMixin, OperationView, AclUpdateView,
FormOperationMixin, FilterMixin, GraphMixin
)
from ..forms import (
AclUserOrGroupAddForm, VmResourcesForm, VmCustomizeForm, VmDeployForm)
AclUserOrGroupAddForm, VmResourcesForm, VmCustomizeForm, VmDeployForm, VmFromPlainImageForm)
logger = logging.getLogger(__name__)
......@@ -105,7 +107,7 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
"password": instance.pw}
def get_object(self, queryset=None):
return super(VmDetailView, self).get_object(queryset).get_from_os(self.request)
return Instance(os_server_id=self.kwargs['pk']).get_from_os(self.request)
def get_context_data(self, **kwargs):
context = super(VmDetailView, self).get_context_data(**kwargs)
......@@ -1007,6 +1009,28 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
"interface_set__host").distinct()
class VmPlainImageCreate(LoginRequiredMixin, TemplateView):
form_class = VmFromPlainImageForm
template_name = "dashboard/vm-plain-image-create.html"
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context.update({
'form': VmFromPlainImageForm(request),
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
server_created = openstack_api.nova.server_create(
request,
request.POST.get("name"),
request.POST.get("image"),
request.POST.get("flavor"),
)
return HttpResponseRedirect("vm/%s#activity" % server_created.id)
class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCustomizeForm
......@@ -1030,9 +1054,6 @@ class VmCreate(LoginRequiredMixin, TemplateView):
return template
def get(self, request, form=None, *args, **kwargs):
if not request.user.has_perm('vm.create_vm'):
raise PermissionDenied()
if form is None:
template_pk = request.GET.get("template")
else:
......@@ -1043,8 +1064,9 @@ class VmCreate(LoginRequiredMixin, TemplateView):
if form is None:
form = self.form_class(user=request.user, template=template)
else:
templates = InstanceTemplate.get_objects_with_level(
'user', request.user, disregard_superuser=True)
templates = InstanceTemplate.objects
images = openstack_api.glance.image_list_detailed(request)
context = self.get_context_data(**kwargs)
if template_pk:
......@@ -1062,17 +1084,20 @@ class VmCreate(LoginRequiredMixin, TemplateView):
'ajax_title': True,
'templates': templates.all(),
'template_access_types': TemplateAccessType.objects.exists(),
'form': TemplateRequestForm(request=request),
'form': TemplateRequestForm(request=request)
})
return self.render_to_response(context)
def __create_normal(self, request, template, *args, **kwargs):
def _create_from_plain_image(self, request, *args, **kwargs):
pass
def __create_normal(self, request, *args, **kwargs):
instances = [Instance.create_from_template(
template=template,
owner=request.user)]
return self.__deploy(request, instances)
def __create_customized(self, request, template, *args, **kwargs):
def __create_customized(self, request, *args, **kwargs):
user = request.user
# no form yet, using POST directly:
form = self.form_class(
......@@ -1130,39 +1155,16 @@ class VmCreate(LoginRequiredMixin, TemplateView):
return HttpResponseRedirect("%s#activity" % path)
def post(self, request, *args, **kwargs):
user = request.user
#TODO: check if user can create VM
#TODO: limit chekcs
if not request.user.has_perm('vm.create_vm'):
raise PermissionDenied()
template = self.get_template(request, request.POST.get("template"))
# limit chekcs
try:
limit = user.profile.instance_limit
except Exception as e:
logger.debug('No profile or instance limit: %s', e)
else:
try:
amount = int(request.POST.get("amount", 1))
except:
amount = limit # TODO this should definitely use a Form
current = Instance.active.filter(owner=user).count()
logger.debug('current use: %d, limit: %d', current, limit)
if current + amount > limit:
messages.error(request,
_('Instance limit (%d) exceeded.') % limit)
if request.is_ajax():
return HttpResponse(json.dumps({'redirect': '/'}),
content_type="application/json")
else:
return redirect('/')
create_func = (self.__create_normal if
create_func = (self._create_from_plain_image if
request.POST.get("from_plain_image") is not None else
self.__create_normal if
request.POST.get("customized") is None else
self.__create_customized)
return create_func(request, template, *args, **kwargs)
return create_func(request, *args, **kwargs)
# @require_GET
......
......@@ -362,6 +362,7 @@ class VxlanSuperUserForm(ModelForm):
'vlan',
'description',
'comment',
'owner',
)
),
FormActions(
......@@ -371,11 +372,6 @@ class VxlanSuperUserForm(ModelForm):
)
)
class Meta:
model = Vxlan
fields = ('name', 'vni', 'vlan', 'description', 'comment', )
class VxlanForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
......@@ -394,7 +390,3 @@ class VxlanForm(ModelForm):
'network.vxlan-list'))
)
)
class Meta:
model = Vxlan
fields = ('name', 'description', 'comment', 'vni', )
......@@ -18,6 +18,7 @@
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.urlresolvers import reverse
from django.db.models import CharField
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.fields import (
GenericRelation, GenericForeignKey
......@@ -60,53 +61,12 @@ 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'))
modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at'))
os_network_id = CharField(blank=False, max_length=100, unique=True)
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
......
......@@ -33,10 +33,10 @@ Keystone/Nova/Glance/Swift et. al.
"""
from openstack_api import base
# from openstack_api import cinder
# from openstack_api import glance
from openstack_api import glance
# from openstack_api import keystone
# from openstack_api import network
# from openstack_api import neutron
from openstack_api import neutron
from openstack_api import nova
# from openstack_api import swift
......
......@@ -506,16 +506,16 @@ def keypair_get(request, name):
def server_create(request, name, image, flavor, key_name, user_data,
security_groups, block_device_mapping=None,
block_device_mapping_v2=None, nics=None,
def server_create(request, name, image, flavor, key_name=None, user_data=None,
security_groups=None, block_device_mapping=None,
block_device_mapping_v2=None, nics="auto",
availability_zone=None, instance_count=1, admin_pass=None,
disk_config=None, config_drive=None, meta=None,
scheduler_hints=None, description=None):
kwargs = {}
if description is not None:
kwargs['description'] = description
return InstanceServer(get_novaclient_with_instance_desc(request).servers.create(
return Server(get_novaclient_with_instance_desc(request).servers.create(
name.strip(), image, flavor, userdata=user_data,
security_groups=security_groups,
key_name=key_name, block_device_mapping=block_device_mapping,
......
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_api.utils import settings as utils_settings
def check(actions, request, target=None):
"""Wrapper of the configurable policy method."""
policy_check = utils_settings.import_setting("POLICY_CHECK_FUNCTION")
if policy_check:
return policy_check(actions, request, target)
return True
class PolicyTargetMixin(object):
"""Mixin that adds the get_policy_target function
policy_target_attrs - a tuple of tuples which defines
the relationship between attributes in the policy
target dict and attributes in the passed datum object.
policy_target_attrs can be overwritten by sub-classes
which do not use the default, so they can neatly define
their policy target information, without overriding the
entire get_policy_target function.
"""
policy_target_attrs = (("project_id", "tenant_id"),
("tenant_id", "tenant_id"),
("user_id", "user_id"),
("domain_id", "domain_id"),
("target.project.domain_id", "domain_id"),
("target.user.domain_id", "domain_id"),
("target.group.domain_id", "domain_id"))
def get_policy_target(self, request, datum=None):
policy_target = {}
for policy_attr, datum_attr in self.policy_target_attrs:
if datum:
policy_target[policy_attr] = getattr(datum, datum_attr, None)
else:
policy_target[policy_attr] = None
return policy_target
......@@ -29,7 +29,7 @@ from .views import (
urlpatterns = [
url(r'^list/$', RequestList.as_view(),
name="request.views.request-list"),
url(r'^(?P<pk>\d+)/$', RequestDetail.as_view(),
url(r'^(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', RequestDetail.as_view(),
name="request.views.request-detail"),
url(r'^type/list/$', RequestTypeList.as_view(),
......@@ -55,10 +55,10 @@ urlpatterns = [
# request views (visible for users)
url(r'template/$', TemplateRequestView.as_view(),
name="request.views.request-template"),
url(r'lease/(?P<vm_pk>\d+)/$', LeaseRequestView.as_view(),
url(r'lease/(?P<vm_pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', LeaseRequestView.as_view(),
name="request.views.request-lease"),
url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(),
url(r'resource/(?P<vm_pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', ResourceRequestView.as_view(),
name="request.views.request-resource"),
url(r'resize/(?P<vm_pk>\d+)/(?P<disk_pk>\d+)/$',
url(r'resize/(?P<vm_pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/(?P<disk_pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$',
ResizeRequestView.as_view(), name="request.views.request-resize"),
]
......@@ -305,6 +305,10 @@ class Instance(OperatedMixin, TimeStampedModel):
return " ".join(s for s in parts if s != "")
@property
def pk(self):
return self.os_server_id
@property
def name(self):
return self._os_server.name
......@@ -470,7 +474,7 @@ class Instance(OperatedMixin, TimeStampedModel):
@permalink
def get_absolute_url(self):
return ('dashboard.views.detail', None, {'pk': self.id})
return ('dashboard.views.detail', None, {'pk': self.os_server_id})
@property
def vm_name(self):
......
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