From 2874515c22ec361cb005cadcfaf9ee0666b6505d Mon Sep 17 00:00:00 2001 From: Dmytro Vasylchenko Date: Thu, 14 May 2015 14:27:02 +0300 Subject: [PATCH] Initial to akanda-horizon repo Change-Id: I3ee98229d2feea20abeb0fa996a9108bfba294a8 --- .gitignore | 12 + README.md | 21 ++ _80_admin_rug.py | 6 + _81_admin_rug_rugrouters.py | 10 + _82_admin_rug_rugtenants.py | 10 + akanda/rug_openstack_dashboard/__init__.py | 0 .../rug_openstack_dashboard/api/__init__.py | 0 akanda/rug_openstack_dashboard/api/rug.py | 139 +++++++++++ .../dashboards/__init__.py | 0 .../dashboards/admin/__init__.py | 0 .../dashboards/admin/rugrouters/__init__.py | 0 .../dashboards/admin/rugrouters/forms.py | 68 ++++++ .../dashboards/admin/rugrouters/models.py | 3 + .../dashboards/admin/rugrouters/panel.py | 13 + .../dashboards/admin/rugrouters/tables.py | 137 +++++++++++ .../templates/rugrouters/_poll.html | 21 ++ .../templates/rugrouters/_rebuild.html | 27 +++ .../templates/rugrouters/index.html | 17 ++ .../rugrouters/templates/rugrouters/poll.html | 11 + .../templates/rugrouters/rebuild.html | 11 + .../dashboards/admin/rugrouters/tests.py | 0 .../dashboards/admin/rugrouters/urls.py | 13 + .../dashboards/admin/rugrouters/views.py | 65 +++++ .../dashboards/admin/rugtenants/__init__.py | 0 .../dashboards/admin/rugtenants/models.py | 3 + .../dashboards/admin/rugtenants/panel.py | 13 + .../dashboards/admin/rugtenants/tables.py | 228 ++++++++++++++++++ .../templates/rugtenants/_rebuild.html | 27 +++ .../templates/rugtenants/index.html | 17 ++ .../templates/rugtenants/rebuild.html | 11 + .../templates/rugtenants/router-index.html | 17 ++ .../dashboards/admin/rugtenants/tests.py | 0 .../dashboards/admin/rugtenants/urls.py | 14 ++ .../dashboards/admin/rugtenants/views.py | 123 ++++++++++ setup.py | 12 + test-requirements.txt | 12 + tox.ini | 33 +++ 37 files changed, 1094 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 _80_admin_rug.py create mode 100644 _81_admin_rug_rugrouters.py create mode 100644 _82_admin_rug_rugtenants.py create mode 100644 akanda/rug_openstack_dashboard/__init__.py create mode 100644 akanda/rug_openstack_dashboard/api/__init__.py create mode 100644 akanda/rug_openstack_dashboard/api/rug.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/__init__.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/__init__.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/__init__.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/forms.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/models.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/panel.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tables.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_poll.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_rebuild.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/index.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/poll.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/rebuild.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tests.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/urls.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/views.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/__init__.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/models.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/panel.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tables.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/_rebuild.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/index.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/rebuild.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/router-index.html create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tests.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/urls.py create mode 100644 akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/views.py create mode 100644 setup.py create mode 100644 test-requirements.txt create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..733810a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.coverage +*.py[co] +*.sw[po] +*.pyc +*.egg-info +.venv +dropin.cache +.tox +build +dist +fpm.log* +packages/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4a3738 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Akanda Rug Horizon Extension + +1. Install module + + ``` + pip install akanda-rug-horizon + ``` + +2. Copy extension files from the project root folder to ```/etc/openstack_dashboard/local/enabled``` or to ```/opt/stack/horizon/openstack_dashboard/local/enabled``` folder + + ``` + cp _80_admin_rug.py _81_admin_rug_rugrouters.py _82_admin_rug_rugtenants.py /opt/stack/horizon/openstack_dashboard/local/enabled/ + ``` + +3. Specify rug management prefix, rug api port, and router image uuid in ```local_setting.py``` + + ``` + RUG_MANAGEMENT_PREFIX = "fdca:3ba5:a17a:acda::/64" + RUG_API_PORT = 44250 + ROUTER_IMAGE_UUID = "1e9c16f3-e070-47b7-b49c-ffcf38df5f9a" + ``` diff --git a/_80_admin_rug.py b/_80_admin_rug.py new file mode 100644 index 0000000..939cd50 --- /dev/null +++ b/_80_admin_rug.py @@ -0,0 +1,6 @@ +# The name of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'rug' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = 'RUG' +# The name of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'admin' diff --git a/_81_admin_rug_rugrouters.py b/_81_admin_rug_rugrouters.py new file mode 100644 index 0000000..fd6639c --- /dev/null +++ b/_81_admin_rug_rugrouters.py @@ -0,0 +1,10 @@ +# The name of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'rugrouters' +# 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 = 'rug' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'rug_openstack_dashboard.dashboards.admin.rugrouters.panel.Rugrouters' diff --git a/_82_admin_rug_rugtenants.py b/_82_admin_rug_rugtenants.py new file mode 100644 index 0000000..f7fdc5e --- /dev/null +++ b/_82_admin_rug_rugtenants.py @@ -0,0 +1,10 @@ +# The name of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'rugtenants' +# 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 = 'rug' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'rug_openstack_dashboard.dashboards.admin.rugtenants.panel.Rugtenants' diff --git a/akanda/rug_openstack_dashboard/__init__.py b/akanda/rug_openstack_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/api/__init__.py b/akanda/rug_openstack_dashboard/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/api/rug.py b/akanda/rug_openstack_dashboard/api/rug.py new file mode 100644 index 0000000..54d7809 --- /dev/null +++ b/akanda/rug_openstack_dashboard/api/rug.py @@ -0,0 +1,139 @@ +from datetime import datetime +from django.conf import settings +from horizon.utils import functions as utils +import netaddr +import requests as r +from openstack_dashboard.api.nova import novaclient +from openstack_dashboard.api.neutron import neutronclient + + +def _mgt_url(host, port, path): + if ':' in host: + host = '[%s]' % host + return 'http://%s:%s%s' % (host, port, path) + + +def _make_request(url): + try: + return r.put(url).ok + except r.RequestException: + return False + + +def _get_local_service_ip(management_prefix): + mgt_net = netaddr.IPNetwork(management_prefix) + rug_ip = '%s/%s' % (netaddr.IPAddress(mgt_net.first + 1), + mgt_net.prefixlen) + return rug_ip + + +class Router(object): + id = '' + name = '' + status = '' + latest = '' + image_name = '' + last_fetch = '' + booted = '' + + def __init__(self, **kw): + for k, v in kw.items(): + setattr(self, k, v) + + +class RugClient(object): + def __init__(self): + self.host = ( + _get_local_service_ip(settings.RUG_MANAGEMENT_PREFIX) + .split('/')[0] + ) + self.port = settings.RUG_API_PORT + self.image_uuid = settings.ROUTER_IMAGE_UUID + self.api_limit = getattr(settings, 'API_RESULT_LIMIT', 1000) + + def poll(self): + path = '/poll' + return _make_request(_mgt_url(self.host, self.port, path)) + + def config_reload(self): + path = '/config/reload' + return _make_request(_mgt_url(self.host, self.port, path)) + + def workers_debug(self): + path = '/workers/debug' + return _make_request(_mgt_url(self.host, self.port, path)) + + def router_debug(self, router_id): + path = '/router/debug/{router_id}'.format(router_id=router_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def router_manage(self, router_id): + path = '/router/manage/{router_id}'.format(router_id=router_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def router_update(self, router_id): + path = '/router/update/{router_id}'.format(router_id=router_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def router_rebuild(self, router_id, router_image_uuid=None): + if router_image_uuid: + path = ('/router/rebuild/{router_id}/--router_image_uuid/' + + '{router_image_uuid}').format( + router_id=router_id, + router_image_uuid=router_image_uuid + ) + else: + path = '/router/rebuild/{router_id}/'.format(router_id=router_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def tenant_debug(self, tenant_id): + path = '/tenant/debug/{tenant_id}'.format(tenant_id=tenant_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def tenant_manage(self, tenant_id): + path = '/tenant/manage/{tenant_id}'.format(tenant_id=tenant_id) + return _make_request(_mgt_url(self.host, self.port, path)) + + def get_routers(self, request, **search_opts): + page_size = utils.get_page_size(request) + paginate = False + if 'paginate' in search_opts: + paginate = search_opts.pop('paginate') + search_opts['limit'] = page_size + 1 + if 'tenant_id' not in search_opts: + search_opts['all_tenants'] = True + + routers_metadata = [] + nova_client = novaclient(request) + routers = ( + neutronclient(request) + .list_routers(**search_opts) + .get("routers", []) + ) + for router in routers: + search_opts = {'name': 'ak-' + router['id'], 'all_tenants': True} + instances = nova_client.servers.list(True, search_opts=search_opts) + instance = instances[0] if instances else None + image = ( + nova_client.images.get(instance.image['id']) + if instance else None + ) + routers_metadata.append(Router( + id=router['id'], + name=router['name'], + latest=image.id == self.image_uuid if image else '', + image_name=image.name if image else '', + last_fetch=datetime.utcnow(), + booted=instance.created if instance else '', + status=router['status'], + tenant_id=router['tenant_id'], + )) + + has_more_data = False + if paginate and len(routers_metadata) > page_size: + routers_metadata.pop(-1) + has_more_data = True + elif paginate and len(routers_metadata) == self.api_limit: + has_more_data = True + + return routers_metadata, has_more_data diff --git a/akanda/rug_openstack_dashboard/dashboards/__init__.py b/akanda/rug_openstack_dashboard/dashboards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/__init__.py b/akanda/rug_openstack_dashboard/dashboards/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/__init__.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/forms.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/forms.py new file mode 100644 index 0000000..df5d3a6 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/forms.py @@ -0,0 +1,68 @@ +from django.utils.translation import ugettext_lazy as _ +from django.template.defaultfilters import filesizeformat + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard.dashboards.project.images import utils + +from rug_openstack_dashboard.api.rug import RugClient + + +rc = RugClient() + + +def _image_choice_title(img): + gb = filesizeformat(img.size) + return '%s (%s)' % (img.name or img.id, gb) + + +class PollForm(forms.SelfHandlingForm): + def handle(self, request, data): + try: + rc.poll() + messages.success(request, _('Routers were polled')) + except Exception: + exceptions.handle(request, _('Unable to poll routers.')) + return True + + +class RebuildForm(forms.SelfHandlingForm): + router_id = forms.CharField(label=_("ID"), + widget=forms.HiddenInput(), + required=True) + router_name = forms.CharField(label=_("Router Name"), + widget=forms.HiddenInput(), + required=False) + attrs = {'class': 'image-selector'} + image = forms.ChoiceField(label=_("Select Image"), + widget=forms.SelectWidget(attrs=attrs, + data_attrs=('size', 'display-name'), + transform=_image_choice_title), + required=False) + + def __init__(self, request, *args, **kwargs): + super(RebuildForm, self).__init__(request, *args, **kwargs) + images = utils.get_available_images(request, request.user.tenant_id) + choices = [(image.id, image) for image in images] + if choices: + choices.insert(0, ("", _("Select Image"))) + else: + choices.insert(0, ("", _("No images available"))) + self.fields['image'].choices = choices + + def handle(self, request, data): + try: + if data['image']: + rc.router_rebuild(data['router_id'], data['image']) + else: + rc.router_rebuild(data['router_id']) + messages.success(request, + _('Rebuilt Router: %s.') % data['router_name']) + except Exception: + exceptions.handle( + request, + _('Unable to rebuild router %s.' % data['router_name']) + ) + return True diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/models.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/models.py new file mode 100644 index 0000000..1b3d5f9 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/models.py @@ -0,0 +1,3 @@ +""" +Stub file to work around django bug: https://code.djangoproject.com/ticket/7198 +""" diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/panel.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/panel.py new file mode 100644 index 0000000..8f9b42f --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/panel.py @@ -0,0 +1,13 @@ +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.admin import dashboard + + +class Rugrouters(horizon.Panel): + name = _("Routers") + slug = "rugrouters" + + +dashboard.Admin.register(Rugrouters) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tables.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tables.py new file mode 100644 index 0000000..d8dfe13 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tables.py @@ -0,0 +1,137 @@ +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables + +from rug_openstack_dashboard.api.rug import RugClient + + +rc = RugClient() + + +class ManageAction(tables.BatchAction): + name = "manage" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Manage Router", + u"Manage Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Managed Router", + u"Managed Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_manage(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class DebugAction(tables.BatchAction): + name = "debug" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Debug Router", + u"Debug Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Debugged Router", + u"Debugged Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_debug(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class UpdateAction(tables.BatchAction): + name = "update" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Update Router", + u"Update Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Updated Router", + u"Updated Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_update(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class PollAction(tables.LinkAction): + name = "poll" + verbose_name = _("Poll Routers") + url = "horizon:admin:rugrouters:poll" + classes = ("ajax-modal",) + + +class RebuildAction(tables.LinkAction): + name = "rebuild" + verbose_name = _("Rebuild Router") + url = "horizon:admin:rugrouters:rebuild" + classes = ("ajax-modal",) + + +class RouterTable(tables.DataTable): + name = tables.Column("name", verbose_name=_("Name"), + link="horizon:admin:routers:detail") + status = tables.Column("status", verbose_name=_("Status")) + latest = tables.Column('latest', verbose_name=_("Latest")) + image_name = tables.Column('image_name', verbose_name=_("Image Name")) + last_fetch = tables.Column('last_fetch', verbose_name=_("Last Fetch")) + booted = tables.Column('booted', verbose_name=_("Booted")) + + class Meta: + name = "routers" + verbose_name = _("Routers") + table_actions = (ManageAction, DebugAction, PollAction) + status_columns = ('status',) + row_actions = (RebuildAction, UpdateAction, ManageAction, DebugAction,) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_poll.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_poll.html new file mode 100644 index 0000000..4642b63 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_poll.html @@ -0,0 +1,21 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}poll_rugrouters_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:rugrouters:poll' %}{% endblock %} + +{% block modal_id %}poll_rugrouters_modal{% endblock %} + +{% block modal-header %}{% trans "Poll Routers" %}{% endblock %} + +{% block modal-body %} +
+

