Update APMEC Horizon code

This commit is contained in:
Tung Doan 2018-05-07 14:02:57 +02:00
commit d7d59584e7
94 changed files with 4059 additions and 0 deletions

16
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,16 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/apmec

19
HACKING.rst Normal file
View File

@ -0,0 +1,19 @@
Apmec Style Commandments
=========================
- Step 1: Read the OpenStack Style Commandments
http://docs.openstack.org/developer/hacking/
- Step 2: Read on
Apmec Specific Commandments
----------------------------
- [N320] Validate that LOG messages, except debug ones, have translations
Creating Unit Tests
-------------------
For every new feature, unit tests should be created that both test and
(implicitly) document the usage of said feature. If submitting a patch for a
bug that had no unit test, a new passing unit test should be added. If a
submitted bug fix does have a unit test, be sure to add a new one that fails
without the patch and passes with the patch.

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

12
MANIFEST.in Normal file
View File

@ -0,0 +1,12 @@
include AUTHORS
include ChangeLog
include LICENSE
include manage.py
include README.rst
recursive-include apmec_horizon *.html *.css *.js
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

1
README.md Normal file
View File

@ -0,0 +1 @@
# apmec-gui

44
README.rst Normal file
View File

@ -0,0 +1,44 @@
========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/apmec-horizon.svg
:target: http://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
Apmec Horizon UI
=================
Horizon UI for Apmec MEA Manager
Installation
============
1. Install module
::
sudo python setup.py install
2. Copy files to Horizon tree
::
cp apmec_horizon/enabled/* /opt/stack/horizon/openstack_dashboard/enabled/
3. Restart the apache webserver
::
sudo service apache2 restart
More Information
================
Apmec Wiki:
https://wiki.openstack.org/wiki/Apmec

16
apmec_horizon/__init__.py Normal file
View File

@ -0,0 +1,16 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# 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__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,19 @@
# Copyright 2017 99Cloud Inc.
#
# 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.
DASHBOARD = 'mec'
DISABLED = False
ADD_INSTALLED_APPS = [
'apmec_horizon.openstack_dashboard.dashboards.mec',
]

View File

View File

@ -0,0 +1,33 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# 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.
"""
Methods and interface objects used to interact with external APIs.
API method calls return objects that are in many cases objects with
attributes that are direct maps to the data returned from the API http call.
Unfortunately, these objects are also often constructed dynamically, making
it difficult to know what data is available from the API object. Because of
this, all API calls should wrap their returned object in one defined here,
using only explicitly defined attributes and/or methods.
In other words, Horizon developers not working on openstack_dashboard.api
shouldn't need to understand the finer details of APIs for
Keystone/Nova/Glance/Swift et. al.
"""
from apmec_horizon.openstack_dashboard.api import apmec
__all__ = [
"apmec",
]

View File

@ -0,0 +1,160 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# 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 __future__ import absolute_import
from django.conf import settings
from oslo_log import log as logging
from apmecclient.v1_0 import client as apmec_client
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
@memoized
def apmecclient(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
c = apmec_client.Client(
token=request.user.token.id,
auth_url=base.url_for(request, 'identity'),
endpoint_url=base.url_for(request, 'mec-orchestration'),
insecure=insecure, ca_cert=cacert)
return c
def mea_list(request, **params):
LOG.debug("mea_list(): params=%s", params)
meas = apmecclient(request).list_meas(**params).get('meas')
return meas
def mead_list(request, **params):
LOG.debug("mead_list(): params=%s", params)
meads = apmecclient(request).list_meads(**params).get('meads')
return meads
def create_mead(request, tosca_body=None, **params):
LOG.debug("create_mead(): params=%s", params)
mead_instance = apmecclient(request).create_mead(body=tosca_body)
return mead_instance
def create_mea(request, mea_arg, **params):
LOG.debug("create_mea(): mea_arg=%s", str(mea_arg))
mea_instance = apmecclient(request).create_mea(body=mea_arg)
return mea_instance
def get_mead(request, mead_id):
LOG.debug("mead_get(): mead_id=%s", str(mead_id))
mead = apmecclient(request).show_mead(mead_id)
return mead
def get_mea(request, mea_id):
LOG.debug("mea_get(): mea_id=%s", str(mea_id))
mea_instance = apmecclient(request).show_mea(mea_id)
return mea_instance
def delete_mea(request, mea_id):
LOG.debug("delete_mea():mea_id=%s", str(mea_id))
apmecclient(request).delete_mea(mea_id)
def delete_mead(request, mead_id):
LOG.debug("delete_mead():mead_id=%s", str(mead_id))
apmecclient(request).delete_mead(mead_id)
def create_vim(request, vim_arg):
LOG.debug("create_vim(): vim_arg=%s", str(vim_arg))
vim_instance = apmecclient(request).create_vim(body=vim_arg)
return vim_instance
def get_vim(request, vim_id):
LOG.debug("vim_get(): vim_id=%s", str(vim_id))
vim_instance = apmecclient(request).show_vim(vim_id)
return vim_instance
def delete_vim(request, vim_id):
LOG.debug("delete_vim():vim_id=%s", str(vim_id))
apmecclient(request).delete_vim(vim_id)
def vim_list(request, **params):
LOG.debug("vim_list(): params=%s", params)
vims = apmecclient(request).list_vims(**params).get('vims')
return vims
def events_list(request, resource_id):
params = {'resource_id': resource_id}
events = apmecclient(request).list_events(**params).get('events')
LOG.debug("events_list() params=%s events=%s l=%s", params, events,
len(events))
return events
def create_mesd(request, tosca_body=None, **params):
LOG.debug("create_mesd(): params=%s", params)
mesd_instance = apmecclient(request).create_mesd(body=tosca_body)
return mesd_instance
def mesd_list(request, **params):
LOG.debug("mesd_list(): params=%s", params)
mesds = apmecclient(request).list_mesds(**params).get('mesds')
return mesds
def get_mesd(request, mesd_id):
LOG.debug("mesd_get(): mesd_id=%s", str(mesd_id))
mesd = apmecclient(request).show_mesd(mesd_id)
return mesd
def delete_mesd(request, mesd_id):
LOG.debug("delete_mesd():mesd_id=%s", str(mesd_id))
apmecclient(request).delete_mesd(mesd_id)
def get_mes(request, mes_id):
LOG.debug("mes_get(): mes_id=%s", str(mes_id))
mes_instance = apmecclient(request).show_mes(mes_id)
return mes_instance
def delete_mes(request, mes_id):
LOG.debug("delete_mes():mes_id=%s", str(mes_id))
apmecclient(request).delete_ns(mes_id)
def mes_list(request, **params):
LOG.debug("mes_list(): params=%s", params)
mess = apmecclient(request).list_mess(**params).get('mess')
return mess
def create_mes(request, mes_arg, **params):
LOG.debug("create_mes(): mes_arg=%s", str(mes_arg))
mes_instance = apmecclient(request).create_mes(body=mes_arg)
return mes_instance

View File

@ -0,0 +1,40 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
class Meamgroup(horizon.PanelGroup):
slug = "mecgroup"
name = _("MEA Management")
panels = ('meacatalog', 'meamanager',)
class Mecogroup(horizon.PanelGroup):
slug = "meogroup"
name = _("MEC Orchestration")
panels = ('vim', 'mescatalog', 'mesmanager')
class Mec(horizon.Dashboard):
name = _("MEC")
slug = "mec"
panels = (Meamgroup, Mecogroup,) # Add your panels here.
default_panel = 'meacatalog' # Specify the slug of the dashboard's
# default panel.
horizon.register(Mec)

View File

@ -0,0 +1,107 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from apmec_horizon.openstack_dashboard import api
class OnBoardMEA(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
source_type = forms.ChoiceField(
label=_('TOSCA Template Source'),
required=False,
choices=[('file', _('TOSCA Template File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
toscal_file = forms.FileField(
label=_("TOSCA Template File"),
help_text=_("A local TOSCA template file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('TOSCA Template File')}),
required=False)
direct_input = forms.CharField(
label=_('TOSCA YAML'),
help_text=_('The YAML formatted contents of a TOSCA template.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('TOSCA YAML')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(OnBoardMEA, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(OnBoardMEA, self).clean()
# The key can be missing based on particular upload
# conditions. Code defensively for it here...
toscal_file = data.get('toscal_file', None)
toscal_raw = data.get('direct_input', None)
source_type = data.get("source_type")
if source_type == "file" and not toscal_file:
raise ValidationError(
_("No TOSCA template file selected."))
if source_type == "raw" and not toscal_raw:
raise ValidationError(
_("No direct input specified."))
if toscal_file and not toscal_file.name.endswith(('.yaml', '.csar')):
raise ValidationError(_("Only .yaml or .csar file uploads \
are supported"))
try:
if toscal_file:
toscal_str = self.files['toscal_file'].read()
else:
toscal_str = data['direct_input']
# toscal = yaml.loads(toscal_str)
data['tosca'] = toscal_str
except Exception as e:
msg = _('There was a problem loading the namespace: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
toscal = data['tosca']
mead_name = data['name']
mead_description = data['description']
tosca_arg = {'mead': {'name': mead_name,
'description': mead_description,
'attributes': {'mead': toscal}}}
mead_instance = api.apmec.create_mead(request, tosca_arg)
messages.success(request,
_('MEA Catalog entry %s has been created.') %
mead_instance['mead']['name'])
return toscal
except Exception as e:
msg = _('Unable to create TOSCA. %s')
msg %= e.message.split('Failed validating', 1)[0]
exceptions.handle(request, message=msg)
return False

View File

@ -0,0 +1,27 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from apmec_horizon.openstack_dashboard.dashboards.mec import dashboard
class Meacatalog(horizon.Panel):
name = _("MEA Catalog")
slug = "meacatalog"
dashboard.Mec.register(Meacatalog)

View File

@ -0,0 +1,72 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from openstack_dashboard import policy
from apmec_horizon.openstack_dashboard import api
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class DeleteMEAD(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete MEA",
u"Delete MEAs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Delete MEA",
u"Delete MEAs",
count
)
def action(self, request, obj_id):
api.apmec.delete_mead(request, obj_id)
class OnBoardMEA(tables.LinkAction):
name = "onboardmea"
verbose_name = _("Onboard MEA")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:mec:meacatalog:onboardmea"
class MEACatalogTable(tables.DataTable):
name = tables.Column('name',
link="horizon:mec:meacatalog:detail",
verbose_name=_("Name"))
description = tables.Column('description',
verbose_name=_("Description"))
services = tables.Column('service types',
verbose_name=_("Service Types"))
id = tables.Column('id',
verbose_name=_("Catalog Id"))
class Meta(object):
name = "meacatalog"
verbose_name = _("MEACatalog")
table_actions = (OnBoardMEA, DeleteMEAD, MyFilterAction,)

View File

@ -0,0 +1,122 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from apmec_horizon.openstack_dashboard import api
from apmec_horizon.openstack_dashboard.dashboards.mec import utils
from apmec_horizon.openstack_dashboard.dashboards.mec.meacatalog import tables
class MEACatalogItem(object):
def __init__(self, name, description, services, mead_id):
self.id = mead_id
self.name = name
self.description = description
self.services = services
class MEACatalogTab(tabs.TableTab):
name = _("MEACatalog Tab")
slug = "meacatalog_tab"
table_classes = (tables.MEACatalogTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_meacatalog_data(self):
try:
# marker = self.request.GET.get(
# tables.MEACatalogTable._meta.pagination_param, None)
self._has_more = False
catalogs = []
meads = api.apmec.mead_list(self.request,
template_source="onboarded")
for mead in meads:
s_types = [s_type for s_type in mead['service_types']
if s_type != 'mead']
s_types_string = ""
if len(s_types) > 0:
s_types_string = ', '.join(
[str(item) for item in s_types])
item = MEACatalogItem(mead['name'],
mead['description'],
s_types_string, mead['id'])
catalogs.append(item)
return catalogs
except Exception:
self._has_more = False
error_message = _('Unable to get mea catalogs')
exceptions.handle(self.request, error_message)
return []
class MEACatalogTabs(tabs.TabGroup):
slug = "meacatalog_tabs"
tabs = (MEACatalogTab,)
sticky = True
class TemplateTab(tabs.Tab):
name = _("Template")
slug = "template"
template_name = ("mec/meacatalog/template.html")
def get_context_data(self, request):
return {'mead': self.tab_group.kwargs['mead']}
class MEADEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.apmec.events_list(self.request,
self.tab_group.kwargs['mead_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class MEADDetailTabs(tabs.TabGroup):
slug = "MEAD_details"
tabs = (TemplateTab, MEADEventsTab)
sticky = True

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,22 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
class MeacatalogTests(test.TestCase):
# Unit tests for meacatalog.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,24 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from apmec_horizon.openstack_dashboard.dashboards.mec.meacatalog import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^onboardmea', views.OnBoardMEAView.as_view(), name='onboardmea'),
url(r'^(?P<mead_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,112 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
from apmec_horizon.openstack_dashboard import api as apmec_api
from apmec_horizon.openstack_dashboard.dashboards.mec.meacatalog \
import tabs as mec_tabs
from apmec_horizon.openstack_dashboard.dashboards.mec.meacatalog \
import forms as project_forms
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = mec_tabs.MEACatalogTabs
template_name = 'mec/meacatalog/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class OnBoardMEAView(forms.ModalFormView):
form_class = project_forms.OnBoardMEA
template_name = 'mec/meacatalog/onboardmea.html'
success_url = reverse_lazy("horizon:mec:meacatalog:index")
modal_id = "onboardmea_modal"
modal_header = _("OnBoard MEA")
submit_label = _("OnBoard MEA")
submit_url = "horizon:mec:meacatalog:onboardmea"
@memoized.memoized_method
def get_object(self):
try:
return api.nova.server_get(self.request,
self.kwargs["instance_id"])
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve instance."))
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(OnBoardMEAView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = mec_tabs.MEADDetailTabs
template_name = 'mec/meacatalog/detail.html'
redirect_url = 'horizon:mec:meacatalog:index'
page_title = _("MEAD Details: {{ mead_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
mead = self.get_data()
context['mead'] = mead
context['mead_id'] = kwargs['mead_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
mead_id = self.kwargs['mead_id']
try:
template = None
mead = apmec_api.apmec.get_mead(self.request, mead_id)
attributes_json = mead['mead']['attributes']
template = attributes_json.get('mead', None)
mead['template'] = template
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'MEAD "%s".') % mead_id,
redirect=redirect)
raise exceptions.Http302(redirect)
return mead
def get_tabs(self, request, *args, **kwargs):
mead = self.get_data()
return self.tab_group_class(request, mead=mead, **kwargs)

View File

@ -0,0 +1,239 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# 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 yaml
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from oslo_log import log as logging
from horizon import exceptions
from horizon import forms
from horizon import messages
from apmec_horizon.openstack_dashboard import api
LOG = logging.getLogger(__name__)
class DeployMEA(forms.SelfHandlingForm):
mea_name = forms.CharField(max_length=255, label=_("MEA Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
mead_id = forms.ChoiceField(label=_("MEA Catalog Name"),
required=False)
template_source = forms.ChoiceField(
label=_('MEAD template Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'template'}))
template_file = forms.FileField(
label=_('MEAD template File'),
help_text=_('MEAD template to create MEA'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'template',
'data-template-file': _('TOSCA Template File')}),
required=False)
template_input = forms.CharField(
label=_('MEAD template'),
help_text=_('The YAML formatted contents of MEAD template.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'template',
'data-template-raw': _('MEAD template')}),
required=False)
vim_id = forms.ChoiceField(label=_("VIM Name"), required=False)
region_name = forms.CharField(label=_("Region Name"), required=False)
source_type = forms.ChoiceField(
label=_('Parameter Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
param_file = forms.FileField(
label=_('Parameter Value File'),
help_text=_('A local Parameter Value file to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('Parameter Value File')}),
required=False)
direct_input = forms.CharField(
label=_('Parameter Value YAML'),
help_text=_('The YAML formatted contents of Parameter Values.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('Parameter Values')}),
required=False)
config_type = forms.ChoiceField(
label=_('Configuration Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'config'}))
config_file = forms.FileField(
label=_('Configuration Value File'),
help_text=_('MEA Configuration file with YAML '
'formatted contents to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-file': _('Configuration Value File')}),
required=False)
config_input = forms.CharField(
label=_('Configuration Value YAML'),
help_text=_('YAML formatted MEA configuration text.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-raw': _('Configuration Values')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(DeployMEA, self).__init__(request, *args, **kwargs)
try:
mead_list = api.apmec.mead_list(request,
template_source='onboarded')
available_choices_mead = [(mea['id'], mea['name']) for mea in
mead_list]
except Exception as e:
available_choices_mead = []
msg = _('Failed to retrieve available MEA Catalog names: %s') % e
LOG.error(msg)
try:
vim_list = api.apmec.vim_list(request)
available_choices_vims = [(vim['id'], vim['name']) for vim in
vim_list]
except Exception as e:
available_choices_vims = []
msg = _('Failed to retrieve available VIM names: %s') % e
LOG.error(msg)
self.fields['mead_id'].choices = [('', _('Select a MEA Catalog Name'))
]+available_choices_mead
self.fields['vim_id'].choices = [('',
_('Select a VIM Name'))
]+available_choices_vims
def clean(self):
data = super(DeployMEA, self).clean()
template_file = data.get('template_file', None)
template_raw = data.get('template_input', None)
if template_raw and template_file:
raise ValidationError(
_("Cannot specify both file and direct input."))
if template_file and not template_file.name.endswith('.yaml'):
raise ValidationError(
_("Please upload .yaml file only."))
if template_file:
data['mead_template'] = yaml.load(template_file,
Loader=yaml.SafeLoader)
elif template_raw:
data['mead_template'] = yaml.load(data['template_input'],
Loader=yaml.SafeLoader)
else:
data['mead_template'] = None
param_file = data.get('param_file', None)
param_raw = data.get('direct_input', None)
if param_raw and param_file:
raise ValidationError(
_("Cannot specify both file and direct input."))
if param_file and not param_file.name.endswith('.yaml'):
raise ValidationError(
_("Please upload .yaml file only."))
if param_file:
data['param_values'] = self.files['param_file'].read()
elif param_raw:
data['param_values'] = data['direct_input']
else:
data['param_values'] = None
config_file = data.get('config_file', None)
config_raw = data.get('config_input', None)
if config_file and config_raw:
raise ValidationError(
_("Cannot specify both file and direct input."))
if config_file and not config_file.name.endswith('.yaml'):
raise ValidationError(_("Only .yaml file uploads supported"))
if config_file:
data['config_values'] = self.files['config_file'].read()
elif config_raw:
data['config_values'] = data['config_input']
else:
data['config_values'] = None
return data
def handle(self, request, data):
try:
mea_name = data['mea_name']
description = data['description']
mead_id = data.get('mead_id')
mead_template = data.get('mead_template')
vim_id = data['vim_id']
region_name = data['region_name']
param_val = data['param_values']
config_val = data['config_values']
if (mead_id == '') and (mead_template is None):
raise ValidationError(_("Both MEAD id and template cannot be "
"empty. Please specify one of them"))
if (mead_id != '') and (mead_template is not None):
raise ValidationError(_("Both MEAD id and template cannot be "
"specified. Please specify any one"))
mea_arg = {'mea': {'mead_id': mead_id, 'name': mea_name,
'description': description,
'vim_id': vim_id,
'mead_template': mead_template}}
if region_name:
mea_arg.setdefault('placement_attr', {})[
region_name] = region_name
mea_attr = mea_arg['mea'].setdefault('attributes', {})
if param_val:
mea_attr['param_values'] = param_val
if config_val:
mea_attr['config'] = config_val
api.apmec.create_mea(request, mea_arg)
messages.success(request,
_('MEA %s create operation initiated.') %
mea_name)
return True
except Exception as e:
exceptions.handle(request,
_('Failed to create MEA: %s') %
e.message)

View File

@ -0,0 +1,27 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from apmec_horizon.openstack_dashboard.dashboards.mec import dashboard
class Meamanager(horizon.Panel):
name = _("MEA Manager")
slug = "meamanager"
dashboard.Mec.register(Meamanager)

View File

@ -0,0 +1,261 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.http import Http404
from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from apmec_horizon.openstack_dashboard import api
from apmecclient.common.exceptions import NotFound
class MEAManagerItem(object):
def __init__(self, name, description, meas, vim, status,
stack_status, stack_id, error_reason):
self.name = name
self.description = description
self.meas = meas
self.vim = vim
self.status = status
self.stack_status = stack_status
self.id = stack_id
self.error_reason = error_reason
class MEAManagerItemList(object):
MEALIST_P = []
@classmethod
def get_obj_given_stack_id(cls, mea_id):
for obj in cls.MEALIST_P:
if obj.id == mea_id:
return obj
@classmethod
def add_item(cls, item):
cls.MEALIST_P.append(item)
@classmethod
def clear_list(cls):
cls.MEALIST_P = []
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class StacksUpdateRow(tables.Row):
ajax = True
def can_be_selected(self, datum):
return datum.status != 'DELETE_COMPLETE'
def get_data(self, request, stack_id):
try:
stack = api.heat.stack_get(request, stack_id)
if stack.stack_status == 'DELETE_COMPLETE':
# returning 404 to the ajax call removes the
# row from the table on the ui
raise Http404
item = MEAManagerItemList.get_obj_given_stack_id(stack_id)
item.status = stack.status
item.stack_status = stack.stack_status
return item
except Http404:
raise
except Exception as e:
messages.error(request, e)
raise
class MEAUpdateRow(tables.Row):
ajax = True
def can_be_selected(self, datum):
return datum.status != 'DELETE_COMPLETE'
def get_data(self, request, mea_id):
try:
# stack = api.heat.stack_get(request, stack_id)
# if stack.stack_status == 'DELETE_COMPLETE':
# returning 404 to the ajax call removes the
# row from the table on the ui
# raise Http404
item = MEAManagerItemList.get_obj_given_stack_id(mea_id)
mea_instance = api.apmec.get_mea(request, mea_id)
if not mea_instance and not item:
# TODO(NAME) - bail with error
return None
if not mea_instance and item:
# API failure, just keep the current state
return item
mea = mea_instance['mea']
try:
mea_services_str = mea['attributes']['service_type']
except KeyError:
mea_services_str = ""
try:
mea_desc_str = mea['description']
except KeyError:
mea_desc_str = ""
vim = mea['placement_attr']['vim_name']
if not item:
# Add an item entry
item = MEAManagerItem(mea['name'], mea_desc_str,
mea_services_str, str(vim),
mea['status'], mea['status'], mea['id'],
mea['error_reason'])
else:
item.description = mea_desc_str
item.meas = mea_services_str
item.status = mea['status']
item.stack_status = mea['status']
return item
except (Http404, NotFound):
raise Http404
except Exception as e:
messages.error(request, e)
raise
class DeleteMEA(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Terminate MEA",
u"Terminate MEAs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Terminate MEA",
u"Terminate MEAs",
count
)
def action(self, request, obj_id):
api.apmec.delete_mea(request, obj_id)
class DeployMEA(tables.LinkAction):
name = "deploymea"
verbose_name = _("Deploy MEA")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:mec:meamanager:deploymea"
class MEAManagerTable(tables.DataTable):
STATUS_CHOICES = (
("ACTIVE", True),
("ERROR", False),
)
STACK_STATUS_DISPLAY_CHOICES = (
("init_in_progress", pgettext_lazy("current status of stack",
u"Init In Progress")),
("init_complete", pgettext_lazy("current status of stack",
u"Init Complete")),
("init_failed", pgettext_lazy("current status of stack",
u"Init Failed")),
("create_in_progress", pgettext_lazy("current status of stack",
u"Create In Progress")),
("create_complete", pgettext_lazy("current status of stack",
u"Create Complete")),
("create_failed", pgettext_lazy("current status of stack",
u"Create Failed")),
("delete_in_progress", pgettext_lazy("current status of stack",
u"Delete In Progress")),
("delete_complete", pgettext_lazy("current status of stack",
u"Delete Complete")),
("delete_failed", pgettext_lazy("current status of stack",
u"Delete Failed")),
("update_in_progress", pgettext_lazy("current status of stack",
u"Update In Progress")),
("update_complete", pgettext_lazy("current status of stack",
u"Update Complete")),
("update_failed", pgettext_lazy("current status of stack",
u"Update Failed")),
("rollback_in_progress", pgettext_lazy("current status of stack",
u"Rollback In Progress")),
("rollback_complete", pgettext_lazy("current status of stack",
u"Rollback Complete")),
("rollback_failed", pgettext_lazy("current status of stack",
u"Rollback Failed")),
("suspend_in_progress", pgettext_lazy("current status of stack",
u"Suspend In Progress")),
("suspend_complete", pgettext_lazy("current status of stack",
u"Suspend Complete")),
("suspend_failed", pgettext_lazy("current status of stack",
u"Suspend Failed")),
("resume_in_progress", pgettext_lazy("current status of stack",
u"Resume In Progress")),
("resume_complete", pgettext_lazy("current status of stack",
u"Resume Complete")),
("resume_failed", pgettext_lazy("current status of stack",
u"Resume Failed")),
("adopt_in_progress", pgettext_lazy("current status of stack",
u"Adopt In Progress")),
("adopt_complete", pgettext_lazy("current status of stack",
u"Adopt Complete")),
("adopt_failed", pgettext_lazy("current status of stack",
u"Adopt Failed")),
("snapshot_in_progress", pgettext_lazy("current status of stack",
u"Snapshot In Progress")),
("snapshot_complete", pgettext_lazy("current status of stack",
u"Snapshot Complete")),
("snapshot_failed", pgettext_lazy("current status of stack",
u"Snapshot Failed")),
("check_in_progress", pgettext_lazy("current status of stack",
u"Check In Progress")),
("check_complete", pgettext_lazy("current status of stack",
u"Check Complete")),
("check_failed", pgettext_lazy("current status of stack",
u"Check Failed")),
)
name = tables.Column("name",
link="horizon:mec:meamanager:detail",
verbose_name=_("MEA Name"))
description = tables.Column("description",
verbose_name=_("Description"))
meas = tables.Column("meas",
verbose_name=_("Deployed Services"))
vim = tables.Column("vim", verbose_name=_("VIM"))
status = tables.Column("status",
hidden=True,
status=True,
status_choices=STATUS_CHOICES)
stack_status = tables.Column("stack_status",
verbose_name=_("Status"),
display_choices=STACK_STATUS_DISPLAY_CHOICES)
error_reason = tables.Column("error_reason",
verbose_name=_("Error Reason"))
class Meta(object):
name = "meamanager"
verbose_name = _("MEAManager")
status_columns = ["status", ]
row_class = MEAUpdateRow
table_actions = (DeployMEA, DeleteMEA, MyFilterAction,)

View File

@ -0,0 +1,124 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from apmec_horizon.openstack_dashboard import api
from apmec_horizon.openstack_dashboard.dashboards.mec import utils
from apmec_horizon.openstack_dashboard.dashboards.mec.meamanager import tables
class MEAManagerTab(tabs.TableTab):
name = _("MEAManager Tab")
slug = "meamanager_tab"
table_classes = (tables.MEAManagerTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_meamanager_data(self):
try:
# marker = self.request.GET.get(
# tables.MEAManagerTable._meta.pagination_param, None)
# instances, self._has_more = api.nova.server_list(
# self.request,
# search_opts={'marker': marker, 'paginate': True})
self._has_more = True
tables.MEAManagerItemList.clear_list()
meas = api.apmec.mea_list(self.request)
for mea in meas:
try:
mea_services_str = mea['attributes']['service_type']
except KeyError:
mea_services_str = ""
try:
mea_desc_str = mea['description']
except KeyError:
mea_desc_str = ""
vim = mea['placement_attr']['vim_name']
obj = tables.MEAManagerItem(
mea['name'],
mea_desc_str,
mea_services_str,
vim,
mea['status'],
mea['status'],
mea['id'],
mea['error_reason'])
tables.MEAManagerItemList.add_item(obj)
return tables.MEAManagerItemList.MEALIST_P
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class MEAManagerTabs(tabs.TabGroup):
slug = "meamanager_tabs"
tabs = (MEAManagerTab,)
sticky = True
class VDUDetailTab(tabs.Tab):
name = _("VDU Detail")
slug = "VDU_Details"
template_name = "mec/meamanager/vdu_details.html"
def get_context_data(self, request):
return {'mea': self.tab_group.kwargs['mea']}
class MEAEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.apmec.events_list(self.request,
self.tab_group.kwargs['mea_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class MEADetailsTabs(tabs.TabGroup):
slug = "MEA_details"
tabs = (VDUDetailTab, MEAEventsTab)
sticky = True

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,59 @@
{% load i18n %}
<head>
</head>
<body>
<b><h4>{% trans "MEA information" %}<br/></b></h4>
<hr class="header_rule">
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ mea.mea.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ mea.mea.id }}</dd>
<dt>{% trans "Tenant ID" %}</dt>
<dd>{{ mea.mea.tenant_id }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ mea.mea.description }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ mea.mea.status|title }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ mea.mea.created_at|parse_isotime }}</dd>
<dt>{% trans "Updated" %}</dt>
<dd>{{ mea.mea.updated_at|parse_isotime }}</dd>
<dt>{% trans "Stack ID" %}</dt>
<dd>{{ mea.mea.instance_id }}</dd>
<dt>{% trans "MEAD ID" %}</dt>
<dd>{{ mea.mea.mead_id }}</dd>
<dt>{% trans "VIM ID" %}</dt>
<dd>{{ mea.mea.vim_id }}</dd>
<dt>{% trans "Error reason" %}</dt>
<dd>{{ mea.mea.error_reason }}</dd>
</dl>
<h4>{% trans "Mgmt IP Addresses" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for key, value in mea.mea.mgmt_url.items %}
<dt>{{ key|title }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
<h4>{% trans "Placement Attributes" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for key, value in mea.mea.placement_attr.items %}
<dt>{{ key|title }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
<h4>{% trans "Attributes" %}</h4>
<hr class="header_rule">
<pre style="white-space:pre-line;">
{{ mea.mea.attributes }}
</pre>
</div>
</body>

View File

@ -0,0 +1,22 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
class MeamanagerTests(test.TestCase):
# Unit tests for meamanager.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,24 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from apmec_horizon.openstack_dashboard.dashboards.mec.meamanager import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^deploymea$', views.DeployMEAView.as_view(), name='deploymea'),
url(r'^(?P<mea_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,114 @@
# Copyright 2015 Brocade Communications System, Inc.
#
# 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.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from oslo_log import log as logging
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from apmec_horizon.openstack_dashboard import api as apmec_api
from apmec_horizon.openstack_dashboard.dashboards.mec.meamanager \
import forms as project_forms
from apmec_horizon.openstack_dashboard.dashboards.mec.meamanager \
import tabs as mec_tabs
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = mec_tabs.MEAManagerTabs
template_name = 'mec/meamanager/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class DeployMEAView(forms.ModalFormView):
form_class = project_forms.DeployMEA
template_name = 'mec/meamanager/deploy_mea.html'
success_url = reverse_lazy("horizon:mec:meamanager:index")
modal_id = "deploy_mea_modal"
modal_header = _("Deploy MEA")
submit_label = _("Deploy MEA")
submit_url = "horizon:mec:meamanager:deploymea"
# @memoized.memoized_method
# def get_object(self):
# try:
# return api.nova.server_get(self.request,
# self.kwargs["instance_id"])
# except Exception:
# exceptions.handle(self.request,
# _("Unable to retrieve instance."))
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(DeployMEAView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = mec_tabs.MEADetailsTabs
template_name = 'mec/meamanager/detail.html'
redirect_url = 'horizon:mec:meamanager:index'
page_title = _("MEA Details: {{ mea_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
mea = self.get_data()
context['mea'] = mea
context['mea_id'] = kwargs['mea_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
mea_id = self.kwargs['mea_id']
try:
mea = apmec_api.apmec.get_mea(self.request, mea_id)
mea["mea"]["mgmt_url"] = json.loads(mea["mea"]["mgmt_url"]) if \
mea["mea"]["mgmt_url"] else None
return mea
except ValueError as e:
msg = _('Cannot decode json : %s') % e
LOG.error(msg)
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'MEA "%s".') % mea_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
mea = self.get_data()
return self.tab_group_class(request, mea=mea, **kwargs)

View File

@ -0,0 +1,105 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from apmec_horizon.openstack_dashboard import api
class OnBoardMES(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
source_type = forms.ChoiceField(
label=_('TOSCA Template Source'),
required=False,
choices=[('file', _('TOSCA Template File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
toscal_file = forms.FileField(
label=_("TOSCA Template File"),
help_text=_("A local TOSCA template file to upload."),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('TOSCA Template File')}),
required=False)
direct_input = forms.CharField(
label=_('TOSCA YAML'),
help_text=_('The YAML formatted contents of a TOSCA template.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('TOSCA YAML')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(OnBoardMES, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(OnBoardMES, self).clean()
# The key can be missing based on particular upload
# conditions. Code defensively for it here...
toscal_file = data.get('toscal_file', None)
toscal_raw = data.get('direct_input', None)
source_type = data.get("source_type")
if source_type == "file" and not toscal_file:
raise ValidationError(
_("No TOSCA template file selected."))
if source_type == "raw" and not toscal_raw:
raise ValidationError(
_("No direct input specified."))
if toscal_file and not toscal_file.name.endswith(('.yaml', '.csar')):
raise ValidationError(_("Only .yaml or .csar file uploads \
are supported"))
try:
if toscal_file:
toscal_str = self.files['toscal_file'].read()
else:
toscal_str = data['direct_input']
# toscal = yaml.loads(toscal_str)
data['tosca'] = toscal_str
except Exception as e:
msg = _('There was a problem loading the namespace: %s.') % e
raise forms.ValidationError(msg)
return data
def handle(self, request, data):
try:
toscal = data['tosca']
mesd_name = data['name']
mesd_description = data['description']
tosca_arg = {'mesd': {'name': mesd_name,
'description': mesd_description,
'attributes': {'mesd': toscal}}}
mesd_instance = api.apmec.create_mesd(request, tosca_arg)
messages.success(request,
_('MES Catalog entry %s has been created.') %
mesd_instance['mesd']['name'])
return toscal
except Exception as e:
msg = _('Unable to create TOSCA. %s')
msg %= e.message.split('Failed validating', 1)[0]
exceptions.handle(request, message=msg)
return False

View File

@ -0,0 +1,25 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from apmec_horizon.openstack_dashboard.dashboards.mec import dashboard
class Mescatalog(horizon.Panel):
name = _("MES Catalog")
slug = "mescatalog"
dashboard.Mec.register(Mescatalog)

View File

@ -0,0 +1,68 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from openstack_dashboard import policy
from apmec_horizon.openstack_dashboard import api
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class DeleteMESD(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete MES",
u"Delete MESs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Delete MES",
u"Delete MESs",
count
)
def action(self, request, obj_id):
api.apmec.delete_mesd(request, obj_id)
class OnBoardMES(tables.LinkAction):
name = "onboardmes"
verbose_name = _("Onboard MES")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:mec:mescatalog:onboardmes"
class MESCatalogTable(tables.DataTable):
name = tables.Column('name',
link="horizon:mec:mescatalog:detail",
verbose_name=_("Name"))
description = tables.Column('description',
verbose_name=_("Description"))
id = tables.Column('id',
verbose_name=_("Catalog Id"))
class Meta(object):
name = "mescatalog"
verbose_name = _("MESCatalog")
table_actions = (OnBoardMES, DeleteMESD, MyFilterAction,)

View File

@ -0,0 +1,109 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from apmec_horizon.openstack_dashboard import api
from apmec_horizon.openstack_dashboard.dashboards.mec.mescatalog import tables
from apmec_horizon.openstack_dashboard.dashboards.mec import utils
class MESCatalogItem(object):
def __init__(self, name, description, mesd_id):
self.id = mesd_id
self.name = name
self.description = description
class MESCatalogTab(tabs.TableTab):
name = _("MESCatalog Tab")
slug = "mescatalog_tab"
table_classes = (tables.MESCatalogTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_mescatalog_data(self):
try:
self._has_more = False
instances = []
mesds = api.apmec.mesd_list(self.request)
for mesd in mesds:
item = MESCatalogItem(mesd['name'],
mesd['description'],
mesd['id'])
instances.append(item)
return instances
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class MESCatalogTabs(tabs.TabGroup):
slug = "mescatalog_tabs"
tabs = (MESCatalogTab,)
sticky = True
class TemplateTab(tabs.Tab):
name = _("Template")
slug = "template"
template_name = ("mec/mescatalog/template.html")
def get_context_data(self, request):
return {'mesd': self.tab_group.kwargs['mesd']}
class MESDEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.apmec.events_list(self.request,
self.tab_group.kwargs['mesd_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class MESDDetailTabs(tabs.TabGroup):
slug = "MESD_details"
tabs = (TemplateTab, MESDEventsTab)
sticky = True

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
class MescatalogTests(test.TestCase):
# Unit tests for mescatalog.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,22 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from apmec_horizon.openstack_dashboard.dashboards.mec.mescatalog import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^onboardmes', views.OnBoardMESView.as_view(), name='onboardmes'),
url(r'^(?P<mesd_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,110 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
from apmec_horizon.openstack_dashboard import api as apmec_api
from apmec_horizon.openstack_dashboard.dashboards.mec.mescatalog \
import tabs as mec_tabs
from apmec_horizon.openstack_dashboard.dashboards.mec.mescatalog \
import forms as project_forms
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = mec_tabs.MESCatalogTabs
template_name = 'mec/mescatalog/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class OnBoardMESView(forms.ModalFormView):
form_class = project_forms.OnBoardMES
template_name = 'mec/mescatalog/onboardmes.html'
success_url = reverse_lazy("horizon:mec:mescatalog:index")
modal_id = "onboardmes_modal"
modal_header = _("OnBoard MES")
submit_label = _("OnBoard MES")
submit_url = "horizon:mec:mescatalog:onboardmes"
@memoized.memoized_method
def get_object(self):
try:
return api.nova.server_get(self.request,
self.kwargs["instance_id"])
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve instance."))
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(OnBoardMESView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = mec_tabs.MESDDetailTabs
template_name = 'mec/mescatalog/detail.html'
redirect_url = 'horizon:mec:mescatalog:index'
page_title = _("MESD Details: {{ mesd_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
mesd = self.get_data()
context['mesd'] = mesd
context['mesd_id'] = kwargs['mesd_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
mesd_id = self.kwargs['mesd_id']
try:
template = None
mesd = apmec_api.apmec.get_mesd(self.request, mesd_id)
attributes_json = mesd['mesd']['attributes']
template = attributes_json.get('mesd', None)
mesd['template'] = template
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'MESD "%s".') % mesd_id,
redirect=redirect)
raise exceptions.Http302(redirect)
return mesd
def get_tabs(self, request, *args, **kwargs):
mesd = self.get_data()
return self.tab_group_class(request, mesd=mesd, **kwargs)

View File

@ -0,0 +1,176 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from oslo_log import log as logging
from horizon import exceptions
from horizon import forms
from horizon import messages
from apmec_horizon.openstack_dashboard import api
LOG = logging.getLogger(__name__)
class DeployMES(forms.SelfHandlingForm):
mes_name = forms.CharField(max_length=255, label=_("MES Name"))
description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
mesd_id = forms.ChoiceField(label=_("MES Catalog Name"))
vim_id = forms.ChoiceField(label=_("VIM Name"), required=False)
source_type = forms.ChoiceField(
label=_('Parameter Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'source'}))
param_file = forms.FileField(
label=_('Parameter Value File'),
help_text=_('A local Parameter Value file to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-file': _('Parameter Value File')}),
required=False)
direct_input = forms.CharField(
label=_('Parameter Value YAML'),
help_text=_('The YAML formatted contents of Parameter Values.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'source',
'data-source-raw': _('Parameter Values')}),
required=False)
config_type = forms.ChoiceField(
label=_('Configuration Value Source'),
required=False,
choices=[('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable', 'data-slug': 'config'}))
config_file = forms.FileField(
label=_('Configuration Value File'),
help_text=_('MES Configuration file with YAML '
'formatted contents to upload.'),
widget=forms.FileInput(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-file': _('Configuration Value File')}),
required=False)
config_input = forms.CharField(
label=_('Configuration Value YAML'),
help_text=_('YAML formatted MES configuration text.'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched', 'data-switch-on': 'config',
'data-config-raw': _('Configuration Values')}),
required=False)
def __init__(self, request, *args, **kwargs):
super(DeployMES, self).__init__(request, *args, **kwargs)
try:
mesd_list = api.apmec.mesd_list(request)
available_choices_mesd = [(mes['id'], mes['name']) for mes in
mesd_list]
except Exception as e:
available_choices_mesd = []
msg = _('Failed to retrieve available MES Catalog names: %s') % e
LOG.error(msg)
try:
vim_list = api.apmec.vim_list(request)
available_choices_vims = [(vim['id'], vim['name']) for vim in
vim_list]
except Exception as e:
available_choices_vims = []
msg = _('Failed to retrieve available VIM names: %s') % e
LOG.error(msg)
self.fields['mesd_id'].choices = [('', _('Select a MES Catalog Name'))
]+available_choices_mesd
self.fields['vim_id'].choices = [('',
_('Select a VIM Name'))
]+available_choices_vims
def clean(self):
data = super(DeployMES, self).clean()
param_file = data.get('param_file', None)
param_raw = data.get('direct_input', None)
if param_raw and param_file:
raise ValidationError(
_("Cannot specify both file and direct input."))
if param_file and not param_file.name.endswith('.yaml'):
raise ValidationError(
_("Please upload .yaml file only."))
if param_file:
data['param_values'] = self.files['param_file'].read()
elif param_raw:
data['param_values'] = data['direct_input']
else:
data['param_values'] = None
config_file = data.get('config_file', None)
config_raw = data.get('config_input', None)
if config_file and config_raw:
raise ValidationError(
_("Cannot specify both file and direct input."))
if config_file and not config_file.name.endswith('.yaml'):
raise ValidationError(_("Only .yaml file uploads supported"))
if config_file:
data['config_values'] = self.files['config_file'].read()
elif config_raw:
data['config_values'] = data['config_input']
else:
data['config_values'] = None
return data
def handle(self, request, data):
try:
mes_name = data['mes_name']
description = data['description']
mesd_id = data['mesd_id']
vim_id = data['vim_id']
param_val = data['param_values']
config_val = data['config_values']
mes_arg = {'mes': {'mesd_id': mesd_id, 'name': mes_name,
'description': description,
'vim_id': vim_id}}
mes_attr = mes_arg['mes'].setdefault('attributes', {})
if param_val:
mes_attr['param_values'] = param_val
if config_val:
mes_attr['config'] = config_val
api.apmec.create_mes(request, mes_arg)
messages.success(request,
_('MES %s create operation initiated.') %
mes_name)
return True
except Exception as e:
exceptions.handle(request,
_('Failed to create MES: %s') %
e.message)

View File

@ -0,0 +1,25 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from apmec_horizon.openstack_dashboard.dashboards.mec import dashboard
class Mesmanager(horizon.Panel):
name = _("MES Manager")
slug = "mesmanager"
dashboard.Mec.register(Mesmanager)

View File

@ -0,0 +1,159 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.http import Http404
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from apmec_horizon.openstack_dashboard import api
from apmecclient.common.exceptions import NotFound
class MESManagerItem(object):
def __init__(self, name, description, vim, status,
mes_id, error_reason):
self.name = name
self.description = description
self.vim = vim
self.status = status
self.id = mes_id
self.error_reason = error_reason
class MESManagerItemList(object):
MESLIST_P = []
@classmethod
def get_obj_given_stack_ids(cls, mes_id):
for obj in cls.MESLIST_P:
if obj.id == mes_id:
return obj
@classmethod
def add_item(cls, item):
cls.MESLIST_P.append(item)
@classmethod
def clear_list(cls):
cls.MESLIST_P = []
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class MESUpdateRow(tables.Row):
ajax = True
def can_be_selected(self, datum):
return datum.status != 'DELETE_COMPLETE'
def get_data(self, request, mes_id):
try:
# stack = api.heat.stack_get(request, stack_id)
# if stack.stack_status == 'DELETE_COMPLETE':
# returning 404 to the ajax call removes the
# row from the table on the ui
# raise Http404
item = MESManagerItemList.get_obj_given_stack_ids(mes_id)
mes_instance = api.apmec.get_mes(request, mes_id)
if not mes_instance and not item:
# TODO(NAME) - bail with error
return None
if not mes_instance and item:
# API failure, just keep the current state
return item
mes = mes_instance['mes']
try:
mes_desc_str = mes['description']
except KeyError:
mes_desc_str = ""
vim = mes['vim_id']
if not item:
# Add an item entry
item = MESManagerItem(mes['name'], mes_desc_str,
str(vim),
mes['status'], mes['id'],
mes['error_reason'])
else:
item.description = mes_desc_str
item.status = mes['status']
item.id = mes['id']
return item
except (Http404, NotFound):
raise Http404
except Exception as e:
messages.error(request, e)
raise
class DeleteMES(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Terminate MES",
u"Terminate MESs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Terminate MES",
u"Terminate MESs",
count
)
def action(self, request, obj_id):
api.apmec.delete_mes(request, obj_id)
class DeployMES(tables.LinkAction):
name = "deploymes"
verbose_name = _("Deploy MES")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:mec:mesmanager:deploymes"
class MESManagerTable(tables.DataTable):
STATUS_CHOICES = (
("ACTIVE", True),
("ERROR", False),
)
name = tables.Column("name",
link="horizon:mec:mesmanager:detail",
verbose_name=_("MES Name"))
description = tables.Column("description",
verbose_name=_("Description"))
vim = tables.Column("vim", verbose_name=_("VIM"))
status = tables.Column("status",
status=True,
status_choices=STATUS_CHOICES)
error_reason = tables.Column("error_reason",
verbose_name=_("Error Reason"))
class Meta(object):
name = "mesmanager"
verbose_name = _("MESManager")
status_columns = ["status", ]
row_class = MESUpdateRow
table_actions = (DeployMES, DeleteMES, MyFilterAction,)

View File

@ -0,0 +1,101 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from apmec_horizon.openstack_dashboard import api
from apmec_horizon.openstack_dashboard.dashboards.mec.mesmanager import tables
from apmec_horizon.openstack_dashboard.dashboards.mec import utils
class MESManagerTab(tabs.TableTab):
name = _("MESManager Tab")
slug = "mesmanager_tab"
table_classes = (tables.MESManagerTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_mesmanager_data(self):
try:
self._has_more = True
tables.MESManagerItemList.clear_list()
mess = api.apmec.mes_list(self.request)
for mes in mess:
try:
mes_desc_str = mes['description']
except KeyError:
mes_desc_str = ""
vim = mes['vim_id']
obj = tables.MESManagerItem(
mes['name'],
mes_desc_str,
vim,
mes['status'],
mes['id'],
mes['error_reason'])
tables.MESManagerItemList.add_item(obj)
return tables.MESManagerItemList.MESLIST_P
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class MESManagerTabs(tabs.TabGroup):
slug = "mesmanager_tabs"
tabs = (MESManagerTab,)
sticky = True
class MESEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.apmec.events_list(self.request,
self.tab_group.kwargs['mes_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class MESDetailsTabs(tabs.TabGroup):
slug = "MES_details"
tabs = (MESEventsTab,)
sticky = True

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,19 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
class MesmanagerTests(test.TestCase):
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,22 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from apmec_horizon.openstack_dashboard.dashboards.mec.mesmanager import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^deploymes$', views.DeployMESView.as_view(), name='deploymes'),
url(r'^(?P<mes_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,99 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from oslo_log import log as logging
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from apmec_horizon.openstack_dashboard import api as apmec_api
from apmec_horizon.openstack_dashboard.dashboards.mec.mesmanager \
import forms as project_forms
from apmec_horizon.openstack_dashboard.dashboards.mec.mesmanager \
import tabs as mec_tabs
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = mec_tabs.MESManagerTabs
template_name = 'mec/mesmanager/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class DeployMESView(forms.ModalFormView):
form_class = project_forms.DeployMES
template_name = 'mec/mesmanager/deploy_mes.html'
success_url = reverse_lazy("horizon:mec:mesmanager:index")
modal_id = "deploy_mes_modal"
modal_header = _("Deploy MES")
submit_label = _("Deploy MES")
submit_url = "horizon:mec:mesmanager:deploymes"
def get_initial(self):
# return {"instance_id": self.kwargs["instance_id"]}
return {}
def get_context_data(self, **kwargs):
context = super(DeployMESView, self).get_context_data(**kwargs)
# instance_id = self.kwargs['instance_id']
# context['instance_id'] = instance_id
# context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = mec_tabs.MESDetailsTabs
template_name = 'mec/mesmanager/detail.html'
redirect_url = 'horizon:mec:mesmanager:index'
page_title = _("MES Details: {{ mes_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
mes = self.get_data()
context['mes'] = mes
context['mes_id'] = kwargs['mes_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
mes_id = self.kwargs['mes_id']
try:
mes = apmec_api.apmec.get_mes(self.request, mes_id)
return mes
except ValueError as e:
msg = _('Cannot decode json : %s') % e
LOG.error(msg)
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'MES "%s".') % mes_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
mes = self.get_data()
return self.tab_group_class(request, mes=mes, **kwargs)

View File

@ -0,0 +1 @@
/* Additional CSS for mec. */

