From 2d5f4a29ae8ef8eadc7b563be05afd823da0eaaf Mon Sep 17 00:00:00 2001 From: Vivek Jain Date: Tue, 28 Jul 2015 19:19:08 -0700 Subject: [PATCH] Initial commit for horizon lbaas v2 dashboard Co-Authored-By: Milan Tonse Co-Authored-By: Eric Peterson Co-Authored-By: Vivek Jain Partially-Implements: blueprint horizon-lbaas-v2-ui Change-Id: I8a9af14aa13fe928541c299862f1231bab3a7437 --- README.rst | 33 +- neutron_lbaas_dashboard/api/__init__.py | 1 + neutron_lbaas_dashboard/api/lbaasv2.py | 678 ++++++++++++++++++ .../dashboards/__init__.py | 0 .../dashboards/project/__init__.py | 0 .../project/loadbalancersv2/__init__.py | 0 .../project/loadbalancersv2/exceptions.py | 36 + .../project/loadbalancersv2/panel.py | 47 ++ .../project/loadbalancersv2/tables.py | 193 +++++ .../project/loadbalancersv2/tabs.py | 33 + .../loadbalancersv2/_detail_overview.html | 50 ++ .../loadbalancersv2/_launch_lb_help.html | 15 + .../loadbalancersv2/_monitor_create.html | 49 ++ .../loadbalancersv2/_monitor_help.html | 10 + .../loadbalancersv2/_ssl_cert_help.html | 13 + .../templates/loadbalancersv2/detail.html | 15 + .../templates/loadbalancersv2/index.html | 11 + .../templates/loadbalancersv2/launch.html | 11 + .../templates/loadbalancersv2/launch_lb.html | 82 +++ .../loadbalancersv2/port_config.html | 76 ++ .../templates/loadbalancersv2/ssl_cert.html | 68 ++ .../templates/loadbalancersv2/update.html | 11 + .../loadbalancersv2/update_lb_step.html | 63 ++ .../loadbalancersv2/update_ssl_cert.html | 91 +++ .../project/loadbalancersv2/urls.py | 36 + .../project/loadbalancersv2/views.py | 146 ++++ .../loadbalancersv2/workflows/__init__.py | 16 + .../loadbalancersv2/workflows/create_lb.py | 462 ++++++++++++ .../loadbalancersv2/workflows/update_lb.py | 192 +++++ .../_1480_project_loadbalancersv2_panel.py | 23 + neutron_lbaas_dashboard/enabled/__init__.py | 0 31 files changed, 2459 insertions(+), 2 deletions(-) create mode 100644 neutron_lbaas_dashboard/api/__init__.py create mode 100644 neutron_lbaas_dashboard/api/lbaasv2.py create mode 100644 neutron_lbaas_dashboard/dashboards/__init__.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/__init__.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/__init__.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/exceptions.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/panel.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tables.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tabs.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_detail_overview.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_launch_lb_help.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_create.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_help.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_ssl_cert_help.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/detail.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/index.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch_lb.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/port_config.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/ssl_cert.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_lb_step.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_ssl_cert.html create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/urls.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/views.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/__init__.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/create_lb.py create mode 100644 neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/update_lb.py create mode 100644 neutron_lbaas_dashboard/enabled/_1480_project_loadbalancersv2_panel.py create mode 100644 neutron_lbaas_dashboard/enabled/__init__.py diff --git a/README.rst b/README.rst index a0f40ce..f71bdd0 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -=============================== +======================= neutron-lbaas-dashboard -=============================== +======================= Horizon panels for Neutron LBaaS @@ -14,3 +14,32 @@ Features * Please see neutron-lbaas repository + +Howto +----- + +1. Package the neutron_lbaas_dashboard by running:: + + python setup.py sdist + + This will create a python egg in the dist folder, which can be used to install + on the horizon machine or within horizon's python virtual environment. + +2. Modify horizon's settings file to enabled neutron_lbaas_dashboard, note the two lines to add below:: + + import neutron_lbaas_dashboard.enabled # ADD THIS LINE + + ... + + INSTALLED_APPS = list(INSTALLED_APPS) # Make sure it's mutable + settings.update_dashboards([ + openstack_dashboard.enabled, + openstack_dashboard.local.enabled, + neutron_lbaas_dashboard.enabled, # ADD THIS LINE TOO + ], HORIZON_CONFIG, INSTALLED_APPS) + +3. (Optional/TODO) Copy the policy file into horizon's policy files folder, and add this config:: + + 'neutron_lbaas': 'neutron_lbaas_policy.json', + +4. (Optional) Add extra config settings for the add in: TODO \ No newline at end of file diff --git a/neutron_lbaas_dashboard/api/__init__.py b/neutron_lbaas_dashboard/api/__init__.py new file mode 100644 index 0000000..4b9ac4a --- /dev/null +++ b/neutron_lbaas_dashboard/api/__init__.py @@ -0,0 +1 @@ +from . import lbaasv2 # noqa diff --git a/neutron_lbaas_dashboard/api/lbaasv2.py b/neutron_lbaas_dashboard/api/lbaasv2.py new file mode 100644 index 0000000..38adb67 --- /dev/null +++ b/neutron_lbaas_dashboard/api/lbaasv2.py @@ -0,0 +1,678 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import + +from django.utils.datastructures import SortedDict +from django.utils.translation import ugettext_lazy as _ + +from horizon import messages + +from openstack_dashboard.api import neutron + +neutronclient = neutron.neutronclient + + +class LBDetails(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer vip.""" + + def __init__(self, vip, listener=None, pool=None, members=None, + monitors=None, profile_name=None, cert=None, key=None, + chain=None): + vip['pool'] = pool + pool['members'] = members + pool['monitors'] = monitors + # vip['cert_name'] = cert_name + vip['listener'] = listener + vip['cert'] = cert + vip['key'] = key + vip['chain'] = chain + vip['profile_name'] = profile_name + super(LBDetails, self).__init__(vip) + + class AttributeDict(dict): + + def __getattr__(self, attr): + return self[attr] + + def __setattr__(self, attr, value): + self[attr] = value + + def convert_status(self, value): + return "Enabled" if value else "Disabled" + + def readable(self, request=None): + + pFormatted = {'id': self.id, + 'name': self.name, + 'dns_name': self.name, + # 'lb_method': self.lb_method, + 'description': self.description, + # 'protocol': self.protocol, + 'address': self.vip_address, + # 'port': self.port, + 'enabled': self.convert_status(self.admin_state_up), + 'use_common_cert': False, + 'provisioning_status': self.provisioning_status, + 'operating_status': self.operating_status, + # 'monitor' : self.monitor + } + + # status_string = 'vip: %s' % self['status'].lower() + pFormatted['status'] = 'na' + + # if self.profile_name: + # try: + # if self.profile_name.upper() == + # _construct_common_cert_profile_name(request).upper(): + # pFormatted['use_common_cert'] = True + # pFormatted['cert_name'] = self.profile_name + # else: + # pFormatted['use_common_cert'] = False + # pFormatted['cert_name'] = self.profile_name + # pFormatted['cert'] = self.cert + # pFormatted['private_key'] = self.key + # pFormatted['chain_cert'] = self.chain + # except Exception as e: + # LOG.error("unable to read cert") + + if self.listener is not None: + try: + listener = self.AttributeDict(self.listener) + pFormatted['protocol'] = listener.protocol + pFormatted['port'] = listener.protocol_port + except Exception: + pass + + if self.pool is not None: + try: + pool = self.AttributeDict(self.pool) + # status_string = '%s \n pool: %s' % (pFormatted['status'], + # pool['status'].lower()) + # pFormatted['status'] = status_string + pFormatted['pool'] = pool + pFormatted['pool_id'] = pool.id + pFormatted['lb_method'] = pool.lb_algorithm + + if pool.members is not None: + try: + ips = [] + pool_members = [] + for m in pool.members: + member = self.AttributeDict(m) + pFormatted['instance_port'] = member.protocol_port + + pool_member = member + pool_member.port = member.protocol_port + pool_members.append(pool_member) + + ips.append(member.address) + pFormatted['pool']['members'] = pool_members + pFormatted['members'] = ips + except Exception: + pass # ignore + + if pool.monitors is not None: + try: + for m in pool.monitors: + monitor = self.AttributeDict(m) + # monitor_status =_get_monitor_status(pool['id'],m) + # status_string = '%s \n monitor: %s' % + # (pFormatted['status'], monitor_status.lower()) + # pFormatted['status'] = status_string + interval = int(monitor.delay) + # timeout = int(monitor.timeout) + retry = int(monitor.max_retries) + monitor_type = 'http-ecv' + # if monitor.name.upper() in basic_monitors: + # monitor_type = monitor.name + monitor_type = monitor.name + pFormatted['pool']['monitor'] = monitor_type + pFormatted['monitor'] = monitor_type + pFormatted['interval'] = interval + pFormatted['timeout'] = retry + pFormatted['send'] = monitor.url_path \ + if hasattr(monitor, 'url_path') else '' + pFormatted['receive'] = monitor.response_string \ + if hasattr(monitor, 'response_string') else '' + break + except Exception: + pass # ignore + except Exception: + pass # ignore + + if 'cert_name' not in pFormatted: + pFormatted['cert_name'] = '' + if 'cert' not in pFormatted: + pFormatted['cert'] = '' + if 'private_key' not in pFormatted: + pFormatted['private_key'] = '' + if 'chain_cert' not in pFormatted: + pFormatted['chain_cert'] = '' + if 'pool_id' not in pFormatted: + pFormatted['pool_id'] = 'UNKNOWN' + if 'lb_method' not in pFormatted: + pFormatted['lb_method'] = 'UNKNOWN' + if 'monitor' not in pFormatted: + pFormatted['monitor'] = 'None' + # if 'monitor' not in pFormatted['pool']: + # pFormatted['pool']['monitor'] = 'None' + if 'interval' not in pFormatted: + pFormatted['interval'] = 1 + if 'timeout' not in pFormatted: + pFormatted['timeout'] = 1 + if 'send' not in pFormatted: + pFormatted['send'] = None + if 'receive' not in pFormatted: + pFormatted['receive'] = None + if 'members' not in pFormatted: + pFormatted['members'] = [] + if 'instance_port' not in pFormatted: + pFormatted['instance_port'] = '' + + return self.AttributeDict(pFormatted) + + +class Vip(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer vip.""" + + def __init__(self, apiresource): + super(Vip, self).__init__(apiresource) + + +class Pool(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer pool.""" + + def __init__(self, apiresource): + if 'provider' not in apiresource: + apiresource['provider'] = None + super(Pool, self).__init__(apiresource) + + +class Member(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer member.""" + + def __init__(self, apiresource): + super(Member, self).__init__(apiresource) + + +class PoolStats(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer pool stats.""" + + def __init__(self, apiresource): + super(PoolStats, self).__init__(apiresource) + + +class PoolMonitor(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron load balancer pool health monitor.""" + + def __init__(self, apiresource): + super(PoolMonitor, self).__init__(apiresource) + + +def vip_create(request, **kwargs): + """Create a vip for a specified pool. + + :param request: request context + :param address: virtual IP address + :param name: name for vip + :param description: description for vip + :param subnet_id: subnet_id for subnet of vip + :param protocol_port: transport layer port number for vip + :returns: Vip object + """ + body = {'vip': {'name': kwargs['name'], + 'description': kwargs['description'], + 'subnet_id': kwargs['subnet_id'], + 'protocol_port': kwargs['protocol_port'], + 'protocol': kwargs['protocol'], + 'pool_id': kwargs['pool_id'], + 'session_persistence': kwargs['session_persistence'], + 'admin_state_up': kwargs['admin_state_up'] + }} + if kwargs.get('connection_limit'): + body['vip']['connection_limit'] = kwargs['connection_limit'] + + if kwargs.get('address'): + body['vip']['address'] = kwargs['address'] + + vip = neutronclient(request).create_vip(body).get('vip') + return Vip(vip) + + +def vip_list(request, **kwargs): + vips = neutronclient(request).list_vips(**kwargs).get('vips') + return [Vip(v) for v in vips] + + +def create_loadbalancer_full(request, **kwargs): + loadbalancer_body = {'loadbalancer': {'name': kwargs['name'], + 'description': kwargs['description'], + 'vip_subnet_id': kwargs['subnet_id'], + 'admin_state_up': + kwargs['admin_state_up'], + 'vip_address': kwargs['address'], + # 'provider': 'HAProxy' + }} + + listener_body = {'listener': {'name': kwargs['name'], + 'description': kwargs['description'], + 'protocol': kwargs['protocol'], + 'protocol_port': kwargs['protocol_port'], + 'default_tls_container_id': None, + 'sni_container_ids': [], + 'connection_limit': 100, + 'admin_state_up': kwargs['admin_state_up'], + 'loadbalancer_id': None}} + + pool_body = {'pool': {'name': kwargs['name'], + 'description': kwargs['description'], + 'protocol': kwargs['protocol'], + 'lb_method': kwargs['lb_method'], + 'admin_state_up': kwargs['admin_state_up'] + }} + + member_body = {'member': {'pool_id': kwargs['pool_id'], + 'address': kwargs['address'], + 'protocol_port': kwargs['protocol_port'], + 'admin_state_up': kwargs['admin_state_up'], + 'pool_id': None + }} + if kwargs.get('weight'): + member_body['member']['weight'] = kwargs['weight'] + + monitor_type = kwargs['type'].upper() + + health_monitor_body = {'health_monitor': {'tenant_id': kwargs['tenant_id'], + 'type': monitor_type, + 'delay': kwargs['delay'], + 'timeout': kwargs['timeout'], + 'max_retries': + kwargs['max_retries'], + 'admin_state_up': + kwargs['admin_state_up'], + 'pool_id': None + } + } + + if monitor_type in ['HTTP', 'HTTPS']: + health_mon = health_monitor_body['health_monitor'] + health_mon['http_method'] = kwargs['http_method'] + health_mon['url_path'] = kwargs['url_path'] + health_mon['expected_codes'] = kwargs['expected_codes'] + + try: + client = neutronclient(request) + loadbalancer = client.\ + create_loadbalancer(loadbalancer_body).get('loadbalancer') + listener_body['listener']['loadbalancer_id'] = loadbalancer['id'] + listener = client.\ + create_listener(listener_body).get('listener') + pool = client.create_lbaas_pool(pool_body).get('pool') + member_body['member']['pool_id'] = pool['id'] + health_monitor_body['health_monitor']['pool_id'] = pool['id'] + health_monitor = client.create_lbaas_healthmonitor(health_monitor_body)\ + .get('health_monitor') + member = client.create_lbaas_member(member_body).get('member') + except Exception: + raise Exception(_("Could not create full loadbalancer.")) + return [LBDetails(loadbalancer, listener, pool, member, health_monitor)] + + +def list_loadbalancers(request, **kwargs): + vips = neutronclient(request).list_loadbalancers(**kwargs) + vips = [] if not vips else vips + vips = vips.get('loadbalancers') + for vip in vips: + listeners = vip.get('listeners') + listeners = [] if not listeners else listeners + + for listener in listeners: + listener = neutronclient(request).show_listener(listener.get('id'), + **kwargs) + if not listener: + continue + listener = listener.get('listener') + + try: + pool = neutronclient(request).\ + show_lbaas_pool(listener.get('default_pool_id'), **kwargs) + if not pool: + continue + pool = pool.get('pool') + if pool.get('healthmonitor_id'): + health_monitor = neutronclient(request).\ + show_lbaas_healthmonitor(pool.get('healthmonitor_id'), + **kwargs) + health_monitor = health_monitor.get('healthmonitor') + + members = neutronclient(request).\ + list_lbaas_members(listener.get('default_pool_id'), + **kwargs) + + except Exception: + raise Exception(_("Could not get load balancer list.")) + return [LBDetails(v, listener, pool, members, health_monitor) + for v in vips] + + +def show_loadbalancer(request, lbaas_loadbalancer, **kwargs): + vip = neutronclient(request).show_loadbalancer(lbaas_loadbalancer, + **kwargs) + if not vip: + return + loadbalancer = vip.get('loadbalancer') + viplisteners = loadbalancer.get('listeners') + if not viplisteners: + return + for viplistener in viplisteners: + listener = neutronclient(request).\ + show_listener(viplistener.get('id'), **kwargs) + if not listener: + continue + listener = listener.get('listener') + pool = neutronclient(request).\ + show_lbaas_pool(listener.get('default_pool_id'), **kwargs) + if not pool: + continue + pool = pool.get('pool') + health_monitor = None + if pool.get('healthmonitor_id'): + health_monitor = neutronclient(request).\ + show_lbaas_healthmonitor(pool.get('healthmonitor_id'), + **kwargs) + health_monitor = health_monitor.get('healthmonitor') + members = neutronclient(request).\ + list_lbaas_members(listener.get('default_pool_id'), **kwargs) + return LBDetails(vip.get('loadbalancer'), listener, pool, members, + health_monitor) + + +def vip_get(request, vip_id): + return _vip_get(request, vip_id, expand_resource=True) + + +def _vip_get(request, vip_id, expand_resource=False): + vip = neutronclient(request).show_vip(vip_id).get('vip') + if expand_resource: + vip['subnet'] = neutron.subnet_get(request, vip['subnet_id']) + vip['port'] = neutron.port_get(request, vip['port_id']) + vip['pool'] = _pool_get(request, vip['pool_id']) + return Vip(vip) + + +def vip_update(request, vip_id, **kwargs): + vip = neutronclient(request).update_vip(vip_id, kwargs).get('vip') + return Vip(vip) + + +def vip_delete(request, vip_id): + neutronclient(request).delete_vip(vip_id) + + +def pool_create(request, **kwargs): + """Create a pool for specified protocol + + :param request: request context + :param name: name for pool + :param description: description for pool + :param subnet_id: subnet_id for subnet of pool + :param protocol: load balanced protocol + :param lb_method: load balancer method + :param admin_state_up: admin state (default on) + """ + body = {'pool': {'name': kwargs['name'], + 'description': kwargs['description'], + 'subnet_id': kwargs['subnet_id'], + 'protocol': kwargs['protocol'], + 'lb_method': kwargs['lb_method'], + 'admin_state_up': kwargs['admin_state_up'], + 'provider': kwargs['provider'], + }} + pool = neutronclient(request).create_pool(body).get('pool') + return Pool(pool) + + +def _get_vip(request, pool, vip_dict, expand_name_only=False): + if pool['vip_id'] is not None: + try: + if vip_dict: + vip = vip_dict.get(pool['vip_id']) + else: + vip = _vip_get(request, pool['vip_id']) + except Exception: + messages.warning(request, _("Unable to get VIP for pool " + "%(pool)s.") % {"pool": pool["id"]}) + vip = Vip({'id': pool['vip_id'], 'name': ''}) + if expand_name_only: + vip = vip.name_or_id + return vip + else: + return None + + +def pool_list(request, **kwargs): + return _pool_list(request, expand_subnet=True, expand_vip=True, **kwargs) + + +def _pool_list(request, expand_subnet=False, expand_vip=False, **kwargs): + pools = neutronclient(request).list_pools(**kwargs).get('pools') + if expand_subnet: + subnets = neutron.subnet_list(request) + subnet_dict = SortedDict((s.id, s) for s in subnets) + for p in pools: + subnet = subnet_dict.get(p['subnet_id']) + p['subnet_name'] = subnet.cidr if subnet else None + if expand_vip: + vips = vip_list(request) + vip_dict = SortedDict((v.id, v) for v in vips) + for p in pools: + p['vip_name'] = _get_vip(request, p, vip_dict, + expand_name_only=True) + return [Pool(p) for p in pools] + + +def pool_get(request, pool_id): + return _pool_get(request, pool_id, expand_resource=True) + + +def _pool_get(request, pool_id, expand_resource=False): + try: + pool = neutronclient(request).show_pool(pool_id).get('pool') + except Exception: + messages.warning(request, _("Unable to get pool detail.")) + return None + if expand_resource: + # TODO(lyj): The expand resource(subnet, member etc.) attached + # to a pool could be deleted without cleanup pool related database, + # this will cause exceptions if we trying to get the deleted resources. + # so we need to handle the situation by showing a warning message here. + # we can safely remove the try/except once the neutron bug is fixed + # https://bugs.launchpad.net/neutron/+bug/1406854 + try: + pool['subnet'] = neutron.subnet_get(request, pool['subnet_id']) + except Exception: + messages.warning(request, _("Unable to get subnet for pool " + "%(pool)s.") % {"pool": pool_id}) + pool['vip'] = _get_vip(request, pool, vip_dict=None, + expand_name_only=False) + try: + pool['members'] = _member_list(request, expand_pool=False, + pool_id=pool_id) + except Exception: + messages.warning(request, _("Unable to get members for pool " + "%(pool)s.") % {"pool": pool_id}) + try: + pool['health_monitors'] = pool_health_monitor_list( + request, id=pool['health_monitors']) + except Exception: + messages.warning(request, + _("Unable to get health monitors " + "for pool %(pool)s.") % {"pool": pool_id}) + return Pool(pool) + + +def pool_update(request, pool_id, **kwargs): + pool = neutronclient(request).update_pool(pool_id, kwargs).get('pool') + return Pool(pool) + + +def pool_delete(request, pool): + neutronclient(request).delete_pool(pool) + + +# not linked to UI yet +def pool_stats(request, pool_id, **kwargs): + stats = neutronclient(request).retrieve_pool_stats(pool_id, **kwargs) + return PoolStats(stats) + + +def pool_health_monitor_create(request, **kwargs): + """Create a health monitor + + :param request: request context + :param type: type of monitor + :param delay: delay of monitor + :param timeout: timeout of monitor + :param max_retries: max retries [1..10] + :param http_method: http method + :param url_path: url path + :param expected_codes: http return code + :param admin_state_up: admin state + """ + monitor_type = kwargs['type'].upper() + body = {'health_monitor': {'type': monitor_type, + 'delay': kwargs['delay'], + 'timeout': kwargs['timeout'], + 'max_retries': kwargs['max_retries'], + 'admin_state_up': kwargs['admin_state_up'] + }} + if monitor_type in ['HTTP', 'HTTPS']: + body['health_monitor']['http_method'] = kwargs['http_method'] + body['health_monitor']['url_path'] = kwargs['url_path'] + body['health_monitor']['expected_codes'] = kwargs['expected_codes'] + mon = neutronclient(request).create_health_monitor(body).get( + 'health_monitor') + + return PoolMonitor(mon) + + +def pool_health_monitor_list(request, **kwargs): + monitors = neutronclient(request).list_health_monitors( + **kwargs).get('health_monitors') + return [PoolMonitor(m) for m in monitors] + + +def pool_health_monitor_get(request, monitor_id): + return _pool_health_monitor_get(request, monitor_id, expand_resource=True) + + +def _pool_health_monitor_get(request, monitor_id, expand_resource=False): + monitor = neutronclient(request + ).show_health_monitor(monitor_id + ).get('health_monitor') + if expand_resource: + pool_ids = [p['pool_id'] for p in monitor['pools']] + monitor['pools'] = _pool_list(request, id=pool_ids) + return PoolMonitor(monitor) + + +def pool_health_monitor_update(request, monitor_id, **kwargs): + monitor = neutronclient(request).update_health_monitor(monitor_id, kwargs) + return PoolMonitor(monitor) + + +def pool_health_monitor_delete(request, mon_id): + neutronclient(request).delete_health_monitor(mon_id) + + +def member_create(request, **kwargs): + """Create a load balance member + + :param request: request context + :param pool_id: pool_id of pool for member + :param address: IP address + :param protocol_port: transport layer port number + :param weight: weight for member + :param admin_state_up: admin_state + """ + body = {'member': {'pool_id': kwargs['pool_id'], + 'address': kwargs['address'], + 'protocol_port': kwargs['protocol_port'], + 'admin_state_up': kwargs['admin_state_up'] + }} + if kwargs.get('weight'): + body['member']['weight'] = kwargs['weight'] + member = neutronclient(request).create_member(body).get('member') + return Member(member) + + +def member_list(request, **kwargs): + return _member_list(request, expand_pool=True, **kwargs) + + +def _member_list(request, expand_pool, **kwargs): + members = neutronclient(request).list_members(**kwargs).get('members') + if expand_pool: + pools = _pool_list(request) + pool_dict = SortedDict((p.id, p) for p in pools) + for m in members: + m['pool_name'] = pool_dict.get(m['pool_id']).name_or_id + return [Member(m) for m in members] + + +def member_get(request, member_id): + return _member_get(request, member_id, expand_pool=True) + + +def _member_get(request, member_id, expand_pool): + member = neutronclient(request).show_member(member_id).get('member') + if expand_pool: + member['pool'] = _pool_get(request, member['pool_id']) + return Member(member) + + +def member_update(request, member_id, **kwargs): + member = neutronclient(request).update_member(member_id, kwargs) + return Member(member) + + +def member_delete(request, mem_id): + neutronclient(request).delete_member(mem_id) + + +def pool_monitor_association_create(request, **kwargs): + """Associate a health monitor with pool + + :param request: request context + :param monitor_id: id of monitor + :param pool_id: id of pool + """ + + body = {'health_monitor': {'id': kwargs['monitor_id'], }} + + neutronclient(request).associate_health_monitor( + kwargs['pool_id'], body) + + +def pool_monitor_association_delete(request, **kwargs): + """Disassociate a health monitor from pool + + :param request: request context + :param monitor_id: id of monitor + :param pool_id: id of pool + """ + + neutronclient(request).disassociate_health_monitor( + kwargs['pool_id'], kwargs['monitor_id']) diff --git a/neutron_lbaas_dashboard/dashboards/__init__.py b/neutron_lbaas_dashboard/dashboards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_lbaas_dashboard/dashboards/project/__init__.py b/neutron_lbaas_dashboard/dashboards/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/__init__.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/exceptions.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/exceptions.py new file mode 100644 index 0000000..0155935 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/exceptions.py @@ -0,0 +1,36 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class LBaaSException(Exception): + + """The base exception class for all exceptions this library raises. + + """ + + def __init__(self, code, message=None, details=None, request_id=None, + url=None, method=None): + self.code = code + self.message = message or self.__class__.message + self.details = details + self.request_id = request_id + self.url = url + self.method = method + + def __str__(self): + formatted_string = "%s (HTTP %s)" % (self.message, self.code) + if self.request_id: + formatted_string += " (Request-ID: %s)" % self.request_id + + return formatted_string diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/panel.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/panel.py new file mode 100644 index 0000000..224e503 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/panel.py @@ -0,0 +1,47 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.project import dashboard + + +class LoadBalancersUI(horizon.Panel): + name = _("Load Balancers V2") + slug = 'loadbalancersv2' + permissions = ('openstack.services.network',) + + def allowed(self, context): + # todo temporarily enabling panel for any user + # request = context['request'] + # if not request.user.has_perms(self.permissions): + # return False + # try: + # if not neutron.is_service_enabled(request, + # config_name='enable_lb', + # ext_name='lbaas'): + # return False + # except Exception: + # LOG.error("Call to list enabled services failed. This is likely " + # "due to a problem communicating with the Neutron " + # "endpoint. Load Balancers panel will not be displayed") + # return False + # if not super(LoadBalancer, self).allowed(context): + # return False + return True + + +dashboard.Project.register(LoadBalancersUI) diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tables.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tables.py new file mode 100644 index 0000000..3a4b2ab --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tables.py @@ -0,0 +1,193 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +import re + +from django.core import urlresolvers +from django.template.defaultfilters import linebreaksbr +from django.utils.http import urlencode +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables + +from neutron_lbaas_dashboard import api + + +LOG = logging.getLogger(__name__) + + +class TerminateLoadBalancer(tables.BatchAction): + name = "terminate" + action_present = _("Terminate") + action_past = _("Scheduled termination of") + data_type_singular = _("Load Balancer") + data_type_plural = _("Load Balancers") + classes = ('btn-danger', 'btn-terminate') + + def allowed(self, request, loadbalancer=None): + return True + + def action(self, request, obj_id): + api.lbaasv2.vip_delete(request, obj_id) + + +class EnableLoadBalancer(tables.BatchAction): + name = "enable" + action_present = _("Enable") + action_past = _("Enabled LB") + data_type_singular = _("Load Balancer") + data_type_plural = _("Load Balancers") + classes = ('btn-enable', "btn-action-required") + action_failure = "error enable" + + def allowed(self, request, loadbalancer=None): + if loadbalancer.admin_state_up: + return False + return True + + def action(self, request, obj_id): + api.lbaasv2.vip_set_status(request, obj_id, True) + + +class DisableLoadBalancer(tables.BatchAction): + name = "disable" + action_present = _("Disable") + action_past = _("Disabled LB") + data_type_singular = _("Load Balancer") + data_type_plural = _("Load Balancers") + classes = ("btn-confirm", 'btn-disable',) + action_failure = "error disable" + + def allowed(self, request, loadbalancer=None): + if loadbalancer.admin_state_up: + return True + return False + + def action(self, request, obj_id): + api.lbaasv2.vip_set_status(request, obj_id, False) + + +class LaunchLink(tables.LinkAction): + name = "launch" + verbose_name = _("Launch Load Balancer") + url = "horizon:project:loadbalancersv2:launch" + classes = ("btn-launch", "ajax-modal") + + def allowed(self, request, datum): + return True + + +class EditLoadBalancer(tables.LinkAction): + name = "edit" + verbose_name = _("Edit Load Balancer") + url = "horizon:project:loadbalancersv2:update" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, project): + return self._get_link_url(project, 'loadbalancer_info') + + def _get_link_url(self, project, step_slug): + base_url = urlresolvers.reverse(self.url, args=[project.id]) + param = urlencode({"step": step_slug}) + return "?".join([base_url, param]) + + def allowed(self, request, loadbalancer): + # return not is_deleting(loadbalancer) when async is implemented + return True + + +class UpdateRow(tables.Row): + ajax = True + + def get_data(self, request, loadbalancer_id): + loadbalancer = api.lbaasv2.vip_update(request, loadbalancer_id) + return loadbalancer + + +def convert_title(value): + return re.sub("([A-Z])", " \g<0>", value) + + +def convert_camel(value): + if not value: + return value + if value.isupper() or value.islower(): + return value.title() + else: + value = value.replace(' ', '') + return re.sub("([A-Z])", " \g<0>", value) + + +def upper_case(value): + return value.upper() + + +def convert_status(value): + return "Enabled" if value else "Disabled" + + +def get_lb_method(value): + return value.pool['lb_algorithm'] + + +def get_protocol(value): + return value.listener['protocol'] + + +def get_monitor(value): + return value['pool']['monitors']['type'] + + +def get_lb(instance): + if hasattr(instance, "vip_address"): + return "%s:%s" % (instance.vip_address, + instance.listener['protocol_port']) + return _("Not available") + + +class LoadBalancersTable(tables.DataTable): + vip = tables.Column(get_lb, + link=("horizon:project:loadbalancersv2:detail"), + verbose_name=_("Load Balancer")) + name = tables.Column("name", + link=("horizon:project:loadbalancersv2:detail"), + verbose_name=_("Name")) + lb_method = tables.Column(get_lb_method, + filters=(upper_case,), + verbose_name=_("Method")) + protocol = tables.Column(get_protocol, + filters=(upper_case,), + verbose_name=_("Protocol")) + monitor = tables.Column(get_monitor, + filters=(upper_case,), + verbose_name=_("Monitor")) + status = tables.Column("provisioning_status", + filters=(convert_camel, linebreaksbr), + verbose_name=_("Provisioning Status")) + operating_status = tables.Column("operating_status", + filters=(convert_camel, linebreaksbr), + verbose_name=_("Operating Status")) + enabled = tables.Column("admin_state_up", + filters=(convert_status,), + verbose_name=_("Admin Status")) + + class Meta(object): + name = "loadbalancersv2" + verbose_name = _("Load Balancers") + row_class = UpdateRow + table_actions = (LaunchLink, TerminateLoadBalancer) + row_actions = (EditLoadBalancer, TerminateLoadBalancer, + EnableLoadBalancer, DisableLoadBalancer) diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tabs.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tabs.py new file mode 100644 index 0000000..5a3543f --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/tabs.py @@ -0,0 +1,33 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = ("project/loadbalancersv2/" + "_detail_overview.html") + + def get_context_data(self, request): + return {"loadbalancer": self.tab_group.kwargs['loadbalancer']} + + +class LoadBalancerDetailTabs(tabs.TabGroup): + slug = "loadbalancer_details" + tabs = (OverviewTab, ) + sticky = True diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_detail_overview.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_detail_overview.html new file mode 100644 index 0000000..d0842c6 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_detail_overview.html @@ -0,0 +1,50 @@ +{% load i18n sizeformat %} + +

