diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..9fb8f2c
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,59 @@
+# Set up globals
+globals:
+ angular: false
+
+extends: openstack
+
+# Most environment options are not explicitly enabled or disabled, only
+# included here for completeness' sake. They are commented out, because the
+# global updates.py script would otherwise override them during a global
+# requirements synchronization.
+#
+# Individual projects should choose which platforms they deploy to.
+
+env:
+ # browser global variables.
+ browser: true
+
+ # Adds all of the Jasmine testing global variables for version 1.3 and 2.0.
+ jasmine: true
+
+# Enable eslint-plugin-angular
+plugins:
+ - angular
+
+# Below we adjust rules specific to horizon's usage of openstack's linting
+# rules, and its own plugin inclusions.
+rules:
+ #############################################################################
+ # Disabled Rules from eslint-config-openstack
+ #############################################################################
+ valid-jsdoc: [1, {
+ requireParamDescription: false
+ }]
+ brace-style: 1
+ block-scoped-var: 1
+ callback-return: 1
+ consistent-return: 1
+ guard-for-in: 1
+ no-extra-parens: 1
+ no-new: 1
+ no-redeclare: 1
+ no-undefined: 1
+ no-unneeded-ternary: 1
+ no-use-before-define: 1
+ quote-props: 0
+ semi-spacing: 1
+ space-in-parens: 1
+
+ #############################################################################
+ # Angular Plugin Customization
+ #############################################################################
+
+ angular/controller-as-vm:
+ - 1
+ - "ctrl"
+
+ # Remove after migrating to angular 1.4 or later.
+ angular/no-cookiestore:
+ - 1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..31afc0d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+AUTHORS
+ChangeLog
+build
+cover
+doc/source/sourcecode
+node_modules
+npm-debug.log
+releasenotes/build
+blazar_dashboard/test/.secret_key_store
+.coverage*
+.jshintrc
+.project
+.pydevproject
+.settings
+.tox
+.venv
+*.egg*
+*.lock
+*.mo
+*.py[cod]
+*.swp
+*nose_results.html
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..36003f8
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,17 @@
+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
+
+If you already have a good understanding of how the system works and your
+OpenStack accounts are set up, you can skip to the development workflow
+section of this documentation to learn how changes to OpenStack should be
+submitted for review via the Gerrit tool:
+
+ 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/blazar
diff --git a/HACKING.rst b/HACKING.rst
new file mode 100644
index 0000000..7862641
--- /dev/null
+++ b/HACKING.rst
@@ -0,0 +1,4 @@
+blazar-dashboard Style Commandments
+===================================
+
+Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..68c771a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+
+ 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.
+
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..8af7877
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,29 @@
+================
+Blazar dashboard
+================
+
+Horizon plugin for the Blazar Reservation Service for OpenStack
+
+* Free software: Apache license
+* Source: https://git.openstack.org/cgit/openstack/blazar-dashboard
+* Bugs: https://bugs.launchpad.net/blazar
+
+Features
+--------
+
+The following features are currently supported:
+
+* Show a list of leases
+* Show details of a lease
+* Update a lease
+* Delete lease(s)
+
+Enabling in DevStack
+--------------------
+
+* Not yet supported
+
+Manual Installation
+-------------------
+
+See doc/source/install.rst
\ No newline at end of file
diff --git a/babel-django.cfg b/babel-django.cfg
new file mode 100644
index 0000000..ad09d34
--- /dev/null
+++ b/babel-django.cfg
@@ -0,0 +1,5 @@
+[extractors]
+django = django_babel.extract:extract_django
+
+[python: **.py]
+[django: templates/**.html]
diff --git a/babel-djangojs.cfg b/babel-djangojs.cfg
new file mode 100644
index 0000000..a8273b6
--- /dev/null
+++ b/babel-djangojs.cfg
@@ -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]
diff --git a/blazar_dashboard/__init__.py b/blazar_dashboard/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/api/__init__.py b/blazar_dashboard/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/api/client.py b/blazar_dashboard/api/client.py
new file mode 100644
index 0000000..f7a975d
--- /dev/null
+++ b/blazar_dashboard/api/client.py
@@ -0,0 +1,84 @@
+# 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
+
+import logging
+
+from horizon import exceptions
+from horizon.utils.memoized import memoized
+from openstack_dashboard.api import base
+
+from blazarclient import client as blazar_client
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Lease(base.APIDictWrapper):
+ """Represents one Blazar lease."""
+ ACTIONS = (CREATE, DELETE, UPDATE, START, STOP
+ ) = ('CREATE', 'DELETE', 'UPDATE', 'START', 'STOP')
+
+ STATUSES = (IN_PROGRESS, FAILED, COMPLETE
+ ) = ('IN_PROGRESS', 'FAILED', 'COMPLETE')
+
+ _attrs = ['id', 'name', 'start_date', 'end_date', 'user_id', 'project_id',
+ 'before_end_date', 'action', 'status', 'status_reason']
+
+ def __init__(self, apiresource):
+ super(Lease, self).__init__(apiresource)
+
+
+@memoized
+def blazarclient(request):
+ try:
+ api_url = base.url_for(request, 'reservation')
+ except exceptions.ServiceCatalogException:
+ LOG.debug('No Reservation service is configured.')
+ return None
+
+ LOG.debug('blazarclient connection created using the token "%s" and url'
+ '"%s"' % (request.user.token.id, api_url))
+ return blazar_client.Client(
+ blazar_url=api_url,
+ auth_token=request.user.token.id)
+
+
+def lease_list(request):
+ """List the leases."""
+ leases = blazarclient(request).lease.list()
+ return [Lease(l) for l in leases]
+
+
+def lease_get(request, lease_id):
+ """Get a lease."""
+ lease = blazarclient(request).lease.get(lease_id)
+ return Lease(lease)
+
+
+def lease_create(request, name, start, end, reservations, events):
+ """Create a lease."""
+ lease = blazarclient(request).lease.create(
+ name, start, end, reservations, events)
+ return Lease(lease)
+
+
+def lease_update(request, lease_id, **kwargs):
+ """Update a lease."""
+ lease = blazarclient(request).lease.update(lease_id, **kwargs)
+ return Lease(lease)
+
+
+def lease_delete(request, lease_id):
+ """Delete a lease."""
+ blazarclient(request).lease.delete(lease_id)
diff --git a/blazar_dashboard/content/__init__.py b/blazar_dashboard/content/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/content/leases/__init__.py b/blazar_dashboard/content/leases/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/content/leases/forms.py b/blazar_dashboard/content/leases/forms.py
new file mode 100644
index 0000000..f6dc1e2
--- /dev/null
+++ b/blazar_dashboard/content/leases/forms.py
@@ -0,0 +1,88 @@
+# Copyright 2014 Intel Corporation
+# 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 logging
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import exceptions
+from horizon import forms
+from horizon import messages
+
+from blazar_dashboard import api
+
+LOG = logging.getLogger(__name__)
+
+
+class UpdateForm(forms.SelfHandlingForm):
+
+ class Meta(object):
+ name = _('Update Lease Parameters')
+
+ lease_id = forms.CharField(
+ label=_('Lease ID'), widget=forms.widgets.HiddenInput, required=True)
+ lease_name = forms.CharField(
+ label=_('Lease name'), widget=forms.TextInput(), required=False)
+ start_time = forms.CharField(
+ label=_('Start time'),
+ widget=forms.TextInput(
+ attrs={'placeholder': _('Valid suffix are d/h/m (e.g. +1h)')}),
+ required=False)
+ end_time = forms.CharField(
+ label=_('End time'),
+ widget=forms.TextInput(
+ attrs={'placeholder': _('Valid suffix are d/h/m (e.g. +1h)')}),
+ required=False)
+
+ def handle(self, request, data):
+ lease_id = data.get('lease_id')
+
+ fields = {}
+
+ lease_name = data.get('lease_name', None)
+ if lease_name:
+ fields['name'] = lease_name
+
+ start_time = data.get('start_time', None)
+ end_time = data.get('end_time', None)
+ if start_time:
+ if start_time[0] == '+':
+ fields['defer_by'] = start_time[1:]
+ elif start_time[0] == '-':
+ fields['advance_by'] = start_time[1:]
+ if end_time:
+ if end_time[0] == '+':
+ fields['prolong_for'] = end_time[1:]
+ elif end_time[0] == '-':
+ fields['reduce_by'] = end_time[1:]
+
+ try:
+ api.client.lease_update(self.request, lease_id=lease_id, **fields)
+ messages.success(request, _("Lease update started."))
+ return True
+ except Exception as e:
+ LOG.error('Error updating lease: %s', e)
+ exceptions.handle(request,
+ message="An error occurred while updating this"
+ " lease: %s. Please try again." % e)
+
+ def clean(self):
+ cleaned_data = super(UpdateForm, self).clean()
+
+ lease_name = cleaned_data.get("lease_name")
+ start_time = cleaned_data.get("start_time")
+ end_time = cleaned_data.get("end_time")
+
+ if not (lease_name or start_time or end_time):
+ raise forms.ValidationError("Nothing to update.")
diff --git a/blazar_dashboard/content/leases/panel.py b/blazar_dashboard/content/leases/panel.py
new file mode 100644
index 0000000..44281aa
--- /dev/null
+++ b/blazar_dashboard/content/leases/panel.py
@@ -0,0 +1,22 @@
+# Copyright 2014 Intel Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+
+
+class Leases(horizon.Panel):
+ name = _("Leases")
+ slug = "leases"
diff --git a/blazar_dashboard/content/leases/tables.py b/blazar_dashboard/content/leases/tables.py
new file mode 100644
index 0000000..db9891e
--- /dev/null
+++ b/blazar_dashboard/content/leases/tables.py
@@ -0,0 +1,87 @@
+# Copyright 2014 Intel Corporation
+# 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 datetime import datetime
+from functools import partial
+
+from django.template import defaultfilters as django_filters
+from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ungettext_lazy
+from horizon import tables
+from horizon.utils import filters
+import pytz
+
+from blazar_dashboard import api
+
+
+class UpdateLease(tables.LinkAction):
+ name = "update"
+ verbose_name = _("Update Lease")
+ url = "horizon:project:leases:update"
+ classes = ("btn-create", "ajax-modal")
+
+ def allowed(self, request, lease):
+ if datetime.strptime(lease.end_date, '%Y-%m-%dT%H:%M:%S.%f').\
+ replace(tzinfo=pytz.utc) > datetime.now(pytz.utc):
+ return True
+ return False
+
+
+class DeleteLease(tables.DeleteAction):
+ name = "delete"
+ data_type_singular = _("Lease")
+ data_type_plural = _("Leases")
+ classes = ('btn-danger', 'btn-terminate')
+
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Lease",
+ u"Delete Leases",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Lease",
+ u"Deleted Leases",
+ count
+ )
+
+ def delete(self, request, lease_id):
+ api.client.lease_delete(request, lease_id)
+
+
+class LeasesTable(tables.DataTable):
+ name = tables.Column("name", verbose_name=_("Lease name"),
+ link="horizon:project:leases:detail",)
+ start_date = tables.Column("start_date", verbose_name=_("Start date"),
+ filters=(filters.parse_isotime,
+ partial(django_filters.date,
+ arg='Y-m-d H:i T')),)
+ end_date = tables.Column("end_date", verbose_name=_("End date"),
+ filters=(filters.parse_isotime,
+ partial(django_filters.date,
+ arg='Y-m-d H:i T')),)
+ action = tables.Column("action", verbose_name=_("Action"),)
+ status = tables.Column("status", verbose_name=_("Status"),)
+ status_reason = tables.Column("status_reason", verbose_name=_("Reason"),)
+
+ class Meta(object):
+ name = "leases"
+ verbose_name = _("Leases")
+ table_actions = (DeleteLease, )
+ row_actions = (UpdateLease, DeleteLease, )
diff --git a/blazar_dashboard/content/leases/tabs.py b/blazar_dashboard/content/leases/tabs.py
new file mode 100644
index 0000000..07cae8d
--- /dev/null
+++ b/blazar_dashboard/content/leases/tabs.py
@@ -0,0 +1,43 @@
+# Copyright 2014 Intel Corporation
+# 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.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+from horizon import exceptions
+from horizon import tabs
+
+from blazar_dashboard.api import client
+
+
+class OverviewTab(tabs.Tab):
+ name = _("Overview")
+ slug = "overview"
+ template_name = "project/leases/_detail_overview.html"
+
+ def get_context_data(self, request):
+ lease_id = self.tab_group.kwargs['lease_id']
+ try:
+ lease = client.lease_get(self.request, lease_id)
+ except Exception:
+ redirect = reverse('horizon:project:leases:index')
+ msg = _('Unable to retrieve lease details.')
+ exceptions.handle(request, msg, redirect=redirect)
+
+ return {'lease': lease}
+
+
+class LeaseDetailTabs(tabs.TabGroup):
+ slug = "lease_details"
+ tabs = (OverviewTab,)
diff --git a/blazar_dashboard/content/leases/templates/leases/_detail_overview.html b/blazar_dashboard/content/leases/templates/leases/_detail_overview.html
new file mode 100644
index 0000000..be8a707
--- /dev/null
+++ b/blazar_dashboard/content/leases/templates/leases/_detail_overview.html
@@ -0,0 +1,64 @@
+{% load i18n sizeformat %}
+
{% trans "Update a lease with the provided values." %}
+
+{% endblock %}
+
+{% block modal-footer %}
+
+ {% trans "Cancel" %}
+{% endblock %}
diff --git a/blazar_dashboard/content/leases/templates/leases/detail.html b/blazar_dashboard/content/leases/templates/leases/detail.html
new file mode 100644
index 0000000..3f6441c
--- /dev/null
+++ b/blazar_dashboard/content/leases/templates/leases/detail.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Lease Detail"%}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Lease Detail") %}
+{% endblock page_header %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
diff --git a/blazar_dashboard/content/leases/templates/leases/index.html b/blazar_dashboard/content/leases/templates/leases/index.html
new file mode 100644
index 0000000..3f05e6c
--- /dev/null
+++ b/blazar_dashboard/content/leases/templates/leases/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Leases" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Leases") %}
+{% endblock page_header %}
+
+{% block main %}
+ {{ table.render }}
+{% endblock %}
diff --git a/blazar_dashboard/content/leases/templates/leases/update.html b/blazar_dashboard/content/leases/templates/leases/update.html
new file mode 100644
index 0000000..c42ecd7
--- /dev/null
+++ b/blazar_dashboard/content/leases/templates/leases/update.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Update Lease" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Update Lease") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'project/leases/_update.html' %}
+{% endblock %}
diff --git a/blazar_dashboard/content/leases/tests.py b/blazar_dashboard/content/leases/tests.py
new file mode 100644
index 0000000..93e74c0
--- /dev/null
+++ b/blazar_dashboard/content/leases/tests.py
@@ -0,0 +1,169 @@
+# 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 import http
+from mox3.mox import IsA
+
+from blazar_dashboard import api
+from blazar_dashboard.test import helpers as test
+
+import logging
+LOG = logging.getLogger(__name__)
+
+INDEX_TEMPLATE = 'project/leases/index.html'
+INDEX_URL = reverse('horizon:project:leases:index')
+DETAIL_TEMPLATE = 'project/leases/detail.html'
+DETAIL_URL_BASE = 'horizon:project:leases:detail'
+UPDATE_URL_BASE = 'horizon:project:leases:update'
+UPDATE_TEMPLATE = 'project/leases/update.html'
+
+
+class LeasesTests(test.TestCase):
+ @test.create_stubs({api.client: ('lease_list',)})
+ def test_index(self):
+ leases = self.leases.list()
+ api.client.lease_list(IsA(http.HttpRequest)).AndReturn(leases)
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, INDEX_TEMPLATE)
+ self.assertNoMessages(res)
+ self.assertContains(res, 'lease-2')
+ self.assertContains(res, 'lease-1')
+
+ @test.create_stubs({api.client: ('lease_list',)})
+ def test_index_no_leases(self):
+ api.client.lease_list(IsA(http.HttpRequest)).AndReturn(())
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, INDEX_TEMPLATE)
+ self.assertNoMessages(res)
+ self.assertContains(res, 'No items to display')
+
+ @test.create_stubs({api.client: ('lease_list',)})
+ def test_index_error(self):
+ api.client.lease_list(
+ IsA(http.HttpRequest)
+ ).AndRaise(self.exceptions.blazar)
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, INDEX_TEMPLATE)
+ self.assertMessageCount(res, error=1)
+
+ def test_lease_actions(self):
+ pass
+
+ @test.create_stubs({api.client: ('lease_get',)})
+ def test_lease_detail(self):
+ lease = self.leases.get(name='lease-1')
+ api.client.lease_get(IsA(http.HttpRequest),
+ lease['id']).AndReturn(lease)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse(DETAIL_URL_BASE, args=[lease['id']]))
+ self.assertTemplateUsed(res, DETAIL_TEMPLATE)
+ self.assertContains(res, 'lease-1')
+
+ @test.create_stubs({api.client: ('lease_get',)})
+ def test_lease_detail_error(self):
+ api.client.lease_get(IsA(http.HttpRequest),
+ 'invalid').AndRaise(self.exceptions.blazar)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse(DETAIL_URL_BASE, args=['invalid']))
+ self.assertTemplateNotUsed(res, DETAIL_TEMPLATE)
+ self.assertMessageCount(error=1)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.client: ('lease_get', 'lease_update')})
+ def test_update_lease(self):
+ lease = self.leases.get(name='lease-1')
+ api.client.lease_get(
+ IsA(http.HttpRequest),
+ lease['id']
+ ).AndReturn(lease)
+ api.client.lease_update(
+ IsA(http.HttpRequest),
+ lease_id=lease['id'],
+ name='newname',
+ prolong_for='1h'
+ )
+ form_data = {
+ 'lease_id': lease['id'],
+ 'lease_name': 'newname',
+ 'end_time': '+1h'
+ }
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse(UPDATE_URL_BASE, args=[lease['id']]),
+ form_data)
+ self.assertNoFormErrors(res)
+ self.assertMessageCount(success=1)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.client: ('lease_get', 'lease_update')})
+ def test_update_lease_error(self):
+ lease = self.leases.get(name='lease-1')
+ api.client.lease_get(
+ IsA(http.HttpRequest),
+ lease['id']
+ ).AndReturn(lease)
+ api.client.lease_update(
+ IsA(http.HttpRequest),
+ lease_id=lease['id'],
+ name='newname',
+ prolong_for='1h'
+ ).AndRaise(self.exceptions.blazar)
+ form_data = {
+ 'lease_id': lease['id'],
+ 'lease_name': 'newname',
+ 'end_time': '+1h'
+ }
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse(UPDATE_URL_BASE, args=[lease['id']]),
+ form_data)
+ self.assertTemplateUsed(UPDATE_TEMPLATE)
+ self.assertNoFormErrors(res)
+ self.assertContains(res, 'An error occurred while updating')
+
+ @test.create_stubs({api.client: ('lease_list', 'lease_delete')})
+ def test_delete_lease(self):
+ leases = self.leases.list()
+ lease = self.leases.get(name='lease-1')
+ api.client.lease_list(IsA(http.HttpRequest)).AndReturn(leases)
+ api.client.lease_delete(IsA(http.HttpRequest), lease['id'])
+ self.mox.ReplayAll()
+
+ action = 'leases__delete__%s' % lease['id']
+ form_data = {'action': action}
+ res = self.client.post(INDEX_URL, form_data)
+ self.assertMessageCount(success=1)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.client: ('lease_list', 'lease_delete')})
+ def test_delete_lease_error(self):
+ leases = self.leases.list()
+ lease = self.leases.get(name='lease-1')
+ api.client.lease_list(IsA(http.HttpRequest)).AndReturn(leases)
+ api.client.lease_delete(IsA(http.HttpRequest),
+ lease['id']).AndRaise(self.exceptions.blazar)
+ self.mox.ReplayAll()
+
+ action = 'leases__delete__%s' % lease['id']
+ form_data = {'action': action}
+ res = self.client.post(INDEX_URL, form_data)
+ self.assertMessageCount(error=1)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/blazar_dashboard/content/leases/urls.py b/blazar_dashboard/content/leases/urls.py
new file mode 100644
index 0000000..b099515
--- /dev/null
+++ b/blazar_dashboard/content/leases/urls.py
@@ -0,0 +1,27 @@
+# Copyright 2014 Intel Corporation
+# 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.urls import url
+
+from blazar_dashboard.content.leases import views as leases_views
+
+
+urlpatterns = [
+ url(r'^$', leases_views.IndexView.as_view(), name='index'),
+ url(r'^(?P[^/]+)/$', leases_views.DetailView.as_view(),
+ name='detail'),
+ url(r'^(?P[^/]+)/update$', leases_views.UpdateView.as_view(),
+ name='update'),
+]
diff --git a/blazar_dashboard/content/leases/views.py b/blazar_dashboard/content/leases/views.py
new file mode 100644
index 0000000..67781f5
--- /dev/null
+++ b/blazar_dashboard/content/leases/views.py
@@ -0,0 +1,79 @@
+# Copyright 2014 Intel Corporation
+# 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.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 tables
+from horizon import tabs
+from horizon.utils import memoized
+
+from blazar_dashboard import api
+from blazar_dashboard.content.leases import forms as project_forms
+from blazar_dashboard.content.leases import tables as project_tables
+from blazar_dashboard.content.leases import tabs as project_tabs
+
+
+class IndexView(tables.DataTableView):
+ table_class = project_tables.LeasesTable
+ template_name = 'project/leases/index.html'
+
+ def get_data(self):
+ try:
+ leases = api.client.lease_list(self.request)
+ except Exception:
+ leases = []
+ msg = _('Unable to retrieve lease information.')
+ exceptions.handle(self.request, msg)
+ return leases
+
+
+class DetailView(tabs.TabView):
+ tab_group_class = project_tabs.LeaseDetailTabs
+ template_name = 'project/leases/detail.html'
+
+
+class UpdateView(forms.ModalFormView):
+ form_class = project_forms.UpdateForm
+ template_name = 'project/leases/update.html'
+ success_url = reverse_lazy('horizon:project:leases:index')
+
+ def get_initial(self):
+ initial = super(UpdateView, self).get_initial()
+
+ initial['lease'] = self.get_object()
+ if initial['lease']:
+ initial['lease_id'] = initial['lease'].id
+ initial['name'] = initial['lease'].name
+
+ return initial
+
+ def get_context_data(self, **kwargs):
+ context = super(UpdateView, self).get_context_data(**kwargs)
+ context['lease'] = self.get_object()
+ return context
+
+ @memoized.memoized_method
+ def get_object(self):
+ lease_id = self.kwargs['lease_id']
+ try:
+ lease = api.client.lease_get(self.request, lease_id)
+ except Exception:
+ msg = _("Unable to retrieve lease.")
+ redirect = reverse('horizon:project:leases:index')
+ exceptions.handle(self.request, msg, redirect=redirect)
+ return lease
diff --git a/blazar_dashboard/enabled/_90_project_reservations_panelgroup.py b/blazar_dashboard/enabled/_90_project_reservations_panelgroup.py
new file mode 100644
index 0000000..19184bc
--- /dev/null
+++ b/blazar_dashboard/enabled/_90_project_reservations_panelgroup.py
@@ -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.utils.translation import ugettext_lazy as _
+
+# The slug of the panel group to be added to HORIZON_CONFIG. Required.
+PANEL_GROUP = 'reservations'
+# The display name of the PANEL_GROUP. Required.
+PANEL_GROUP_NAME = _('Reservations')
+# The slug of the dashboard the PANEL_GROUP associated with. Required.
+PANEL_GROUP_DASHBOARD = 'project'
+
+ADD_INSTALLED_APPS = ['blazar_dashboard']
diff --git a/blazar_dashboard/enabled/_91_project_reservations_leases_panel.py b/blazar_dashboard/enabled/_91_project_reservations_leases_panel.py
new file mode 100644
index 0000000..59a5c2d
--- /dev/null
+++ b/blazar_dashboard/enabled/_91_project_reservations_leases_panel.py
@@ -0,0 +1,21 @@
+# 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.
+
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'leases'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'reservations'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'project'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = 'blazar_dashboard.content.leases.panel.Leases'
diff --git a/blazar_dashboard/enabled/__init__.py b/blazar_dashboard/enabled/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/test/__init__.py b/blazar_dashboard/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/test/helpers.py b/blazar_dashboard/test/helpers.py
new file mode 100644
index 0000000..05e7ab0
--- /dev/null
+++ b/blazar_dashboard/test/helpers.py
@@ -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 openstack_dashboard.test import helpers
+
+from blazar_dashboard.test.test_data import utils
+
+
+def create_stubs(stubs_to_create={}):
+ return helpers.create_stubs(stubs_to_create)
+
+
+class TestCase(helpers.TestCase):
+ def _setup_test_data(self):
+ super(TestCase, self)._setup_test_data()
+ utils.load_test_data(self)
diff --git a/blazar_dashboard/test/integration_tests/__init__.py b/blazar_dashboard/test/integration_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/test/settings.py b/blazar_dashboard/test/settings.py
new file mode 100644
index 0000000..4088e09
--- /dev/null
+++ b/blazar_dashboard/test/settings.py
@@ -0,0 +1,37 @@
+# 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.
+
+# Default to Horizons test settings to avoid any missing keys
+from horizon.test.settings import * # noqa: F403,H303
+from openstack_dashboard.test.settings import * # noqa: F403,H303
+
+# pop these keys to avoid log warnings about deprecation
+# update_dashboards will populate them anyway
+HORIZON_CONFIG.pop('dashboards', None)
+HORIZON_CONFIG.pop('default_dashboard', None)
+
+# Update the dashboards with blazar_dashboard
+import blazar_dashboard.enabled
+import openstack_dashboard.enabled
+from openstack_dashboard.utils import settings
+
+settings.update_dashboards(
+ [
+ blazar_dashboard.enabled,
+ openstack_dashboard.enabled,
+ ],
+ HORIZON_CONFIG,
+ INSTALLED_APPS
+)
+
+# Ensure any duplicate apps are removed after the update_dashboards call
+INSTALLED_APPS = list(set(INSTALLED_APPS))
diff --git a/blazar_dashboard/test/test_data/__init__.py b/blazar_dashboard/test/test_data/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/blazar_dashboard/test/test_data/blazar_data.py b/blazar_dashboard/test/test_data/blazar_data.py
new file mode 100644
index 0000000..86bd35c
--- /dev/null
+++ b/blazar_dashboard/test/test_data/blazar_data.py
@@ -0,0 +1,141 @@
+# 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.test_data import utils
+
+from blazar_dashboard import api
+
+
+lease_sample1 = {
+ 'status': None,
+ 'user_id': None,
+ 'name': 'lease-1',
+ 'end_date': '2030-06-30T18:00:00.000000',
+ 'reservations': [
+ {
+ 'status': 'pending',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'min': 1,
+ 'max': 1,
+ 'hypervisor_properties': '',
+ 'resource_id': '3850a831-8c08-49c4-b703-d804284a6baf',
+ 'resource_properties': '[">=", "$vcpus", "2"]',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'id': '087bc740-6d2d-410b-9d47-c7b2b55a9d36',
+ 'resource_type': 'physical:host'
+ }
+ ],
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'events': [
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'start_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2017-06-27T18:00:00.000000',
+ 'id': '188a8584-f832-4df9-9a4a-51e6364420ff'
+ },
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'end_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2030-06-30T18:00:00.000000',
+ 'id': '277d6436-dfcb-4eae-ae5e-ac7fa9c2fd56'
+ },
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'before_end_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2030-06-28T18:00:00.000000',
+ 'id': 'f583af71-ca21-4b66-87de-52211d118029'
+ }
+ ],
+ 'id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'action': None,
+ 'project_id': 'aa45f56901ef45ee95e3d211097c0ea3',
+ 'status_reason': None,
+ 'start_date': '2017-06-27T18:00:00.000000',
+ 'trust_id': 'b442a580b9504ababf305bf2b4c49512'
+}
+
+lease_sample2 = {
+ 'status': None,
+ 'user_id': None,
+ 'name': 'lease-2',
+ 'end_date': '2030-06-30T18:00:00.000000',
+ 'reservations': [
+ {
+ 'status': 'pending',
+ 'lease_id': '9bcfff36-872e-4f47-9abe-9a58a4f22038',
+ 'min': 1,
+ 'max': 1,
+ 'hypervisor_properties': '',
+ 'resource_id': '369c83cb-d3de-4e15-9e15-b74625cf9ee5',
+ 'resource_properties': '[">=", "$vcpus", "2"]',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'id': '1b05370e-d92a-452d-80db-89842666b604',
+ 'resource_type': 'physical:host'
+ }
+ ],
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'events': [
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'start_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2017-06-27T18:00:00.000000',
+ 'id': '0d81cdd7-9390-4d19-8acf-746bc8f0167d'
+ },
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'end_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2030-06-30T18:00:00.000000',
+ 'id': 'b2ac8924-b6d1-46fe-b1a9-43d9d5c683cf'
+ },
+ {
+ 'status': 'UNDONE',
+ 'lease_id': '6ee55c78-ac52-41a6-99af-2d2d73bcc466',
+ 'event_type': 'before_end_lease',
+ 'created_at': '2017-06-27 15:00:00',
+ 'updated_at': None,
+ 'time': '2030-06-28T18:00:00.000000',
+ 'id': 'ba97b406-e721-47fe-9097-8ce6569f15d3'
+ }
+ ],
+ 'id': 'ef32abe8-a1f7-4c2f-b5f2-941428848230',
+ 'action': None,
+ 'project_id': 'aa45f56901ef45ee95e3d211097c0ea3',
+ 'status_reason': None,
+ 'start_date': '2017-06-27T18:00:00.000000',
+ 'trust_id': 'b442a580b9504ababf305bf2b4c49512'
+}
+
+
+def data(TEST):
+ TEST.leases = utils.TestDataContainer()
+
+ TEST.leases.add(api.client.Lease(lease_sample1))
+ TEST.leases.add(api.client.Lease(lease_sample2))
diff --git a/blazar_dashboard/test/test_data/exceptions.py b/blazar_dashboard/test/test_data/exceptions.py
new file mode 100644
index 0000000..9f9610e
--- /dev/null
+++ b/blazar_dashboard/test/test_data/exceptions.py
@@ -0,0 +1,56 @@
+# 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.test_data import exceptions
+from openstack_dashboard.test.test_data import utils
+import six
+
+import blazarclient.exception as blazar_exceptions
+
+
+def create_stubbed_exception(cls, status_code=500):
+ msg = "Expected failure."
+
+ def fake_init_exception(self, code=None, message=None, **kwargs):
+ if code is not None:
+ if hasattr(self, 'http_status'):
+ self.http_status = code
+ else:
+ self.code = code
+ self.message = message or self.__class__.message
+
+ try:
+ # Neutron sometimes updates the message with additional
+ # information, like a reason.
+ self.message = self.message % kwargs
+ except Exception:
+ pass # We still have the main error message.
+
+ def fake_str(self):
+ return str(self.message)
+
+ def fake_unicode(self):
+ return six.text_type(self.message)
+
+ cls.__init__ = fake_init_exception
+ cls.__str__ = fake_str
+ cls.__unicode__ = fake_unicode
+ cls.silence_logging = True
+ return cls(status_code, msg)
+
+
+def data(TEST):
+ TEST.exceptions = utils.TestDataContainer()
+
+ blazar_exception = blazar_exceptions.BlazarClientException
+ TEST.exceptions.blazar = (exceptions.
+ create_stubbed_exception(blazar_exception))
diff --git a/blazar_dashboard/test/test_data/utils.py b/blazar_dashboard/test/test_data/utils.py
new file mode 100644
index 0000000..a654c9d
--- /dev/null
+++ b/blazar_dashboard/test/test_data/utils.py
@@ -0,0 +1,30 @@
+# 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.test_data import utils
+
+from blazar_dashboard.test.test_data import blazar_data
+from blazar_dashboard.test.test_data import exceptions
+
+
+def load_test_data(load_onto=None):
+ # The order of these loaders matters, some depend on others.
+ loaders = (
+ exceptions.data,
+ blazar_data.data,
+ )
+ if load_onto:
+ for data_func in loaders:
+ data_func(load_onto)
+ return load_onto
+ else:
+ return utils.TestData(*loaders)
diff --git a/blazar_dashboard/version.py b/blazar_dashboard/version.py
new file mode 100644
index 0000000..ba0c453
--- /dev/null
+++ b/blazar_dashboard/version.py
@@ -0,0 +1,14 @@
+# 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 pbr.version
+
+version_info = pbr.version.VersionInfo('blazar_dashboard')
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..3928ac7
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,152 @@
+# Makefile for Sphinx documentation
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Blazar-dashboard.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Blazar-dashboard.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Blazar-dashboard"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Blazar-dashboard"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 0000000..5b67e27
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,437 @@
+# 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.
+#
+# Horizon documentation build configuration file, created by
+# sphinx-quickstart on Thu Oct 27 11:38:59 2011.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+from __future__ import print_function
+
+import os
+import sys
+
+import django
+
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
+
+sys.path.insert(0, ROOT)
+
+# This is required for ReadTheDocs.org, but isn't a bad idea anyway.
+os.environ.setdefault('DJANGO_SETTINGS_MODULE',
+ 'blazar_dashboard.test.settings')
+
+# Starting in Django 1.7, standalone scripts, such as a sphinx build
+# require that django.setup() be called first.
+# https://docs.djangoproject.com/en/1.8/releases/1.7/#standalone-scripts
+django.setup()
+
+from blazar_dashboard import version as ui_ver
+
+
+def write_autodoc_index():
+
+ def find_autodoc_modules(module_name, sourcedir):
+ """returns a list of modules in the SOURCE directory."""
+ modlist = []
+ os.chdir(os.path.join(sourcedir, module_name))
+ print("SEARCHING %s" % sourcedir)
+ for root, dirs, files in os.walk("."):
+ for filename in files:
+ if filename == 'tests.py':
+ continue
+ if filename.endswith(".py"):
+ # remove the pieces of the root
+ elements = root.split(os.path.sep)
+ # replace the leading "." with the module name
+ elements[0] = module_name
+ # and get the base module name
+ base, extension = os.path.splitext(filename)
+ if not (base == "__init__"):
+ elements.append(base)
+ result = ".".join(elements)
+ # print result
+ modlist.append(result)
+ return modlist
+
+ RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode"))
+ SRCS = [('blazar_dashboard', ROOT), ]
+
+ EXCLUDED_MODULES = ()
+ CURRENT_SOURCES = {}
+
+ if not(os.path.exists(RSTDIR)):
+ os.mkdir(RSTDIR)
+ CURRENT_SOURCES[RSTDIR] = ['autoindex.rst']
+
+ INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w")
+ INDEXOUT.write("""
+=================
+Source Code Index
+=================
+
+.. contents::
+ :depth: 1
+ :local:
+
+""")
+
+ for modulename, path in SRCS:
+ sys.stdout.write("Generating source documentation for %s\n" %
+ modulename)
+ INDEXOUT.write("\n%s\n" % modulename.capitalize())
+ INDEXOUT.write("%s\n" % ("=" * len(modulename),))
+ INDEXOUT.write(".. toctree::\n")
+ INDEXOUT.write(" :maxdepth: 1\n")
+ INDEXOUT.write("\n")
+
+ MOD_DIR = os.path.join(RSTDIR, modulename)
+ CURRENT_SOURCES[MOD_DIR] = []
+ if not(os.path.exists(MOD_DIR)):
+ os.mkdir(MOD_DIR)
+ for module in find_autodoc_modules(modulename, path):
+ if any([module.startswith(exclude) for exclude
+ in EXCLUDED_MODULES]):
+ print("Excluded module %s." % module)
+ continue
+ mod_path = os.path.join(path, *module.split("."))
+ generated_file = os.path.join(MOD_DIR, "%s.rst" % module)
+
+ INDEXOUT.write(" %s/%s\n" % (modulename, module))
+
+ # Find the __init__.py module if this is a directory
+ if os.path.isdir(mod_path):
+ source_file = ".".join((os.path.join(mod_path, "__init__"),
+ "py",))
+ else:
+ source_file = ".".join((os.path.join(mod_path), "py"))
+
+ CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module)
+ # Only generate a new file if the source has changed or we don't
+ # have a doc file to begin with.
+ if not os.access(generated_file, os.F_OK) or (
+ os.stat(generated_file).st_mtime <
+ os.stat(source_file).st_mtime):
+ print("Module %s updated, generating new documentation."
+ % module)
+ FILEOUT = open(generated_file, "w")
+ header = "The :mod:`%s` Module" % module
+ FILEOUT.write("%s\n" % ("=" * len(header),))
+ FILEOUT.write("%s\n" % header)
+ FILEOUT.write("%s\n" % ("=" * len(header),))
+ FILEOUT.write(".. automodule:: %s\n" % module)
+ FILEOUT.write(" :members:\n")
+ FILEOUT.write(" :undoc-members:\n")
+ FILEOUT.write(" :show-inheritance:\n")
+ FILEOUT.write(" :noindex:\n")
+ FILEOUT.close()
+
+ INDEXOUT.close()
+
+ # Delete auto-generated .rst files for sources which no longer exist
+ for directory, subdirs, files in list(os.walk(RSTDIR)):
+ for old_file in files:
+ if old_file not in CURRENT_SOURCES.get(directory, []):
+ print("Removing outdated file for %s" % old_file)
+ os.remove(os.path.join(directory, old_file))
+
+
+write_autodoc_index()
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings.
+# They can be extensions coming with Sphinx (named 'sphinx.ext.*')
+# or your custom ones.
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.viewcode',
+ ]
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Blazar dashboard'
+copyright = u'2017, OpenStack Foundation'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = ui_ver.version_info.version_string()
+# The full version, including alpha/beta/rc tags.
+release = ui_ver.version_info.release_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['**/#*', '**~', '**/#*#']
+
+# The reST default role (used for this markup: `text`)
+# to use for all documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+primary_domain = 'py'
+nitpicky = False
+
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Horizondoc'
+
+
+# -- Options for LaTeX output -------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass
+# [howto/manual]).
+latex_documents = [
+ ('index', 'Horizon.tex', u'Horizon Documentation',
+ u'OpenStack Foundation', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output -------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', u'Blazar dashboard Documentation',
+ 'Documentation for the Blazar dashboard plugin to the OpenStack '
+ 'Dashboard (Horizon)',
+ [u'OpenStack'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'Horizon', u'Horizon Documentation', u'OpenStack',
+ 'Horizon', 'One line description of project.', 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+
+# -- Options for Epub output --------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'Horizon'
+epub_author = u'OpenStack'
+epub_publisher = u'OpenStack'
+epub_copyright = u'2012, OpenStack'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+# epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+# epub_scheme = ''
+
+# The unique identifier of the text. This can be an ISBN number
+# or the project homepage.
+# epub_identifier = ''
+
+# A unique identification for the text.
+# epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+# epub_cover = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+# epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+# epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+# epub_exclude_files = []
+
+# The depth of the table of contents in toc.ncx.
+# epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+# epub_tocdup = True
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..78e226b
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,37 @@
+================
+Blazar dashboard
+================
+
+Horizon plugin for the Blazar Reservation Service for OpenStack
+
+* Free software: Apache license
+* Source: https://git.openstack.org/cgit/openstack/blazar-dashboard
+* Bugs: https://bugs.launchpad.net/blazar
+
+Features
+--------
+
+The following features are currently supported:
+
+* Show a list of leases
+* Show details of a lease
+* Update a lease
+* Delete lease(s)
+
+
+Using Blazar dashboard
+----------------------
+
+.. toctree::
+ :maxdepth: 1
+
+ install
+
+Source Code Reference
+---------------------
+
+.. toctree::
+ :glob:
+ :maxdepth: 1
+
+ sourcecode/autoindex
diff --git a/doc/source/install.rst b/doc/source/install.rst
new file mode 100644
index 0000000..8903d65
--- /dev/null
+++ b/doc/source/install.rst
@@ -0,0 +1,52 @@
+============
+Installation
+============
+
+Enabling in DevStack
+--------------------
+
+* Not yet supported
+
+Manual Installation
+-------------------
+
+Begin by cloning the Horizon and Blazar dashboard repositories::
+
+ git clone https://git.openstack.org/openstack/horizon
+ git clone https://git.openstack.org/openstack/blazar-dashboard
+
+Create a virtual environment and install Horizon dependencies::
+
+ cd horizon
+ tox -e runserver --notest
+
+Set up your ``local_settings.py`` file::
+
+ cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
+
+Open up the copied ``local_settings.py`` file in your preferred text
+editor. You will want to customize several settings:
+
+- ``OPENSTACK_HOST`` should be configured with the hostname of your
+ OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and
+ ``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your
+ environment. (They should be correct unless you modified your
+ OpenStack server to change them.)
+
+Install Blazar dashboard with all dependencies in your virtual environment::
+
+ .tox/runserver/bin/pip install -e ../blazar-dashboard/
+
+And enable it in Horizon::
+
+ ln -s ../blazar-dashboard/blazar_dashboard/enabled/_90_project_reservations_panelgroup.py openstack_dashboard/local/enabled
+ ln -s ../blazar-dashboard/blazar_dashboard/enabled/_91_project_reservations_leasess_panel.py openstack_dashboard/local/enabled
+
+Start horizon and it runs with the newly enabled Blazar dashboard.
+
+Or to test the plugin run::
+
+ tox -e runserver -- 0.0.0.0:8080
+
+to have the application start on port 8080 and the horizon dashboard will be
+available in your browser at http://localhost:8080/
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..172aa88
--- /dev/null
+++ b/manage.py
@@ -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",
+ "blazar_dashboard.test.settings")
+ execute_from_command_line(sys.argv)
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
new file mode 100644
index 0000000..e69de29
diff --git a/releasenotes/notes/dashboard-support-1b429d36f395d93a.yaml b/releasenotes/notes/dashboard-support-1b429d36f395d93a.yaml
new file mode 100644
index 0000000..d669b4f
--- /dev/null
+++ b/releasenotes/notes/dashboard-support-1b429d36f395d93a.yaml
@@ -0,0 +1,7 @@
+features:
+ - |
+ The following features are currently supported:
+ - Show a list of leases
+ - Show details of a lease
+ - Update a lease
+ - Delete lease(s)
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..2590af1
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+# Glance Release Notes documentation build configuration file, created by
+# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Blazar dashboard Release Notes'
+copyright = u'2017, OpenStack Foundation'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+from blazar_dashboard import version as ui_ver
+# The short X.Y version.
+# The full version, including alpha/beta/rc tags.
+release = ui_ver.version_info.release_string()
+# The short X.Y version.
+version = ui_ver.version_info.version_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = []
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Blazar-dashboardReleaseNotesdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ ('index',
+ 'Blazar-dashboardReleaseNotes.tex',
+ u'Blazar dashboard Release Notes Documentation',
+ u'Blazar dashboard Developers',
+ 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'blazar_dashboardreleasenotes',
+ u'Blazar dashboard Release Notes Documentation',
+ [u'Blazar dashboard Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'Blazar-dashboardReleaseNotes',
+ u'Blazar dashboard Release Notes Documentation',
+ u'Blazar dashboard Developers', 'Blazar-dashboardReleaseNotes',
+ 'Resource reservation service for OpenStack',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
+
+# -- Options for Internationalization output ------------------------------
+locale_dirs = ['locale/']
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..d2c9b5a
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,8 @@
+==============================
+blazar_dashboard Release Notes
+==============================
+
+.. toctree::
+ :maxdepth: 1
+
+ unreleased
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..bd360ba
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+=============================
+ Current Series Release Notes
+=============================
+
+.. release-notes::
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ede6d33
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,17 @@
+# 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
+pbr>=2.0.0,!=2.1.0 # Apache-2.0
+Babel>=2.3.4,!=2.4.0 # BSD
+Django>=1.8,<2.0 # BSD
+django-babel>=0.5.1 # BSD
+django_compressor>=2.0 # MIT
+django_openstack_auth>=3.5.0 # Apache-2.0
+django-pyscss>=2.0.2 # BSD License (2 clause)
+http://tarballs.openstack.org/python-blazarclient/python-blazarclient-master.tar.gz#egg=python-blazarclient
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..ed562a9
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,29 @@
+[metadata]
+name = blazar-dashboard
+summary = Horizon plugin for the Blazar Reservation Service for OpenStack
+description-file =
+ README.rst
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+home-page = http://www.openstack.org/
+classifier =
+ Environment :: OpenStack
+ 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
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.5
+
+[files]
+packages =
+ blazar_dashboard
+
+[build_sphinx]
+all_files = 1
+build-dir = doc/build
+source-dir = doc/source
+warning-is-error = 1
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..d74ff58
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+# 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)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..7cee992
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,24 @@
+# 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.0,!=4.4 # Apache-2.0
+django-nose>=1.4.4 # BSD
+mock>=2.0 # BSD
+mox3>=0.7.0,!=0.19.0 # Apache-2.0
+nodeenv>=0.9.4 # BSD
+nose # LGPL
+nose-exclude # LGPL
+nosehtmloutput>=0.0.3 # Apache-2.0
+nosexcover # BSD
+openstack.nose-plugin>=0.7 # Apache-2.0
+reno>=1.8.0,!=2.3.1 # Apache-2.0
+sphinx>=1.6.2 # BSD
+testtools>=1.4.0 # MIT
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
new file mode 100755
index 0000000..8ed3308
--- /dev/null
+++ b/tools/tox_install.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+
+# Client constraint file contains this client version pin that is in conflict
+# with installing the client from source. We should remove the version pin in
+# the constraints file before applying it for from-source installation.
+# The script also has a secondary purpose to install certain special
+# dependencies directly from git.
+
+# Wrapper for pip install that always uses constraints.
+function pip_install() {
+ pip install -c"$localfile" -U "$@"
+}
+
+# Grab the library from git using either zuul-cloner or pip. The former is
+# there to a take advantage of the setup done by the gate infrastructure
+# and honour any/all Depends-On headers in the commit message
+function install_from_git() {
+ ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
+ GIT_HOST=git.openstack.org
+ PROJ=$1
+ EGG=$2
+
+ edit-constraints "$localfile" -- "$EGG"
+ if [ -x "$ZUUL_CLONER" ]; then
+ SRC_DIR="$VIRTUAL_ENV/src"
+ mkdir -p "$SRC_DIR"
+ cd "$SRC_DIR" >/dev/null
+ ZUUL_CACHE_DIR=${ZUUL_CACHE_DIR:-/opt/git} $ZUUL_CLONER \
+ --branch "$BRANCH_NAME" \
+ "git://$GIT_HOST" "$PROJ"
+ pip_install -e "$PROJ/."
+ cd - >/dev/null
+ else
+ SRC_DIR="$VIRTUAL_ENV/src/$PROJ"
+ git clone --depth 1 --branch $BRANCH_NAME https\://$GIT_HOST/$PROJ $SRC_DIR
+ pip_install -e $SRC_DIR
+ fi
+}
+
+
+
+CONSTRAINTS_FILE="$1"
+shift 1
+
+# This script will either complete with a return code of 0 or the return code
+# of whatever failed.
+set -e
+
+# NOTE(tonyb): Place this in the tox environment's log dir so it will get
+# published to logs.openstack.org for easy debugging.
+mkdir -p "$VIRTUAL_ENV/log/"
+localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
+
+if [[ "$CONSTRAINTS_FILE" != http* ]]; then
+ CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
+fi
+# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
+curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
+
+pip_install openstack-requirements
+
+# This is the main purpose of the script: Allow local installation of
+# the current repo. It is listed in constraints file and thus any
+# install will be constrained and we need to unconstrain it.
+edit-constraints "$localfile" -- "$CLIENT_NAME"
+
+declare -a passthrough_args
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ # If we have any special os: deps then process them
+ os:*)
+ declare -a pkg_spec
+ IFS=: pkg_spec=($1)
+ install_from_git "${pkg_spec[1]}" "${pkg_spec[2]}"
+ ;;
+ # Otherwise just pass the other deps through to the constrained pip install
+ *)
+ passthrough_args+=("$1")
+ ;;
+ esac
+ shift 1
+done
+
+# If *only* had special args then then isn't any need to run pip.
+if [ -n "$passthrough_args" ] ; then
+ pip_install "${passthrough_args[@]}"
+fi
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..555a485
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,75 @@
+[tox]
+envlist = py35,py27,py27dj18,pep8
+minversion = 2.0
+skipsdist = True
+
+[testenv]
+usedevelop = True
+setenv = VIRTUAL_ENV={envdir}
+ BRANCH_NAME=master
+ CLIENT_NAME=blazar-dashboard
+ NOSE_WITH_OPENSTACK=1
+ NOSE_OPENSTACK_COLOR=1
+ NOSE_OPENSTACK_RED=0.05
+ NOSE_OPENSTACK_YELLOW=0.025
+ NOSE_OPENSTACK_SHOW_ELAPSED=1
+install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
+ # os:* is handled by tox_install.sh
+ os:openstack/horizon:horizon
+commands = python manage.py test {posargs} --settings=blazar_dashboard.test.settings
+
+[testenv:pep8]
+commands = flake8 {posargs}
+
+[testenv:venv]
+commands = {posargs}
+
+[testenv:cover]
+commands =
+ coverage erase
+ coverage run {toxinidir}/manage.py test blazar_dashboard --settings=blazar_dashboard.test.settings {posargs} --exclude-dir=blazar_dashboard/test/integration_tests {posargs}
+ coverage xml --omit '.tox/cover/*' -o 'cover/coverage.xml'
+ coverage html --omit '.tox/cover/*' -d 'cover/htmlcov'
+
+[testenv:py27dj18]
+basepython = python2.7
+commands =
+ pip install django>=1.8,<1.9
+ python manage.py test {posargs} --settings=blazar_dashboard.test.settings
+
+[testenv:py27dj19]
+basepython = python2.7
+commands =
+ pip install django>=1.9,<1.10
+ python manage.py test {posargs} --settings=blazar_dashboard.test.settings
+
+[testenv:py27dj110]
+basepython = python2.7
+commands =
+ pip install django>=1.10,<1.11
+ python manage.py test {posargs} --settings=blazar_dashboard.test.settings
+
+[testenv:py27dj111]
+basepython = python2.7
+commands =
+ pip install django>=1.11,<2.0
+ python manage.py test {posargs} --settings=blazar_dashboard.test.settings
+
+[testenv:eslint]
+whitelist_externals = npm
+commands =
+ npm install
+ npm run postinstall
+ npm run lint
+
+[testenv:docs]
+commands = python setup.py build_sphinx
+
+[testenv:releasenotes]
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+
+[flake8]
+exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,node_modules
+max-complexity = 20