View File

@ -0,0 +1 @@
/* Additional JavaScript for mec. */

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block sidebar %}
{% include 'horizon/common/_sidebar.html' %}
{% endblock %}
{% block main %}
{% include "horizon/_messages.html" %}
{% block mec_main %}{% endblock %}
{% endblock %}

View File

@ -0,0 +1,54 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class EventItem(object):
def __init__(self, id, state, type, timestamp, details):
self.id = id
self.resource_state = state
self.event_type = type
self.timestamp = timestamp
self.event_details = details
class EventItemList(object):
EVTLIST_P = []
@classmethod
def add_item(cls, item):
cls.EVTLIST_P.append(item)
@classmethod
def clear_list(cls):
cls.EVTLIST_P = []
class EventsTable(tables.DataTable):
id = tables.Column('id', verbose_name=_("Event ID"))
resource_state = tables.Column('resource_state',
verbose_name=_("Resource State"))
timestamp = tables.Column('timestamp',
verbose_name=_("Time Since Event"))
event_type = tables.Column("event_type", verbose_name=_("Event Type"))
event_details = tables.Column("event_details",
verbose_name=_("Event Details"))
class Meta(object):
name = "events"

View File

@ -0,0 +1,89 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables
from horizon import exceptions
from horizon import forms
from horizon import messages
from apmec_horizon.openstack_dashboard import api
class RegisterVim(forms.SelfHandlingForm):
vim_name = forms.CharField(max_length=255, label=_("Name"))
vim_description = forms.CharField(widget=forms.widgets.Textarea(
attrs={'rows': 4}),
label=_("Description"),
required=False)
auth_url = forms.URLField(label=_("Auth URL"))
username = forms.CharField(max_length=80, label=_("Username"))
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
project_name = forms.CharField(max_length=80, label=_("Project Name"))
domain_name = forms.CharField(max_length=80, label=_("Domain Name"),
help_text=_('Applicable for OpenStack site '
'running keystone v3. Run '
'openstack domain list from '
'CLI to find domain name'),
required=False)
is_default = forms.BooleanField(
label=_("Default"),
initial=False,
required=False,
widget=forms.CheckboxInput(
attrs={
'class': 'switched',
}
)
)
def __init__(self, request, *args, **kwargs):
super(RegisterVim, self).__init__(request, *args, **kwargs)
def clean(self):
data = super(RegisterVim, self).clean()
return data
@sensitive_variables('data', 'password')
def handle(self, request, data):
try:
vim_name = data['vim_name']
description = data['vim_description']
password = data['password']
username = data['username']
project_name = data['project_name']
is_default = data['is_default']
auth_url = data['auth_url']
vim_type = 'openstack'
domain_name = data['domain_name']
vim_arg = {'vim': {'name': vim_name, 'description': description,
'type': vim_type, 'auth_url': auth_url,
'auth_cred': {'username': username,
'password': password,
'user_domain_name': domain_name},
'vim_project': {'name': project_name,
'project_domain_name':
domain_name},
'is_default': is_default}}
api.apmec.create_vim(request, vim_arg)
messages.success(request,
_('VIM %s create operation initiated.') %
vim_name)
return True
except Exception as e:
exceptions.handle(request,
_('Failed to register VIM: %s') %
e.message)

