Add Panel for NSD

Change-Id: Ib94f5197cb571a3cbccd7cff68e6ce8642878e7c
Partially-implements: blueprint nsd-support
This commit is contained in:
Bharath Thiruveedula
2016-12-22 21:43:43 +05:30
parent 9455bd8143
commit 46d42afb63
27 changed files with 1237 additions and 1 deletions

View File

@@ -159,3 +159,49 @@ def delete_vnffg(request, vnffg_id):
def delete_vnffgd(request, vnffgd_id):
LOG.debug("delete_vnffgd():vnffgd_id=%s", str(vnffgd_id))
tackerclient(request).delete_vnffgd(vnffgd_id)
def create_nsd(request, tosca_body=None, **params):
LOG.debug("create_nsd(): params=%s", params)
nsd_instance = tackerclient(request).create_nsd(body=tosca_body)
return nsd_instance
def nsd_list(request, **params):
LOG.debug("nsd_list(): params=%s", params)
nsds = tackerclient(request).list_nsds(**params).get('nsds')
return nsds
def get_nsd(request, nsd_id):
LOG.debug("nsd_get(): nsd_id=%s", str(nsd_id))
nsd = tackerclient(request).show_vnfd(nsd_id)
return nsd
def delete_nsd(request, nsd_id):
LOG.debug("delete_nsd():nsd_id=%s", str(nsd_id))
tackerclient(request).delete_nsd(nsd_id)
def get_ns(request, ns_id):
LOG.debug("ns_get(): ns_id=%s", str(ns_id))
ns_instance = tackerclient(request).show_ns(ns_id)
return ns_instance
def delete_ns(request, ns_id):
LOG.debug("delete_ns():ns_id=%s", str(ns_id))
tackerclient(request).delete_ns(ns_id)
def ns_list(request, **params):
LOG.debug("ns_list(): params=%s", params)
nss = tackerclient(request).list_nss(**params).get('nss')
return nss
def create_ns(request, ns_arg, **params):
LOG.debug("create_ns(): ns_arg=%s", str(ns_arg))
ns_instance = tackerclient(request).create_ns(body=ns_arg)
return ns_instance

View File

@@ -27,7 +27,8 @@ class Vnfmgroup(horizon.PanelGroup):
class Nfvogroup(horizon.PanelGroup):
slug = "nfvogroup"
name = _("NFV Orchestration")
panels = ('vim', 'vnffgcatalog', 'vnffgmanager',)
panels = ('vim', 'vnffgcatalog', 'vnffgmanager',
'nscatalog', 'nsmanager')
class Nfv(horizon.Dashboard):

View File

