Plugin setup

This patch adds an empty Index view and basic test infrastructure,
including a basic test that verifies the panel has been correctly
registered with Horizon.

Following this patch, it is possible to install and register the Ironic
UI, and see the "Bare Metal Provisioning" panel under Horizon's Admin
dashboard.

Change-Id: I5efff7707fb44e6ba016d2aaaf3f46e1a9583084
Co-Authored-By: Rob Cresswell <robert.cresswell@outlook.com>
This commit is contained in:
Elizabeth Elwell 2016-01-21 12:22:00 +00:00
parent 20df465894
commit 1063e15424
24 changed files with 383 additions and 47 deletions

3
.gitignore vendored
View File

@ -52,3 +52,6 @@ ChangeLog
*~
.*.swp
.*sw?
.secret_key_store
*.lock

View File

@ -2,5 +2,8 @@ include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
include setup.py
recursive-include ironic_ui *.js *.html *.scss
global-exclude *.pyc

View File

View File

View File

@ -0,0 +1,27 @@
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 name of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'ironic'
# The name of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'admin'
# The name of the panel group the PANEL is associated with.
PANEL_GROUP = 'admin'
# Python panel class of the PANEL to be added.
ADD_PANEL = 'ironic_ui.ironic.panel.Ironic'
# A list of applications to be prepended to INSTALLED_APPS
ADD_INSTALLED_APPS = ['ironic_ui', ]
# Automatically discover static resources in installed apps
AUTO_DISCOVER_STATIC_FILES = True

View File

View File

23
ironic_ui/ironic/panel.py Normal file
View File

@ -0,0 +1,23 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 Ironic(horizon.Panel):
name = _("Ironic Bare Metal Provisioning")
slug = 'ironic'

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{{ page_title }}
{% endblock %}
{% block main %}
{% endblock %}

21
ironic_ui/ironic/urls.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 ironic_ui.ironic import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name="index"),
]

22
ironic_ui/ironic/views.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import views
class IndexView(views.HorizonTemplateView):
template_name = 'admin/ironic/index.html'
page_title = _("Ironic Bare Metal Provisioning")

View File

189
ironic_ui/test/settings.py Normal file
View File

@ -0,0 +1,189 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 importlib
import os
import six
from horizon.test.settings import * # noqa
from horizon.utils import secret_key
from openstack_dashboard import exceptions
DEBUG = True
TEMPLATE_DEBUG = DEBUG
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(TEST_DIR, ".."))
MEDIA_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'media'))
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'static'))
STATIC_URL = '/static/'
SECRET_KEY = secret_key.generate_or_read_from_file(
os.path.join(TEST_DIR, '.secret_key_store'))
ROOT_URLCONF = 'ironic_ui.test.urls'
TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'),
)
TEMPLATE_CONTEXT_PROCESSORS += (
'openstack_dashboard.context_processors.openstack',
)
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
'django.contrib.humanize',
'django_nose',
'openstack_auth',
'compressor',
'horizon',
'openstack_dashboard',
'openstack_dashboard.dashboards',
)
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
SITE_BRANDING = 'OpenStack'
HORIZON_CONFIG = {
"password_validator": {
"regex": '^.{8,18}$',
"help_text": "Password must be between 8 and 18 characters."
},
'user_home': None,
'help_url': "http://docs.openstack.org",
'exceptions': {'recoverable': exceptions.RECOVERABLE,
'not_found': exceptions.NOT_FOUND,
'unauthorized': exceptions.UNAUTHORIZED},
'angular_modules': [],
'js_files': [],
}
# Load the pluggable dashboard settings
from openstack_dashboard.utils import settings
dashboard_module_names = [
'openstack_dashboard.enabled',
'openstack_dashboard.local.enabled',
'ironic_ui.enabled',
]
dashboard_modules = []
# All dashboards must be enabled for the namespace to get registered, which is
# needed by the unit tests.
for module_name in dashboard_module_names:
module = importlib.import_module(module_name)
dashboard_modules.append(module)
for submodule in six.itervalues(settings.import_submodules(module)):
if getattr(submodule, 'DISABLED', None):
delattr(submodule, 'DISABLED')
INSTALLED_APPS = list(INSTALLED_APPS) # Make sure it's mutable
settings.update_dashboards(dashboard_modules, HORIZON_CONFIG, INSTALLED_APPS)
# Set to True to allow users to upload images to glance via Horizon server.
# When enabled, a file form field will appear on the create image form.
# See documentation for deployment considerations.
HORIZON_IMAGES_ALLOW_UPLOAD = True
AVAILABLE_REGIONS = [
('http://localhost:5000/v2.0', 'local'),
('http://remote:5000/v2.0', 'remote'),
]
OPENSTACK_API_VERSIONS = {
"identity": 3
}
OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain'
OPENSTACK_KEYSTONE_BACKEND = {
'name': 'native',
'can_edit_user': True,
'can_edit_group': True,
'can_edit_project': True,
'can_edit_domain': True,
'can_edit_role': True
}
OPENSTACK_CINDER_FEATURES = {
'enable_backup': True,
}
OPENSTACK_NEUTRON_NETWORK = {
'enable_lb': False,
'enable_firewall': False,
'enable_vpn': False,
}
OPENSTACK_HYPERVISOR_FEATURES = {
'can_set_mount_point': True,
# NOTE: as of Grizzly this is not yet supported in Nova so enabling this
# setting will not do anything useful
'can_encrypt_volumes': False
}
LOGGING['loggers']['openstack_dashboard'] = {
'handlers': ['test'],
'propagate': False,
}
LOGGING['loggers']['selenium'] = {
'handlers': ['test'],
'propagate': False,
}
LOGGING['loggers']['ironic_ui'] = {
'handlers': ['test'],
'propagate': False,
}
SECURITY_GROUP_RULES = {
'all_tcp': {
'name': 'ALL TCP',
'ip_protocol': 'tcp',
'from_port': '1',
'to_port': '65535',
},
'http': {
'name': 'HTTP',
'ip_protocol': 'tcp',
'from_port': '80',
'to_port': '80',
},
}
NOSE_ARGS = ['--nocapture',
'--nologcapture',
'--cover-package=openstack_dashboard',
'--cover-inclusive',
'--all-modules']
POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf")
POLICY_FILES = {
'identity': 'keystone_policy.json',
'compute': 'nova_policy.json'
}
# The openstack_auth.user.Token object isn't JSON-serializable ATM
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