View File

@ -0,0 +1,27 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from apmec_horizon.openstack_dashboard.dashboards.mec import dashboard
class Vimmanager(horizon.Panel):
name = _("VIM Management")
slug = "vim"
dashboard.Mec.register(Vimmanager)

View File

@ -0,0 +1,72 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from openstack_dashboard import policy
from apmec_horizon.openstack_dashboard import api
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class DeleteVIMLink(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete VIM",
u"Delete VIMs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Delete VIM",
u"Delete VIMs",
count
)
def action(self, request, obj_id):
api.apmec.delete_vim(request, obj_id)
class RegisterVIMLink(tables.LinkAction):
name = "registervim"
verbose_name = _("Register VIM")
classes = ("ajax-modal",)
icon = "plus"
url = "horizon:mec:vim:registervim"
class VIMTable(tables.DataTable):
name = tables.Column('name', verbose_name=_("Name"),
link="horizon:mec:vim:detail",)
description = tables.Column('description', verbose_name=_("Description"))
id = tables.Column('id', verbose_name=_("VIM Id"))
auth_url = tables.Column('auth_url', verbose_name=_("Auth URL"))
regions = tables.Column('regions', verbose_name=_("Regions"))
user = tables.Column('user', verbose_name=_("User"))
project = tables.Column('project', verbose_name=_("Project"))
status = tables.Column('status', verbose_name=_("Status"))
class Meta(object):
name = "vim"
verbose_name = _("VIM")
table_actions = (RegisterVIMLink, DeleteVIMLink, MyFilterAction,)