{% trans "Load Balancer Overview" %}

+ +
+

{% trans "Info" %}

+
+
+
{% trans "Name" %}
+
{{ loadbalancer.name }}
+
{% trans "ID" %}
+
{{ loadbalancer.id }}
+
{% trans "Address" %}
+
{{ loadbalancer.address }}
+
{% trans "Port" %}
+
{{ loadbalancer.port }}
+
{% trans "Protocol" %}
+
{{ loadbalancer.protocol }}
+
{% trans "Load Balancing Method" %}
+
{{ loadbalancer.lb_method }}
+
{% trans "Monitor" %}
+
{{ loadbalancer.pool.monitors.type }}
+
{% trans "Operating Status" %}
+
{{ loadbalancer.operating_status }}
+
{% trans "Provisioning Status" %}
+
{{ loadbalancer.provisioning_status }}
+
{% trans "Description" %}
+
- {{ loadbalancer.description }}
+
{% trans "Admin Status" %}
+
{{ loadbalancer.enabled|title }}
+{#
{% trans "Load Balancing Method" %}
#} +{#
{{ loadbalancer.pool.lb_method }}
#} +{#
{% trans "Monitor" %}
#} +{#
{{ loadbalancer.pool.monitor }}
#} +
{% trans "Certificate" %}
+
- {{ loadbalancer.cert_name }}
+
+
+ +
+

{% trans "Members" %}

+
+
+ {% for group in loadbalancer.pool.members.members %} +
{{ group.id }}
+
IP: {{ group.address }}
+
Port: {{ group.protocol_port }}
+ {% endfor %} +
+
diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_launch_lb_help.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_launch_lb_help.html new file mode 100644 index 0000000..a57993d --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_launch_lb_help.html @@ -0,0 +1,15 @@ +{% load i18n horizon %} + +

{% blocktrans %}This wizard will walk you through setting up a new Load Balancer. A load balancer is a logical device. It is used to distribute workloads between multiple services called ’instances', based on the criteria defined as part of its configuration.{% endblocktrans %}

+ +

{% blocktrans %}Name: Enter a unique name for your load balancer (e.g., my-demo-lb).{% endblocktrans %}

+ +

{% blocktrans %}Description: Provide a friendly description for your records.{% endblocktrans %}

+ +

{% blocktrans %}Load Balancer Method: All load balancers utilize an algorithm that defines how traffic should be directed between instances. The following algorithms are supported: ROUND_ROBIN, LEAST_CONNECTIONS, LEAST_SESSIONS.{% endblocktrans %}

+ +

{% blocktrans %}Load Balancer Protocol: Load Balancing of applications using HTTP, HTTPS (Secure HTTP), TCP, and SSL (Secure TCP) protocols are supported.{% endblocktrans %}

+ +

{% blocktrans %}Load Balancer Port: This is automatically assigned based on the Load Balancer Protocol selection.{% endblocktrans %}

+ +

{% blocktrans %}Instance Port: The acceptable instance ports for both HTTPS/SSL and HTTP/TCP connections are 0-65535.{% endblocktrans %}

diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_create.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_create.html new file mode 100644 index 0000000..a2e12fe --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_create.html @@ -0,0 +1,49 @@ +{% load i18n %} + +{% block main %} +{##} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{#
#} +{#
#} +{# {% include "horizon/common/_form_fields.html" %}#} +{#
#} +{#
#} +{# {{ step.get_help_text }}#} +{#
#} +
+ {% include "horizon/common/_form_fields.html" %} +
+
+ {{ step.get_help_text }} +
+{% endblock %} + + + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_help.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_help.html new file mode 100644 index 0000000..356156e --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_monitor_help.html @@ -0,0 +1,10 @@ +{% load i18n %} + +

{% blocktrans %}When adding a load balancer, you can also specify a health check monitor to use to determine the health of your instances. Health checks routinely run against each instance within a target load balancer and the result of the health check is used to determine if the instance receives new connections.{% endblocktrans %}

+ +

{% blocktrans %}The following health check options are currently supported:{% endblocktrans %}

+ +

{% blocktrans %}TCP – Default Option – A basic TCP ping check is performed against IP Address of each instance within a target load balancer.{% endblocktrans %}

+ +

{% blocktrans %}HTTP – A basic HTTP health check is performed against each instance within a target load balancer on the specified instance port.{% endblocktrans %}

+ diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_ssl_cert_help.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_ssl_cert_help.html new file mode 100644 index 0000000..c45d23b --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/_ssl_cert_help.html @@ -0,0 +1,13 @@ +{% load i18n %} + +

{% blocktrans %}For secure website or application using Load Balancers to offload SSL decryption requires SSL server certificate installed on the load balancer.{% endblocktrans %}

+ +

{% blocktrans %}If you do not have individual SSL certificate requirements (including custom vanity domains) for your secure website or application, you can choose to use the common certificate that we provide.{% endblocktrans %}

+ +

{% blocktrans %}For individual SSL certificates, if you already have a SSL certificate and want to upload it; please specify the following details:{% endblocktrans %}

+ +

{% blocktrans %}Certificate Name – Supply a certificate name (i.e. my_server_cert).{% endblocktrans %}

+ +

{% blocktrans %}Private Key - Copy and paste the contents of the private key file in the Private Key field.{% endblocktrans %}

+ +

{% blocktrans %}Certificate Chain – Optional - Copy and paste the contents of the public key certificate chain file in the Certificate Chain field.{% endblocktrans %}

diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/detail.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/detail.html new file mode 100644 index 0000000..d361da6 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/detail.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n sizeformat %} +{% block title %}{% trans "Load Balancer Detail" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title="Load Balancer Detail: "|add:loadbalancer.name %} +{% endblock page_header %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/index.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/index.html new file mode 100644 index 0000000..2a59c01 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Load Balancers" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Load Balancers") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch.html new file mode 100644 index 0000000..fb7f968 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Launch Load Balancer" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Launch Load Balancer") %} +{% endblock page_header %} + +{% block main %} + {% include 'horizon/common/_workflow.html' %} +{% endblock %} diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch_lb.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch_lb.html new file mode 100644 index 0000000..e521960 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/launch_lb.html @@ -0,0 +1,82 @@ +{% load i18n %} + +{% block main %} +{##} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{#
#} +{#
#} +{#
#} +{# {% include "horizon/common/_form_fields.html" %}#} +{#
#} +{#
#} +{#
#} +{# {{ step.get_help_text }}#} +{#
#} +
+ {% include "horizon/common/_form_fields.html" %} +
+
+ {{ step.get_help_text }} +
+{% endblock %} + + + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/port_config.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/port_config.html new file mode 100644 index 0000000..2c73d73 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/port_config.html @@ -0,0 +1,76 @@ +{% load i18n %} + + + + + + + + + + + + +
FromprotocolToAction
+ + + + + + + + +
+ + + + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/ssl_cert.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/ssl_cert.html new file mode 100644 index 0000000..a14f6d2 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/ssl_cert.html @@ -0,0 +1,68 @@ +{% load i18n %} + +{% block main %} +{##} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{#
#} +{#
#} +{# {% include "horizon/common/_form_fields.html" %}#} +{#
#} +{#
#} +{# {{ step.get_help_text }}#} +{#
#} + +
+ {% include "horizon/common/_form_fields.html" %} +
+
+ {{ step.get_help_text }} +
+{% endblock %} + + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update.html new file mode 100644 index 0000000..f04c27c --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Load Balancer" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Load Balancer") %} +{% endblock page_header %} + +{% block main %} + {% include 'horizon/common/_workflow.html' %} +{% endblock %} diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_lb_step.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_lb_step.html new file mode 100644 index 0000000..7b1ca2e --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_lb_step.html @@ -0,0 +1,63 @@ + +{##} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{#
#} +{# {% include "horizon/common/_form_fields.html" %}#} +{# #} +{# {{ step.get_help_text }}#} +{#
#} +
+ {% include "horizon/common/_form_fields.html" %} +
+
+ {{ step.get_help_text }} +
+ + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_ssl_cert.html b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_ssl_cert.html new file mode 100644 index 0000000..7081d8c --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/templates/loadbalancersv2/update_ssl_cert.html @@ -0,0 +1,91 @@ +{% load i18n %} + +{% block main %} +{##} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{#
#} +{#
#} +{# {% include "horizon/common/_form_fields.html" %}#} +{#
#} +{#
#} +{# {{ step.get_help_text }}#} +{#
#} + +
+ {% include "horizon/common/_form_fields.html" %} +
+
+ {{ step.get_help_text }} +
+ + {% endblock %} + + diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/urls.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/urls.py new file mode 100644 index 0000000..f7641fd --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/urls.py @@ -0,0 +1,36 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import patterns +from django.conf.urls import url + +from .views import DetailView # noqa +from .views import IndexView # noqa +from .views import LaunchLoadBalancerView # noqa +from .views import UpdateView # noqa + + +INSTANCES = r'^(?P[^/]+)/%s$' +VIEW_MOD = 'openstack_dashboard.dashboards.project.loadbalancersv2.views' + + +urlpatterns = patterns(VIEW_MOD, + url(r'^$', IndexView.as_view(), name='index'), + url(r'^launch$', + LaunchLoadBalancerView.as_view(), name='launch'), + url(r'^(?P[^/]+)/$', + DetailView.as_view(), name='detail'), + url(INSTANCES % + 'update', UpdateView.as_view(), name='update'), + ) diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/views.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/views.py new file mode 100644 index 0000000..e9f3b8a --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/views.py @@ -0,0 +1,146 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +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 tables +from horizon import tabs +from horizon import workflows + +from neutron_lbaas_dashboard import api + +from .tables import LoadBalancersTable # noqa +from .tabs import LoadBalancerDetailTabs # noqa +from .workflows import LaunchLoadBalancer # noqa +from .workflows import UpdateLoadBalancer # noqa + + +LOG = logging.getLogger(__name__) + + +class IndexView(tables.DataTableView): + table_class = LoadBalancersTable + template_name = 'project/loadbalancersv2/index.html' + + def get_data(self): + pools = [] + try: + pools = api.lbaasv2.list_loadbalancers(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve pools list.')) + return pools + + +class LaunchLoadBalancerView(workflows.WorkflowView): + workflow_class = LaunchLoadBalancer + template_name = "project/loadbalancersv2/launch.html" + + def get_initial(self): + initial = super(LaunchLoadBalancerView, self).get_initial() + initial['project_id'] = self.request.user.tenant_id + initial['user_id'] = self.request.user.id + return initial + + +class UpdateView(workflows.WorkflowView): + workflow_class = UpdateLoadBalancer + template_name = 'project/loadbalancersv2/update.html' + success_url = reverse_lazy("horizon:project:loadbalancersv2:index") + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + context["loadbalancer_id"] = self.kwargs['loadbalancer_id'] + return context + + def get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + loadbalancer_id = self.kwargs['loadbalancer_id'] + LOG.info("DEBUGGING - loadbalancer_id=%s" % loadbalancer_id) + try: + vip = api.lbaasv2.show_loadbalancer(self.request, + loadbalancer_id) + self._object = vip.readable(self.request) + except Exception as e: + redirect = reverse("horizon:project:loadbalancersv2:index") + msg = _('Unable to retrieve load balancer details. %s')\ + % e.message + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_initial(self): + initial = super(UpdateView, self).get_initial() + + initial.update({ + 'loadbalancer_id': self.kwargs['loadbalancer_id'], + 'address': getattr(self.get_object(), 'address', ''), + 'name': getattr(self.get_object(), 'name', ''), + 'description': getattr(self.get_object(), 'description', ''), + 'lb_method': getattr(self.get_object(), 'lb_method', ''), + 'monitor': getattr(self.get_object(), 'monitor', ''), + 'interval': getattr(self.get_object(), 'interval', ''), + 'timeout': getattr(self.get_object(), 'timeout', ''), + 'send': getattr(self.get_object(), 'send', ''), + 'receive': getattr(self.get_object(), 'receive', ''), + + 'source_type': getattr(self.get_object(), 'protocol', ''), + 'http': getattr(self.get_object(), 'port', ''), + 'https': getattr(self.get_object(), 'port', ''), + 'instance_port': getattr(self.get_object(), 'instance_port', ''), + 'selected_members': getattr(self.get_object(), 'members', ''), + 'cert_name': getattr(self.get_object(), 'cert_name', ''), + 'cert': getattr(self.get_object(), 'cert', ''), + 'private_key': getattr(self.get_object(), 'private_key', ''), + 'chain_cert': getattr(self.get_object(), 'chain_cert', ''), + 'enabled': getattr(self.get_object(), 'enabled', ''), + 'use_common_cert': getattr(self.get_object(), + 'use_common_cert', ''), + 'port': getattr(self.get_object(), 'port', ''), + }) + return initial + + +class DetailView(tabs.TabView): + tab_group_class = LoadBalancerDetailTabs + template_name = 'project/loadbalancersv2/detail.html' + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + context["loadbalancer"] = self.get_data() + return context + + def get_data(self): + if not hasattr(self, "_loadbalancer"): + try: + loadbalancer_id = self.kwargs['loadbalancer_id'] + loadbalancer = api.lbaasv2.show_loadbalancer(self.request, + loadbalancer_id) + except Exception: + redirect = reverse('horizon:project:loadbalancersv2:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'loadbalancer "%s".') % loadbalancer_id, + redirect=redirect) + self._loadbalancer = loadbalancer + return self._loadbalancer.readable(self.request) + + def get_tabs(self, request, *args, **kwargs): + loadbalancer = self.get_data() + return self.tab_group_class(request, loadbalancer=loadbalancer, + **kwargs) diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/__init__.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/__init__.py new file mode 100644 index 0000000..7c50756 --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from . create_lb import * # noqa +from update_lb import * # noqa diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/create_lb.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/create_lb.py new file mode 100644 index 0000000..d269b8e --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/create_lb.py @@ -0,0 +1,462 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from openstack_dashboard.api import nova + +from neutron_lbaas_dashboard import api + +LOG = logging.getLogger(__name__) +__create_new__ = "Create New" + + +class SetLBDetailsAction(workflows.Action): + + address = forms.ChoiceField(label=_("IP"), + help_text=_("Select from existing VIP IPs")) + + name = forms.CharField(max_length=80, label=_("Name"), + required=True) + + description = forms.CharField(widget=forms.Textarea(attrs={'rows': 2}), + label=_( + "Load Balancer Description"), + required=False, + help_text=_("Provide Load Balancer " + "Description.")) + + all_vips = None + is_update = False + + LOAD_BALANCING_CHOICES = ( + ("RoundRobin", _("Round Robin")), + ("LeastConnection", _("Least Connection")), + ("LeastSessions", _("Least Sessions")) + ) + lb_method = forms.ChoiceField(label=_("Load Balancing Method"), + choices=LOAD_BALANCING_CHOICES) + + PROTOCOL_CHOICES = ( + ("HTTP", _("HTTP")), + ("HTTPS", _("HTTPS")), + ("TCP", _("TCP")), + ("SSL", _("SSL")), + ) + + protocol_type = forms.ChoiceField( + label=_("LB Protocol"), choices=PROTOCOL_CHOICES) + + port = forms.IntegerField(label=_("LB Port"), + required=False, + min_value=1, + max_value=65535, + help_text=_("LB Port on which " + "LB is listening.")) + + instance_port = forms.IntegerField(label=_("Instance Port"), + required=False, + min_value=1, + max_value=65535, + help_text=_("Instance Port on which " + "service is running.")) + + def __init__(self, request, *args, **kwargs): + super(SetLBDetailsAction, self).__init__(request, *args, **kwargs) + self.all_vips = [] + try: + # todo - this should be obtained in view via an initial method + self.all_vips = api.lbaasv2.list_loadbalancers(request) + except Exception: + pass + + if len(self.fields['address'].choices) == 0: + del self.fields['address'] + + class Meta(object): + name = _("LB Details") + help_text_template = ("project/loadbalancersv2/_launch_lb_help.html") + + def clean(self): + cleaned_data = super(SetLBDetailsAction, self).clean() + + lb_method = cleaned_data['lb_method'] + if not (lb_method == 'RoundRobin' + or lb_method == 'LeastConnection' + or lb_method == 'LeastSessions'): + raise forms.ValidationError(_("Please select an option for " + "the load balancing method.")) + + if not self.is_update: + all_vips = self.all_vips + ipPortCombo = [] + for vip in all_vips: + vip = vip.readable() + ipPortCombo.append('%s:%s' % (vip.address, vip.port)) + + data = self.data + if 'address' in data \ + and data['address'] != 'new' \ + and data['address'] != '': + address = data['address'] + selected_lb_port = data['port'] + selected_ip_port_combo = '%s:%s' % (address.split(':')[0], + selected_lb_port) + if selected_ip_port_combo in ipPortCombo: + raise forms.ValidationError(_('Requested IP and port ' + 'combination already ' + 'exists %s ') % + selected_ip_port_combo) + + instance_port = cleaned_data.get('instance_port', None) + if not instance_port: + raise forms.ValidationError( + _('Please provide instance port')) + + return cleaned_data + + def populate_address_choices(self, request, context): + if self.is_update: + return [] + try: + vips = api.lbaasv2.list_loadbalancers(request) + if len(vips) == 0: + return [] + + distict_ips = set() + for vip in vips: + vip = vip.readable() + distict_ips.add(vip.address) + + existing = [] + for vip in vips: + vip = vip.readable() + if vip.address in distict_ips: + item = ("%s:%s:%s" % + (vip.address, vip.name, 443), + "%s" % vip.address) + existing.append(item) + distict_ips.remove(vip.address) + + vip_list = [] + if len(existing) > 0: + vip_list.append(('new', __create_new__)) + vip_list.append(('Select Existing', existing)) + return vip_list + + except Exception: + exceptions.handle(request, + _('Unable to retrieve vips.')) + return [] + + def get_help_text(self): + extra = {} + return super(SetLBDetailsAction, self).get_help_text(extra) + + +class SetLBDetails(workflows.Step): + action_class = SetLBDetailsAction + contributes = ("name", "description", "lb_method", "protocol_type", "port", + "source_id", "instance_port", "address", "monitor") + + def contribute(self, data, context): + context = super(SetLBDetails, self).contribute(data, context) + return context + + template_name = "project/loadbalancersv2/launch_lb.html" + + +class UploadSSLAction(workflows.Action): + update_cert = forms.BooleanField(label='Update SSL Certificate', + required=False, + widget=forms.HiddenInput()) + + cert_name = forms.CharField(max_length=80, + label=_("Certificate Name"), + required=False) + + cert = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), + label=_("Certificate"), + required=False, + help_text=_("Certificate")) + + private_key = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), + label=_("Private Key"), + required=False, + help_text=_("Private Key")) + + chain_cert = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), + label=_("Certificate Chain (Optional)"), + required=False, + help_text=_("Intermediate Chain" + " Certificates")) + + def clean(self): + cleaned_data = super(UploadSSLAction, self).clean() + data = self.data + protocol = data.get('source_type') + if protocol == 'HTTPS': + use_common_cert = data.get('use_common_cert') + if not use_common_cert: + # check to see if ssl cert is provided + cert_name = data.get('cert_name') + cert = data.get('cert') + private_key = data.get('private_key') + + if (not cert_name) \ + or (not cert) \ + or (not private_key): + raise forms.ValidationError( + _('Please provide all certificate parameters.')) + return cleaned_data + + class Meta(object): + name = _("SSL Certificate") + help_text_template = ("project/loadbalancersv2/_ssl_cert_help.html") + + +class UploadSSLStep(workflows.Step): + action_class = UploadSSLAction + contributes = ("cert_name", "cert", + "private_key", "chain_cert", 'use_common_cert') + template_name = "project/loadbalancersv2/ssl_cert.html" + + def contribute(self, data, context): + post = self.workflow.request.POST + context['cert_name'] = post['cert_name'] if 'cert_name' in post else '' + context['cert'] = post['cert'] if 'cert' in post else '' + context['private_key'] = post[ + 'private_key'] if 'private_key' in post else '' + context['chain_cert'] = post[ + 'chain_cert'] if 'chain_cert' in post else '' + context['use_common_cert'] = post[ + 'use_common_cert'] if 'use_common_cert' in post else '' + return context + + +class SelectInstancesAction(workflows.MembershipAction): + instance_details = {} + + def __init__(self, request, *args, **kwargs): + super(SelectInstancesAction, self).__init__(request, *args, **kwargs) + err_msg = _('Unable to retrieve members list. ' + 'Please try again later.') + + default_role_field_name = self.get_default_role_field_name() + self.fields[default_role_field_name] = forms.CharField(required=False, + label='') + self.fields[default_role_field_name].initial = 'member' + + role_member_field_name = self.get_member_field_name('member') + self.fields[role_member_field_name] = forms.MultipleChoiceField( + required=False, label='') + + # Get list of available instances + all_instances = [] + try: + all_instances, has_more_data = nova.server_list(request) + except Exception: + exceptions.handle(request, err_msg) + + available_instances = [] + for instance in all_instances: + # skip shutoff instances + # if instance.status == 'SHUTOFF': + # continue + instance_ip = self.get_ip(instance) + # skip instances which has no network + if not instance_ip: + continue + key = instance_ip + value = instance.name + ' (' + self.get_ip(instance) + ')' + available_instances.append((key, value)) + self.instance_details[instance_ip] = (instance.name, instance.id) + + self.fields[self.get_member_field_name('member')].\ + choices = available_instances + + def get_ip(self, instance): + ipaddress = None + for networks in instance.addresses.itervalues(): + for ip in networks: + # only one IP present + ipaddress = ip + break + if ipaddress is not None: + addr = ipaddress["addr"] + else: + addr = None # '10.10.10.10' + return addr + + def clean(self): + cleaned_data = super(SelectInstancesAction, self).clean() + members = cleaned_data.get(self.get_member_field_name('member'), None) + if not members: + raise forms.ValidationError( + _('Please select at least one member')) + return cleaned_data + + class Meta(object): + name = _("Instances") + slug = "select_instances" + + +class SelectInstancesStep(workflows.UpdateMembersStep): + action_class = SelectInstancesAction + help_text = _("Please select a list of instances that should handle" + " traffic for this target load balancer. All instances " + "must reside in the same Project as the target load " + "balancer.") + available_list_title = _("All Instances") + members_list_title = _("Selected Instances") + no_available_text = _("No instances found.") + no_members_text = _("No members enabled.") + show_roles = False + contributes = ( + "wanted_members", "instances_details", "monitor", "instance_port") + template_name = "horizon/common/_workflow_step_update_members.html" + + def contribute(self, data, context): + request = self.workflow.request + if data: + context["wanted_members"] = request.POST.getlist( + self.get_member_field_name('member')) + context["instances_details"] = self.action.instance_details + context["monitor"] = request.POST.get("monitor") + context["instance_port"] = request.POST.get("instance_port") + return context + + +class SelectMonitorAction(workflows.Action): + MONITOR_CHOICES = ( + ("tcp", _("TCP")), + ("ping", _("PING")), + ("http", _("HTTP")), + ) + monitor = forms.ChoiceField(label=_("Monitor"), + choices=MONITOR_CHOICES) + + interval = forms.IntegerField(label=_("Health Check Interval" + " (in seconds)"), + required=False, + min_value=1, + max_value=600, + help_text=_("Health Check Interval" + " (in seconds)")) + + timeout = forms.IntegerField(label=_("Retry count before markdown"), + required=False, + min_value=1, + max_value=100, + help_text=_("Number of times health check " + "should be attempted before " + "marking down a member")) + + send = forms.CharField(widget=forms.Textarea(attrs={'rows': 1}), + label=_("Send String"), + required=False, + help_text=_("Send String")) + + receive = forms.CharField(widget=forms.Textarea(attrs={'rows': 1}), + label=_("Receive String"), + required=False, + help_text=_("Receive String")) + + class Meta(object): + name = _("Monitor") + help_text_template = ("project/loadbalancersv2/_monitor_help.html") + + +class SelectMonitorStep(workflows.Step): + action_class = SelectMonitorAction + contributes = ("monitor", "interval", "timeout", "send", "receive") + template_name = "project/loadbalancersv2/_monitor_create.html" + + def contribute(self, data, context): + post = self.workflow.request.POST + + context['interval'] = post['interval'] if 'interval' in post else '' + context['timeout'] = post['timeout'] if 'timeout' in post else '' + context['send'] = post['send'] if 'send' in post else '' + context['receive'] = post['receive'] if 'receive' in post else '' + return context + + +class LaunchLoadBalancer(workflows.Workflow): + slug = "launch_loadbalancer" + name = _("Launch Load Balancer") + finalize_button_name = _("Launch") + success_message = _('Launched %(count)s named "%(name)s".') + failure_message = _('Unable to launch %(count)s named "%(name)s".') + success_url = "horizon:project:loadbalancersv2:index" + default_steps = (SetLBDetails, + UploadSSLStep, + SelectMonitorStep, + SelectInstancesStep, + ) + attrs = {'data-help-text': 'LB creation may take a few minutes'} + + def format_status_message(self, message): + name = self.context.get('name', 'unknown loadbalancer') + count = self.context.get('count', 1) + if int(count) > 1: + return message % {"count": _("%s loadbalancers") % count, + "name": name} + else: + return message % {"count": _("loadbalancer"), "name": name} + + def handle(self, request, context): + try: + protocol = context['source_type'] + address = context['address'] + if not address\ + or address == "new": + address = '' + else: + tokens = address.split(':') + address = tokens[0] + + api.lbaasv2.\ + create_loadbalancer_full(request, + address=address, + name=context['name'], + description=context['description'], + lb_method=context['lb_method'], + monitor=context['monitor'], + protocol=protocol, + port=context[protocol], + instance_port=context['instance_port'], # noqa + wanted_members=context['wanted_members'], # noqa + instances_details=context['instances_details'], # noqa + cert_name=context['cert_name'], + cert=context['cert'], + private_key=context['private_key'], + chain_cert=context['chain_cert'], + use_common_cert=True if + context['use_common_cert'] == 'on' + else False, + interval=context['interval'], + timeout=context['timeout'], + send=context['send'], + receive=context['receive'], + ) + return True + except Exception as e: + exceptions.handle(request, e.message, ignore=False) + return False diff --git a/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/update_lb.py b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/update_lb.py new file mode 100644 index 0000000..e5b5bcc --- /dev/null +++ b/neutron_lbaas_dashboard/dashboards/project/loadbalancersv2/workflows/update_lb.py @@ -0,0 +1,192 @@ +# Copyright 2015, eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext as _ +from neutron_lbaas_dashboard import api +from create_lb import * # noqa + +LOG = logging.getLogger(__name__) +INDEX_URL = "horizon:projects:loadbalancersv2:index" + + +READ_ONLY = {'readonly': 'readonly'} + + +class UpdateLBDetailsAction(SetLBDetailsAction): + address = forms.CharField(widget=forms.widgets.Input(attrs=READ_ONLY), + label=_('IP'), + required=True) + + name = forms.CharField(widget=forms.widgets.Input(attrs=READ_ONLY), + label=_('Name'), + required=False) + + port = forms.IntegerField(widget=forms.widgets.Input(attrs=READ_ONLY), + label=_("LB Port"), + required=False, + min_value=1, + max_value=65535, + help_text=_("LB Port on which " + "LB is listening.")) + + is_update = True + + def __init__(self, request, context, *args, **kwargs): + super(UpdateLBDetailsAction, self).__init__(request, context, *args, + **kwargs) + self.fields['address'].initial = context['address'] + + class Meta(object): + name = _("LB Details") + help_text_template = ("project/loadbalancersv2/" + "_launch_lb_help.html") + + +class UpdateLBDetails(SetLBDetails): + action_class = UpdateLBDetailsAction + template_name = "project/loadbalancersv2/update_lb_step.html" + + +class UpdateSSLAction(UploadSSLAction): + + update_cert = forms.BooleanField(label='Update SSL Certificate', + required=False, + widget=forms.CheckboxInput()) + + # def clean(self): + # cleaned_data = super(UploadSSLAction, self).clean() + # data = self.data + # protocol = data.get('source_type') + # if protocol == 'HTTPS': + # update_cert = data.get('update_cert') + # if update_cert: + # use_common_cert = data.get('use_common_cert') + # if not use_common_cert: + # # check to see if ssl cert is provided + # cert_name = data.get('cert_name') + # cert = data.get('cert') + # private_key = data.get('private_key') + # + # if (not cert_name) \ + # or (not cert) \ + # or (not private_key): + # raise forms.ValidationError( + # _('Please provide all certificate parameters.')) + # return cleaned_data + + class Meta(object): + name = _("SSL Certificate") + help_text_template = ("project/loadbalancersv2/_ssl_cert_help.html") + + +class UpdateSSLStep(UploadSSLStep): + action_class = UpdateSSLAction + contributes = ("cert_name", "cert", "private_key", + "chain_cert", 'use_common_cert', "update_cert") + template_name = "project/loadbalancersv2/update_ssl_cert.html" + + def contribute(self, data, context): + post = self.workflow.request.POST + context['cert_name'] = post['cert_name'] if 'cert_name' in post else '' + context['cert'] = post['cert'] if 'cert' in post else '' + context['private_key'] = post[ + 'private_key'] if 'private_key' in post else '' + context['chain_cert'] = post[ + 'chain_cert'] if 'chain_cert' in post else '' + context['use_common_cert'] = post[ + 'use_common_cert'] if 'use_common_cert' in post else '' + context['update_cert'] = post[ + 'update_cert'] if 'update_cert' in post else '' + return context + + +class UpdateInstancesAction(SelectInstancesAction): + + def __init__(self, request, *args, **kwargs): + super(UpdateInstancesAction, self).__init__(request, *args, **kwargs) + err_msg = _('Unable to retrieve members list. ' + 'Please try again later.') + + pre_selectd = [] + try: + pre_selectd = args[0]['selected_members'] + except Exception: + exceptions.handle(request, err_msg) + self.fields[self.get_member_field_name('member')].initial = pre_selectd + + class Meta(object): + name = _("Instances") + slug = "select_instances" + + +class UpdateInstancesStep(SelectInstancesStep): + action_class = UpdateInstancesAction + depends_on = ("loadbalancer_id",) + contributes = ("wanted_members", "selected_members", + "loadbalancer_id", "instances_details", + "monitor", "instance_port") + + +class UpdateLoadBalancer(LaunchLoadBalancer): + slug = "update_loadbalancer" + name = _("Edit Load Balancer") + finalize_button_name = _("Update") + success_message = _('Updated load balancer "%s".') + failure_message = _('Unable to modify load balancer "%s".') + success_url = "horizon:project:loadbalancersv2:index" + default_steps = (UpdateLBDetails, + UpdateSSLStep, + SelectMonitorStep, + UpdateInstancesStep) + attrs = {'data-help-text': 'Updating LB may take a few minutes'} + + def format_status_message(self, message): + return message % self.context.get('name', 'unknown load balancer') + + def handle(self, request, context): + + try: + protocol = context['source_type'] + + api.lbui.vip_create(request, + update=True, + loadbalancer_id=context['loadbalancer_id'], + address=context['address'], + name=context['name'], + description=context['description'], + lb_method=context['lb_method'], + monitor=context['monitor'], + protocol=protocol, + port=context['port'], + instance_port=context['instance_port'], + wanted_members=context['wanted_members'], + instances_details=context['instances_details'], + cert_name=context['cert_name'], + cert=context['cert'], + private_key=context['private_key'], + chain_cert=context['chain_cert'], + use_common_cert=True if context[ + 'use_common_cert'] == 'on' else False, + update_cert=True if context[ + 'update_cert'] == 'on' else False, + interval=context['interval'], + timeout=context['timeout'], + send=context['send'], + receive=context['receive'], + ) + + return True + except Exception as e: + exceptions.handle(request, e.message, ignore=False) + return False diff --git a/neutron_lbaas_dashboard/enabled/_1480_project_loadbalancersv2_panel.py b/neutron_lbaas_dashboard/enabled/_1480_project_loadbalancersv2_panel.py new file mode 100644 index 0000000..09e8451 --- /dev/null +++ b/neutron_lbaas_dashboard/enabled/_1480_project_loadbalancersv2_panel.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'loadbalancersv2' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'network' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ( + 'neutron_lbaas_dashboard.dashboards.project.loadbalancersv2.panel' + '.LoadBalancersUI') diff --git a/neutron_lbaas_dashboard/enabled/__init__.py b/neutron_lbaas_dashboard/enabled/__init__.py new file mode 100644 index 0000000..e69de29