Support NFV Orchestration API v2.0

Support the latest NFV Orchestration API v2.0 listed below.

* VNF LCM v2
* VNF FM v1
* VNF PM v2

In addition, to manage VNF packages for testing purposes, also support
Orchestration API v1.0 listed below.

* VNF packages

Implements: blueprint v2-api-horizon
Change-Id: I0284cba30154cf4cc5d496be157288e2c2614ccf
This commit is contained in:
Haejin Choe 2024-02-14 10:19:50 +00:00
parent 4709605bd5
commit a1840d24ba
131 changed files with 5548 additions and 5 deletions

View File

@ -0,0 +1,13 @@
---
features:
- |
Support the latest NFV Orchestration API v2.0 listed below.
* Virtualized Network Function Lifecycle Management Interface (VNF LCM) v2
* Virtualized Network Function Fault Management Interface (VNF FM) v1
* Virtualized Network Function Performance Management Interface (VNF PM) v2
In addition, to manage VNF packages for testing purposes, also support
Orchestration API v1.0 listed below.
* Virtualized Network Function Packages (VNF packages)

View File

@ -44,10 +44,11 @@ CERT_VERIFY_TYPES = (
@memoized
def tackerclient(request):
def tackerclient(request, api_version="1"):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
c = tacker_client.Client(
api_version=api_version,
token=request.user.token.id,
auth_url=base.url_for(request, 'identity'),
endpoint_url=base.url_for(request, 'nfv-orchestration'),
@ -251,3 +252,370 @@ 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
# VNF Packages v1
@profiler.trace
def create_vnf_package(request, body):
LOG.debug("create_vnf_package(): body=%s", body)
vnf_package = tackerclient(request).create_vnf_package(body=body)
return vnf_package
@profiler.trace
def list_vnf_packages(request, **params):
LOG.debug("list_vnf_packages(): params=%s", params)
vnf_packages = tackerclient(request).list_vnf_packages(**params).get(
'vnf_packages')
return vnf_packages
@profiler.trace
def get_vnf_package(request, vnf_pkg_id):
LOG.debug("get_vnf_package(): vnf_pkg_id=%s", vnf_pkg_id)
vnf_package = tackerclient(request).show_vnf_package(vnf_pkg_id)
return vnf_package
@profiler.trace
def delete_vnf_package(request, vnf_pkg_id):
LOG.debug("delete_vnf_package(): vnf_pkg_id=%s", vnf_pkg_id)
result = tackerclient(request).delete_vnf_package(vnf_pkg_id)
return result
@profiler.trace
def upload_vnf_package(request, vnf_pkg_id, file_data=None, **params):
LOG.debug("upload_vnf_package(): vnf_pkg_id=%s, params=%s",
vnf_pkg_id, params)
vnf_package = tackerclient(request).upload_vnf_package(
vnf_pkg_id, file_data=file_data, **params)
return vnf_package
@profiler.trace
def update_vnf_package(request, vnf_pkg_id, body):
LOG.debug("update_vnf_package(): vnf_pkg_id=%s, body=%s",
vnf_pkg_id, body)
updated_values = tackerclient(request).update_vnf_package(
vnf_pkg_id, body=body)
return updated_values
@profiler.trace
def fetch_vnf_package(request, vnf_pkg_id):
LOG.debug("fetch_vnf_package(): vnf_pkg_id=%s", vnf_pkg_id)
vnf_package_file = tackerclient(request).download_vnf_package(vnf_pkg_id)
return vnf_package_file
# VNF LCM v2
@profiler.trace
def create_vnf_instance(request, body):
LOG.debug("create_vnf_instance(): body=%s", body)
vnf_instance = (tackerclient(request, api_version="2")
.create_vnf_instance(body=body))
return vnf_instance
@profiler.trace
def get_vnf_instance(request, vnf_instance_id):
LOG.debug("get_vnf_instance(): vnf_instance_id=%s", vnf_instance_id)
vnf_instance = (tackerclient(request, api_version="2")
.show_vnf_instance(vnf_instance_id))
return vnf_instance
@profiler.trace
def list_vnf_instances(request, **params):
LOG.debug("list_vnf_instances(): params=%s", params)
vnf_instances = (tackerclient(request, api_version="2")
.list_vnf_instances(**params))
return vnf_instances
@profiler.trace
def delete_vnf_instance(request, vnf_instance_id):
LOG.debug("delete_vnf_instance(): vnf_instance_id=%s", vnf_instance_id)
result = (tackerclient(request, api_version="2")
.delete_vnf_instance(vnf_instance_id))
return result
@profiler.trace
def instantiate_vnf_instance(request, vnf_instance_id, body):
LOG.debug("instantiate_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.instantiate_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def terminate_vnf_instance(request, vnf_instance_id, body):
LOG.debug("terminate_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.terminate_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def heal_vnf_instance(request, vnf_instance_id, body):
LOG.debug("heal_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.heal_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def update_vnf_instance(request, vnf_instance_id, body):
LOG.debug("update_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.update_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def scale_vnf_instance(request, vnf_instance_id, body):
LOG.debug("scale_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.scale_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def change_ext_conn_vnf_instance(request, vnf_instance_id, body):
LOG.debug("change_ext_conn_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.change_ext_conn_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def change_vnfpkg_vnf_instance(request, vnf_instance_id, body):
LOG.debug("change_vnfpkg_vnf_instance(): vnf_instance_id=%s, body=%s",
vnf_instance_id, body)
result = (tackerclient(request, api_version="2")
.change_vnfpkg_vnf_instance(vnf_instance_id, body=body))
return result
@profiler.trace
def list_vnf_lcm_op_occs(request, **params):
LOG.debug("list_vnf_lcm_op_occs(): params=%s", params)
op_occs = (tackerclient(request, api_version="2")
.list_vnf_lcm_op_occs(**params))
return op_occs
@profiler.trace
def get_vnf_lcm_op_occ(request, op_occ_id):
LOG.debug("get_vnf_lcm_op_occ(): op_occ_id=%s", op_occ_id)
op_occ = (tackerclient(request, api_version="2")
.show_vnf_lcm_op_occs(op_occ_id))
return op_occ
@profiler.trace
def rollback_vnf_lcm_op_occ(request, op_occ_id):
LOG.debug("rollback_vnf_lcm_op_occ(): op_occ_id=%s", op_occ_id)
result = (tackerclient(request, api_version="2")
.rollback_vnf_instance(op_occ_id))
return result
@profiler.trace
def retry_vnf_lcm_op_occ(request, op_occ_id):
LOG.debug("retry_vnf_lcm_op_occ(): op_occ_id=%s", op_occ_id)
result = (tackerclient(request, api_version="2")
.retry_vnf_instance(op_occ_id))
return result
@profiler.trace
def fail_vnf_lcm_op_occ(request, op_occ_id):
LOG.debug("fail_vnf_lcm_op_occs(): op_occ_id=%s", op_occ_id)
result = (tackerclient(request, api_version="2")
.fail_vnf_instance(op_occ_id))
return result
@profiler.trace
def list_vnf_lcm_subscriptions(request, **params):
LOG.debug("list_vnf_lcm_subscriptions(): params=%s", params)
subscriptions = (tackerclient(request, api_version="2")
.list_lccn_subscriptions(**params))
return subscriptions
@profiler.trace
def get_vnf_lcm_subscription(request, subsc_id):
LOG.debug("get_vnf_lcm_subscription(): subsc_id=%s", subsc_id)
subscription = (tackerclient(request, api_version="2")
.show_lccn_subscription(subsc_id))
return subscription
@profiler.trace
def delete_vnf_lcm_subscription(request, subsc_id):
LOG.debug("delete_vnf_lcm_subscription(): subsc_id=%s", subsc_id)
result = (tackerclient(request, api_version="2")
.delete_lccn_subscription(subsc_id))
return result
@profiler.trace
def create_vnf_lcm_subscription(request, param):
LOG.debug("create_vnf_lcm_subscription(): param=%s", param)
subscription = (tackerclient(request, api_version="2")
.create_lccn_subscription(body=param))
return subscription
# VNF FM v1
@profiler.trace
def list_fm_alarms(request, **params):
LOG.debug("list_fm_alarms(): params=%s", params)
fm_alarms = tackerclient(request).list_vnf_fm_alarms(**params).get(
'vnf_fm_alarms')
return fm_alarms
@profiler.trace
def get_fm_alarm(request, alarm_id):
LOG.debug("get_fm_alarm(): alarm_id=%s", alarm_id)
fm_alarm = tackerclient(request).show_vnf_fm_alarm(alarm_id)
return fm_alarm
@profiler.trace
def update_fm_alarm(request, alarm_id, body):
LOG.debug("update_fm_alarm(): alarm_id=%s, body=%s", alarm_id, body)
updated_values = tackerclient(request).update_vnf_fm_alarm(
alarm_id, body=body)
return updated_values
@profiler.trace
def create_fm_subscription(request, body):
LOG.debug("create_fm_subscription(): body=%s", body)
fm_subscription = tackerclient(request).create_vnf_fm_sub(body=body)
return fm_subscription
@profiler.trace
def list_fm_subscriptions(request, **params):
LOG.debug("list_fm_subscriptions(): params=%s", params)
fm_subscriptions = tackerclient(request).list_vnf_fm_subs(**params).get(
'vnf_fm_subs')
return fm_subscriptions
@profiler.trace
def get_fm_subscription(request, subsc_id):
LOG.debug("get_fm_subscription(): subsc_id=%s", subsc_id)
fm_subscription = tackerclient(request).show_vnf_fm_sub(subsc_id)
return fm_subscription
@profiler.trace
def delete_fm_subscription(request, subsc_id):
LOG.debug("delete_fm_subscription(): subsc_id=%s", subsc_id)
result = tackerclient(request).delete_vnf_fm_sub(subsc_id)
return result
# VNF PM v2
@profiler.trace
def create_pm_job(request, body):
LOG.debug("create_pm_job(): body=%s", body)
pm_job = (tackerclient(request, api_version="2")
.create_vnf_pm_job(body=body))
return pm_job
@profiler.trace
def list_pm_jobs(request, **params):
LOG.debug("list_pm_jobs(): params=%s", params)
pm_jobs = (tackerclient(request, api_version="2")
.list_vnf_pm_jobs(**params).get('vnf_pm_jobs'))
return pm_jobs
@profiler.trace
def get_pm_job(request, pm_job_id):
LOG.debug("get_pm_job(): pm_job_id=%s", pm_job_id)
pm_job = tackerclient(request, api_version="2").show_vnf_pm_job(pm_job_id)
return pm_job
@profiler.trace
def update_pm_job(request, pm_job_id, body):
LOG.debug("update_pm_job(): pm_job_id=%s, body=%s", pm_job_id, body)
updated_values = (tackerclient(request, api_version="2")
.update_vnf_pm_job(pm_job_id, body=body))
return updated_values
@profiler.trace
def delete_pm_job(request, pm_job_id):
LOG.debug("delete_pm_job(): pm_job_id=%s", pm_job_id)
result = (tackerclient(request, api_version="2")
.delete_vnf_pm_job(pm_job_id))
return result
@profiler.trace
def get_pm_report(request, pm_job_id, pm_report_id):
LOG.debug("get_pm_report(): pm_job_id=%s, pm_report_id=%s",
pm_job_id, pm_report_id)
pm_report = (tackerclient(request, api_version="2")
.show_vnf_pm_report(pm_job_id, pm_report_id))
return pm_report
@profiler.trace
def create_pm_threshold(request, body):
LOG.debug("create_pm_threshold(): body=%s", body)
pm_threshold = (tackerclient(request, api_version="2")
.create_vnf_pm_threshold(body=body))
return pm_threshold
@profiler.trace
def list_pm_thresholds(request, **params):
LOG.debug("list_pm_thresholds(): params=%s", params)
pm_thresholds = (tackerclient(request, api_version="2")
.list_vnf_pm_thresholds(**params)
.get('vnf_pm_thresholds'))
return pm_thresholds
@profiler.trace
def get_pm_threshold(request, pm_threshold_id):
LOG.debug("get_pm_threshold(): pm_threshold_id=%s", pm_threshold_id)
pm_threshold = (tackerclient(request, api_version="2")
.show_vnf_pm_threshold(pm_threshold_id))
return pm_threshold
@profiler.trace
def update_pm_threshold(request, pm_threshold_id, body):
LOG.debug("update_pm_threshold(): pm_threshold_id=%s, body=%s",
pm_threshold_id, body)
updated_values = (tackerclient(request, api_version="2")
.update_vnf_pm_threshold(pm_threshold_id, body=body))
return updated_values
@profiler.trace
def delete_pm_threshold(request, pm_threshold_id):
LOG.debug("delete_pm_threshold(): pm_threshold_id=%s", pm_threshold_id)
result = (tackerclient(request, api_version="2")
.delete_vnf_pm_threshold(pm_threshold_id))
return result

View File

@ -20,21 +20,46 @@ import horizon
class Vnfmgroup(horizon.PanelGroup):
slug = "nfvgroup"
name = _("VNF Management")
name = _("(Legacy)VNF Management")
panels = ('vnfcatalog', 'vnfmanager',)
class Nfvogroup(horizon.PanelGroup):
slug = "nfvogroup"
name = _("NFV Orchestration")
name = _("(Legacy)NFV Orchestration")
panels = ('vim', 'vnffgcatalog', 'vnffgmanager',
'nscatalog', 'nsmanager')
class Vnfpkggroup(horizon.PanelGroup):
slug = "vnfpkggroup"
name = _("VNF Packages v1")
panels = ('vnfpackages',)
class Vnflcmgroup(horizon.PanelGroup):
slug = "vnflcmgroup"
name = _("VNF LCM v2")
panels = ('vnflcm', 'vnflcmopocc', 'lccnsubscription',)
class Vnffmgroup(horizon.PanelGroup):
slug = "vnffmgroup"
name = _("VNF FM v1")
panels = ('vnffmalarm', 'vnffmsubscription',)
class Vnfpmgroup(horizon.PanelGroup):
slug = "vnfpmgroup"
name = _("VNF PM v2")
panels = ('vnfpmjob', 'vnfpmthreshold',)
class Nfv(horizon.Dashboard):
name = _("NFV")
slug = "nfv"
panels = (Vnfmgroup, Nfvogroup,) # Add your panels here.
panels = (Vnfmgroup, Nfvogroup, Vnfpkggroup, Vnflcmgroup, Vnffmgroup,
Vnfpmgroup,) # Add your panels here.
default_panel = 'vnfcatalog' # Specify the slug of the dashboard's
# default panel.

View File

@ -0,0 +1,65 @@
# Copyright (C) 2024 Fujitsu
# 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.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class CreateLccnSubscription(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(CreateLccnSubscription, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(CreateLccnSubscription, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param_data'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
param = data['param_data']
subsc = api.tacker.create_vnf_lcm_subscription(request, param)
messages.success(
request,
_('Create LCCN Subscription. (id: %s)') % subsc['id'])
except Exception:
exceptions.handle(
request,
_('Failed to create LCCN Subscription.'))
return False
return True

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class LccnSubscription(horizon.Panel):
name = _("LCCN Subscription")
slug = "lccnsubscription"
dashboard.Nfv.register(LccnSubscription)

View File

@ -0,0 +1,69 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class DeleteLccnSubscription(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete Lccn Subscription",
"Delete Lccn Subscriptions",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Delete Lccn Subscription",
"Delete Lccn Subscriptions",
count
)
def action(self, request, obj_id):
api.tacker.delete_vnf_lcm_subscription(request, obj_id)
class CreateLccnSubscription(tables.LinkAction):
name = "create_lccnsubsc"
verbose_name = _("Create Lccn Subscription")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:lccnsubscription:createlccnsubscription"
class LccnSubscriptionTable(tables.DataTable):
id = tables.Column('id', verbose_name=_("ID"),
link="horizon:nfv:lccnsubscription:detail",)
callback_uri = tables.Column('callback_uri',
verbose_name=_("Callback URI"))
class Meta(object):
name = "lccnsubsc"
verbose_name = _("LCCN Subscription")
pagination_param = 'subsc_marker'
prev_pagination_param = 'prev_subsc_marker'
table_actions = (CreateLccnSubscription, DeleteLccnSubscription,
tables.FilterAction)
row_actions = (DeleteLccnSubscription,)
multi_select = True

View File

@ -0,0 +1,109 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.lccnsubscription \
import tables
class LccnSubscItem(object):
def __init__(self, subsc_id, callback_uri):
self.id = subsc_id
self.name = subsc_id
self.callback_uri = callback_uri
class LccnSubscriptionTab(tabs.TableTab):
name = _("LCCN Subscription tab")
slug = "lccnsubsc_tab"
table_classes = (tables.LccnSubscriptionTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_lccnsubsc_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get(
"subsc_marker", None)
prev_marker = self.request.GET.get(
"prev_subsc_marker", None)
subscs = api.tacker.list_vnf_lcm_subscriptions(
self.request)
if marker is not None or prev_marker is not None:
for i, subsc in enumerate(subscs):
if subsc["id"] == marker and i < len(subscs) - 1:
subscs = subscs[i + 1:]
self._has_prev = True
break
if subsc["id"] == prev_marker and i > page_size:
subscs = subscs[i - page_size:]
self._has_prev = True
break
if len(subscs) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, subsc in enumerate(subscs):
if i >= page_size:
break
item = LccnSubscItem(
subsc_id=subsc.get('id', ''),
callback_uri=subsc.get('callbackUri', ''))
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get LCCN Subscriptions.')
exceptions.handle(self.request, error_message)
return []
class LccnSubscriptionTabs(tabs.TabGroup):
slug = "lccnsubsc_tabs"
tabs = (LccnSubscriptionTab,)
sticky = True
class LccnSubscDetailTab(tabs.Tab):
name = _("LCCN Subscription Detail Tab")
slug = "lccnsubsc_detail_tab"
template_name = "nfv/lccnsubscription/lccnsubsc_detail.html"
def get_context_data(self, request):
return {'lccnsubscription': self.tab_group.kwargs['lccnsubscription']}
class LccnSubscDetailTabs(tabs.TabGroup):
slug = "lccnsubsc_detail_tabs"
tabs = (LccnSubscDetailTab,)
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 "Create a Lccn Subscription." %}</p>
{% endblock %}

View File

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

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Lccn Subscription Detail" %}{% 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "LCCN Subscription" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("LCCN Subscription") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Callback URI" %}</dt>
<dd>{{ lccnsubscription.callbackUri }}</dd>
<dt>{% trans "Filter" %}</dt>
<dd>{{ lccnsubscription.filter }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ lccnsubscription.id }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ lccnsubscription.links }}</dd>
</dl>
</div>

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.lccnsubscription \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(),
name='detail'),
re_path(r'^createlccnsubscription$',
views.CreateLccnSubscriptionView.as_view(),
name='createlccnsubscription'),
]

View File

@ -0,0 +1,88 @@
# Copyright (C) 2024 Fujitsu
# 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.urls import reverse_lazy
from django.utils.translation import gettext_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.lccnsubscription \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.lccnsubscription \
import tabs as lccnsubsctabs
class IndexView(tabs.TabbedTableView):
tab_group_class = lccnsubsctabs.LccnSubscriptionTabs
template_name = 'nfv/lccnsubscription/index.html'
class CreateLccnSubscriptionView(forms.ModalFormView):
form_class = project_forms.CreateLccnSubscription
template_name = 'nfv/lccnsubscription/create_lccn_subscription.html'
success_url = reverse_lazy("horizon:nfv:lccnsubscription:index")
modal_id = "add_service_modal"
modal_header = _("Create Lccn Subscription")
submit_label = _("Create Lccn Subscription")
submit_url = "horizon:nfv:lccnsubscription:createlccnsubscription"
def get_context_data(self, **kwargs):
context = super(
CreateLccnSubscriptionView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = lccnsubsctabs.LccnSubscDetailTabs
template_name = 'nfv/lccnsubscription/detail.html'
redirect_url = 'horizon:nfv:lccnsubscription:index'
page_title = _("LCCN Subscription Detail")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
lccnsubscription = self.get_data()
context['lccnsubscription'] = lccnsubscription
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
subsc_id = self.kwargs.get('id', None)
try:
lccnsubscription = tacker_api.tacker.get_vnf_lcm_subscription(
self.request, subsc_id)
lccnsubscription['links'] = lccnsubscription.get('_links', '')
return lccnsubscription
except Exception as e:
redirect = reverse(self.redirect_url)
exceptions.handle(
self.request,
_('Failed to get LCCN Subscription. (id: %s)') % subsc_id,
redirect=redirect)
raise exceptions.Http302(redirect) from e
def get_tabs(self, request, *args, **kwargs):
lccnsubscription = self.get_data()
return self.tab_group_class(
request, lccnsubscription=lccnsubscription, **kwargs)

View File

@ -0,0 +1,52 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class UpdateVnfFmAlarm(forms.SelfHandlingForm):
ack_state = forms.ChoiceField(
label=_('ACK State'),
choices=[('ACKNOWLEDGED', _('ACKNOWLEDGED')),
('UNACKNOWLEDGED', _('UNACKNOWLEDGED'))])
def __init__(self, request, *args, **kwargs):
super(UpdateVnfFmAlarm, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(UpdateVnfFmAlarm, self).clean()
return data
def handle(self, request, data):
try:
ack_state = data.get('ack_state', None)
body = {}
body['ackState'] = ack_state
alarm_id = request.resolver_match.kwargs['id']
api.tacker.update_fm_alarm(request, alarm_id, body)
messages.success(request,
_('Update FM Alarm. (id: %s)') % alarm_id)
return True
except Exception:
msg = _('Failed to update FM Alarm. (id: %s)') % alarm_id
exceptions.handle(request, msg)
return False

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfFmAlarm(horizon.Panel):
name = _("Alarm")
slug = "vnffmalarm"
dashboard.Nfv.register(VnfFmAlarm)

View File

@ -0,0 +1,48 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import tables
class UpdateVnfFmAlarm(tables.LinkAction):
name = "updatealarm"
verbose_name = _("Update Alarm")
url = "horizon:nfv:vnffmalarm:updatealarm"
classes = ("ajax-modal",)
class VnfFmAlarmTable(tables.DataTable):
id = tables.Column('id', link="horizon:nfv:vnffmalarm:detail",
verbose_name=_("ID"))
managed_object_id = tables.Column('managed_object_id',
verbose_name=_("Managed Object ID"))
ack_state = tables.Column('ack_state', verbose_name=_("Ack State"))
event_type = tables.Column('event_type', verbose_name=_("Event Type"))
perceived_severity = tables.Column('perceived_severity',
verbose_name=_("Perceived Severity"))
probable_cause = tables.Column('probable_cause',
verbose_name=_("Probable Cause"))
class Meta(object):
name = "alarm"
verbose_name = _("Alarm")
pagination_param = 'vnffmalarm_marker'
prev_pagination_param = 'prev_vnffmalarm_marker'
table_actions = (tables.FilterAction,)
row_actions = (UpdateVnfFmAlarm,)

View File

@ -0,0 +1,114 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmalarm \
import tables
class VnfFmAlarmItem(object):
def __init__(self, alarm_id, managed_object_id, ack_state, event_type,
perceived_severity, probable_cause):
self.id = alarm_id
self.name = alarm_id
self.managed_object_id = managed_object_id
self.ack_state = ack_state
self.event_type = event_type
self.perceived_severity = perceived_severity
self.probable_cause = probable_cause
class VnfFmAlarmTab(tabs.TableTab):
name = _("VNFFMAlarm Tab")
slug = "vnffmalarm_tab"
table_classes = (tables.VnfFmAlarmTable,)
template_name = "horizon/common/_detail_table.html"
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_alarm_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get("vnffmalarm_marker", None)
prev_marker = self.request.GET.get("prev_vnffmalarm_marker", None)
alarms = api.tacker.list_fm_alarms(self.request)
if marker is not None or prev_marker is not None:
for i, alarm in enumerate(alarms):
if alarm["id"] == marker and i < len(alarms) - 1:
alarms = alarms[i + 1:]
self._has_prev = True
break
if alarm["id"] == prev_marker and i > page_size:
alarms = alarms[i - page_size:]
self._has_prev = True
break
if len(alarms) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, alarm in enumerate(alarms):
if i >= page_size:
break
item = VnfFmAlarmItem(alarm['id'],
alarm['managedObjectId'],
alarm['ackState'],
alarm['eventType'],
alarm['perceivedSeverity'],
alarm['probableCause'])
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get FM Alarms.')
exceptions.handle(self.request, error_message)
return []
class VnfFmAlarmTabs(tabs.TabGroup):
slug = "vnffmalarm_tabs"
tabs = (VnfFmAlarmTab,)
sticky = True
class VnfFmAlarmDetailTab(tabs.Tab):
name = _("VNF FM Alarm")
slug = "vnffmalarm_detail_tab"
template_name = "nfv/vnffmalarm/alarm_detail.html"
def get_context_data(self, request):
return {'alarm': self.tab_group.kwargs['alarm']}
class VnfFmAlarmDetailTabs(tabs.TabGroup):
slug = "vnffmalarm_detail_tabs"
tabs = (VnfFmAlarmDetailTab,)
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 "Update a Alarm." %}</p>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Ack State" %}</dt>
<dd>{{ alarm.ackState }}</dd>
<dt title="Alarm Acknowledged Time">{% trans "Alarm Acknowledged Time" %}</dt>
<dd>{{ alarm.alarmAcknowledgedTime }}</dd>
<dt>{% trans "Alarm Changed Time" %}</dt>
<dd>{{ alarm.alarmChangedTime }}</dd>
<dt>{% trans "Alarm Cleared Time" %}</dt>
<dd>{{ alarm.alarmClearedTime }}</dd>
<dt>{% trans "Alarm Raised Time" %}</dt>
<dd>{{ alarm.alarmRaisedTime }}</dd>
<dt>{% trans "Correlated Alarm IDs" %}</dt>
<dd>{{ alarm.correlatedAlarmIds }}</dd>
<dt>{% trans "Event Time" %}</dt>
<dd>{{ alarm.eventTime }}</dd>
<dt>{% trans "Event Type" %}</dt>
<dd>{{ alarm.eventType }}</dd>
<dt>{% trans "Fault Details" %}</dt>
<dd>{{ alarm.faultDetails }}</dd>
<dt>{% trans "Fault Type" %}</dt>
<dd>{{ alarm.faultType }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ alarm.id }}</dd>
<dt>{% trans "Is Root Cause" %}</dt>
<dd>{{ alarm.isRootCause }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ alarm.links }}</dd>
<dt>{% trans "Managed Object ID" %}</dt>
<dd>{{ alarm.managedObjectId }}</dd>
<dt>{% trans "Perceived Severity" %}</dt>
<dd>{{ alarm.perceivedSeverity }}</dd>
<dt>{% trans "Probable Cause" %}</dt>
<dd>{{ alarm.probableCause }}</dd>
<dt title="Root Cause Faulty Resource">{% trans "Root Cause Faulty Resource" %}</dt>
<dd>{{ alarm.rootCauseFaultyResource }}</dd>
<dt>{% trans "VNFC Instance IDs" %}</dt>
<dd>{{ alarm.vnfcInstanceIds }}</dd>
</dl>
</div>

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Alarm Detail" %}{% 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Alarm" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Alarm") %}
{% 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 "Update Alarm" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Alarm") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnffmalarm/_update_alarm.html' %}
{% endblock %}

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmalarm \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^(?P<id>[^/]+)/updatealarm$',
views.UpdateVnfFmAlarmView.as_view(),
name='updatealarm'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(),
name='detail'),
]

View File

@ -0,0 +1,90 @@
# Copyright (C) 2024 Fujitsu
# 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.urls import reverse_lazy
from django.utils.translation import gettext_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.vnffmalarm \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmalarm \
import tabs as alarm_tabs
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = alarm_tabs.VnfFmAlarmTabs
template_name = 'nfv/vnffmalarm/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class UpdateVnfFmAlarmView(forms.ModalFormView):
form_class = project_forms.UpdateVnfFmAlarm
template_name = 'nfv/vnffmalarm/update_alarm.html'
success_url = reverse_lazy("horizon:nfv:vnffmalarm:index")
modal_id = "update_alarm_modal"
modal_header = _("Update Alarm")
submit_label = _("Update Alarm")
submit_url = "horizon:nfv:vnffmalarm:updatealarm"
def get_context_data(self, **kwargs):
context = super(UpdateVnfFmAlarmView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class DetailView(tabs.TabView):
tab_group_class = alarm_tabs.VnfFmAlarmDetailTabs
template_name = 'nfv/vnffmalarm/detail.html'
redirect_url = 'horizon:nfv:vnffmalarm:index'
page_title = _("Alarm Detail: {{ id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
alarm = self.get_data()
context['alarm'] = alarm
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
alarm_id = self.kwargs['id']
try:
alarm = tacker_api.tacker.get_fm_alarm(self.request, alarm_id)
alarm['links'] = alarm.get('_links', '')
return alarm
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Failed to get FM Alarm. (id: %s)') % alarm_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
alarm = self.get_data()
return self.tab_group_class(request, alarm=alarm, **kwargs)

View File

@ -0,0 +1,64 @@
# Copyright (C) 2024 Fujitsu
# 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.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class CreateVnfFmSubscription(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
help_text=_("parameter file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-file': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(CreateVnfFmSubscription, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(CreateVnfFmSubscription, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
param = data['param']
subscription_instance = api.tacker.create_fm_subscription(
request, param)
messages.success(request,
_('Create FM Subscription. (id: %s)') %
subscription_instance['id'])
return True
except Exception:
msg = _('Failed to create FM Subscription.')
exceptions.handle(request, msg)
return False

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfFmSubscription(horizon.Panel):
name = _("Subscription")
slug = "vnffmsubscription"
dashboard.Nfv.register(VnfFmSubscription)

View File

@ -0,0 +1,68 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class DeleteVnfFmSubscription(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete Subscription",
"Delete Subscriptions",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Delete Subscription",
"Delete Subscriptions",
count
)
def action(self, request, obj_id):
api.tacker.delete_fm_subscription(request, obj_id)
class CreateVnfFmSubscription(tables.LinkAction):
name = "createsubscription"
verbose_name = _("Create Subscription")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:vnffmsubscription:createsubscription"
class VnfFmSubscriptionTable(tables.DataTable):
id = tables.Column('id', link="horizon:nfv:vnffmsubscription:detail",
verbose_name=_("ID"))
callback_uri = tables.Column('callback_uri',
verbose_name=_("Callback Uri"))
class Meta(object):
name = "subscription"
verbose_name = _("Subscription")
pagination_param = 'vnffmsubscription_marker'
prev_pagination_param = 'prev_vnffmsubscription_marker'
table_actions = (CreateVnfFmSubscription, DeleteVnfFmSubscription,
tables.FilterAction,)
row_actions = (DeleteVnfFmSubscription,)

View File

@ -0,0 +1,106 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmsubscription \
import tables
class VnfFmSubscriptionItem(object):
def __init__(self, subscription_id, callback_uri):
self.id = subscription_id
self.name = subscription_id
self.callback_uri = callback_uri
class VnfFmSubscriptionTab(tabs.TableTab):
name = _("VNFFMSubscription Tab")
slug = "vnffmsubscription_tab"
table_classes = (tables.VnfFmSubscriptionTable,)
template_name = "horizon/common/_detail_table.html"
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_subscription_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get("vnffmsubscription_marker", None)
prev_marker = self.request.GET.get(
"prev_vnffmsubscription_marker", None)
subscriptions = api.tacker.list_fm_subscriptions(self.request)
if marker is not None or prev_marker is not None:
for i, subsc in enumerate(subscriptions):
if subsc["id"] == marker and i < len(subscriptions) - 1:
subscriptions = subscriptions[i + 1:]
self._has_prev = True
break
if subsc["id"] == prev_marker and i > page_size:
subscriptions = subscriptions[i - page_size:]
self._has_prev = True
break
if len(subscriptions) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, subsc in enumerate(subscriptions):
if i >= page_size:
break
item = VnfFmSubscriptionItem(subsc['id'],
subsc['callbackUri'])
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get FM Subscriptions.')
exceptions.handle(self.request, error_message)
return []
class VnfFmSubscriptionTabs(tabs.TabGroup):
slug = "vnffmsubscription_tabs"
tabs = (VnfFmSubscriptionTab,)
sticky = True
class VnfFmSubscriptionDetailTab(tabs.Tab):
name = _("VNF FM Subscription")
slug = "vnffmsubscription_detail_tab"
template_name = "nfv/vnffmsubscription/subscription_detail.html"
def get_context_data(self, request):
return {'subscription': self.tab_group.kwargs['subscription']}
class VnfFmSubscriptionDetailTabs(tabs.TabGroup):
slug = "vnffmsubscription_detail_tabs"
tabs = (VnfFmSubscriptionDetailTab,)
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 "Create a Subscription." %}</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Subscription" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Subscription") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnffmsubscription/_create_subscription.html' %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Subscription Detail" %}{% 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Subscription" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Subscription") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "ID" %}</dt>
<dd>{{ subscription.id }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ subscription.links }}</dd>
<dt>{% trans "Callback Uri" %}</dt>
<dd>{{ subscription.callback_uri }}</dd>
<dt>{% trans "Filter" %}</dt>
<dd>{{ subscription.filter }}</dd>
</dl>
</div>

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmsubscription \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^createsubscription',
views.CreateVnfFmSubscriptionView.as_view(),
name='createsubscription'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(),
name='detail'),
]

