Horizon plugin to let the admin handle BGPVPN
Add a new panel BGPVPN in Admin section to display BGPVPN created. The page allow actions as create, delete or modify BGPVPN resources. Add an API file bgpvpn to handle the bgpvpn Python API via neutronclient. Change-Id: Iee2de53c8cb9d61f80033ab63485bdcf0e742abb Implements: blueprint horizon-admin-workflow
This commit is contained in:
47 changed files with 1726 additions and 28 deletions
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
@@ -27,6 +27,8 @@ pip-log.txt
nosetests.xml
.testrepository
.venv
bgpvpn_dashboard/test/.secret_key_store
*.lock
# Translations
*.mo
@@ -54,3 +56,4 @@ releasenotes/build
*~
.*.swp
.*sw?
.idea/*
0
bgpvpn_dashboard/__init__.py
Executable file
0
bgpvpn_dashboard/__init__.py
Executable file
0
bgpvpn_dashboard/api/__init__.py
Executable file
0
bgpvpn_dashboard/api/__init__.py
Executable file
114
bgpvpn_dashboard/api/bgpvpn.py
Executable file
114
bgpvpn_dashboard/api/bgpvpn.py
Executable file
@@ -0,0 +1,114 @@
# Copyright (c) 2016 Orange.
# 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 openstack_dashboard.api import neutron
LOG = logging.getLogger(__name__)
neutronclient = neutron.neutronclient
class Bgpvpn(neutron.NeutronAPIDictWrapper):
"""Wrapper for neutron bgpvpn."""
class NetworkAssociation(neutron.NeutronAPIDictWrapper):
"""Wrapper for neutron bgpvpn networks associations."""
class RouterAssociation(neutron.NeutronAPIDictWrapper):
"""Wrapper for neutron bgpvpn routers associations."""
def bgpvpns_list(request, **kwargs):
LOG.debug("bgpvpn_list(): params=%s", kwargs)
bgpvpns = neutronclient(request).list_bgpvpns(**kwargs).get('bgpvpns')
return [Bgpvpn(v) for v in bgpvpns]
def bgpvpn_get(request, bgpvpn_id, **kwargs):
LOG.debug("bgpvpn_get(): bgpvpnid=%s, kwargs=%s" % (bgpvpn_id, kwargs))
bgpvpn = neutronclient(request).show_bgpvpn(bgpvpn_id,
**kwargs).get('bgpvpn')
return Bgpvpn(bgpvpn)
def bgpvpn_create(request, **kwargs):
LOG.debug("bgpvpn_create(): params=%s" % kwargs)
body = {'bgpvpn': kwargs}
bgpvpn = neutronclient(request).create_bgpvpn(body=body)
return Bgpvpn(bgpvpn)
def bgpvpn_update(request, bgpvpn_id, **kwargs):
LOG.debug("bgpvpn_update(): bgpvpnid=%s, kwargs=%s" % (bgpvpn_id, kwargs))
body = {'bgpvpn': kwargs}
bgpvpn = neutronclient(request).update_bgpvpn(bgpvpn_id,
body=body).get('bgpvpn')
return Bgpvpn(bgpvpn)
def bgpvpn_delete(request, bgpvpn_id):
LOG.debug("bgpvpn_delete(): bgpvpnid=%s" % bgpvpn_id)
neutronclient(request).delete_bgpvpn(bgpvpn_id)
def network_association_list(request, bgpvpn_id, **kwargs):
LOG.debug("network_association_list(): params=%s", kwargs)
network_associations = neutronclient(
request).list_network_associations(
bgpvpn_id,
**kwargs).get('network_associations')
return [NetworkAssociation(v) for v in network_associations]
def network_association_create(request, bgpvpn_id, **kwargs):
LOG.debug("network_association_create(): bgpvpnid=%s params=%s" %
(bgpvpn_id, kwargs))
body = {'network_association': kwargs}
network_association = neutronclient(
request).create_network_association(bgpvpn_id, body=body)
return NetworkAssociation(network_association)
def network_association_delete(request, resource_id, bgpvpn_id):
LOG.debug("router_association_delete(): resource_id=%s bgpvpnid=%s" %
(resource_id, bgpvpn_id))
neutronclient(request).delete_network_association(resource_id, bgpvpn_id)
def router_association_list(request, bgpvpn_id, **kwargs):
LOG.debug("router_association_list(): params=%s", kwargs)
router_associations = neutronclient(
request).list_router_associations(bgpvpn_id,
**kwargs).get('router_associations')
return [RouterAssociation(v) for v in router_associations]
def router_association_create(request, bgpvpn_id, **kwargs):
LOG.debug("router_association_create(): bgpvpnid=%s params=%s" %
(bgpvpn_id, kwargs))
body = {'router_association': kwargs}
router_associations = neutronclient(
request).create_router_association(bgpvpn_id, body=body)
return RouterAssociation(router_associations)
def router_association_delete(request, resource_id, bgpvpn_id):
LOG.debug("router_association_delete(): resource_id=%s bgpvpnid=%s" %
(resource_id, bgpvpn_id))
neutronclient(request).delete_router_association(resource_id, bgpvpn_id)
0
bgpvpn_dashboard/common/__init__.py
Executable file
0
bgpvpn_dashboard/common/__init__.py
Executable file
31
bgpvpn_dashboard/common/bgpvpn.py
Executable file
31
bgpvpn_dashboard/common/bgpvpn.py
Executable file
@@ -0,0 +1,31 @@
# Copyright (c) 2016 Orange.
# 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 re
from django.utils.translation import ugettext_lazy as _
ROUTE_TARGET_HELP = _("A single BGP Route Target or a "
"comma-separated list of BGP Route Target. Example: "
"64512:1 or 64512:1,64512:2,64512:3")
RT_FORMAT_ATTRIBUTES = ('route_targets', 'import_targets', 'export_targets')
def format_rt(route_targets):
if route_targets:
return re.compile(" *, *").split(route_targets)
else:
return route_targets
0
bgpvpn_dashboard/dashboards/__init__.py
Executable file
0
bgpvpn_dashboard/dashboards/__init__.py
Executable file
0
bgpvpn_dashboard/dashboards/admin/__init__.py
Executable file
0
bgpvpn_dashboard/dashboards/admin/__init__.py
Executable file
0
bgpvpn_dashboard/dashboards/admin/bgpvpn/__init__.py
Executable file
0
bgpvpn_dashboard/dashboards/admin/bgpvpn/__init__.py
Executable file
203
bgpvpn_dashboard/dashboards/admin/bgpvpn/forms.py
Executable file
203
bgpvpn_dashboard/dashboards/admin/bgpvpn/forms.py
Executable file
@@ -0,0 +1,203 @@
# Copyright (c) 2016 Orange.
# 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 collections
import logging
from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common
from networking_bgpvpn.neutron.services.common import constants
LOG = logging.getLogger(__name__)
RT_REGEX = constants.RT_REGEX[1:-1]
RTS_REGEX = '^%s( *, *%s)*$' % (RT_REGEX, RT_REGEX)
class BgpvpnAttributes(forms.SelfHandlingForm):
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
route_targets = forms.CharField(
max_length=255,
validators=[RegexValidator(regex=RTS_REGEX,
message=_("Route targets is not valid"))],
label=_("Route targets"),
required=False,
help_text=bgpvpn_common.ROUTE_TARGET_HELP)
import_targets = forms.CharField(
max_length=255,
validators=[RegexValidator(regex=RTS_REGEX,
message=_("Import targets is not valid"))],
label=_("Import targets"),
required=False,
help_text=bgpvpn_common.ROUTE_TARGET_HELP + ' To use only on import.')
export_targets = forms.CharField(
max_length=255,
validators=[RegexValidator(regex=RTS_REGEX,
message=_("Export targets is not valid"))],
label=_("Export targets"),
required=False,
help_text=bgpvpn_common.ROUTE_TARGET_HELP + ' To use only on export.')
def clean(self):
cleaned_data = super(BgpvpnAttributes, self).clean()
# Route targets can be empty
if not cleaned_data.get('route_targets'):
cleaned_data['route_targets'] = None
if not cleaned_data.get('import_targets'):
cleaned_data['import_targets'] = None
if not cleaned_data.get('export_targets'):
cleaned_data['export_targets'] = None
return cleaned_data
@staticmethod
def handle_data(data, action):
params = {}
for key in bgpvpn_common.RT_FORMAT_ATTRIBUTES:
params[key] = bgpvpn_common.format_rt(data.pop(key))
params.update(data)
if action == 'update':
del params['bgpvpn_id']
del params['type']
del params['tenant_id']
return params
@staticmethod
def order_fields(fields, fields_order):
if 'keyOrder' in fields:
fields.keyOrder = fields_order
else:
fields = collections.OrderedDict(
(k, fields[k]) for k in fields_order)
return fields
class CreateBgpVpn(BgpvpnAttributes):
tenant_id = forms.ChoiceField(label=_("Project"))
type = forms.ChoiceField(choices=[("l3", _('l3')),
("l2", _('l2'))],
label=_("Type"),
help_text=_("The type of VPN "
" and the technology behind it."))
def __init__(self, request, *args, **kwargs):
super(CreateBgpVpn, self).__init__(request, *args, **kwargs)
fields_order = ['name', 'tenant_id', 'type',
'route_targets', 'import_targets', 'export_targets']
self.fields = self.order_fields(self.fields, fields_order)
tenant_choices = [('', _("Select a project"))]
tenants, has_more = api.keystone.tenant_list(request)
for tenant in tenants:
if tenant.enabled:
tenant_choices.append((tenant.id, tenant.name))
self.fields['tenant_id'].choices = tenant_choices
def clean(self):
cleaned_data = super(CreateBgpVpn, self).clean()
name = cleaned_data.get('name')
tenant_id = cleaned_data.get('tenant_id')
try:
bgpvpns = bgpvpn_api.bgpvpns_list(self.request,
name=name,
tenant_id=tenant_id)
except Exception:
msg = _('Unable to get BGPVPN with name %s') % name
exceptions.check_message(["Connection", "refused"], msg)
raise
if bgpvpns:
raise forms.ValidationError(
_('The name "%s" is already used by another BGPVPN.') % name)
return cleaned_data
def handle(self, request, data):
try:
params = self.handle_data(data, 'create')
bgpvpn = bgpvpn_api.bgpvpn_create(request, **params)
msg = _('BGPVPN %s was successfully created.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
return bgpvpn
except Exception:
redirect = reverse('horizon:admin:bgpvpn:index')
msg = _('Failed to create the BGPVPN %s') % data['name']
exceptions.handle(request, msg, redirect=redirect)
class EditDataBgpVpn(BgpvpnAttributes):
bgpvpn_id = forms.CharField(label=_("ID"), widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
type = forms.CharField(label=_("Type"), widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
tenant_id = forms.CharField(widget=forms.HiddenInput())
def __init__(self, request, *args, **kwargs):
super(EditDataBgpVpn, self).__init__(request, *args, **kwargs)
fields_order = ['name', 'bgpvpn_id', 'tenant_id', 'type',
'route_targets', 'import_targets', 'export_targets']
self.fields = self.order_fields(self.fields, fields_order)
def clean(self):
cleaned_data = super(EditDataBgpVpn, self).clean()
name = cleaned_data.get('name')
bgpvpn_id = cleaned_data.get('bgpvpn_id')
tenant_id = cleaned_data.get('tenant_id')
try:
bgpvpns = bgpvpn_api.bgpvpns_list(self.request,
name=name,
tenant_id=tenant_id)
except Exception:
msg = _('Unable to get BGPVPN with name %s') % name
exceptions.check_message(["Connection", "refused"], msg)
raise
if bgpvpns:
for bgpvpn in bgpvpns:
if bgpvpn.id != bgpvpn_id:
raise forms.ValidationError(
_('The name "%s" is already used by another BGPVPN.') %
name)
return cleaned_data
def handle(self, request, data):
try:
params = self.handle_data(data, 'update')
bgpvpn = bgpvpn_api.bgpvpn_update(request,
data['bgpvpn_id'],
**params)
msg = _('BGPVPN %s was successfully updated.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
except Exception:
redirect = reverse('horizon:admin:bgpvpn:index')
msg = _('Failed to modify BGPVPN %s') % data['name']
exceptions.handle(request, msg, redirect=redirect)
return False
return bgpvpn
26
bgpvpn_dashboard/dashboards/admin/bgpvpn/panel.py
Executable file
26
bgpvpn_dashboard/dashboards/admin/bgpvpn/panel.py
Executable file
@@ -0,0 +1,26 @@
# Copyright (c) 2016 Orange.
# 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
from openstack_dashboard.dashboards.admin import dashboard
class BGPVPNInterconnections(horizon.Panel):
name = _("BGPVPN Interconnections")
slug = "bgpvpn"
dashboard.Admin.register(BGPVPNInterconnections)
162
bgpvpn_dashboard/dashboards/admin/bgpvpn/tables.py
Executable file
162
bgpvpn_dashboard/dashboards/admin/bgpvpn/tables.py
Executable file
@@ -0,0 +1,162 @@
# Copyright (c) 2016 Orange.
# 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.core.urlresolvers import reverse
from django.utils import html
from django.utils.http import urlencode
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from openstack_dashboard import policy
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
LOG = logging.getLogger(__name__)
class DeleteBgpvpn(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(u"Delete BGPVPN",
u"Delete BGPVPNs",
count)
@staticmethod
def action_past(count):
return ungettext_lazy(u"Deleted BGPVPN",
u"Deleted BGPVPNs",
count)
def delete(self, request, obj_id):
try:
bgpvpn_api.bgpvpn_delete(request, obj_id)
except Exception:
msg = _('Failed to delete BGPVPN %s') % obj_id
LOG.info(msg)
redirect = reverse('horizon:admin:bgpvpn:index')
exceptions.handle(request, msg, redirect=redirect)
class CreateBgpVpn(tables.LinkAction):
name = "create"
verbose_name = _("Create BGPVPN")
url = "horizon:admin:bgpvpn:create"
classes = ("ajax-modal",)
icon = "plus"
class EditInfoBgpVpn(tables.LinkAction):
name = "update_info"
verbose_name = _("Edit BGPVPN")
url = "horizon:admin:bgpvpn:edit"
classes = ("ajax-modal",)
icon = "pencil"
class UpdateNetworkAssociations(tables.LinkAction):
name = "update_network_associations"
verbose_name = _("Update Network Associations")
url = "horizon:admin:bgpvpn:update-associations"
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, bgpvpn):
step = 'update_bgpvpn_network'
base_url = reverse(self.url, args=[bgpvpn.id])
param = urlencode({"step": step})
return "?".join([base_url, param])
class UpdateRouterAssociations(tables.LinkAction):
name = "update_router_associations"
verbose_name = _("Update Router Associations")
url = "horizon:admin:bgpvpn:update-associations"
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, bgpvpn):
step = 'update_bgpvpn_router'
base_url = reverse(self.url, args=[bgpvpn.id])
param = urlencode({"step": step})
return "?".join([base_url, param])
def get_route_targets(bgpvpn):
return ', '.join(rt for rt in bgpvpn.route_targets)
def get_import_targets(bgpvpn):
return ', '.join(it for it in bgpvpn.import_targets)
def get_export_targets(bgpvpn):
return ', '.join(et for et in bgpvpn.export_targets)
def get_network_url(network):
url = reverse('horizon:admin:networks:detail', args=[network.id])
instance = '<a href=%s>%s</a>' % (url, html.escape(network.name_or_id))
return instance
def get_router_url(router):
url = reverse('horizon:admin:routers:detail', args=[router.id])
instance = '<a href=%s>%s</a>' % (url, html.escape(router.name_or_id))
return instance
def get_tenant(bgpvpn):
return bgpvpn.tenant.name
class NetworksColumn(tables.Column):
def get_raw_data(self, bgpvpn):
networks = [get_network_url(network) for network in bgpvpn.networks]
return safestring.mark_safe(', '.join(networks))
class RoutersColumn(tables.Column):
def get_raw_data(self, bgpvpn):
routers = [get_router_url(router) for router in bgpvpn.routers]
return safestring.mark_safe(', '.join(routers))
class BgpvpnTable(tables.DataTable):
tenant_id = tables.Column(get_tenant, verbose_name=_("Project"))
name = tables.Column("name_or_id",
verbose_name=_("Name"),
link=("horizon:admin:bgpvpn:detail"))
type = tables.Column("type", verbose_name=_("Type"))
route_targets = tables.Column(get_route_targets,
verbose_name=_("Route Targets"))
import_targets = tables.Column(get_import_targets,
verbose_name=_("Import Targets"))
export_targets = tables.Column(get_export_targets,
verbose_name=_("Export Targets"))
networks = NetworksColumn("networks", verbose_name=_("Networks"))
routers = RoutersColumn("routers", verbose_name=_("Routers"))
class Meta(object):
name = "bgpvpns"
verbose_name = _("BGPVPN")
table_actions = (CreateBgpVpn, DeleteBgpvpn)
row_actions = (EditInfoBgpVpn,
UpdateNetworkAssociations,
UpdateRouterAssociations,
DeleteBgpvpn)
26
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_create.html
Executable file
26
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_create.html
Executable file
@@ -0,0 +1,26 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}create_bgpvpn_form{% endblock %}
{% block form_action %}{% url 'horizon:admin:bgpvpn:create' %}{% endblock %}
{% block modal_id %}create_bgpvpn_modal{% endblock %}
{% block modal-header %}{% trans "Create BGPVPN" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Create a new BGP VPN for any project as you need."%}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create BGPVPN" %}" />
<a href="{% url 'horizon:admin:bgpvpn:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}
@@ -0,0 +1,58 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "BGPVPN Name" %}</dt>
<dd>{{ bgpvpn.name }}</dd>
<dt>{% trans "BGPVPN ID" %}</dt>
<dd>{{ bgpvpn.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ bgpvpn.type }}</dd>
<dt>{% trans "Route Targets" %}</dt>
<dd>
{% for rt in bgpvpn.route_targets %}
{{ rt }}
{% endfor %}
</dd>
<dt>{% trans "Import Targets" %}</dt>
<dd>
{% for it in bgpvpn.import_targets %}
{{ it }}
{% endfor %}
</dd>
<dt>{% trans "Export Targets" %}</dt>
<dd>
{% for et in bgpvpn.export_targets %}
{{ et }}
{% endfor %}
</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ bgpvpn.tenant_id }}</dd>
<dt>
{% if bgpvpn.networks|length > 1 %}
{% trans "Associated Networks" %}
{% else %}
{% trans "Associated Network" %}
{% endif %}
</dt>
{% for network in bgpvpn.networks %}
<dd>
{% url 'horizon:admin:networks:detail' network as network_url %}
<a href="{{ network_url }}">{{ network }}</a>
</dd>
{% endfor %}
<dt>
{% if bgpvpn.routers|length > 1 %}
{% trans "Associated Routers" %}
{% else %}
{% trans "Associated Router" %}
{% endif %}
</dt>
{% for router in bgpvpn.routers %}
<dd>
{% url 'horizon:admin:routers:detail' router as router_url %}
<a href="{{ router_url }}">{{ router }}</a>
</dd>
{% endfor %}
</dl>
</div>
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_modify.html
Executable file
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_modify.html
Executable file
@@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of your BGP VPN here." %}</p>
{% endblock %}
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/create.html
Executable file
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/create.html
Executable file
@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create BGPVPN" %}{% endblock %}
{% block main %}
{% include "admin/bgpvpn/_create.html" %}
{% endblock %}
16
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/detail.html
Executable file
16
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/detail.html
Executable file
@@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "BGPVPN Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_detail_header.html" %}
{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include "admin/bgpvpn/_detail_overview.html" %}
</div>
</div>
{% endblock %}
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/index.html
Executable file
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/index.html
Executable file
@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "BGP VPNs" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/modify.html
Executable file
7
bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/modify.html
Executable file
@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update BGPVPN" %}{% endblock %}
{% block main %}
{% include "admin/bgpvpn/_modify.html" %}
{% endblock %}
@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Edit BGPVPN associations" %}{% endblock %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}
33
bgpvpn_dashboard/dashboards/admin/bgpvpn/urls.py
Executable file
33
bgpvpn_dashboard/dashboards/admin/bgpvpn/urls.py
Executable file
@@ -0,0 +1,33 @@
# Copyright (c) 2016 Orange.
# 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 patterns
from django.conf.urls import url
import bgpvpn_dashboard.dashboards.admin.bgpvpn.views as bgpvpn_views
BGPVPN = r'^(?P<bgpvpn_id>[^/]+)/%s$'
urlpatterns = patterns(
'',
url(r'^$', bgpvpn_views.IndexView.as_view(), name='index'),
url(r'^create/$', bgpvpn_views.CreateView.as_view(), name='create'),
url(BGPVPN % 'edit', bgpvpn_views.EditDataView.as_view(), name='edit'),
url(BGPVPN % 'update-associations',
bgpvpn_views.UpdateAssociationsView.as_view(),
name='update-associations'),
url(r'^(?P<bgpvpn_id>[^/]+)/detail/$',
bgpvpn_views.DetailProjectView.as_view(), name='detail'),
)
149
bgpvpn_dashboard/dashboards/admin/bgpvpn/views.py
Executable file
149
bgpvpn_dashboard/dashboards/admin/bgpvpn/views.py
Executable file
@@ -0,0 +1,149 @@
# Copyright (c) 2016 Orange.
# 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.utils import memoized
from horizon import views
from horizon import workflows
from openstack_dashboard import api
import bgpvpn_dashboard.api.bgpvpn as bgpvpn_api
from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common
from bgpvpn_dashboard.dashboards.admin.bgpvpn import forms as bgpvpn_forms
from bgpvpn_dashboard.dashboards.admin.bgpvpn import tables as bgpvpn_tables
from bgpvpn_dashboard.dashboards.admin.bgpvpn import workflows \
as bgpvpn_workflows
class IndexView(tables.DataTableView):
table_class = bgpvpn_tables.BgpvpnTable
template_name = 'admin/bgpvpn/index.html'
page_title = _("BGP VPNs")
def get_data(self):
bgpvpns = bgpvpn_api.bgpvpns_list(self.request)
networks = api.neutron.network_list(self.request)
routers = api.neutron.router_list(self.request)
for bgpvpn in bgpvpns:
networks_list = [network for network in networks
if network.id in bgpvpn.networks]
routers_list = [router for router in routers
if router.id in bgpvpn.routers]
bgpvpn.tenant = api.keystone.tenant_get(self.request,
bgpvpn.tenant_id)
bgpvpn.networks = networks_list
bgpvpn.routers = routers_list
return bgpvpns
class CreateView(forms.ModalFormView):
form_class = bgpvpn_forms.CreateBgpVpn
template_name = 'admin/bgpvpn/create.html'
success_url = reverse_lazy('horizon:admin:bgpvpn:index')
page_title = _("Create BGPVPN")
class EditDataView(forms.ModalFormView):
form_class = bgpvpn_forms.EditDataBgpVpn
form_id = "edit_data_bgpvpn_form"
modal_header = _("Edit BGPVPN")
submit_label = _("Save Changes")
submit_url = 'horizon:admin:bgpvpn:edit'
success_url = reverse_lazy('horizon:admin:bgpvpn:index')
template_name = 'admin/bgpvpn/modify.html'
page_title = _("Edit BGPVPN")
@staticmethod
def _join_rts(route_targets_list):
return ','.join(route_targets_list)
def get_context_data(self, **kwargs):
context = super(EditDataView, self).get_context_data(**kwargs)
args = (self.kwargs['bgpvpn_id'],)
context["bgpvpn_id"] = self.kwargs['bgpvpn_id']
context["submit_url"] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
bgpvpn_id = self.kwargs['bgpvpn_id']
try:
# Get initial bgpvpn information
bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id)
except Exception:
exceptions.handle(
self.request,
_('Unable to retrieve BGPVPN details.'),
redirect=reverse_lazy("horizon:project:bgpvpn:index"))
data = bgpvpn.to_dict()
for attribute in bgpvpn_common.RT_FORMAT_ATTRIBUTES:
data[attribute] = self._join_rts(bgpvpn[attribute])
data['bgpvpn_id'] = data.pop('id')
return data
class UpdateAssociationsView(workflows.WorkflowView):
workflow_class = bgpvpn_workflows.UpdateBgpVpnAssociations
template_name = 'admin/bgpvpn/update-associations.html'
page_title = _("Edit BGPVPN associations")
def get_initial(self):
bgpvpn_id = self.kwargs['bgpvpn_id']
try:
# Get initial bgpvpn information
bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id)
except Exception:
exceptions.handle(
self.request,
_('Unable to retrieve BGPVPN details.'),
redirect=reverse_lazy("horizon:admin:bgpvpn:index"))
data = bgpvpn.to_dict()
data['bgpvpn_id'] = data.pop('id')
return data
class DetailProjectView(views.HorizonTemplateView):
template_name = 'admin/bgpvpn/detail.html'
page_title = "{{ bgpvpn.name }}"
def get_context_data(self, **kwargs):
context = super(DetailProjectView, self).get_context_data(**kwargs)
bgpvpn = self.get_data()
table = bgpvpn_tables.BgpvpnTable(self.request)
context["bgpvpn"] = bgpvpn
context["url"] = self.get_redirect_url()
context["actions"] = table.render_row_actions(bgpvpn)
return context
@memoized.memoized_method
def get_data(self):
try:
bgpvpn_id = self.kwargs['bgpvpn_id']
bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id)
except Exception:
redirect = self.get_redirect_url()
exceptions.handle(self.request,
_('Unable to retrieve BGPVPN details.'),
redirect=redirect)
return bgpvpn
def get_redirect_url(self):
return reverse('horizon:admin:bgpvpn:index')
235
bgpvpn_dashboard/dashboards/admin/bgpvpn/workflows.py
Executable file
235
bgpvpn_dashboard/dashboards/admin/bgpvpn/workflows.py
Executable file
@@ -0,0 +1,235 @@
# Copyright (c) 2016 Orange.
# 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 _
from horizon import exceptions
from horizon import forms
from horizon import workflows
from openstack_dashboard import api
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
class UpdateAssociations(workflows.MembershipAction):
def __init__(self, request, resource_type, *args, **kwargs):
super(UpdateAssociations, self).__init__(request,
*args,
**kwargs)
err_msg = _('Unable to retrieve %ss list. '
'Please try again later.') % resource_type
context = args[0]
default_role_field_name = self.get_default_role_field_name()
self.fields[default_role_field_name] = forms.CharField(required=False)
self.fields[default_role_field_name].initial = 'member'
field_name = self.get_member_field_name('member')
self.fields[field_name] = forms.MultipleChoiceField(required=False)
# Get list of available resources.
all_resources = []
try:
if resource_type == 'router':
all_resources = api.neutron.router_list(
request,
tenant_id=context.get('tenant_id'))
elif resource_type == 'network':
all_resources = api.neutron.network_list_for_tenant(
request,
context.get('tenant_id'))
else:
raise Exception(
'Resource type %s is not supported' % resource_type)
except Exception:
exceptions.handle(request, err_msg)
resources_list = [(resource.id, resource.name)
for resource in all_resources]
self.fields[field_name].choices = resources_list
bgpvpn_id = context.get('bgpvpn_id')
try:
if bgpvpn_id:
associations = []
list_method = getattr(bgpvpn_api, '%s_association_list' %
resource_type)
associations = [
getattr(association, '%s_id' %
resource_type) for association in
list_method(request, bgpvpn_id)
]
except Exception:
exceptions.handle(request, err_msg)
self.fields[field_name].initial = associations
class UpdateBgpVpnRoutersAction(UpdateAssociations):
def __init__(self, request, *args, **kwargs):
super(UpdateBgpVpnRoutersAction, self).__init__(request,
'router',
*args,
**kwargs)
class Meta(object):
name = _("Router Associations")
slug = "update_bgpvpn_router"
class UpdateBgpVpnRouters(workflows.UpdateMembersStep):
action_class = UpdateBgpVpnRoutersAction
help_text = _("Select the routers to be associated.")
available_list_title = _("All Routers")
members_list_title = _("Selected Routers")
no_available_text = _("No router found.")
no_members_text = _("No router selected.")
show_roles = False
depends_on = ("bgpvpn_id", "tenant_id", "name")
contributes = ("routers_association",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['routers_association'] = data.get(member_field_name, [])
return context
class UpdateBgpVpnNetworksAction(UpdateAssociations):
def __init__(self, request, *args, **kwargs):
super(UpdateBgpVpnNetworksAction, self).__init__(request,
'network',
*args,
**kwargs)
class Meta(object):
name = _("Network Associations")
slug = "update_bgpvpn_network"
class UpdateBgpVpnNetworks(workflows.UpdateMembersStep):
action_class = UpdateBgpVpnNetworksAction
help_text = _("Select the networks to be associated.")
available_list_title = _("All Networks")
members_list_title = _("Selected Networks")
no_available_text = _("No network found.")
no_members_text = _("No network selected.")
show_roles = False
depends_on = ("bgpvpn_id", "tenant_id", "name")
contributes = ("networks_association",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['networks_association'] = data.get(member_field_name, [])
return context
class UpdateBgpVpnAssociations(workflows.Workflow):
slug = "update_bgpvpn_associations"
name = _("Edit BGPVPN associations")
finalize_button_name = _("Save")
success_message = _('Modified BGPVPN associations "%s".')
failure_message = _('Unable to modify BGPVPN associations "%s".')
success_url = "horizon:admin:bgpvpn:index"
default_steps = (UpdateBgpVpnNetworks,
UpdateBgpVpnRouters)
def format_status_message(self, message):
return message % self.context['name']
@staticmethod
def _handle_type(request, data, association_type):
tenant_id = data["tenant_id"]
list_method = getattr(bgpvpn_api,
'%s_association_list' % association_type)
associations = data["%ss_association" % association_type]
try:
old_associations = [
getattr(association,
'%s_id' % association_type) for association in
list_method(request, data['bgpvpn_id'])]
except Exception:
exceptions.handle(request,
_('Unable to retrieve %ss associations') %
association_type)
raise
to_remove_associations = list(set(old_associations) -
set(associations))
to_add_associations = list(set(associations) -
set(old_associations))
# If new resource added to the list
if len(to_add_associations) > 0:
for resource in to_add_associations:
try:
create_asso = getattr(bgpvpn_api,
'%s_association_create' %
association_type)
params = dict()
params['%s_id' % association_type] = resource
params['tenant_id'] = tenant_id
create_asso(request,
data['bgpvpn_id'],
**params)
except Exception as e:
exceptions.handle(
request,
_('Unable to associate {}{}{}').format(
association_type,
resource, str(e)))
raise
# If resource has been deleted from the list
if len(to_remove_associations) > 0:
for resource in to_remove_associations:
try:
list_method = getattr(bgpvpn_api,
'%s_association_list' %
association_type)
asso_list = list_method(request, data['bgpvpn_id'])
for association in asso_list:
if getattr(association,
'%s_id' % association_type) == resource:
delete_method = getattr(bgpvpn_api,
'%s_association_delete' %
association_type)
delete_method(request,
association.id, data['bgpvpn_id'])
except Exception:
exceptions.handle(
request,
_('Unable to disassociate {}s {}').format(
association_type,
resource))
raise
def handle(self, request, data):
action = False
try:
if 'networks_association' in data:
self._handle_type(request, data, 'network')
action = True
if 'routers_association' in data:
self._handle_type(request, data, 'router')
action = True
if not action:
raise Exception('Associations type is not supported')
except Exception:
return False
return True
10
bgpvpn_dashboard/enabled/_2115_admin_bgpvpn_panel.py
Executable file
10
bgpvpn_dashboard/enabled/_2115_admin_bgpvpn_panel.py
Executable file
@@ -0,0 +1,10 @@
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'BGPVPN Interconnections'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'admin'
# The slug of the panel group the PANEL is associated with.
PANEL_GROUP = 'admin'
# Python panel class of the PANEL to be added.
ADD_PANEL = ('bgpvpn_dashboard.'
'dashboards.admin.bgpvpn.panel.BGPVPNInterconnections')
0
bgpvpn_dashboard/enabled/__init__.py
Executable file
0
bgpvpn_dashboard/enabled/__init__.py
Executable file
0
bgpvpn_dashboard/test/__init__.py
Executable file
0
bgpvpn_dashboard/test/__init__.py
Executable file
0
bgpvpn_dashboard/test/api_tests/__init__.py
Executable file
0
bgpvpn_dashboard/test/api_tests/__init__.py
Executable file
170
bgpvpn_dashboard/test/api_tests/bgpvpn_tests.py
Executable file
170
bgpvpn_dashboard/test/api_tests/bgpvpn_tests.py
Executable file
@@ -0,0 +1,170 @@
# Copyright (c) 2016 Orange.
# 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 openstack_dashboard.test import helpers as test
from bgpvpn_dashboard import api
from bgpvpn_dashboard.test import helpers as bgpvpn_test
from neutronclient.v2_0.client import Client as neutronclient
class BgpvpnApiTests(bgpvpn_test.APITestCase):
@test.create_stubs({neutronclient: ('list_ext',)})
def test_bgpvpn_list(self):
bgpvpns = {'bgpvpns': self.api_bgpvpns.list()}
neutronclient.list_ext('bgpvpns',
'/bgpvpn/bgpvpns', True).AndReturn(bgpvpns)
self.mox.ReplayAll()
ret_val = api.bgpvpn.bgpvpns_list(self.request)
for n in ret_val:
self.assertIsInstance(n, api.bgpvpn.Bgpvpn)
@test.create_stubs({neutronclient: ('create_ext',)})
def test_bgpvpn_create(self):
bgpvpn = self.bgpvpns.first()
data = {'name': bgpvpn.name,
'route_targets': bgpvpn.route_targets,
'tenant_id': bgpvpn.tenant_id}
ret_dict = {'bgpvpn': data}
neutronclient.create_ext('/bgpvpn/bgpvpns',
ret_dict).AndReturn(ret_dict)
self.mox.ReplayAll()
ret_val = api.bgpvpn.bgpvpn_create(self.request, **data)
self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn)
@test.create_stubs({neutronclient: ('show_ext',)})
def test_bgpvpn_get(self):
bgpvpn = self.bgpvpns.first()
ret_dict = {'bgpvpn': self.api_bgpvpns.first()}
neutronclient.show_ext('/bgpvpn/bgpvpns/%s',
bgpvpn.id).AndReturn(ret_dict)
self.mox.ReplayAll()
ret_val = api.bgpvpn.bgpvpn_get(self.request, bgpvpn.id)
self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn)
@test.create_stubs({neutronclient: ('update_ext',)})
def test_bgpvpn_update(self):
bgpvpn = self.bgpvpns.first()
bgpvpn_dict = self.api_bgpvpns.first()
bgpvpn.name = 'new name'
bgpvpn.route_targets = '65001:2'
bgpvpn_dict['name'] = 'new name'
bgpvpn_dict['route_targets'] = '65001:2'
form_data = {'name': bgpvpn.name,
'route_targets': bgpvpn.route_targets}
form_dict = {'bgpvpn': form_data}
ret_dict = {'bgpvpn': bgpvpn_dict}
neutronclient.update_ext('/bgpvpn/bgpvpns/%s',
bgpvpn.id, form_dict).AndReturn(ret_dict)
self.mox.ReplayAll()
ret_val = api.bgpvpn.bgpvpn_update(self.request,
bgpvpn.id,
**form_data)
self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn)
@test.create_stubs({neutronclient: ('delete_ext',)})
def test_bgpvpn_delete(self):
bgpvpn = self.bgpvpns.first()
api_bgpvpn = {'bgpvpn': self.api_bgpvpns.first()}
neutronclient.delete_ext('/bgpvpn/bgpvpns/%s',
bgpvpn.id).AndReturn(api_bgpvpn)
self.mox.ReplayAll()
api.bgpvpn.bgpvpn_delete(self.request, bgpvpn.id)
@test.create_stubs({neutronclient: ('list_ext',)})
def test_network_association_list(self):
bgpvpn = self.bgpvpns.first()
na = {'network_associations': self.api_network_associations.list()}
neutronclient.list_ext(
'network_associations',
'/bgpvpn/bgpvpns/%s/network_associations' % bgpvpn.id,
True).AndReturn(na)
self.mox.ReplayAll()
ret_val = api.bgpvpn.network_association_list(self.request, bgpvpn.id)
for n in ret_val:
self.assertIsInstance(n, api.bgpvpn.NetworkAssociation)
@test.create_stubs({neutronclient: ('create_ext',)})
def test_network_association_create(self):
bgpvpn = self.bgpvpns.first()
network = self.networks.first()
data = {'network_id': network.id}
ret_dict = {'network_association': data}
neutronclient.create_ext(
'/bgpvpn/bgpvpns/%s/network_associations' % bgpvpn.id,
ret_dict).AndReturn(ret_dict)
self.mox.ReplayAll()
ret_val = api.bgpvpn.network_association_create(self.request,
bgpvpn.id,
**data)
self.assertIsInstance(ret_val, api.bgpvpn.NetworkAssociation)
@test.create_stubs({neutronclient: ('delete_ext',)})
def test_network_association_delete(self):
bgpvpn = self.bgpvpns.first()
na = self.network_associations.first()
api_bgpvpn = {
'network_association': self.api_network_associations.first()}
neutronclient.delete_ext(
'/bgpvpn/bgpvpns/' + bgpvpn.id + '/network_associations/%s',
na.id).AndReturn(api_bgpvpn)
self.mox.ReplayAll()
api.bgpvpn.network_association_delete(self.request, na.id, bgpvpn.id)
@test.create_stubs({neutronclient: ('list_ext',)})
def test_router_association_list(self):
bgpvpn = self.bgpvpns.first()
na = {'router_associations': self.api_network_associations.list()}
neutronclient.list_ext(
'router_associations',
'/bgpvpn/bgpvpns/%s/router_associations' % bgpvpn.id,
True).AndReturn(na)
self.mox.ReplayAll()
ret_val = api.bgpvpn.router_association_list(self.request, bgpvpn.id)
for n in ret_val:
self.assertIsInstance(n, api.bgpvpn.RouterAssociation)
@test.create_stubs({neutronclient: ('create_ext',)})
def test_router_association_create(self):
bgpvpn = self.bgpvpns.first()
router = self.routers.first()
data = {'router_id': router.id}
ret_dict = {'router_association': data}
neutronclient.create_ext(
'/bgpvpn/bgpvpns/%s/router_associations' % bgpvpn.id,
ret_dict).AndReturn(ret_dict)
self.mox.ReplayAll()
ret_val = api.bgpvpn.router_association_create(self.request,
bgpvpn.id,
**data)
self.assertIsInstance(ret_val, api.bgpvpn.RouterAssociation)
@test.create_stubs({neutronclient: ('delete_ext',)})
def test_router_association_delete(self):
bgpvpn = self.bgpvpns.first()
na = self.router_associations.first()
api_bgpvpn = {
'router_association': self.api_router_associations.first()
}
neutronclient.delete_ext(
'/bgpvpn/bgpvpns/' + bgpvpn.id + '/router_associations/%s',
na.id).AndReturn(api_bgpvpn)
self.mox.ReplayAll()
api.bgpvpn.router_association_delete(self.request, na.id, bgpvpn.id)
36
bgpvpn_dashboard/test/helpers.py
Executable file
36
bgpvpn_dashboard/test/helpers.py
Executable file
@@ -0,0 +1,36 @@
# Copyright (c) 2016 Orange.
# 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 openstack_dashboard.test import helpers
from bgpvpn_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)
class APITestCase(helpers.APITestCase):
def _setup_test_data(self):
super(APITestCase, self)._setup_test_data()
utils.load_test_data(self)
187
bgpvpn_dashboard/test/settings.py
Executable file
187
bgpvpn_dashboard/test/settings.py
Executable file
@@ -0,0 +1,187 @@
#
# 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 = 'bgpvpn_dashboard.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',
'bgpvpn_dashboard.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 = False
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']['bgpvpn_dashboard'] = {
'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'
20
bgpvpn_dashboard/test/test.py
Executable file
20
bgpvpn_dashboard/test/test.py
Executable 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 import urls
import openstack_dashboard.urls
urlpatterns = urls.patterns(
'',
urls.url(r'', urls.include(openstack_dashboard.urls))
)
0
bgpvpn_dashboard/test/test_data/__init__.py
Executable file
0
bgpvpn_dashboard/test/test_data/__init__.py
Executable file
51
bgpvpn_dashboard/test/test_data/bgpvpn_data.py
Executable file
51
bgpvpn_dashboard/test/test_data/bgpvpn_data.py
Executable file
@@ -0,0 +1,51 @@
# 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 copy
from bgpvpn_dashboard.api import bgpvpn
from openstack_dashboard.test.test_data import utils
def data(TEST):
TEST.bgpvpns = utils.TestDataContainer()
TEST.api_bgpvpns = utils.TestDataContainer()
TEST.network_associations = utils.TestDataContainer()
TEST.api_network_associations = utils.TestDataContainer()
TEST.router_associations = utils.TestDataContainer()
TEST.api_router_associations = utils.TestDataContainer()
bgpvpn_dict = {'id': 'b595e758-1877-4aec-92a2-6834d76f1025',
'tenant_id': '1',
'name': 'bgpvpn1',
'route_targets': '64500:1'
}
TEST.api_bgpvpns.add(bgpvpn_dict)
b = bgpvpn.Bgpvpn(copy.deepcopy(bgpvpn_dict))
TEST.bgpvpns.add(b)
network_association_dict = {
'id': '99ef096d-21fb-43a7-9e2a-b3c464abef3a',
'network_id': '063cf7f3-ded1-4297-bc4c-31eae876cc91',
'tenant_id': '1'}
TEST.api_network_associations.add(network_association_dict)
na = bgpvpn.NetworkAssociation(copy.deepcopy(network_association_dict))
TEST.network_associations.add(na)
router_association_dict = {
'id': '9736c228-745d-4e78-83a5-d971d9fd8f2c',
'router_id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
'tenant_id': '1'}
TEST.api_router_associations.add(router_association_dict)
ra = bgpvpn.RouterAssociation(copy.deepcopy(router_association_dict))
TEST.router_associations.add(ra)
34
bgpvpn_dashboard/test/test_data/utils.py
Executable file
34
bgpvpn_dashboard/test/test_data/utils.py
Executable file
@@ -0,0 +1,34 @@
#
# 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
def load_test_data(load_onto=None):
from openstack_dashboard.test.test_data import exceptions
from openstack_dashboard.test.test_data import neutron_data
from bgpvpn_dashboard.test.test_data import bgpvpn_data
# The order of these loaders matters, some depend on others.
loaders = (
exceptions.data,
neutron_data.data,
bgpvpn_data.data,
)
if load_onto:
for data_func in loaders:
data_func(load_onto)
return load_onto
else:
return utils.TestData(*loaders)
18
bgpvpn_dashboard/test/urls.py
Executable file
18
bgpvpn_dashboard/test/urls.py
Executable file
@@ -0,0 +1,18 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import urls
import openstack_dashboard.urls
urlpatterns = [
urls.url(r'', urls.include(openstack_dashboard.urls))
]
8
devstack/plugin.sh
Normal file → Executable file
8
devstack/plugin.sh
Normal file → Executable file
@@ -38,14 +38,18 @@ elif [[ "1ドル" == "stack" && "2ドル" == "post-config" ]]; then
echo_summary "Enabling bgpvpn in $HEAT_CONF"
iniset $HEAT_CONF DEFAULT plugin_dirs $NETWORKING_BGPVPN_DIR/networking_bgpvpn_heat
fi
if is_service_enabled horizon; then
cp $BGPVPN_DASHBOARD_ENABLE_ADMIN \
$HORIZON_DIR/openstack_dashboard/local/enabled/
fi
fi
if [[ "1ドル" == "unstack" ]]; then
#no-op
:
fi
if [[ "1ドル" == "clean" ]]; then
#no-op
:
# Remove bgpvpn-dashboard enabled files and pyc
rm -f ${BGPVPN_DASHBOARD_ENABLE_ADMIN}*
fi
# Restore XTRACE
1
devstack/settings
Normal file → Executable file
1
devstack/settings
Normal file → Executable file
@@ -1,5 +1,6 @@
NETWORKING_BGPVPN_DIR="$DEST/networking-bgpvpn"
NETWORKING_BGPVPN_CONF="$NEUTRON_CONF_DIR/networking_bgpvpn.conf"
BGPVPN_DASHBOARD_ENABLE_ADMIN="$NETWORKING_BGPVPN_DIR/bgpvpn_dashboard/enabled/_2115_admin_bgpvpn_panel.py"
BGPVPN_PLUGIN_CLASS="networking_bgpvpn.neutron.services.plugin.BGPVPNPlugin"
NETWORKING_BGPVPN_DRIVER=${NETWORKING_BGPVPN_DRIVER:-BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriver:default}
39
doc/source/horizon.rst
Executable file
39
doc/source/horizon.rst
Executable file
@@ -0,0 +1,39 @@
========
Horizon
========
General information
===================
Networking-bgpvpn contains the bgpvpn_dashboard plugin for Horizon.
It adds a BGPVPN Interconnections panel in the admin section. Admin users can
handle BGPVPNs resources through this panel.
The operations possible for admin users are:
* listing BGPVPN
* creating a BGPVPN
* editing a BGPVPN
* associating or disassociating a BGPVPN to network(s)
* associating or disassociating a BGPVPN to router(s)
* deleting a BGPVPN
Installation and Configuration
==============================
Devstack will automatically configure Horizon to enable the Horizon plugin.
For others deployments we assume that Horizon and networking-bgpvpn are already
installed. Their installation folders are respectively <horizon> and
<networking-bgpvpn>.
Copy configuration file:
.. code-block:: shell
cp <networking-bgpvpn>/bgpvpn_dashboard/enabled/_[0-9]*.py <horizon>/openstack_dashboard/local/enabled/
Restart the web server hosting Horizon.
The BGPVPN Interconnections panel will now be in your Horizon dashboard.
1
doc/source/index.rst
Normal file → Executable file
1
doc/source/index.rst
Normal file → Executable file
@@ -39,6 +39,7 @@ Contents
installation
usage
heat
horizon
contributing
specs
future/index
2
doc/source/usage.rst
Normal file → Executable file
2
doc/source/usage.rst
Normal file → Executable file
@@ -23,7 +23,7 @@ Example commands to use by the tenant owning the BGPVPN to associate a Network t
Use from Horizon
----------------
(not supported yet)
See :doc:`horizon`.
Use from Heat
-------------
0
networking_bgpvpn/neutron/extensions/bgpvpn.py
Normal file → Executable file
0
networking_bgpvpn/neutron/extensions/bgpvpn.py
Normal file → Executable file
0
networking_bgpvpn/neutron/services/common/constants.py
Normal file → Executable file
0
networking_bgpvpn/neutron/services/common/constants.py
Normal file → Executable file
1
setup.cfg
Normal file → Executable file
1
setup.cfg
Normal file → Executable file
@@ -23,6 +23,7 @@ classifier =
packages =
networking_bgpvpn
networking_bgpvpn_tempest
bgpvpn_dashboard
data_files =
etc/neutron/policy.d =
etc/neutron/policy.d/bgpvpn.conf
1
test-requirements.txt
Normal file → Executable file
1
test-requirements.txt
Normal file → Executable file
@@ -5,6 +5,7 @@
hacking<0.11,>=0.10.0
coverage>=3.6
django-nose>=1.2 # BSD
discover
python-subunit>=0.0.18
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
23
tools/django-manage.py
Executable file
23
tools/django-manage.py
Executable file
@@ -0,0 +1,23 @@
#!/usr/bin/env python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from django.core.management import execute_from_command_line
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"bgpvpn_dashboard.test.settings")
execute_from_command_line(sys.argv)
@@ -15,7 +15,38 @@
ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
BRANCH_NAME=master
neutron_installed=$(echo "import neutron" | python 2>/dev/null ; echo $?)
install_project() {
local project=1ドル
set +e
project_installed=$(echo "import $project" | python 2>/dev/null ; echo $?)
set -e
if [ $project_installed -eq 0 ]; then
echo "ALREADY INSTALLED" > /tmp/tox_install.txt
echo "$project already installed; using existing package"
elif [ -x "$ZUUL_CLONER" ]; then
export ZUUL_BRANCH=${ZUUL_BRANCH-$BRANCH}
echo "ZUUL CLONER" > /tmp/tox_install.txt
cwd=$(/bin/pwd)
cd /tmp
$ZUUL_CLONER --cache-dir \
/opt/git \
--branch $BRANCH_NAME \
git://git.openstack.org \
openstack/$project
cd openstack/$project
$install_cmd -e .
cd "$cwd"
else
echo "PIP HARDCODE" > /tmp/tox_install.txt
if [ -z "$PIP_LOCATION" ]; then
PIP_LOCATION="git+https://git.openstack.org/openstack/$project@$BRANCH_NAME#egg=$project"
fi
$install_cmd -U -e ${PIP_LOCATION}
fi
}
set -e
@@ -27,29 +58,8 @@ if [ $CONSTRAINTS_FILE != "unconstrained" ]; then
install_cmd="$install_cmd -c$CONSTRAINTS_FILE"
fi
if [ $neutron_installed -eq 0 ]; then
echo "ALREADY INSTALLED" > /tmp/tox_install.txt
echo "Neutron already installed; using existing package"
elif [ -x "$ZUUL_CLONER" ]; then
export ZUUL_BRANCH=${ZUUL_BRANCH-$BRANCH}
echo "ZUUL CLONER" > /tmp/tox_install.txt
cwd=$(/bin/pwd)
cd /tmp
$ZUUL_CLONER --cache-dir \
/opt/git \
--branch $BRANCH_NAME \
git://git.openstack.org \
openstack/neutron
cd openstack/neutron
$install_cmd -e .
cd "$cwd"
else
echo "PIP HARDCODE" > /tmp/tox_install.txt
if [ -z "$NEUTRON_PIP_LOCATION" ]; then
NEUTRON_PIP_LOCATION="git+https://git.openstack.org/openstack/neutron@$BRANCH_NAME#egg=neutron"
fi
$install_cmd -U -e ${NEUTRON_PIP_LOCATION}
fi
install_project neutron
install_project horizon
$install_cmd -U $*
exit $?
exit $?
1
tox.ini
Normal file → Executable file
1
tox.ini
Normal file → Executable file
@@ -11,6 +11,7 @@ setenv =
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
python {toxinidir}/tools/django-manage.py test bgpvpn_dashboard.test.api_tests.bgpvpn_tests
[testenv:releasenotes]
# TODO(tmorin): remove once infra supports constraints for this target
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.