View File

View File

@ -0,0 +1,25 @@
# Copyright 2015 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import horizon
from ironic_ui.ironic.panel import Ironic
from openstack_dashboard.test import helpers as test
class RegistrationTests(test.TestCase):
def test_registered(self):
dashboard = horizon.get_dashboard('admin')
panel = dashboard.get_panel('ironic')
self.assertEqual(panel.__class__, Ironic)

20
ironic_ui/test/urls.py Normal file
View File

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

View File

@ -1,28 +0,0 @@
# -*- 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.
"""
test_ironic-ui
----------------------------------
Tests for `ironic-ui` module.
"""
from ironic_ui.tests import base
class TestIronic_ui(base.TestCase):
def test_something(self):
pass

16
ironic_ui/tests/base.py → manage.py Normal file → Executable file
View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2016 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -15,9 +14,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslotest import base
import os
import sys
from django.core.management import execute_from_command_line # noqa
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"ironic_ui.test.settings")
execute_from_command_line(sys.argv)

View File

@ -1,6 +1,6 @@
[metadata]
name = ironic-ui
summary = cIronic plugin UI for Horizon to allow users to view and manage bare metal nodes, ports and drivers.
summary = Ironic plugin UI for Horizon to allow users to view and manage bare metal nodes, ports and drivers.
description-file =
README.rst
author = OpenStack
@ -16,7 +16,6 @@ classifier =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
[files]
@ -30,3 +29,17 @@ all_files = 1
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = ironic_ui/locale
domain = ironic-ui
[update_catalog]
domain = ironic-ui
output_dir = ironic_ui/locale
input_file = ironic_ui/locale/ironic-ui.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = ironic_ui/locale/ironic_ui.pot

View File

@ -1,4 +1,5 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@ -2,9 +2,13 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
# Require Horizon
-e git://github.com/openstack/horizon.git#egg=horizon
hacking>=0.10.2,<0.11 # Apache-2.0
coverage>=3.6 # Apache-2.0
django-nose>=1.2 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0

21
tox.ini
View File

@ -1,17 +1,20 @@
[tox]
minversion = 1.8
envlist = py34-constraints,py27-constraints,pypy-constraints,pep8-constraints
skipsdist = True
envlist = py34,py27,pep8
[testenv]
usedevelop = True
install_command =
constraints: {[testenv:common-constraints]install_command}
pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt
commands = python setup.py test --slowest --testr-args='{posargs}'
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = {toxinidir}/manage.py test ironic_ui --settings=ironic_ui.test.settings
[testenv:common-constraints]
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
@ -52,9 +55,7 @@ install_command = {[testenv:common-constraints]install_command}
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build