View File

@ -0,0 +1,94 @@
# Copyright (C) 2024 Fujitsu
# 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.urls import reverse_lazy
from django.utils.translation import gettext_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.vnffmsubscription \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnffmsubscription \
import tabs as subscription_tabs
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = subscription_tabs.VnfFmSubscriptionTabs
template_name = 'nfv/vnffmsubscription/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class CreateVnfFmSubscriptionView(forms.ModalFormView):
form_class = project_forms.CreateVnfFmSubscription
template_name = 'nfv/vnffmsubscription/create_subscription.html'
success_url = reverse_lazy("horizon:nfv:vnffmsubscription:index")
modal_id = "create_subscription_modal"
modal_header = _("Create Subscription")
submit_label = _("Create Subscription")
submit_url = "horizon:nfv:vnffmsubscription:createsubscription"
def get_context_data(self, **kwargs):
context = super(CreateVnfFmSubscriptionView,
self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = subscription_tabs.VnfFmSubscriptionDetailTabs
template_name = 'nfv/vnffmsubscription/detail.html'
redirect_url = 'horizon:nfv:vnffmsubscription:index'
page_title = _("Subscription Detail: {{ id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
subscription = self.get_data()
context['subscription'] = subscription
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
subscription_id = self.kwargs['id']
try:
subscription = tacker_api.tacker.get_fm_subscription(
self.request, subscription_id)
subscription['links'] = subscription.get('_links', '')
subscription['callback_uri'] = subscription.get('callbackUri', '')
subscription['filter'] = subscription.get('filter', '')
return subscription
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Failed to get FM Subscription. (id: %s)') %
subscription_id, redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
subscription = self.get_data()
return self.tab_group_class(request,
subscription=subscription, **kwargs)

View File

@ -0,0 +1,425 @@
# Copyright (C) 2024 Fujitsu
# 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.
import json
import time
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class CreateVnfIdentifier(forms.SelfHandlingForm):
vnfd_id = forms.CharField(label=_("VNFD ID"))
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}),
required=False)
name = forms.CharField(label=_("Name"), required=False)
description = forms.CharField(label=_("Description"), required=False)
def __init__(self, request, *args, **kwargs):
super(CreateVnfIdentifier, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(CreateVnfIdentifier, self).clean()
param_file = data.get('param_file', None)
try:
if param_file:
metadata = self.files['param_file'].read()
data['metadata'] = json.loads(metadata)
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnfd_id = data.get('vnfd_id', None)
metadata = data.get('metadata', None)
name = data.get('name', None)
description = data.get('description', None)
body = {}
body['vnfdId'] = vnfd_id
if metadata:
body['metadata'] = metadata
if name:
body['vnfInstanceName'] = name
if description:
body['vnfInstanceDescription'] = description
response = api.tacker.create_vnf_instance(request, body)
messages.success(request,
_('Create VNF Identifier. (id: %s)') %
response['id'])
except Exception:
exceptions.handle(request, _('Failed to create VNF Identifier.'))
return False
return True
class InstantiateVnf(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(InstantiateVnf, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(InstantiateVnf, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param_data'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
param = data['param_data']
api.tacker.instantiate_vnf_instance(request, vnf_id, param)
messages.success(request,
_('Accepted to instantiate VNF Instance. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to instantiate VNF Instance. '
'(id: %s)') % vnf_id)
return False
return True
class TerminateVnf(forms.SelfHandlingForm):
termination_type = forms.ChoiceField(
label=_('Termination Type'),
required=True,
choices=[('GRACEFUL', _('GRACEFUL')),
('FORCEFUL', _('FORCEFUL'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
termination_timeout = forms.IntegerField(
min_value=0,
required=False,
label=_('Graceful Termination Timeout'))
is_delete = forms.BooleanField(
label=_('Delete VNF Instance'),
required=False)
def __init__(self, request, *args, **kwargs):
super(TerminateVnf, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(TerminateVnf, self).clean()
return data
def handle(self, request, data):
try:
is_delete = data.get('is_delete', False)
vnf_id = request.resolver_match.kwargs.get('id', None)
body = {}
termination_type = data.get('termination_type', None)
body['terminationType'] = termination_type
termination_timeout = data.get('terminationTimeout', None)
if termination_timeout:
body['gracefulTerminationTimeout'] = termination_timeout
api.tacker.terminate_vnf_instance(request, vnf_id, body)
if not is_delete:
messages.success(request,
_('Accepted to terminate VNF Instance. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to terminate VNF Instance. '
'(id: %s)') % vnf_id)
return False
if is_delete:
retry = 0
retry_limit = 12
while True:
try:
time.sleep(10)
api.tacker.delete_vnf_instance(request, vnf_id)
messages.success(request,
_('Delete VNF Identifier. (id: %s)') %
vnf_id)
break
except Exception as exc:
if 'is in progress' in str(exc):
# Conflict error occurs even if delete
# is executed immediately after terminate
retry = retry + 1
if retry >= retry_limit:
exceptions.handle(
request,
_('Delete VNF Identifier retry out. (id: %s)')
% vnf_id)
break
continue
else:
exceptions.handle(
request,
_('Failed to delete VNF Identifer. (id: %s)') %
vnf_id)
break
return True
class HealVnf(forms.SelfHandlingForm):
cause = forms.CharField(label=_("Cause"),
required=False)
vnfc_instances = forms.CharField(
label=_('VNFC Instance'), required=False, widget=forms.Textarea,
help_text=_('Enter VNFC instance IDs in comma-separated list format.'))
def __init__(self, request, *args, **kwargs):
super(HealVnf, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(HealVnf, self).clean()
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
body = {}
cause = data.get('cause', None)
if cause:
body['cause'] = cause
array_str = data.get('vnfc_instances', None)
if array_str:
array_str.replace('\n', '')
vnfc_instance_ids = [s.strip() for s in array_str.split(',')]
body['vnfcInstanceId'] = vnfc_instance_ids
api.tacker.heal_vnf_instance(request, vnf_id, body)
messages.success(request,
_('Accepted to heal VNF Instance. (id: %s)') %
vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to heal VNF Instance. (id: %s)') %
vnf_id)
return False
return True
class UpdateVnf(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(UpdateVnf, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(UpdateVnf, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param_data'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
param = data['param_data']
api.tacker.update_vnf_instance(request, vnf_id, param)
messages.success(request,
_('Accepted to update VNF Instance. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to update VNF Instance. '
'(id: %s)') % vnf_id)
return False
return True
class ScaleVnf(forms.SelfHandlingForm):
scale_type = forms.ChoiceField(
label=_('Type'),
choices=[('SCALE_IN', _('SCALE_IN')),
('SCALE_OUT', _('SCALE_OUT'))])
aspect_id = forms.CharField(label=_("Aspect ID"))
num_of_steps = forms.IntegerField(
label=_("Number of Steps"), min_value=1, required=False)
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(ScaleVnf, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(ScaleVnf, self).clean()
param_file = data.get('param_file', None)
try:
if param_file:
additional_params = self.files['param_file'].read()
data['additional_params'] = json.loads(additional_params)
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
scale_type = data.get('scale_type', None)
aspect_id = data.get('aspect_id', None)
num_of_steps = data.get('num_of_steps', None)
additional_params = data.get('additional_params', None)
body = {}
body['type'] = scale_type
body['aspectId'] = aspect_id
if num_of_steps:
body['numberOfSteps'] = num_of_steps
if additional_params:
body['additionalParams'] = additional_params
api.tacker.scale_vnf_instance(request, vnf_id, body)
messages.success(request,
_('Accepted to scale VNF Instance. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to scale VNF Instance. '
'(id: %s)') % vnf_id)
return False
return True
class ChangeExternalVnfConnectivity(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(ChangeExternalVnfConnectivity,
self).__init__(request, *args, **kwargs)
def clean(self):
data = super(ChangeExternalVnfConnectivity, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param_data'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
param = data['param_data']
api.tacker.change_ext_conn_vnf_instance(request, vnf_id, param)
messages.success(request,
_('Accepted to change External VNF Connectivity. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to change External VNF Connectivity. '
'(id: %s)') % vnf_id)
return False
return True
class ChangeCurrentVnfPackage(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-upload': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(ChangeCurrentVnfPackage, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(ChangeCurrentVnfPackage, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param_data'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
vnf_id = request.resolver_match.kwargs.get('id', None)
param = data['param_data']
api.tacker.change_vnfpkg_vnf_instance(request, vnf_id, param)
messages.success(request,
_('Accepted to change Current VNF Package. '
'(id: %s)') % vnf_id)
except Exception:
exceptions.handle(request,
_('Failed to change Current VNF Package. '
'(id: %s)') % vnf_id)
return False
return True

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfLcm(horizon.Panel):
name = _("VNF LCM")
slug = "vnflcm"
dashboard.Nfv.register(VnfLcm)

View File

@ -0,0 +1,129 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class DeleteVnfIdentifier(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete VNF Identifier",
"Delete VNF Identifiers",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Delete VNF Identifier",
"Delete VNF Identifiers",
count
)
def action(self, request, obj_id):
api.tacker.delete_vnf_instance(request, obj_id)
class CreateVnfIdentifier(tables.LinkAction):
name = "createvnfid"
verbose_name = _("Create VNF Identifier")
url = "horizon:nfv:vnflcm:createvnfidentifier"
classes = ("ajax-modal",)
icon = "plus"
class InstantiateVnf(tables.LinkAction):
name = "instantiatevnf"
verbose_name = _("Instantiate VNF")
url = "horizon:nfv:vnflcm:instantiatevnf"
classes = ("ajax-modal",)
class TerminateVnf(tables.LinkAction):
name = "terminate_vnf"
verbose_name = _("Terminate VNF")
url = "horizon:nfv:vnflcm:terminatevnf"
classes = ("ajax-modal",)
class HealVnf(tables.LinkAction):
name = "heal_vnf"
verbose_name = _("Heal VNF")
url = "horizon:nfv:vnflcm:healvnf"
classes = ("ajax-modal",)
class UpdateVnf(tables.LinkAction):
name = "update_Vnf"
verbose_name = _("Update VNF")
url = "horizon:nfv:vnflcm:updatevnf"
classes = ("ajax-modal",)
class ScaleVnf(tables.LinkAction):
name = "scale_vnf"
verbose_name = _("Scale VNF")
url = "horizon:nfv:vnflcm:scalevnf"
classes = ("ajax-modal",)
class ChangeExternalVnfConnectivity(tables.LinkAction):
name = "change_external_vnf_connectivity"
verbose_name = _("Change External VNF Connectivity")
url = "horizon:nfv:vnflcm:changeconnectivity"
classes = ("ajax-modal",)
class ChangeCurrentVnfPackage(tables.LinkAction):
name = "change_current_vnf_package"
verbose_name = _("Change Current VNF Package")
url = "horizon:nfv:vnflcm:changevnfpkg"
classes = ("ajax-modal",)
class VnfLcmTable(tables.DataTable):
id = tables.Column('id', verbose_name=_("ID"),
link="horizon:nfv:vnflcm:detail",)
vnf_instance_name = tables.Column('vnf_instance_name',
verbose_name=_("VNF Instance Name"))
instantiation_state = tables.Column('instantiation_state',
verbose_name=_("Instantiation State"))
vnf_provider = tables.Column('vnf_provider',
verbose_name=_("VNF Provider"))
vnf_software_version = tables.Column(
'vnf_software_version',
verbose_name=_("VNF Software Version"))
vnf_product_name = tables.Column('vnf_product_name',
verbose_name=_("VNF Product Name"))
vnfd_id = tables.Column('vnfd_id', verbose_name=_("VNFD ID"))
class Meta(object):
name = "vnflcm"
verbose_name = _("VNF LCM")
pagination_param = 'vnflcm_marker'
prev_pagination_param = 'prev_vnflcm_marker'
table_actions = (CreateVnfIdentifier, DeleteVnfIdentifier,
tables.FilterAction,)
row_actions = (InstantiateVnf, TerminateVnf, DeleteVnfIdentifier,
HealVnf, UpdateVnf, ScaleVnf,
ChangeExternalVnfConnectivity, ChangeCurrentVnfPackage,)

View File

@ -0,0 +1,131 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcm import tables
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcmopocc \
import tabs as opocc_tab
class VnfLcmItem(object):
def __init__(self, vnf_id, vnf_instance_name, instantiation_state,
vnf_provider, vnf_software_version, vnf_product_name,
vnfd_id):
self.id = vnf_id
self.name = vnf_id
self.vnf_instance_name = vnf_instance_name
self.instantiation_state = instantiation_state
self.vnf_provider = vnf_provider
self.vnf_software_version = vnf_software_version
self.vnf_product_name = vnf_product_name
self.vnfd_id = vnfd_id
class VnfLcmTab(tabs.TableTab):
name = _("VNFLCM tab")
slug = "vnflcm_tab"
table_classes = (tables.VnfLcmTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_vnflcm_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get(
"vnflcm_marker", None)
prev_marker = self.request.GET.get(
"prev_vnflcm_marker", None)
vnf_instances = api.tacker.list_vnf_instances(self.request)
if marker is not None or prev_marker is not None:
for i, instance in enumerate(vnf_instances):
if instance["id"] == marker and i < len(vnf_instances) - 1:
vnf_instances = vnf_instances[i + 1:]
self._has_prev = True
break
if instance["id"] == prev_marker and i > page_size:
vnf_instances = vnf_instances[i - page_size:]
self._has_prev = True
break
if len(vnf_instances) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, instance in enumerate(vnf_instances):
if i >= page_size:
break
item = VnfLcmItem(
vnf_id=instance.get('id', ''),
vnf_instance_name=instance.get('vnfInstanceName', ''),
instantiation_state=instance.get(
'instantiationState', ''),
vnf_provider=instance.get('vnfProvider', ''),
vnf_software_version=instance.get(
'vnfSoftwareVersion', ''),
vnf_product_name=instance.get('vnfProductName', ''),
vnfd_id=instance.get('vnfdId', ''))
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get VNF Instances.')
exceptions.handle(self.request, error_message)
return []
class VnfLcmTabs(tabs.TabGroup):
slug = "vnflcm_tabs"
tabs = (VnfLcmTab,)
sticky = True
class VnfLcmOpOccItem(object):
def __init__(self, id, operation_state, vnf_instance_id, operation):
self.id = id
self.operation_state = operation_state
self.vnf_instance_id = vnf_instance_id
self.operation = operation
class VnfLcmDetailTab(tabs.Tab):
name = _("VNF Instance Detail Tab")
slug = "vnflcm_detail_tab"
template_name = "nfv/vnflcm/vnflcm_detail.html"
def get_context_data(self, request):
return {'vnflcm': self.tab_group.kwargs['vnflcm']}
class VnfLcmDetailTabs(tabs.TabGroup):
slug = "vnflcm_detail_tabs"
tabs = (VnfLcmDetailTab, opocc_tab.VnfLcmOpOccTab,)
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 "Change a External VNF Connectivity." %}</p>
{% endblock %}

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 "Change a Current VNF Package." %}</p>
{% endblock %}

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 "Create a VNF Identifier." %}</p>
{% endblock %}

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 "Heal a VNF instance." %}</p>
{% endblock %}

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 "Instantiate a VNF instance." %}</p>
{% endblock %}

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 "Scale a VNF instance." %}</p>
{% endblock %}

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 "Terminate a VNF instance." %}</p>
{% endblock %}

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 "Update a VNF instance." %}</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Change External VNF Connectivity" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Change External VNF Connectivity") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_change_connectivity.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Change Current VNF Package" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Change Current VNF Package") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_change_vnfpkg.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "create_vnf_id" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create VNF Identifier") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_create_vnf_identifier.html' %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF LCM Detail" %}{% 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,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Heal VNF" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Heal VNF") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_heal_vnf.html' %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF LCM" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VNF LCM") %}
{% 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 "Instantiate VNF" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Instantiate VNF") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_instantiate_vnf.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Scale VNF" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Scale VNF") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_scale_vnf.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Terminate VNF" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Terminate VNF") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_terminate_vnf.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update VNF" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update VNF") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnflcm/_update_vnf.html' %}
{% endblock %}

View File

@ -0,0 +1,30 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "ID" %}</dt>
<dd>{{ vnflcm.id }}</dd>
<dt>{% trans "Instantiated Vnf Info" %}</dt>
<dd>{{ vnflcm.instantiatedVnfInfo }}</dd>
<dt>{% trans "Instantiation State" %}</dt>
<dd>{{ vnflcm.instantiationState }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ vnflcm.links }}</dd>
<dt>{% trans "VIM Connection Info" %}</dt>
<dd>{{ vnflcm.vimConnectionInfo }}</dd>
<dt>{% trans "VNF Instance Description" %}</dt>
<dd>{{ vnflcm.vnfInstanceDescription }}</dd>
<dt>{% trans "VNF Instance Name" %}</dt>
<dd>{{ vnflcm.vnfInstanceName }}</dd>
<dt>{% trans "VNF Product Name" %}</dt>
<dd>{{ vnflcm.vnfProductName }}</dd>
<dt>{% trans "VNF Provider" %}</dt>
<dd>{{ vnflcm.vnfProvider }}</dd>
<dt>{% trans "VNF Software Version" %}</dt>
<dd>{{ vnflcm.vnfSoftwareVersion }}</dd>
<dt>{% trans "VNFD ID" %}</dt>
<dd>{{ vnflcm.vnfdId }}</dd>
<dt>{% trans "VNFD Version" %}</dt>
<dd>{{ vnflcm.vnfdVersion }}</dd>
</dl>
</div>

View File

@ -0,0 +1,44 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcm \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
re_path(r'^createvnfidentifier$', views.CreateVnfIdentifierView.as_view(),
name='createvnfidentifier'),
re_path(r'^(?P<id>[^/]+)/instantiatevnf/$',
views.InstantiateVnfView.as_view(),
name='instantiatevnf'),
re_path(r'^(?P<id>[^/]+)/terminatevnf/$', views.TerminateVnfView.as_view(),
name='terminatevnf'),
re_path(r'^(?P<id>[^/]+)/healvnf/$', views.HealVnfView.as_view(),
name='healvnf'),
re_path(r'^(?P<id>[^/]+)/updatevnf/$', views.UpdateVnfView.as_view(),
name='updatevnf'),
re_path(r'^(?P<id>[^/]+)/scalevnf/$', views.ScaleVnfView.as_view(),
name='scalevnf'),
re_path(r'^(?P<id>[^/]+)/changeconnectivity/$',
views.ChangeExternalVnfConnectivityView.as_view(),
name='changeconnectivity'),
re_path(r'^(?P<id>[^/]+)/changevnfpkg/$',
views.ChangeCurrentVnfPackageView.as_view(),
name='changevnfpkg'),
]

View File

@ -0,0 +1,200 @@
# Copyright (C) 2024 Fujitsu
# 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.urls import reverse_lazy
from django.utils.translation import gettext_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.vnflcm \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcm \
import tabs as vnflcm_tabs
class IndexView(tabs.TabbedTableView):
tab_group_class = vnflcm_tabs.VnfLcmTabs
template_name = 'nfv/vnflcm/index.html'
class CreateVnfIdentifierView(forms.ModalFormView):
form_class = project_forms.CreateVnfIdentifier
template_name = 'nfv/vnflcm/create_vnf_identifier.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Create VNF Identifier")
submit_label = _("Create VNF Identifier")
submit_url = "horizon:nfv:vnflcm:createvnfidentifier"
def get_context_data(self, **kwargs):
context = super(
CreateVnfIdentifierView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url)
return context
class InstantiateVnfView(forms.ModalFormView):
form_class = project_forms.InstantiateVnf
template_name = 'nfv/vnflcm/instantiate_vnf.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Instantiate VNF")
submit_label = _("Instantiate VNF")
submit_url = "horizon:nfv:vnflcm:instantiatevnf"
def get_context_data(self, **kwargs):
context = super(InstantiateVnfView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class TerminateVnfView(forms.ModalFormView):
form_class = project_forms.TerminateVnf
template_name = 'nfv/vnflcm/terminate_vnf.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Terminate VNF")
submit_label = _("Terminate VNF")
submit_url = "horizon:nfv:vnflcm:terminatevnf"
def get_context_data(self, **kwargs):
context = super(TerminateVnfView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class HealVnfView(forms.ModalFormView):
form_class = project_forms.HealVnf
template_name = 'nfv/vnflcm/heal_vnf.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Heal VNF")
submit_label = _("Heal VNF")
submit_url = "horizon:nfv:vnflcm:healvnf"
def get_context_data(self, **kwargs):
context = super(HealVnfView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class UpdateVnfView(forms.ModalFormView):
form_class = project_forms.UpdateVnf
template_name = 'nfv/vnflcm/update_vnf.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Update VNF")
submit_label = _("Update VNF")
submit_url = "horizon:nfv:vnflcm:updatevnf"
def get_context_data(self, **kwargs):
context = super(UpdateVnfView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class ScaleVnfView(forms.ModalFormView):
form_class = project_forms.ScaleVnf
template_name = 'nfv/vnflcm/scale_vnf.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Scale VNF")
submit_label = _("Scale VNF")
submit_url = "horizon:nfv:vnflcm:scalevnf"
def get_context_data(self, **kwargs):
context = super(ScaleVnfView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class ChangeExternalVnfConnectivityView(forms.ModalFormView):
form_class = project_forms.ChangeExternalVnfConnectivity
template_name = 'nfv/vnflcm/change_connectivity.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Change External VNF Connectivity")
submit_label = _("Change External VNF Connectivity")
submit_url = "horizon:nfv:vnflcm:changeconnectivity"
def get_context_data(self, **kwargs):
context = super(
ChangeExternalVnfConnectivityView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class ChangeCurrentVnfPackageView(forms.ModalFormView):
form_class = project_forms.ChangeCurrentVnfPackage
template_name = 'nfv/vnflcm/change_vnfpkg.html'
success_url = reverse_lazy("horizon:nfv:vnflcm:index")
modal_id = "add_service_modal"
modal_header = _("Change Current VNF Package")
submit_label = _("Change Current VNF Package")
submit_url = "horizon:nfv:vnflcm:changevnfpkg"
def get_context_data(self, **kwargs):
context = super(
ChangeCurrentVnfPackageView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class DetailView(tabs.TabbedTableView):
tab_group_class = vnflcm_tabs.VnfLcmDetailTabs
template_name = 'nfv/vnflcm/detail.html'
redirect_url = 'horizon:nfv:vnflcm:index'
page_title = _("VNF Instance Detail: {{ id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
vnflcm = self.get_data()
context['vnflcm'] = vnflcm
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
vnf_id = self.kwargs['id']
try:
vnflcm = tacker_api.tacker.get_vnf_instance(self.request, vnf_id)
vnflcm['links'] = vnflcm.get('_links', '')
return vnflcm
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(
self.request,
_('Failed to get VNF instance. (id: %s)') % vnf_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
vnflcm = self.get_data()
return self.tab_group_class(request, vnflcm=vnflcm, **kwargs)

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfOpOcc(horizon.Panel):
name = _("VNF OP OCC")
slug = "vnflcmopocc"
dashboard.Nfv.register(VnfOpOcc)

View File

@ -0,0 +1,114 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from tacker_horizon.openstack_dashboard import api
class VnfLcmRollback(tables.BatchAction):
name = "vnflcmrollback"
verbose_name = _("Rollback VNF Lifecycle Management Operation")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Rollback VNF Lifecycle Management Operation",
"Rollback VNF Lifecycle Management Operation",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Rollback VNF Lifecycle Management Operation",
"Rollback VNF Lifecycle Management Operation",
count
)
def action(self, request, obj_id):
api.tacker.rollback_vnf_lcm_op_occ(request, obj_id)
class VnfLcmRetry(tables.BatchAction):
name = "vnflcmretry"
verbose_name = _("Retry")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Retry",
"Retry",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Retry",
"Retry",
count
)
def action(self, request, obj_id):
api.tacker.retry_vnf_lcm_op_occ(request, obj_id)
class VnfLcmFail(tables.BatchAction):
name = "vnflcmfail"
verbose_name = _("Fail")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Fail",
"Fail",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Fail",
"Fail",
count
)
def action(self, request, obj_id):
api.tacker.fail_vnf_lcm_op_occ(request, obj_id)
class VnfLcmOpOccTable(tables.DataTable):
id = tables.Column('id', verbose_name=_("ID"),
link="horizon:nfv:vnflcmopocc:detail",)
operation_state = tables.Column('operation_state',
verbose_name=_("OperationState"))
vnf_instance_id = tables.Column('vnf_instance_id',
verbose_name=_("VNFInstanceID"))
operation = tables.Column('operation', verbose_name=_("Operation"))
class Meta(object):
name = "vnflcmopocc"
verbose_name = _("VNF LCM OP OCC")
pagination_param = 'opocc_marker'
prev_pagination_param = 'prev_opocc_marker'
table_actions = (VnfLcmRollback, VnfLcmRetry, VnfLcmFail,
tables.FilterAction,)
row_actions = (VnfLcmRollback, VnfLcmRetry, VnfLcmFail,)
multi_select = True

View File

@ -0,0 +1,118 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcmopocc \
import tables
class VnfLcmOpOccItem(object):
def __init__(self, opocc_id, operation_state, vnf_instance_id,
operation):
self.id = opocc_id
self.name = opocc_id
self.operation_state = operation_state
self.vnf_instance_id = vnf_instance_id
self.operation = operation
class VnfLcmOpOccTab(tabs.TableTab):
name = _("List LCM Operation Occurrences")
slug = "vnflcmopocc_tab"
table_classes = (tables.VnfLcmOpOccTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_vnflcmopocc_data(self):
try:
opoccs = []
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get(
"opocc_marker", None)
prev_marker = self.request.GET.get(
"prev_opocc_marker", None)
params = {}
opocc_id = self.request.resolver_match.kwargs.get("id", None)
if opocc_id:
params["filter"] = "(eq,vnfInstanceId,{0})".format(opocc_id)
opoccs = api.tacker.list_vnf_lcm_op_occs(self.request, **params)
if marker is not None or prev_marker is not None:
for i, opocc in enumerate(opoccs):
if opocc["id"] == marker and i < len(opoccs) - 1:
opoccs = opoccs[i + 1:]
self._has_prev = True
break
if opocc["id"] == prev_marker and i > page_size:
opoccs = opoccs[i - page_size:]
self._has_prev = True
break
if len(opoccs) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, opocc in enumerate(opoccs):
if i >= page_size:
break
item = VnfLcmOpOccItem(
opocc_id=opocc.get('id', ''),
operation_state=opocc.get('operationState', ''),
vnf_instance_id=opocc.get('vnfInstanceId', ''),
operation=opocc.get('operation', ''))
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get VNF LCM operation occurrences.')
exceptions.handle(self.request, error_message)
return []
class VnfLcmOpOccTabs(tabs.TabGroup):
slug = "vnflcmopocc_tabs"
tabs = (VnfLcmOpOccTab,)
sticky = True
class VnfOpOccDetailTab(tabs.Tab):
name = _("LCM OP OCC Detail")
slug = "vnflcmopocc_detail_tab"
template_name = "nfv/vnflcmopocc/vnflcmopocc_detail.html"
def get_context_data(self, request):
return {'vnflcmopocc': self.tab_group.kwargs['vnflcmopocc']}
class VnfOpOccDetailTabs(tabs.TabGroup):
slug = "vnflcmopocc_detail_tabs"
tabs = (VnfOpOccDetailTab,)
sticky = True

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF LCM OP OCC" %}{% 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF LCM OP OCC" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VNF LCM OP OCC") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "CancelMode" %}</dt>
<dd>{{ vnflcmopocc.cancelMode }}</dd>
<dt>{% trans "ChangedExtConnectivity" %}</dt>
<dd>{{ vnflcmopocc.changedExtConnectivity }}</dd>
<dt>{% trans "ChangedInfo" %}</dt>
<dd>{{ vnflcmopocc.changedInfo }}</dd>
<dt>{% trans "Error" %}</dt>
<dd>{{ vnflcmopocc.error }}</dd>
<dt>{% trans "GrantID" %}</dt>
<dd>{{ vnflcmopocc.grantId }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ vnflcmopocc.id }}</dd>
<dt>{% trans "IsAutomaticInvocation" %}</dt>
<dd>{{ vnflcmopocc.isAutomaticInvocation }}</dd>
<dt>{% trans "IsCancelPending" %}</dt>
<dd>{{ vnflcmopocc.isCancelPending }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ vnflcmopocc.links }}</dd>
<dt>{% trans "Operation" %}</dt>
<dd>{{ vnflcmopocc.operation }}</dd>
<dt>{% trans "OperationParams" %}</dt>
<dd>{{ vnflcmopocc.operationParams }}</dd>
<dt>{% trans "OperationState" %}</dt>
<dd>{{ vnflcmopocc.operationState }}</dd>
<dt>{% trans "ResourceChanges" %}</dt>
<dd>{{ vnflcmopocc.resourceChanges }}</dd>
<dt>{% trans "StartTime" %}</dt>
<dd>{{ vnflcmopocc.startTime }}</dd>
<dt>{% trans "StateEnteredTime" %}</dt>
<dd>{{ vnflcmopocc.stateEnteredTime }}</dd>
<dt>{% trans "VnfInstanceID" %}</dt>
<dd>{{ vnflcmopocc.vnfInstanceId }}</dd>
</dl>
</div>

View File

@ -0,0 +1,25 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnflcmopocc \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,73 @@
# Copyright (C) 2024 Fujitsu
# 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 gettext_lazy as _
from horizon import exceptions
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.vnflcmopocc \
import tabs as vnflcmopocc_tabs
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = vnflcmopocc_tabs.VnfLcmOpOccTabs
template_name = 'nfv/vnflcmopocc/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class DetailView(tabs.TabView):
tab_group_class = vnflcmopocc_tabs.VnfOpOccDetailTabs
template_name = 'nfv/vnflcmopocc/detail.html'
redirect_url = 'horizon:nfv:vnflcmopocc:index'
page_title = _("LCM OP OCC Details: {{ id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
vnflcmopocc = self.get_data()
context['vnflcmopocc'] = vnflcmopocc
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
opocc_id = self.kwargs['id']
try:
vnflcmopocc = tacker_api.tacker.get_vnf_lcm_op_occ(self.request,
opocc_id)
vnflcmopocc['links'] = vnflcmopocc.get('_links', '')
return vnflcmopocc
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(
self.request,
_('Failed to get VNF LCM operation occurrence. (id: %s)') %
opocc_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
vnflcmopocc = self.get_data()
return self.tab_group_class(request, vnflcmopocc=vnflcmopocc, **kwargs)

View File

@ -0,0 +1,189 @@
# Copyright (C) 2024 Fujitsu
# 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.
import os
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.files.uploadedfile import TemporaryUploadedFile
from django.forms import ValidationError
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from oslo_log import log as logging
from tacker_horizon.openstack_dashboard import api
LOG = logging.getLogger(__name__)
class UploadVnfPackage(forms.SelfHandlingForm):
user_data = forms.JSONField(
label=_("User Data"), required=False)
source_type = forms.ChoiceField(
label=_('VNF Package Source'),
choices=[('url', _('URL')), ('file', _('File'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
url = forms.URLField(
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-url': _('URL')}),
required=False)
user_name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-url': _('User Name')}),
required=False)
password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-url': _('Password')}),
required=False)
vnf_package = forms.FileField(
widget=forms.FileInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-file': _('VNF Package')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(UploadVnfPackage, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(UploadVnfPackage, self).clean()
return data
def handle(self, request, data):
try:
body = {}
user_data = data.get('user_data', None)
if user_data:
body['userDefinedData'] = user_data
response = api.tacker.create_vnf_package(request, body)
except Exception:
exceptions.handle(request,
_('Failed to create VNF Package.'))
return False
try:
params = {}
source_type = data.get('source_type', None)
if source_type == 'url':
params['url'] = data.get('url', None)
user_name = data.get('user_name', None)
password = data.get('password', None)
if user_name:
params['userName'] = user_name
params['password'] = password
api.tacker.upload_vnf_package(
request, response['id'], **params)
messages.success(
request,
_('Accepted to upload VNF Package. '
'(id: %s)') % response['id'])
else:
file = request.FILES['vnf_package']
if isinstance(file, TemporaryUploadedFile):
# Hack to fool Django, so we can keep file open.
file.file._closer.close_called = True
elif isinstance(file, InMemoryUploadedFile):
# Clone a new file for InMemoryUploadedFile.
# Because the old one will be closed by Django.
file = SimpleUploadedFile(
file.name, file.read(), file.content_type)
try:
api.tacker.upload_vnf_package(
request, response['id'], file, **params)
messages.success(
request,
_('Accepted to upload VNF Package. '
'(id: %s)') % response['id'])
finally:
try:
filename = str(file.file.name)
except AttributeError:
pass
else:
try:
os.remove(filename)
except OSError as e:
LOG.warning(
'Failed to remove temporary file '
'%(file)s (%(e)s)',
{'file': filename, 'e': e})
except Exception:
exceptions.handle(request,
_('Failed to upload VNF Package. (id: %s)') %
response['id'])
return False
return True
class UpdateVnfPackage(forms.SelfHandlingForm):
operationalState = forms.ChoiceField(
label=_('Operational State'),
required=False,
choices=[(None, _(' ')),
('ENABLED', _('ENABLED')),
('DISABLED', _('DISABLED'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
userData = forms.JSONField(label=_('User Data'),
required=False)
def __init__(self, request, *args, **kwargs):
super(UpdateVnfPackage, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(UpdateVnfPackage, self).clean()
operationalState = data.get('operationalState', None)
userData = data.get('userData', None)
if operationalState is None and userData is None:
raise ValidationError(
_("At least one of the \"operationalState\" or \
\"userDefinedData\" parameters shall be present."))
return data
def handle(self, request, data):
try:
vnfpkg_id = request.resolver_match.kwargs.get('id', None)
body = {}
operationalState = data.get('operationalState', None)
if operationalState:
body['operationalState'] = operationalState
userDefinedData = data.get('userData', None)
if userDefinedData:
body['userDefinedData'] = userDefinedData
api.tacker.update_vnf_package(request, vnfpkg_id, body)
messages.success(request,
_('Update VNF Package. (id: %s)') % vnfpkg_id)
except Exception:
exceptions.handle(request,
_('Failed to update VNF Package. (id: %s)') %
vnfpkg_id)
return False
return True

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfPackages(horizon.Panel):
name = _("VNF Packages")
slug = "vnfpackages"
dashboard.Nfv.register(VnfPackages)

View File

@ -0,0 +1,116 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class DeleteVnfPackage(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete VNF Package",
"Delete VNF Packages",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Delete VNF Package",
"Delete VNF Packages",
count
)
def action(self, request, obj_id):
api.tacker.delete_vnf_package(request, obj_id)
class UploadVnfPackage(tables.LinkAction):
name = "uploadvnfpkg"
verbose_name = _("Upload VNF Package")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:vnfpackages:uploadvnfpkg"
class FetchVnfPackage(tables.LinkAction):
name = "fetchvnfpkg"
verbose_name = _("Fetch VNF Package")
verbose_name_plural = _("Fetch VNF Package")
icon = "download"
url = "horizon:nfv:vnfpackages:fetch"
def allowed(self, request, datum=None):
return True
class UpdateVnfPackageInfo(tables.LinkAction):
name = "updatevnfpkginfo"
verbose_name = _("Update VNF Package Info")
url = "horizon:nfv:vnfpackages:updatevnfpkg"
classes = ("ajax-modal",)
class CreateVnfIdentifier(tables.Action):
name = "createvnfid"
verbose_name = _("Create VNF Identifier")
def single(self, table, request, obj_id): # pylint: disable=E0202
try:
vnf_pkg = api.tacker.get_vnf_package(request, obj_id)
vnfd_id = vnf_pkg.get('vnfdId', None)
if not vnfd_id:
msg = _('Failed to get VNFD ID')
raise Exception(msg)
req_body = {}
req_body['vnfdId'] = vnfd_id
response = api.tacker.create_vnf_instance(request, req_body)
messages.success(request,
_('Create VNF Identifier. (id: %s)') %
response['id'])
except Exception:
exceptions.handle(request,
_('Failed to create VNF Identifier.'))
class VnfPackageTable(tables.DataTable):
id = tables.Column('id', verbose_name=_("ID"),
link="horizon:nfv:vnfpackages:detail",)
name = tables.Column('vnf_product_name',
verbose_name=_("VNF Product Name"))
onboarding_state = tables.Column('onboarding_state',
verbose_name=_("Onboarding State"))
usage_state = tables.Column('usage_state', verbose_name=_("Usage State"))
operational_state = tables.Column('operational_state',
verbose_name=_("Operational State"))
class Meta(object):
name = "vnfpackage"
verbose_name = _("VNF Package")
pagination_param = 'package_marker'
prev_pagination_param = 'prev_package_marker'
table_actions = (UploadVnfPackage, DeleteVnfPackage,
tables.FilterAction,)
row_actions = (FetchVnfPackage, UpdateVnfPackageInfo, DeleteVnfPackage,
CreateVnfIdentifier,)

View File

@ -0,0 +1,118 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv import utils # noqa
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnfpackages \
import tables
class VnfPackageItem(object):
def __init__(self, vnfpkg_id, vnf_product_name, onboarding_state,
usage_state, operational_state, vnfd_id,):
self.id = vnfpkg_id
self.name = vnfpkg_id
self.vnf_product_name = vnf_product_name
self.onboarding_state = onboarding_state
self.usage_state = usage_state
self.operational_state = operational_state
self.vnfd_id = vnfd_id
class VnfPackageTab(tabs.TableTab):
name = _("VNFP tab")
slug = "vnfpkg_tab"
table_classes = (tables.VnfPackageTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_vnfpackage_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get(
"package_marker", None)
prev_marker = self.request.GET.get(
"prev_package_marker", None)
packages = api.tacker.list_vnf_packages(self.request)
if marker is not None or prev_marker is not None:
for i, package in enumerate(packages):
if package["id"] == marker and i < len(packages) - 1:
packages = packages[i + 1:]
self._has_prev = True
break
if package["id"] == prev_marker and i > page_size:
packages = packages[i - page_size:]
self._has_prev = True
break
if len(packages) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, package in enumerate(packages):
if i >= page_size:
break
item = VnfPackageItem(
vnfpkg_id=package.get('id', ''),
vnf_product_name=package.get('vnfProductName', ''),
onboarding_state=package.get('onboardingState', ''),
usage_state=package.get('usageState', ''),
operational_state=package.get('operationalState', ''),
vnfd_id=package.get('vnfdId', ''),)
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get VNF Packages.')
exceptions.handle(self.request, error_message)
return []
class VnfPackageTabs(tabs.TabGroup):
slug = "vnfpkg_tabs"
tabs = (VnfPackageTab,)
sticky = True
class VnfPackageDetailTab(tabs.Tab):
name = _("VNFPkg Detail")
slug = "vnfpkg_detail_tab"
template_name = "nfv/vnfpackages/vnfpkg_detail.html"
def get_context_data(self, request):
return {'vnfpkg': self.tab_group.kwargs['vnfpkg']}
class VnfPackageDetailTabs(tabs.TabGroup):
slug = "vnfpkg_detail_tabs"
tabs = (VnfPackageDetailTab,)
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 "Update a VNF Package information." %}</p>
{% endblock %}

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 "Upload a VNF Package." %}</p>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF Package 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VNF Packages" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VNF Packages") %}
{% 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 "Update VNF Package" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update VNF Package") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnfpackages/_update_vnfpkg.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Upload VNF Package" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Upload VNF Package") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnfpackages/_upload_vnfpkg.html' %}
{% endblock %}

View File

@ -0,0 +1,34 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Additional Artifacts" %}</dt>
<dd>{{ vnfpkg.additionalArtifacts }}</dd>
<dt>{% trans "Checksum" %}</dt>
<dd>{{ vnfpkg.checksum }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ vnfpkg.id }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ vnfpkg.links }}</dd>
<dt>{% trans "Onboarding State" %}</dt>
<dd>{{ vnfpkg.onboardingState }}</dd>
<dt>{% trans "Operational State" %}</dt>
<dd>{{ vnfpkg.operationalState }}</dd>
<dt>{% trans "Software Images" %}</dt>
<dd>{{ vnfpkg.softwareImages }}</dd>
<dt>{% trans "Usage State" %}</dt>
<dd>{{ vnfpkg.usageState }}</dd>
<dt>{% trans "User Defined Data" %}</dt>
<dd>{{ vnfpkg.userDefinedData }}</dd>
<dt>{% trans "VNF Product Name" %}</dt>
<dd>{{ vnfpkg.vnfProductName }}</dd>
<dt>{% trans "VNF Provider" %}</dt>
<dd>{{ vnfpkg.vnfProvider }}</dd>
<dt>{% trans "VNF Software Version" %}</dt>
<dd>{{ vnfpkg.vnfSoftwareVersion }}</dd>
<dt>{% trans "VNFD ID" %}</dt>
<dd>{{ vnfpkg.vnfdId }}</dd>
<dt>{% trans "VNFD Version" %}</dt>
<dd>{{ vnfpkg.vnfdVersion }}</dd>
</dl>
</div>

View File

@ -0,0 +1,31 @@
# Copyright (C) 2024 Fujitsu
# 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 re_path
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnfpackages \
import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^(?P<id>[^/]+)/fetch/$', views.fetch_vnf_package,
name='fetch'),
re_path(r'^uploadvnfpkg$', views.UploadVnfPackageView.as_view(),
name='uploadvnfpkg'),
re_path(r'^(?P<id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
re_path(r'^(?P<id>[^/]+)/updatevnfpkg/$',
views.UpdateVnfPackageView.as_view(), name='updatevnfpkg'),
]

View File

@ -0,0 +1,121 @@
# Copyright (C) 2024 Fujitsu
# 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 import http
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_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.vnfpackages \
import forms as project_forms
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnfpackages \
import tabs as vnfpkg_tabs
def fetch_vnf_package(request, id):
try:
file = tacker_api.tacker.fetch_vnf_package(request, id)
except Exception:
exceptions.handle(request,
_('Failed to fetch VNF Package. (id: %s)') % id,
redirect="horizon:nfv:vnfpackages:index")
response = http.HttpResponse(content_type='application/zip')
response.write(file)
response['Content-Disposition'] = (
'attachment; filename="package-%s.zip"' % id)
response['Content-Length'] = len(file)
return response
class IndexView(tabs.TabbedTableView):
tab_group_class = vnfpkg_tabs.VnfPackageTabs
template_name = 'nfv/vnfpackages/index.html'
class UploadVnfPackageView(forms.ModalFormView):
form_class = project_forms.UploadVnfPackage
template_name = 'nfv/vnfpackages/upload_vnfpkg.html'
success_url = reverse_lazy("horizon:nfv:vnfpackages:index")
modal_id = "add_service_modal"
modal_header = _("Upload VNF Package")
submit_label = _("Upload VNF Package")
submit_url = "horizon:nfv:vnfpackages:uploadvnfpkg"
def get_context_data(self, **kwargs):
context = super(UploadVnfPackageView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url)
return context
class UpdateVnfPackageView(forms.ModalFormView):
form_class = project_forms.UpdateVnfPackage
template_name = 'nfv/vnfpackages/update_vnfpkg.html'
success_url = reverse_lazy("horizon:nfv:vnfpackages:index")
modal_id = "add_service_modal"
modal_header = _("Update VNF Package Info")
submit_label = _("Update VNF Package Info")
submit_url = "horizon:nfv:vnfpackages:updatevnfpkg"
def get_context_data(self, **kwargs):
context = super(UpdateVnfPackageView, self).get_context_data(**kwargs)
context['id'] = self.kwargs['id']
context['submit_url'] = reverse(self.submit_url,
kwargs={'id': self.kwargs['id']})
return context
class DetailView(tabs.TabView):
tab_group_class = vnfpkg_tabs.VnfPackageDetailTabs
template_name = 'nfv/vnfpackages/detail.html'
redirect_url = 'horizon:nfv:vnfpackages:index'
page_title = _("VNF Package Details: {{ id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
vnfpkg = self.get_data()
context['vnfpkg'] = vnfpkg
context['id'] = kwargs['id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
vnfpkg_id = self.kwargs.get('id', None)
try:
vnfpkg = tacker_api.tacker.get_vnf_package(self.request, vnfpkg_id)
vnfpkg['links'] = vnfpkg.get('_links', '')
return vnfpkg
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(
self.request,
_('Failed to get VNF Package. (id: %s)') % vnfpkg_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
vnfpkg = self.get_data()
return self.tab_group_class(request, vnfpkg=vnfpkg, **kwargs)

View File

@ -0,0 +1,102 @@
# Copyright (C) 2024 Fujitsu
# 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.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from tacker_horizon.openstack_dashboard import api
class CreatePmJob(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
help_text=_("parameter file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-file': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(CreatePmJob, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(CreatePmJob, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
param = data['param']
pmjob = api.tacker.create_pm_job(request, param)
messages.success(request,
_('Create PM Job. (id: %s)') %
pmjob['id'])
return True
except Exception:
msg = _('Failed to create PM Job.')
exceptions.handle(request, msg)
return False
class UpdatePmJob(forms.SelfHandlingForm):
param_file = forms.FileField(
label=_("Param File"),
help_text=_("parameter file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-file': _('Param File')}))
def __init__(self, request, *args, **kwargs):
super(UpdatePmJob, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(UpdatePmJob, self).clean()
try:
param_str = self.files['param_file'].read()
param = json.loads(param_str)
data['param'] = param
except Exception as e:
msg = _('Failed to read file: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
param = data['param']
job_id = request.resolver_match.kwargs['id']
api.tacker.update_pm_job(request, job_id, param)
messages.success(request,
_('Update PM Job. (id: %s)') % job_id)
return True
except Exception:
msg = _('Failed to update PM Job. (id: %s)') % job_id
exceptions.handle(request, msg)
return False

View File

@ -0,0 +1,29 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
import horizon
from tacker_horizon.openstack_dashboard.dashboards.nfv import dashboard
class VnfPmJob(horizon.Panel):
name = _("PM Job")
slug = "vnfpmjob"
dashboard.Nfv.register(VnfPmJob)

View File

@ -0,0 +1,74 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from openstack_dashboard import policy
from tacker_horizon.openstack_dashboard import api
class DeletePmJob(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete PM Job",
"Delete PM Jobs",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Delete PM Job",
"Delete PM Jobs",
count
)
def action(self, request, obj_id):
api.tacker.delete_pm_job(request, obj_id)
class CreatePmJob(tables.LinkAction):
name = "createpmjob"
verbose_name = _("Create PM Job")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:nfv:vnfpmjob:createpmjob"
class UpdatePmJob(tables.LinkAction):
name = "updatepmjob"
verbose_name = _("Update PM Job")
url = "horizon:nfv:vnfpmjob:updatepmjob"
classes = ("ajax-modal",)
class VnfPmJobTable(tables.DataTable):
id = tables.Column('id', link="horizon:nfv:vnfpmjob:detail",
verbose_name=_("ID"))
object_type = tables.Column('object_type', verbose_name=_("Object Type"))
links = tables.Column('links', verbose_name=_("Links"))
class Meta(object):
name = "pmjob"
verbose_name = _("PM Job")
pagination_param = 'vnfpmjob_marker'
prev_pagination_param = 'prev_vnfpmjob_marker'
table_actions = (CreatePmJob, DeletePmJob, tables.FilterAction,)
row_actions = (UpdatePmJob, DeletePmJob,)

View File

@ -0,0 +1,122 @@
# Copyright (C) 2024 Fujitsu
# 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.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon import utils as horizon_utils
from tacker_horizon.openstack_dashboard import api
from tacker_horizon.openstack_dashboard.dashboards.nfv.vnfpmjob \
import tables
class VnfPmJobItem(object):
def __init__(self, job_id, links, object_type):
self.id = job_id
self.name = job_id
self.links = links
self.object_type = object_type
class VnfPmJobTab(tabs.TableTab):
name = _("VNFPMJob Tab")
slug = "vnfpmjob_tab"
table_classes = (tables.VnfPmJobTable,)
template_name = "horizon/common/_detail_table.html"
preload = False
def has_prev_data(self, table):
return self._has_prev
def has_more_data(self, table):
return self._has_more
def get_pmjob_data(self):
try:
self._has_prev = False
page_size = horizon_utils.functions.get_page_size(self.request)
marker = self.request.GET.get("vnfpmjob_marker", None)
prev_marker = self.request.GET.get("prev_vnfpmjob_marker", None)
pmjobs = api.tacker.list_pm_jobs(self.request)
if marker is not None or prev_marker is not None:
for i, pmjob in enumerate(pmjobs):
if pmjob["id"] == marker and i < len(pmjobs) - 1:
pmjobs = pmjobs[i + 1:]
self._has_prev = True
break
if pmjob["id"] == prev_marker and i > page_size:
pmjobs = pmjobs[i - page_size:]
self._has_prev = True
break
if len(pmjobs) > page_size:
self._has_more = True
else:
self._has_more = False
rows = []
for i, pmjob in enumerate(pmjobs):
if i >= page_size:
break
item = VnfPmJobItem(pmjob['id'],
pmjob['_links'],
pmjob['objectType'])
rows.append(item)
return rows
except Exception:
self._has_more = False
error_message = _('Failed to get PM Jobs.')
exceptions.handle(self.request, error_message)
return []
class VnfPmJobTabs(tabs.TabGroup):
slug = "vnfpmjob_tabs"
tabs = (VnfPmJobTab,)
sticky = True
class VnfPmJobDetailTab(tabs.Tab):
name = _("VNF PM Job")
slug = "vnfpmjob_detail_tab"
template_name = "nfv/vnfpmjob/pmjob_detail.html"
def get_context_data(self, request):
return {'pmjob': self.tab_group.kwargs['pmjob']}
class VnfPmJobDetailTabs(tabs.TabGroup):
slug = "vnfpmjob_detail_tabs"
tabs = (VnfPmJobDetailTab,)
sticky = True
class VnfPmJobReportDetailTab(tabs.Tab):
name = _("PM Job Reports")
slug = "vnfpmjob_report_detail_tab"
template_name = "nfv/vnfpmjob/report_detail.html"
def get_context_data(self, request):
return {'report': self.tab_group.kwargs['report']}
class VnfPmJobReportDetailTabs(tabs.TabGroup):
slug = "vnfpmjob_report_detail_tabs"
tabs = (VnfPmJobReportDetailTab,)
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 "Create a PM Job." %}</p>
{% endblock %}

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 "Update a PM Job." %}</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create PM Job" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create PM Job") %}
{% endblock page_header %}
{% block main %}
{% include 'nfv/vnfpmjob/_create_pmjob.html' %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "PM Job Detail" %}{% 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,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "PM Job" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("PM Job") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,57 @@
{% load i18n %}
<style>
table {
border-collapse: collapse;
width: 100%;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "ID" %}</dt>
<dd>{{ pmjob.id }}</dd>
<dt>{% trans "Links" %}</dt>
<dd>{{ pmjob.links }}</dd>
<dt>{% trans "Callback Uri" %}</dt>
<dd>{{ pmjob.callback_uri }}</dd>
<dt>{% trans "Criteria" %}</dt>
<dd>{{ pmjob.criteria }}</dd>
<dt>{% trans "Object Instance IDs" %}</dt>
<dd>{{ pmjob.object_instance_ids }}</dd>
<dt>{% trans "Object Type" %}</dt>
<dd>{{ pmjob.object_type }}</dd>
<dt>{% trans "Sub Object Instance IDs" %}</dt>
<dd>{{ pmjob.sub_object_instance_ids }}</dd>
<dt>{% trans "Reports" %}</dt>
<dd>
<table style="width:100%">
<tr>
<th><b>{% trans "readyTime" %}<br/></b></th>
<th><b>{% trans "href" %}<br/></b></th>
</tr>
{% for report in pmjob.reports %}
<tr>
<td>{{ report.readyTime }}</td>
<td>
{% if report.href %}
{% if report.id and report.reportId %}
<a href="{% url 'horizon:nfv:vnfpmjob:reportdetail' report.id report.reportId %}">{{ report.href }}</a>
{% else %}
{% trans report.href %}
{% endif %}
{% else %}
{% trans "-" %}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</dd>
</dl>
</div>

View File

@ -0,0 +1,39 @@
{% load i18n %}
<style>
table {
border-collapse: collapse;
width: 100%;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Entries" %}</dt>
<dd>
<table style="width:100%">
<tr>
<th><b>{% trans "objectType" %}<br/></b></th>
<th><b>{% trans "objectInstanceId" %}<br/></b></th>
<th><b>{% trans "subObjectInstanceId" %}<br/></b></th>
<th><b>{% trans "performanceMetric" %}<br/></b></th>
<th><b>{% trans "performanceValues" %}<br/></b></th>
</tr>
{% for entrie in report.entries %}
<tr>
<td>{{ entrie.objectType }}</td>
<td>{{ entrie.objectInstanceId }}</td>
<td>{{ entrie.subObjectInstanceId }}</td>
<td>{{ entrie.performanceMetric }}</td>
<td>{{ entrie.performanceValues }}</td>
</tr>
{% endfor %}
</table>
</dd>
</dl>
</div>

Some files were not shown because too many files have changed in this diff Show More