Allow admin user to create runtime

We should allow the admin user (users with admin role)
to create runtime on the dashboard.

This is equivalent function to following CLI command

openstack runtime create $IMAGE --name python3,

The $IMAGE is a docker image like
openstackqinling/python3-runtime:0.0.2
which should be provided by the user.

According to qinling team, For now,
there is no need to do validation for that
input but in future the qinling-api should do some
sanity checks and reply to the UI accordingly.

Change-Id: I279442ac9b20dce3f141beffbfda7e8b5ef9c692
Story: 2004391
Task: 28018
This commit is contained in:
Keiichi Hikita 2018-11-27 13:29:36 +09:00
parent d4aeab942a
commit c6ead89c12
9 changed files with 181 additions and 1 deletions

View File

@ -52,6 +52,11 @@ def runtime_get(request, runtime_id):
return qinlingclient(request).runtimes.get(runtime_id)
def runtime_create(request, **params):
resource = qinlingclient(request).runtimes.create(**params)
return resource
def set_code(datum):
if isinstance(datum.code, six.string_types):
code_dict = json.loads(datum.code)

View File

@ -4,6 +4,8 @@
"admin_or_owner": "rule:context_is_admin or rule:owner",
"default": "rule:admin_or_owner",
"runtime:create": "rule:context_is_admin",
"function:create": "rule:admin_or_owner",
"function:update": "rule:admin_or_owner",
"function:delete": "rule:admin_or_owner",

View File

@ -0,0 +1,88 @@
# Copyright 2012 Nebula, Inc.
# All rights reserved.
# 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 django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from qinling_dashboard import api
# from qinling_dashboard.utils import calculate_md5
from qinling_dashboard import validators
class CreateRuntimeForm(forms.SelfHandlingForm):
image = forms.CharField(max_length=255,
label=_("Image"),
validators=[validators.validate_one_line_string],
required=True)
name = forms.CharField(max_length=255,
label=_("Name"),
validators=[validators.validate_one_line_string],
required=False)
description = forms.CharField(
max_length=255,
widget=forms.Textarea(
attrs={'class': 'modal-body-fixed-width',
'rows': 3}),
label=_("Description"),
required=False)
untrusted = forms.BooleanField(
label=_("Create as Untrusted Image"),
required=False,
help_text=_("Check this item if you would like to "
"create untrusted runtime"),
initial=False
)
def __init__(self, request, *args, **kwargs):
super(CreateRuntimeForm, self).__init__(request, *args, **kwargs)
def handle(self, request, context):
params = {}
# basic parameters
params.update({'image': context.get('image')})
if context.get('name'):
params.update({'name': context.get('name')})
if context.get('description'):
params.update({'description': context.get('description')})
if context.get('untrusted'):
trusted = not bool(context.get('untrusted'))
params.update({'trusted': trusted})
try:
api.qinling.runtime_create(request, **params)
message = _('Created runtime "%s"') % params.get('name',
'unknown name')
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:runtimes:index")
exceptions.handle(request,
_("Unable to create runtime."),
redirect=redirect)

View File

@ -19,6 +19,16 @@ from qinling_dashboard import api
from qinling_dashboard import utils
class CreateRuntime(tables.LinkAction):
name = "create"
verbose_name = _("Create Runtime")
url = "horizon:project:runtimes:create"
classes = ("ajax-modal", "btn-create")
policy_rules = (("function_engine", "runtime:create"),)
icon = "plus"
ajax = True
class RuntimesFilterAction(tables.FilterAction):
def filter(self, table, runtimes, filter_string):
@ -78,4 +88,5 @@ class RuntimesTable(tables.DataTable):
status_columns = ["status"]
multi_select = True
row_class = UpdateRow
table_actions = (RuntimesFilterAction,)
table_actions = (CreateRuntime,
RuntimesFilterAction,)

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Create a new runtime." %}</p>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Runtime" %}{% endblock %}
{% block main %}
{% include 'project/runtimes/_create.html' %}
{% endblock %}

View File

@ -18,4 +18,5 @@ urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<runtime_id>[^/]+)/$',
views.DetailView.as_view(), name='detail'),
url(r'^create', views.CreateRuntimeView.as_view(), name='create'),
]

View File

@ -15,15 +15,26 @@ from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from qinling_dashboard import api
from qinling_dashboard.content.runtimes import forms as project_forms
from qinling_dashboard.content.runtimes import tables as project_tables
from qinling_dashboard.content.runtimes import tabs as project_tabs
class CreateRuntimeView(forms.ModalFormView):
form_class = project_forms.CreateRuntimeForm
modal_header = submit_label = page_title = _("Create Runtime")
template_name = 'project/runtimes/create.html'
submit_url = reverse_lazy("horizon:project:runtimes:create")
success_url = reverse_lazy("horizon:project:runtimes:index")
class IndexView(tables.DataTableView):
table_class = project_tables.RuntimesTable

View File

@ -26,6 +26,55 @@ INDEX_URL = reverse('horizon:project:runtimes:index')
class RuntimesTests(test.TestCase):
@test.create_mocks({
api.qinling: [
'runtime_create',
]})
def test_execution_create_with_maximum_params(self):
data_runtime = self.runtimes.first()
self.mock_runtime_create.return_value = data_runtime
image_name = 'dummy/dockerimage'
form_data = {'image': image_name,
'name': 'test_name',
'description': 'description',
'untrusted': 'on'}
url = reverse('horizon:project:runtimes:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.mock_runtime_create.assert_called_once_with(
test.IsHttpRequest(),
image=image_name,
name='test_name',
description='description',
trusted=False)
@test.create_mocks({
api.qinling: [
'runtime_create',
]})
def test_execution_create_with_minimum_params(self):
data_runtime = self.runtimes.first()
self.mock_runtime_create.return_value = data_runtime
image_name = 'dummy/dockerimage'
form_data = {'image': image_name}
url = reverse('horizon:project:runtimes:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.mock_runtime_create.assert_called_once_with(
test.IsHttpRequest(), image=image_name)
@test.create_mocks({
api.qinling: [
'runtimes_list',