@@ -0,0 +1,105 @@
# 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.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class OnBoardNS(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
source_type = forms.ChoiceField(
label=_('TOSCA Template Source'),
required=False,
choices=[('file', _('TOSCA Template File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
toscal_file = forms.FileField(
label=_("TOSCA Template File"),
help_text=_("A local TOSCA template file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('TOSCA Template File')}),
required=False)
direct_input = forms.CharField(
label=_('TOSCA YAML'),
help_text=_('The YAML formatted contents of a TOSCA template.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('TOSCA YAML')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(OnBoardNS, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(OnBoardNS, self).clean()
# The key can be missing based on particular upload
# conditions. Code defensively for it here...
toscal_file = data.get('toscal_file', None)
toscal_raw = data.get('direct_input', None)
source_type = data.get("source_type")
if source_type == "file" and not toscal_file:
raise ValidationError(
_("No TOSCA template file selected."))
if source_type == "raw" and not toscal_raw:
raise ValidationError(
_("No direct input specified."))
if toscal_file and not toscal_file.name.endswith(('.yaml', '.csar')):
raise ValidationError(_("Only .yaml or .csar file uploads \
are supported"))
try:
if toscal_file:
toscal_str = self.files['toscal_file'].read()
else:
toscal_str = data['direct_input']
# toscal = yaml.loads(toscal_str)
data['tosca'] = toscal_str
except Exception as e:
msg = _('There was a problem loading the namespace: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
toscal = data['tosca']
nsd_name = data['name']
nsd_description = data['description']
tosca_arg = {'nsd': {'name': nsd_name,
'description': nsd_description,
'attributes': {'nsd': toscal}}}
nsd_instance = api.tacker.create_nsd(request, tosca_arg)
messages.success(request,
_('NS Catalog entry %s has been created.') %
nsd_instance['nsd']['name'])
return toscal
except Exception as e:
msg = _('Unable to create TOSCA. %s')
msg %= e.message.split('Failed validating', 1)[0]
exceptions.handle(request, message=msg)
return False

View File

@@ -0,0 +1,25 @@
# 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.utils.translation import ugettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class Nscatalog(horizon.Panel):
name = _("NS Catalog")
slug = "nscatalog"
dashboard.Nfv.register(Nscatalog)

View File

@@ -0,0 +1,68 @@
# 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.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class DeleteNSD(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete NS",
u"Delete NSs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Delete NS",
u"Delete NSs",
count
)
def action(self, request, obj_id):
api.tacker.delete_nsd(request, obj_id)
class OnBoardNS(tables.LinkAction):
name = "onboardns"
verbose_name = _("Onboard NS")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:nscatalog:onboardns"
class NSCatalogTable(tables.DataTable):
name = tables.Column('name',
link="horizon:nfv:nscatalog:detail",
verbose_name=_("Name"))
description = tables.Column('description',
verbose_name=_("Description"))
id = tables.Column('id',
verbose_name=_("Catalog Id"))
class Meta(object):
name = "nscatalog"
verbose_name = _("NSCatalog")
table_actions = (OnBoardNS, DeleteNSD, MyFilterAction,)

View File

@@ -0,0 +1,109 @@
# 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.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.nscatalog import tables
from tacker_horizon.openstack_dashboard.dashboards.nfv import utils
class NSCatalogItem(object):
def __init__(self, name, description, nsd_id):
self.id = nsd_id
self.name = name
self.description = description
class NSCatalogTab(tabs.TableTab):
name = _("NSCatalog Tab")
slug = "nscatalog_tab"
table_classes = (tables.NSCatalogTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_nscatalog_data(self):
try:
self._has_more = False
instances = []
nsds = api.tacker.nsd_list(self.request)
for nsd in nsds:
item = NSCatalogItem(nsd['name'],
nsd['description'],
nsd['id'])
instances.append(item)
return instances
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class NSCatalogTabs(tabs.TabGroup):
slug = "nscatalog_tabs"
tabs = (NSCatalogTab,)
sticky = True
class TemplateTab(tabs.Tab):
name = _("Template")
slug = "template"
template_name = ("nfv/nscatalog/template.html")
def get_context_data(self, request):
return {'nsd': self.tab_group.kwargs['nsd']}
class NSDEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.tacker.events_list(self.request,
self.tab_group.kwargs['nsd_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class NSDDetailTabs(tabs.TabGroup):
slug = "NSD_details"
tabs = (TemplateTab, NSDEventsTab)
sticky = True

View File

@@ -0,0 +1,9 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Onboards a NS." %}</p>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "NSD Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=page_title %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "NS Catalog" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("NS Catalog") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Onboard NS" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Onboard a NS") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/nscatalog/_onboardns.html' %}
{% endblock %}

View File

@@ -0,0 +1,5 @@
{% load i18n %}
<h4>{% trans "NSD Template" %}</h4>
<pre class="nsd_template">
{{ nsd.template }}
</pre>

View File

@@ -0,0 +1,20 @@
# 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_dashboard.test import helpers as test
class NscatalogTests(test.TestCase):
# Unit tests for nscatalog.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@@ -0,0 +1,25 @@
# 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.conf.urls import patterns
from django.conf.urls import url
from tacker_horizon.openstack_dashboard.dashboards.nfv.nscatalog import views
urlpatterns = patterns(
'tacker_horizon.openstack_dashboard.dashboards.nfv.nscatalog.views',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^onboardns', views.OnBoardNSView.as_view(), name='onboardns'),
url(r'^(?P<nsd_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
)

View File

@@ -0,0 +1,110 @@
# 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.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
from tacker_horizon.openstack_dashboard import api as tacker_api
from tacker_horizon.openstack_dashboard.dashboards.nfv.nscatalog \
import tabs as nfv_tabs
from tacker_horizon.openstack_dashboard.dashboards.nfv.nscatalog \
import forms as project_forms
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = nfv_tabs.NSCatalogTabs
template_name = 'nfv/nscatalog/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class OnBoardNSView(forms.ModalFormView):
form_class = project_forms.OnBoardNS
template_name = 'nfv/nscatalog/onboardns.html'
success_url = reverse_lazy("horizon:nfv:nscatalog:index")
modal_id = "onboardns_modal"
modal_header = _("OnBoard NS")
submit_label = _("OnBoard NS")
submit_url = "horizon:nfv:nscatalog:onboardns"
@memoized.memoized_method
def get_object(self):
try:
return api.nova.server_get(self.request,
self.kwargs["instance_id"])
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve instance."))
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(OnBoardNSView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = nfv_tabs.NSDDetailTabs
template_name = 'nfv/nscatalog/detail.html'
redirect_url = 'horizon:nfv:nscatalog:index'
page_title = _("NSD Details: {{ nsd_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
nsd = self.get_data()
context['nsd'] = nsd
context['nsd_id'] = kwargs['nsd_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
nsd_id = self.kwargs['nsd_id']
try:
template = None
nsd = tacker_api.tacker.get_nsd(self.request, nsd_id)
attributes_json = nsd['nsd']['attributes']
template = attributes_json.get('nsd', None)
nsd['template'] = template
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'NSD "%s".') % nsd_id,
redirect=redirect)
raise exceptions.Http302(redirect)
return nsd
def get_tabs(self, request, *args, **kwargs):
nsd = self.get_data()
return self.tab_group_class(request, nsd=nsd, **kwargs)

View File

@@ -0,0 +1,177 @@
# 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.
import logging
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
LOG = logging.getLogger(__name__)
class DeployNS(forms.SelfHandlingForm):
ns_name = forms.CharField(max_length=255, label=_("NS Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
nsd_id = forms.ChoiceField(label=_("NS Catalog Name"))
vim_id = forms.ChoiceField(label=_("VIM Name"), required=False)
source_type = forms.ChoiceField(
label=_('Parameter Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
param_file = forms.FileField(
label=_('Parameter Value File'),
help_text=_('A local Parameter Value file to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('Parameter Value File')}),
required=False)
direct_input = forms.CharField(
label=_('Parameter Value YAML'),
help_text=_('The YAML formatted contents of Parameter Values.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('Parameter Values')}),
required=False)
config_type = forms.ChoiceField(
label=_('Configuration Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'config'}))
config_file = forms.FileField(
label=_('Configuration Value File'),
help_text=_('NS Configuration file with YAML '
'formatted contents to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-file': _('Configuration Value File')}),
required=False)
config_input = forms.CharField(
label=_('Configuration Value YAML'),
help_text=_('YAML formatted NS configuration text.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-raw': _('Configuration Values')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(DeployNS, self).__init__(request, *args, **kwargs)
try:
nsd_list = api.tacker.nsd_list(request)
available_choices_nsd = [(ns['id'], ns['name']) for ns in
nsd_list]
except Exception as e:
available_choices_nsd = []
msg = _('Failed to retrieve available NS Catalog names: %s') % e
LOG.error(msg)
try:
vim_list = api.tacker.vim_list(request)
available_choices_vims = [(vim['id'], vim['name']) for vim in
vim_list]
except Exception as e:
available_choices_vims = []
msg = _('Failed to retrieve available VIM names: %s') % e
LOG.error(msg)
self.fields['nsd_id'].choices = [('', _('Select a NS Catalog Name'))
]+available_choices_nsd
self.fields['vim_id'].choices = [('',
_('Select a VIM Name'))
]+available_choices_vims
def clean(self):
data = super(DeployNS, self).clean()
param_file = data.get('param_file', None)
param_raw = data.get('direct_input', None)
if param_raw and param_file:
raise ValidationError(
_("Cannot specify both file and direct input."))
if param_file and not param_file.name.endswith('.yaml'):
raise ValidationError(
_("Please upload .yaml file only."))
if param_file:
data['param_values'] = self.files['param_file'].read()
elif param_raw:
data['param_values'] = data['direct_input']
else:
data['param_values'] = None
config_file = data.get('config_file', None)
config_raw = data.get('config_input', None)
if config_file and config_raw:
raise ValidationError(
_("Cannot specify both file and direct input."))
if config_file and not config_file.name.endswith('.yaml'):
raise ValidationError(_("Only .yaml file uploads supported"))
if config_file:
data['config_values'] = self.files['config_file'].read()
elif config_raw:
data['config_values'] = data['config_input']
else:
data['config_values'] = None
return data
def handle(self, request, data):
try:
ns_name = data['ns_name']
description = data['description']
nsd_id = data['nsd_id']
vim_id = data['vim_id']
param_val = data['param_values']
config_val = data['config_values']
ns_arg = {'ns': {'nsd_id': nsd_id, 'name': ns_name,
'description': description,
'vim_id': vim_id}}
ns_attr = ns_arg['ns'].setdefault('attributes', {})
if param_val:
ns_attr['param_values'] = param_val
if config_val:
ns_attr['config'] = config_val
api.tacker.create_ns(request, ns_arg)
messages.success(request,
_('NS %s create operation initiated.') %
ns_name)
return True
except Exception as e:
exceptions.handle(request,
_('Failed to create NS: %s') %
e.message)

View File

@@ -0,0 +1,25 @@
# 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.utils.translation import ugettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class Nsmanager(horizon.Panel):
name = _("NS Manager")
slug = "nsmanager"
dashboard.Nfv.register(Nsmanager)

View File

@@ -0,0 +1,159 @@
# 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.http import Http404
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
from tackerclient.common.exceptions import NotFound
class NSManagerItem(object):
def __init__(self, name, description, vim, status,
ns_id, error_reason):
self.name = name
self.description = description
self.vim = vim
self.status = status
self.id = ns_id
self.error_reason = error_reason
class NSManagerItemList(object):
NSLIST_P = []
@classmethod
def get_obj_given_stack_ids(cls, ns_id):
for obj in cls.NSLIST_P:
if obj.id == ns_id:
return obj
@classmethod
def add_item(cls, item):
cls.NSLIST_P.append(item)
@classmethod
def clear_list(cls):
cls.NSLIST_P = []
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class NSUpdateRow(tables.Row):
ajax = True
def can_be_selected(self, datum):
return datum.status != 'DELETE_COMPLETE'
def get_data(self, request, ns_id):
try:
# stack = api.heat.stack_get(request, stack_id)
# if stack.stack_status == 'DELETE_COMPLETE':
# returning 404 to the ajax call removes the
# row from the table on the ui
# raise Http404
item = NSManagerItemList.get_obj_given_stack_ids(ns_id)
ns_instance = api.tacker.get_ns(request, ns_id)
if not ns_instance and not item:
# TODO(NAME) - bail with error
return None
if not ns_instance and item:
# API failure, just keep the current state
return item
ns = ns_instance['ns']
try:
ns_desc_str = ns['description']
except KeyError:
ns_desc_str = ""
vim = ns['vim_id']
if not item:
# Add an item entry
item = NSManagerItem(ns['name'], ns_desc_str,
str(vim),
ns['status'], ns['id'],
ns['error_reason'])
else:
item.description = ns_desc_str
item.status = ns['status']
item.id = ns['id']
return item
except (Http404, NotFound):
raise Http404
except Exception as e:
messages.error(request, e)
raise
class DeleteNS(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Terminate NS",
u"Terminate NSs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Terminate NS",
u"Terminate NSs",
count
)
def action(self, request, obj_id):
api.tacker.delete_ns(request, obj_id)
class DeployNS(tables.LinkAction):
name = "deployns"
verbose_name = _("Deploy NS")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:nsmanager:deployns"
class NSManagerTable(tables.DataTable):
STATUS_CHOICES = (
("ACTIVE", True),
("ERROR", False),
)
name = tables.Column("name",
link="horizon:nfv:nsmanager:detail",
verbose_name=_("NS Name"))
description = tables.Column("description",
verbose_name=_("Description"))
vim = tables.Column("vim", verbose_name=_("VIM"))
status = tables.Column("status",
status=True,
status_choices=STATUS_CHOICES)
error_reason = tables.Column("error_reason",
verbose_name=_("Error Reason"))
class Meta(object):
name = "nsmanager"
verbose_name = _("NSManager")
status_columns = ["status", ]
row_class = NSUpdateRow
table_actions = (DeployNS, DeleteNS, MyFilterAction,)

View File

@@ -0,0 +1,105 @@
# 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.
import logging
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.nsmanager import tables
from tacker_horizon.openstack_dashboard.dashboards.nfv import utils
LOG = logging.getLogger(__name__)
class NSManagerTab(tabs.TableTab):
name = _("NSManager Tab")
slug = "nsmanager_tab"
table_classes = (tables.NSManagerTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_nsmanager_data(self):
try:
self._has_more = True
tables.NSManagerItemList.clear_list()
nss = api.tacker.ns_list(self.request)
for ns in nss:
try:
ns_desc_str = ns['description']
except KeyError:
ns_desc_str = ""
vim = ns['vim_id']
obj = tables.NSManagerItem(
ns['name'],
ns_desc_str,
vim,
ns['status'],
ns['id'],
ns['error_reason'])
tables.NSManagerItemList.add_item(obj)
return tables.NSManagerItemList.NSLIST_P
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class NSManagerTabs(tabs.TabGroup):
slug = "nsmanager_tabs"
tabs = (NSManagerTab,)
sticky = True
class NSEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.tacker.events_list(self.request,
self.tab_group.kwargs['ns_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class NSDetailsTabs(tabs.TabGroup):
slug = "NS_details"
tabs = (NSEventsTab,)
sticky = True

View File

@@ -0,0 +1,15 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% blocktrans %} Deploys a NS.<br/>
If the NSD template is parameterized,
upload a yaml file with values for those parameters.<br/>
If the NSD template is not parameterized, any
yaml file uploaded will be ignored.<br/>
If a configuration yaml file is uploaded, it will be
applied to the NS post its successful creation.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Deploy NS" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Deploy a NS") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/nsmanager/_deploy_ns.html' %}
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "NS Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=page_title %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "NS Manager" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("NS Manager") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,19 @@
# 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_dashboard.test import helpers as test
class NsmanagerTests(test.TestCase):
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@@ -0,0 +1,25 @@
# 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.conf.urls import patterns
from django.conf.urls import url
from tacker_horizon.openstack_dashboard.dashboards.nfv.nsmanager import views
urlpatterns = patterns(
'tacker_horizon.openstack_dashboard.dashboards.nfv.nsmanager.views',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^deployns$', views.DeployNSView.as_view(), name='deployns'),
url(r'^(?P<ns_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
)

View File

@@ -0,0 +1,100 @@
# 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.
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from tacker_horizon.openstack_dashboard import api as tacker_api
from tacker_horizon.openstack_dashboard.dashboards.nfv.nsmanager \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.nsmanager \
import tabs as nfv_tabs
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = nfv_tabs.NSManagerTabs
template_name = 'nfv/nsmanager/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class DeployNSView(forms.ModalFormView):
form_class = project_forms.DeployNS
template_name = 'nfv/nsmanager/deploy_ns.html'
success_url = reverse_lazy("horizon:nfv:nsmanager:index")
modal_id = "deploy_ns_modal"
modal_header = _("Deploy NS")
submit_label = _("Deploy NS")
submit_url = "horizon:nfv:nsmanager:deployns"
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(DeployNSView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = nfv_tabs.NSDetailsTabs
template_name = 'nfv/nsmanager/detail.html'
redirect_url = 'horizon:nfv:nsmanager:index'
page_title = _("NS Details: {{ ns_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
ns = self.get_data()
context['ns'] = ns
context['ns_id'] = kwargs['ns_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
ns_id = self.kwargs['ns_id']
try:
ns = tacker_api.tacker.get_ns(self.request, ns_id)
return ns
except ValueError as e:
msg = _('Cannot decode json : %s') % e
LOG.error(msg)
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'NS "%s".') % ns_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
ns = self.get_data()
return self.tab_group_class(request, ns=ns, **kwargs)