View File

@ -0,0 +1,120 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from apmec_horizon.openstack_dashboard import api
from apmec_horizon.openstack_dashboard.dashboards.mec import utils # noqa
from apmec_horizon.openstack_dashboard.dashboards.mec.vim import tables
class VIMItem(object):
def __init__(self, name, description, regions, vim_id, auth_url,
user, project, status):
self.id = vim_id
self.name = name
self.description = description
self.regions = regions
self.auth_url = auth_url
self.user = user
self.project = project
self.status = status
class VIMTab(tabs.TableTab):
name = _("VIM Tab")
slug = "vim_tab"
table_classes = (tables.VIMTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_vim_data(self):
try:
self._has_more = False
instances = []
vims = api.apmec.vim_list(self.request)
for vim in vims:
auth_cred = vim['auth_cred']
placement_attr = vim['placement_attr']
vim_regions = ','.join(placement_attr['regions'])
user = auth_cred['username'] if auth_cred[
'username'] else auth_cred['user_id']
project_info = vim['vim_project']
project = project_info['name'] if project_info[
'name'] else project_info['id']
status = vim["status"]
item = VIMItem(name=vim.get('name', ''),
description=vim.get('description', ''),
regions=vim_regions,
vim_id=vim.get('id', ''),
auth_url=vim.get('auth_url', ''),
user=user, project=project, status=status)
instances.append(item)
return instances
except Exception:
self._has_more = False
error_message = _('Unable to fetch vim list')
exceptions.handle(self.request, error_message)
return []
class VIMEventsTab(tabs.TableTab):
name = _("Events Tab")
slug = "events_tab"
table_classes = (utils.EventsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_events_data(self):
try:
self._has_more = True
utils.EventItemList.clear_list()
events = api.apmec.events_list(self.request,
self.tab_group.kwargs['vim_id'])
for event in events:
evt_obj = utils.EventItem(
event['id'], event['resource_state'],
event['event_type'],
event['timestamp'],
event['event_details'])
utils.EventItemList.add_item(evt_obj)
return utils.EventItemList.EVTLIST_P
except Exception as e:
self._has_more = False
error_message = _('Unable to get events %s') % e
exceptions.handle(self.request, error_message)
return []
class VIMTabs(tabs.TabGroup):
slug = "vim_tabs"
tabs = (VIMTab,)
sticky = True
class VIMDetailsTabs(tabs.TabGroup):
slug = "VIM_details"
tabs = (VIMEventsTab,)
sticky = True

View File

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

View File

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

View File

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VIM" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("VIM Management") %}
{% 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 "Register VIM" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Register VIM") %}
{% endblock page_header %}
{% block main %}
{% include 'mec/vim/_registervim.html' %}
{% endblock %}

View File

@ -0,0 +1,22 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test import helpers as test
class VimmanagerTests(test.TestCase):
# Unit tests for vimmanager.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -0,0 +1,24 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from apmec_horizon.openstack_dashboard.dashboards.mec.vim import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^registervim$', views.RegisterVIMView.as_view(), name='registervim'),
url(r'^(?P<vim_id>[^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,89 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from apmec_horizon.openstack_dashboard import api as apmec_api
from apmec_horizon.openstack_dashboard.dashboards.mec.vim \
import forms as project_forms
from apmec_horizon.openstack_dashboard.dashboards.mec.vim \
import tabs as vim_tabs
class IndexView(tabs.TabbedTableView):
# A very simple class-based view...
tab_group_class = vim_tabs.VIMTabs
template_name = 'mec/vim/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class RegisterVIMView(forms.ModalFormView):
form_class = project_forms.RegisterVim
template_name = 'mec/vim/registervim.html'
success_url = reverse_lazy("horizon:mec:vim:index")
modal_id = "add_service_modal"
modal_header = _("Register VIM")
submit_label = _("Register VIM")
submit_url = "horizon:mec:vim:registervim"
def get_context_data(self, **kwargs):
context = super(RegisterVIMView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(self.submit_url)
return context
class DetailView(tabs.TabView):
tab_group_class = vim_tabs.VIMDetailsTabs
template_name = 'mec/vim/detail.html'
redirect_url = 'horizon:mec:vim:index'
page_title = _("VIM Event Details: {{ vim_id }}")
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
vim = self.get_data()
context['vim'] = vim
context['vim_id'] = kwargs['vim_id']
context['url'] = reverse(self.redirect_url)
return context
@memoized.memoized_method
def get_data(self):
vim_id = self.kwargs['vim_id']
try:
vim = apmec_api.apmec.get_vim(self.request, vim_id)
return vim
except Exception:
redirect = reverse(self.redirect_url)
exceptions.handle(self.request,
_('Unable to retrieve details for '
'VIM "%s".') % vim_id,
redirect=redirect)
raise exceptions.Http302(redirect)
def get_tabs(self, request, *args, **kwargs):
vim = self.get_data()
return self.tab_group_class(request, vim=vim, **kwargs)

View File

View File

@ -0,0 +1,58 @@
# Copyright (C) 2015 Yahoo! Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from horizon.test.settings import * # noqa
from horizon.utils import secret_key as secret_key_utils
from openstack_dashboard.test.settings import * # noqa
DEBUG = True
TEMPLATE_DEBUG = DEBUG
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(TEST_DIR, ".."))
MEDIA_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'media'))
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'static'))
STATIC_URL = '/static/'
SECRET_KEY = secret_key_utils.generate_or_read_from_file(
os.path.join(TEST_DIR, '.secret_key_store'))
ROOT_URLCONF = 'apmec_horizon.test.urls'
TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'),
)
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
'django.contrib.humanize',
'django_nose',
'openstack_auth',
'compressor',
'horizon',
'openstack_dashboard',
'apmec_horizon',
)
NOSE_ARGS = ['--nocapture',
'--nologcapture',
'--cover-package=apmec_horizon',
'--cover-inclusive',
'--all-modules']