{% trans "All routers will be polled" %}

+
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_rebuild.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_rebuild.html new file mode 100644 index 0000000..b9d0153 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/_rebuild.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}rebuild_rugrouters_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:rugrouters:rebuild' router_id %}{% endblock %} + +{% block modal_id %}rebuild_rugrouters_modal{% endblock %} + +{% block modal-header %}{% trans "Rebuild Router" %}{% endblock %} + +{% block modal-body %} +
+
+ {% include "horizon/common/_form_fields.html" %} +
+
+
+

{% trans "Description" %}:

+

{% trans "Choose image to rebuild router" %}

+
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/index.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/index.html new file mode 100644 index 0000000..d630cdb --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/index.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Routers" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Routers") %} +{% endblock page_header %} + +{% block main %} + +
+
+ {{ table.render }} +
+
+ +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/poll.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/poll.html new file mode 100644 index 0000000..6b68462 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/poll.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Poll Routers" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Poll Routers") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/rugrouters/_poll.html" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/rebuild.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/rebuild.html new file mode 100644 index 0000000..7327267 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/templates/rugrouters/rebuild.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Rebuild Router" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Rebuild Router") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/rugrouters/_rebuild.html" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tests.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/urls.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/urls.py new file mode 100644 index 0000000..8eb4f01 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls import patterns +from django.conf.urls import url + +from rug_openstack_dashboard.dashboards.admin.rugrouters import views + +ROUTERS = r'^(?P[^/]+)/%s$' + +urlpatterns = patterns( + 'rug_openstack_dashboard.dashboards.admin.rugrouters.views', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^poll$', views.PollView.as_view(), name='poll'), + url(ROUTERS % 'rebuild', views.RebuildView.as_view(), name='rebuild'), +) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/views.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/views.py new file mode 100644 index 0000000..a58c9a6 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugrouters/views.py @@ -0,0 +1,65 @@ +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables +from horizon import forms +from horizon import exceptions + +from openstack_dashboard import api + +from rug_openstack_dashboard.dashboards.admin.rugrouters import \ + tables as router_tables +from rug_openstack_dashboard.dashboards.admin.rugrouters import \ + forms as rugrouters_forms +from rug_openstack_dashboard.api.rug import RugClient + + +rc = RugClient() + + +class IndexView(tables.DataTableView): + table_class = router_tables.RouterTable + template_name = 'admin/rugrouters/index.html' + + def has_prev_data(self, table): + return getattr(self, "_prev", False) + + def has_more_data(self, table): + return getattr(self, "_more", False) + + def get_data(self): + try: + routers, self._more = rc.get_routers(self.request) + return routers + except Exception: + url = reverse('horizon:admin:rugrouters:index') + exceptions.handle(self.request, + _('Unable to retrieve routers\' details.'), + redirect=url) + + +class PollView(forms.ModalFormView): + form_class = rugrouters_forms.PollForm + template_name = 'admin/rugrouters/poll.html' + success_url = reverse_lazy('horizon:admin:rugrouters:index') + + +class RebuildView(forms.ModalFormView): + form_class = rugrouters_forms.RebuildForm + template_name = 'admin/rugrouters/rebuild.html' + success_url = reverse_lazy('horizon:admin:rugrouters:index') + + def get_context_data(self, **kwargs): + self.router = api.neutron.router_get(self.request, + self.kwargs['router_id']) + context = super(RebuildView, self).get_context_data(**kwargs) + context["router_id"] = self.kwargs['router_id'] + context["router_name"] = self.router['name'] + return context + + def get_initial(self): + return { + 'router_id': self.kwargs['router_id'], + 'router_name': self.get_context_data()['router_name'] + } diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/__init__.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/models.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/models.py new file mode 100644 index 0000000..1b3d5f9 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/models.py @@ -0,0 +1,3 @@ +""" +Stub file to work around django bug: https://code.djangoproject.com/ticket/7198 +""" diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/panel.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/panel.py new file mode 100644 index 0000000..f0a6994 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/panel.py @@ -0,0 +1,13 @@ +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.admin import dashboard + + +class Rugtenants(horizon.Panel): + name = _("Tenants") + slug = "rugtenants" + + +dashboard.Admin.register(Rugtenants) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tables.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tables.py new file mode 100644 index 0000000..6c834ee --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tables.py @@ -0,0 +1,228 @@ +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import tables +from horizon import exceptions + +from rug_openstack_dashboard.api.rug import RugClient + + +rc = RugClient() + + +class TenantFilterAction(tables.FilterAction): + def filter(self, table, tenants, filter_string): + q = filter_string.lower() + + def comp(tenant): + if q in tenant.name.lower(): + return True + return False + + return filter(comp, tenants) + + +class TenantManageAction(tables.BatchAction): + name = "manage" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Manage Tenant", + u"Manage Tenants", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Managed Tenant", + u"Managed Tenants", + count + ) + + def action(self, request, obj_id): + try: + rc.tenant_manage(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class TenantDebugAction(tables.BatchAction): + name = "debug" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Debug Tenant", + u"Debug Tenants", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Debugged Tenant", + u"Debugged Tenants", + count + ) + + def action(self, request, obj_id): + try: + rc.tenant_debug(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class TenantsTable(tables.DataTable): + name = tables.Column('name', verbose_name=_('Name'), + link="horizon:admin:rugtenants:tenant") + description = tables.Column(lambda obj: getattr(obj, 'description', None), + verbose_name=_('Description')) + id = tables.Column('id', verbose_name=_('Project ID')) + enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True) + + class Meta: + name = "tenants" + verbose_name = _("Tenants") + row_actions = (TenantDebugAction, TenantManageAction, ) + table_actions = (TenantFilterAction, ) + pagination_param = "tenant_marker" + + +class RouterManageAction(tables.BatchAction): + name = "manage" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Manage Router", + u"Manage Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Managed Router", + u"Managed Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_manage(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class RouterDebugAction(tables.BatchAction): + name = "debug" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Debug Router", + u"Debug Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Debugged Router", + u"Debugged Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_debug(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class RouterUpdateAction(tables.BatchAction): + name = "update" + + def get_default_classes(self): + classes = super(tables.BatchAction, self).get_default_classes() + classes += ("btn-danger", ) + return classes + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Update Router", + u"Update Routers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Updated Router", + u"Updated Routers", + count + ) + + def action(self, request, obj_id): + try: + rc.router_update(obj_id) + except Exception: + msg = _('Failed to manage route %s') % obj_id + exceptions.handle(request, msg) + + +class RouterRebuildAction(tables.LinkAction): + name = "rebuild" + verbose_name = _("Rebuild Router") + classes = ("ajax-modal",) + + def get_link_url(self, datum=None): + return reverse("horizon:admin:rugtenants:rebuild", + kwargs={'tenant_id': datum.tenant_id, + 'router_id': datum.id}) + + +class TenantRouterTable(tables.DataTable): + name = tables.Column("name", verbose_name=_("Name"), + link="horizon:admin:routers:detail") + status = tables.Column("status", verbose_name=_("Status")) + latest = tables.Column('latest', verbose_name=_("Latest")) + image_name = tables.Column('image_name', verbose_name=_("Image Name")) + last_fetch = tables.Column('last_fetch', verbose_name=_("Last Fetch")) + booted = tables.Column('booted', verbose_name=_("Booted")) + + class Meta: + name = "routers" + verbose_name = _("Routers") + table_actions = (RouterManageAction, RouterDebugAction, ) + status_columns = ('status',) + row_actions = (RouterRebuildAction, RouterUpdateAction, + RouterManageAction, RouterDebugAction, ) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/_rebuild.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/_rebuild.html new file mode 100644 index 0000000..ef742f7 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/_rebuild.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}rebuild_rugtenants_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:rugtenants:rebuild' router_id=router_id tenant_id=tenant_id %}{% endblock %} + +{% block modal_id %}rebuild_rugtenants_modal{% endblock %} + +{% block modal-header %}{% trans "Rebuild Router" %}{% endblock %} + +{% block modal-body %} +
+
+ {% include "horizon/common/_form_fields.html" %} +
+
+
+

{% trans "Description" %}:

+

{% trans "Choose image to rebuild router" %}

+
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/index.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/index.html new file mode 100644 index 0000000..c7eaa94 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/index.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tenants" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Tenants") %} +{% endblock page_header %} + +{% block main %} + +
+
+ {{ table.render }} +
+
+ +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/rebuild.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/rebuild.html new file mode 100644 index 0000000..ced6da1 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/rebuild.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Rebuild Router" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Rebuild Router") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/rugtenants/_rebuild.html" %} +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/router-index.html b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/router-index.html new file mode 100644 index 0000000..5c52004 --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/templates/rugtenants/router-index.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Routers" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" %} +{% endblock page_header %} + +{% block main %} + +
+
+ {{ table.render }} +
+
+ +{% endblock %} diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tests.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/urls.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/urls.py new file mode 100644 index 0000000..39f7bee --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/urls.py @@ -0,0 +1,14 @@ +from django.conf.urls import patterns +from django.conf.urls import url + +from rug_openstack_dashboard.dashboards.admin.rugtenants import views + +TENANT = r'^(?P[^/]+)/%s$' + +urlpatterns = patterns( + 'rug_openstack_dashboard.dashboards.admin.rugtenants.views', + url(r'^$', views.TenantIndexView.as_view(), name='index'), + url(TENANT % '$', views.TenantRouterIndexView.as_view(), name='tenant'), + url(r'^(?P[^/]+)/(?P[^/]+)/rebuild$', + views.RebuildView.as_view(), name='rebuild'), +) diff --git a/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/views.py b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/views.py new file mode 100644 index 0000000..c5f021f --- /dev/null +++ b/akanda/rug_openstack_dashboard/dashboards/admin/rugtenants/views.py @@ -0,0 +1,123 @@ +from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy + +from horizon import tables +from horizon import exceptions +from horizon import messages +from horizon import forms + +from openstack_dashboard import api +from openstack_dashboard import policy + +from rug_openstack_dashboard.dashboards.admin.rugtenants import \ + tables as tenant_tables +from rug_openstack_dashboard.dashboards.admin.rugrouters import \ + forms as rugrouters_forms +from rug_openstack_dashboard.api.rug import RugClient + + +rc = RugClient() + + +class TenantIndexView(tables.DataTableView): + table_class = tenant_tables.TenantsTable + template_name = 'admin/rugtenants/index.html' + + def has_more_data(self, table): + return self._more + + def get_data(self): + tenants = [] + marker = self.request.GET.get( + tenant_tables.TenantsTable._meta.pagination_param, None) + domain_context = self.request.session.get('domain_context', None) + if policy.check((("admin", "admin:list_projects"),), self.request): + try: + tenants, self._more = api.keystone.tenant_list( + self.request, + domain=domain_context, + paginate=True, + marker=marker) + except Exception: + self._more = False + exceptions.handle(self.request, + _("Unable to retrieve project list.")) + elif policy.check((("admin", "identity:list_user_projects"),), + self.request): + try: + tenants, self._more = api.keystone.tenant_list( + self.request, + user=self.request.user.id, + paginate=True, + marker=marker, + admin=False) + except Exception: + self._more = False + exceptions.handle(self.request, + _("Unable to retrieve project information.")) + else: + self._more = False + msg = \ + _("Insufficient privilege level to view project information.") + messages.info(self.request, msg) + return tenants + + +class TenantRouterIndexView(tables.DataTableView): + table_class = tenant_tables.TenantRouterTable + template_name = 'admin/rugtenants/router-index.html' + + def has_prev_data(self, table): + return getattr(self, "_prev", False) + + def has_more_data(self, table): + return getattr(self, "_more", False) + + def get_context_data(self, **kwargs): + context = super(TenantRouterIndexView, self).get_context_data(**kwargs) + context["tenant_id"] = self.kwargs['tenant_id'] + tenant = api.keystone.tenant_get(self.request, + self.kwargs['tenant_id'], + admin=True) + context["title"] = "Routers of tenant \"%s\"" % tenant.name + return context + + def get_data(self): + try: + routers, self._more = rc.get_routers( + self.request, + tenant_id=self.kwargs['tenant_id'] + ) + return routers + except Exception: + url = reverse('horizon:admin:rugtenants:index') + exceptions.handle(self.request, + _('Unable to retrieve routers\' details.'), + redirect=url) + + +class RebuildView(forms.ModalFormView): + form_class = rugrouters_forms.RebuildForm + template_name = 'admin/rugtenants/rebuild.html' + success_url = reverse_lazy('horizon:admin:rugtenants:index') + + def get_success_url(self): + return reverse("horizon:admin:rugtenants:tenant", + args=(self.kwargs['tenant_id'],)) + + def get_context_data(self, **kwargs): + self.router = api.neutron.router_get(self.request, + self.kwargs['router_id']) + context = super(RebuildView, self).get_context_data(**kwargs) + context["router_id"] = self.kwargs['router_id'] + context["tenant_id"] = self.kwargs['tenant_id'] + context["router_name"] = self.router['name'] + return context + + def get_initial(self): + return { + 'router_id': self.kwargs['router_id'], + 'tenant_id': self.kwargs['tenant_id'], + 'router_name': self.get_context_data()['router_name'] + } diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6eb3bde --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +setup( + name='akanda-rug-horizon', + version='0.1', + packages=find_packages("akanda"), + package_dir={'': 'akanda'}, + install_requires=[ + 'requests', + ], +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..43d8826 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,12 @@ +unittest2 +nose +coverage +mock>=0.8.0 +pep8 +eventlet==0.12.1 +iso8601 +python-novaclient +WebOb==1.2.3 +mox==0.5.3 +testtools +fixtures diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f976513 --- /dev/null +++ b/tox.ini @@ -0,0 +1,33 @@ +[tox] +envlist = py27,pep8 + +[testenv] +distribute = False +setenv = VIRTUAL_ENV={envdir} +deps = -r{toxinidir}/test-requirements.txt +commands = nosetests --with-coverage --cover-package=akanda.rug {posargs} +sitepackages = False + +[tox:jenkins] + +[testenv:style] +deps = flake8 +setuptools_git>=0.4 +commands = flake8 akanda setup.py + +[testenv:pep8] +deps = {[testenv:style]deps} +commands = {[testenv:style]commands} + +[testenv:doc] +deps = Sphinx +commands = sphinx-build doc/source doc/build + +[testenv:cover] +setenv = NOSE_WITH_COVERAGE=1 + +[testenv:venv] +commands = {posargs} + +[flake8] +ignore = E123,E133,E226,E241,E242,E731