View File

@ -0,0 +1,21 @@
# Copyright (C) 2015 Yahoo! Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import urls
import openstack_dashboard.urls
urlpatterns = [
urls.url(r'', urls.include(openstack_dashboard.urls))
]

5
babel-django.cfg Normal file
View File

@ -0,0 +1,5 @@
[extractors]
django = django_babel.extract:extract_django
[python: **.py]
[django: **/templates/**.html]

14
babel-djangojs.cfg Normal file
View File

@ -0,0 +1,14 @@
[extractors]
# We use a custom extractor to find translatable strings in AngularJS
# templates. The extractor is included in horizon.utils for now.
# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for
# details on how this works.
angular = horizon.utils.babel_extract_angular:extract_angular
[javascript: **.js]
# We need to look into all static folders for HTML files.
# The **/static ensures that we also search within
# /openstack_dashboard/dashboards/XYZ/static which will ensure
# that plugins are also translated.
[angular: **/static/**.html]

23
manage.py Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
# 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
import sys
from django.core.management import execute_from_command_line
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"openstack_dashboard.settings")
execute_from_command_line(sys.argv)

11
requirements.txt Normal file
View File

@ -0,0 +1,11 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
# Order matters to the pip dependency resolver, so sorting this file
# changes how packages are installed. New dependencies should be
# added in alphabetical order, however, some dependencies may need to
# be installed in a specific order.
#
# PBR should always appear first
oslo.log>=3.30.0 # Apache-2.0
pbr!=2.1.0,>=2.0.0 # Apache-2.0

40
setup.cfg Normal file
View File

@ -0,0 +1,40 @@
[metadata]
name = apmec-horizon
summary = Apmec extension for Horizon
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Developers
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[files]
packages =
apmec_horizon
namespace_packages =
apmec_horizon
[global]
setup-hooks =
pbr.hooks.setup_hook
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
[nosetests]
where = test
verbosity = 2
detailed-errors = 1
cover-package = apmec_horizon

29
setup.py Normal file
View File

@ -0,0 +1,29 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

28
test-requirements.txt Normal file
View File

@ -0,0 +1,28 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
# Order matters to the pip dependency resolver, so sorting this file
# changes how packages are installed. New dependencies should be
# added in alphabetical order, however, some dependencies may need to
# be installed in a specific order.
#
# Hacking should appear first in case something else depends on pep8
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
#
coverage!=4.4,>=4.0 # Apache-2.0
django-nose>=1.4.4 # BSD
mock>=2.0.0 # BSD
mox3>=0.20.0 # Apache-2.0
nodeenv>=0.9.4 # BSD
nose>=1.3.7 # LGPL
nose-exclude>=0.3.0 # LGPL
nosehtmloutput>=0.0.3 # Apache-2.0
nosexcover>=1.0.10 # BSD
openstack.nose-plugin>=0.7 # Apache-2.0
oslosphinx>=4.7.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
selenium>=2.50.1 # Apache-2.0
sphinx>=1.6.2 # BSD
testtools>=1.4.0 # MIT
# This also needs xvfb library installed on your OS
xvfbwrapper>=0.1.3 #license: MIT

0
test/README.md Normal file
View File

56
tox.ini Normal file
View File

@ -0,0 +1,56 @@
[tox]
envlist = py27,pep8
minversion = 1.6
skipsdist = True
[testenv]
basepython=python2.7
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
# Note the hash seed is set to 0 until horizon can be tested with a
# random hash seed successfully.
# PYTHONHASHSEED=0
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
http://tarballs.openstack.org/horizon/horizon-master.tar.gz
http://tarballs.openstack.org/python-apmecclient/python-apmecclient-master.tar.gz
commands =
{envpython} {toxinidir}/manage.py test apmec_horizon --settings=apmec_horizon.test.settings {posargs}
[testenv:py27]
commands =
{envpython} {toxinidir}/manage.py test apmec_horizon --settings=apmec_horizon.test.settings {posargs}
[tox:jenkins]
[testenv:pep8]
usedevelop = True
whitelist_externals =
git
setenv =
{[testenv]setenv}
DJANGO_SETTINGS_MODULE=apmec_horizon.test.settings
commands =
flake8
[testenv:doc]
deps = Sphinx
commands = sphinx-build doc/source doc/build
[testenv:cover]
setenv = NOSE_WITH_COVERAGE=1
[testenv:venv]
commands = {posargs}
[flake8]
show-source = True
max-complexity = 20
[testenv:makemessages]
commands =
pybabel extract -F babel-django.cfg -o apmec_horizon/locale/django.pot -k gettext_noop -k gettext_lazy -k ngettext_lazy:1,2 -k ugettext_noop -k ugettext_lazy -k ungettext_lazy:1,2 -k npgettext:1c,2,3 -k pgettext_lazy:1c,2 -k npgettext_lazy:1c,2,3 apmec_horizon
pybabel extract -F babel-djangojs.cfg -o apmec_horizon/locale/djangojs.pot -k gettext_noop -k gettext_lazy -k ngettext_lazy:1,2 -k ugettext_noop -k ugettext_lazy -k ungettext_lazy:1,2 -k npgettext:1c,2,3 -k pgettext_lazy:1c,2 -k npgettext_lazy:1c,2,3 apmec_horizon