vmware-nsx/vmware_nsx/services/lbaas/nsx_v3/implementation/loadbalancer_mgr.py

385 lines
18 KiB
Python

# Copyright 2017 VMware, Inc.
# 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 neutron_lib import exceptions as n_exc
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from oslo_utils import excutils
from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.db import db as nsx_db
from vmware_nsx.services.lbaas import base_mgr
from vmware_nsx.services.lbaas import lb_const
from vmware_nsx.services.lbaas.nsx_v3.implementation import lb_utils
from vmware_nsxlib.v3 import exceptions as nsxlib_exc
from vmware_nsxlib.v3 import utils
LOG = logging.getLogger(__name__)
class EdgeLoadBalancerManagerFromDict(base_mgr.Nsxv3LoadbalancerBaseManager):
@log_helpers.log_method_call
def create(self, context, lb, completor):
if not lb_utils.validate_lb_subnet(context, self.core_plugin,
lb['vip_subnet_id']):
completor(success=False)
msg = (_('Cannot create lb on subnet %(sub)s for '
'loadbalancer %(lb)s. The subnet needs to connect a '
'router which is already set gateway.') %
{'sub': lb['vip_subnet_id'], 'lb': lb['id']})
raise n_exc.BadRequest(resource='lbaas-subnet', msg=msg)
service_client = self.core_plugin.nsxlib.load_balancer.service
nsx_router_id = None
lb_service = None
nsx_router_id = lb_utils.NO_ROUTER_ID
router_id = lb_utils.get_router_from_network(
context, self.core_plugin, lb['vip_subnet_id'])
if router_id:
nsx_router_id = nsx_db.get_nsx_router_id(context.session,
router_id)
lb_service = service_client.get_router_lb_service(nsx_router_id)
if not lb_service:
lb_size = lb_utils.get_lb_flavor_size(
self.flavor_plugin, context, lb.get('flavor_id'))
if router_id:
# Make sure the NSX service router exists
if not self.core_plugin.service_router_has_services(
context, router_id):
self.core_plugin.create_service_router(context, router_id)
lb_service = self._create_lb_service(
context, service_client, lb['tenant_id'],
router_id, nsx_router_id, lb['id'], lb_size)
else:
lb_service = self._create_lb_service_without_router(
context, service_client, lb['tenant_id'],
lb, lb_size)
if not lb_service:
completor(success=False)
msg = (_('Failed to create lb service for loadbalancer '
'%s') % lb['id'])
raise nsx_exc.NsxPluginException(err_msg=msg)
nsx_db.add_nsx_lbaas_loadbalancer_binding(
context.session, lb['id'], lb_service['id'],
nsx_router_id, lb['vip_address'])
completor(success=True)
@log_helpers.log_method_call
def _create_lb_service(self, context, service_client, tenant_id,
router_id, nsx_router_id, lb_id, lb_size):
"""Create NSX LB service for a specific neutron router"""
router = self.core_plugin.get_router(context, router_id)
if not router.get('external_gateway_info'):
msg = (_('Tenant router %(router)s does not connect to '
'external gateway') % {'router': router['id']})
raise n_exc.BadRequest(resource='lbaas-lbservice-create',
msg=msg)
lb_name = utils.get_name_and_uuid(router['name'] or 'router',
router_id)
tags = lb_utils.get_tags(self.core_plugin, router_id,
lb_const.LR_ROUTER_TYPE,
tenant_id, context.project_name)
attachment = {'target_id': nsx_router_id,
'target_type': 'LogicalRouter'}
try:
lb_service = service_client.create(display_name=lb_name,
tags=tags,
attachment=attachment,
size=lb_size)
except nsxlib_exc.ManagerError as e:
LOG.error("Failed to create LB service: %s", e)
return
# Add rule to advertise external vips
lb_utils.update_router_lb_vip_advertisement(
context, self.core_plugin, router, nsx_router_id)
return lb_service
@log_helpers.log_method_call
def _create_lb_service_without_router(self, context, service_client,
tenant_id, lb, lb_size):
"""Create NSX LB service for an external VIP
This service will not be attached to a router yet, and it will be
updated once the first member is created.
"""
lb_id = lb['id']
lb_name = utils.get_name_and_uuid(lb['name'] or 'loadbalancer',
lb_id)
tags = lb_utils.get_tags(self.core_plugin, '',
lb_const.LR_ROUTER_TYPE,
tenant_id, context.project_name)
try:
lb_service = service_client.create(display_name=lb_name,
tags=tags,
size=lb_size)
except nsxlib_exc.ManagerError as e:
LOG.error("Failed to create LB service: %s", e)
return
return lb_service
@log_helpers.log_method_call
def update(self, context, old_lb, new_lb, completor):
vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server
app_client = self.core_plugin.nsxlib.load_balancer.application_profile
if new_lb['name'] != old_lb['name']:
for listener in new_lb['listeners']:
binding = nsx_db.get_nsx_lbaas_listener_binding(
context.session, new_lb['id'], listener['id'])
if binding:
vs_id = binding['lb_vs_id']
app_profile_id = binding['app_profile_id']
new_lb_name = new_lb['name'][:utils.MAX_TAG_LEN]
try:
# Update tag on virtual server with new lb name
vs = vs_client.get(vs_id)
updated_tags = utils.update_v3_tags(
vs['tags'], [{'scope': lb_const.LB_LB_NAME,
'tag': new_lb_name}])
vs_client.update(vs_id, tags=updated_tags)
# Update tag on application profile with new lb name
app_profile = app_client.get(app_profile_id)
app_client.update(
app_profile_id, tags=updated_tags,
resource_type=app_profile['resource_type'])
except nsxlib_exc.ManagerError:
with excutils.save_and_reraise_exception():
completor(success=False)
LOG.error('Failed to update tag %(tag)s for lb '
'%(lb)s', {'tag': updated_tags,
'lb': new_lb['name']})
completor(success=True)
@log_helpers.log_method_call
def delete(self, context, lb, completor):
service_client = self.core_plugin.nsxlib.load_balancer.service
router_client = self.core_plugin.nsxlib.logical_router
lb_binding = nsx_db.get_nsx_lbaas_loadbalancer_binding(
context.session, lb['id'])
if lb_binding:
lb_service_id = lb_binding['lb_service_id']
nsx_router_id = lb_binding['lb_router_id']
try:
lb_service = service_client.get(lb_service_id)
except nsxlib_exc.ManagerError:
LOG.warning("LB service %(lbs)s is not found",
{'lbs': lb_service_id})
else:
vs_list = lb_service.get('virtual_server_ids')
if not vs_list:
try:
service_client.delete(lb_service_id)
# If there is no lb service attached to the router,
# delete the router advertise_lb_vip rule.
if nsx_router_id != lb_utils.NO_ROUTER_ID:
router_client.update_advertisement_rules(
nsx_router_id, [],
name_prefix=lb_utils.ADV_RULE_NAME)
except nsxlib_exc.ManagerError:
completor(success=False)
msg = (_('Failed to delete lb service %(lbs)s from nsx'
) % {'lbs': lb_service_id})
raise n_exc.BadRequest(resource='lbaas-lb', msg=msg)
nsx_db.delete_nsx_lbaas_loadbalancer_binding(
context.session, lb['id'])
if nsx_router_id != lb_utils.NO_ROUTER_ID:
router_id = nsx_db.get_neutron_from_nsx_router_id(
context.session, nsx_router_id)
# Service router is needed only when the LB exist, and
# no other services are using it.
if not self.core_plugin.service_router_has_services(
context,
router_id):
self.core_plugin.delete_service_router(context,
router_id)
completor(success=True)
@log_helpers.log_method_call
def delete_cascade(self, context, lb, completor):
"""Delete all backend and DB resources of this loadbalancer"""
self.delete(context, lb, completor)
@log_helpers.log_method_call
def refresh(self, context, lb):
# TODO(tongl): implement
pass
@log_helpers.log_method_call
def _nsx_status_to_lb_status(self, nsx_status):
if not nsx_status:
# default fallback
return lb_const.ONLINE
# Statuses that are considered ONLINE:
if nsx_status.upper() in ['UP', 'UNKNOWN', 'PARTIALLY_UP',
'NO_STANDBY']:
return lb_const.ONLINE
# Statuses that are considered OFFLINE:
if nsx_status.upper() in ['PRIMARY_DOWN', 'DETACHED', 'DOWN', 'ERROR']:
return lb_const.OFFLINE
if nsx_status.upper() == 'DISABLED':
return lb_const.DISABLED
# default fallback
LOG.debug("NSX LB status %s - interpreted as ONLINE", nsx_status)
return lb_const.ONLINE
@log_helpers.log_method_call
def get_lb_pool_members_statuses(self, nsx_pool_id, members_statuses):
# Combine the NSX pool members data and the NSX statuses to provide
# member statuses list
# Get the member id from the suffix of the member in the NSX pool list
# and find the matching ip+port member in the statuses list
# get the members list from the NSX
nsx_pool = self.core_plugin.nsxlib.load_balancer.pool.get(nsx_pool_id)
if not nsx_pool or not nsx_pool.get('members'):
return []
# create a map of existing members: ip+port -> lbaas ID (which is the
# suffix of the member name)
members_map = {}
for member in nsx_pool['members']:
ip = member['ip_address']
port = member['port']
if ip not in members_map:
members_map[ip] = {}
members_map[ip][port] = member['display_name'][-36:]
# go over the statuses map, and match the member ip_port, to the ID
# in the map
statuses = []
for member in members_statuses:
ip = member['ip_address']
port = member['port']
if ip in members_map and port in members_map[ip]:
member_id = members_map[ip][port]
member_status = self._nsx_status_to_lb_status(member['status'])
statuses.append({'id': member_id, 'status': member_status})
return statuses
@log_helpers.log_method_call
def get_operating_status(self, context, id, with_members=False):
"""Return a map of the operating status of all connected LB objects """
service_client = self.core_plugin.nsxlib.load_balancer.service
lb_binding = nsx_db.get_nsx_lbaas_loadbalancer_binding(
context.session, id)
if not lb_binding:
LOG.warning("Failed to get loadbalancer %s operating status. "
"Mapping was not found", id)
return {}
lb_service_id = lb_binding['lb_service_id']
try:
service_status = service_client.get_status(lb_service_id)
if not isinstance(service_status, dict):
service_status = {}
vs_statuses = service_client.get_virtual_servers_status(
lb_service_id)
if not isinstance(vs_statuses, dict):
vs_statuses = {}
except nsxlib_exc.ManagerError:
LOG.warning("LB service %(lbs)s is not found",
{'lbs': lb_service_id})
return {}
# get the loadbalancer status from the LB service
lb_status = self._nsx_status_to_lb_status(
service_status.get('service_status'))
statuses = {lb_const.LOADBALANCERS: [{'id': id, 'status': lb_status}],
lb_const.LISTENERS: [],
lb_const.POOLS: [],
lb_const.MEMBERS: []}
# Add the listeners statuses from the virtual servers statuses
for vs in vs_statuses.get('results', []):
vs_status = self._nsx_status_to_lb_status(vs.get('status'))
vs_id = vs.get('virtual_server_id')
list_binding = nsx_db.get_nsx_lbaas_listener_binding_by_lb_and_vs(
context.session, id, vs_id)
if list_binding:
listener_id = list_binding['listener_id']
statuses[lb_const.LISTENERS].append(
{'id': listener_id, 'status': vs_status})
# Add the pools statuses from the LB service status
for pool in service_status.get('pools', []):
nsx_pool_id = pool.get('pool_id')
pool_status = self._nsx_status_to_lb_status(pool.get('status'))
pool_binding = nsx_db.get_nsx_lbaas_pool_binding_by_lb_pool(
context.session, id, nsx_pool_id)
if pool_binding:
pool_id = pool_binding['pool_id']
statuses[lb_const.POOLS].append(
{'id': pool_id, 'status': pool_status})
# Add the pools members
if with_members and pool.get('members'):
statuses[lb_const.MEMBERS].extend(
self.get_lb_pool_members_statuses(
nsx_pool_id, pool['members']))
return statuses
@log_helpers.log_method_call
def stats(self, context, lb):
# Since multiple LBaaS loadbalancer can share the same LB service,
# get the corresponding virtual servers' stats instead of LB service.
stats = {'active_connections': 0,
'bytes_in': 0,
'bytes_out': 0,
'total_connections': 0}
service_client = self.core_plugin.nsxlib.load_balancer.service
lb_binding = nsx_db.get_nsx_lbaas_loadbalancer_binding(
context.session, lb['id'])
vs_list = self._get_lb_virtual_servers(context, lb)
if lb_binding:
lb_service_id = lb_binding.get('lb_service_id')
try:
rsp = service_client.get_stats(lb_service_id)
if rsp:
for vs in rsp.get('virtual_servers', []):
# Skip the virtual server that doesn't belong
# to this loadbalancer
if vs['virtual_server_id'] not in vs_list:
continue
vs_stats = vs.get('statistics', {})
for stat in lb_const.LB_STATS_MAP:
lb_stat = lb_const.LB_STATS_MAP[stat]
stats[stat] += vs_stats.get(lb_stat, 0)
except nsxlib_exc.ManagerError:
msg = _('Failed to retrieve stats from LB service '
'for loadbalancer %(lb)s') % {'lb': lb['id']}
raise n_exc.BadRequest(resource='lbaas-lb', msg=msg)
return stats
@log_helpers.log_method_call
def _get_lb_virtual_servers(self, context, lb):
# Get all virtual servers that belong to this loadbalancer
vs_list = []
for listener in lb['listeners']:
vs_binding = nsx_db.get_nsx_lbaas_listener_binding(
context.session, lb['id'], listener['id'])
if vs_binding:
vs_list.append(vs_binding.get('lb_vs_id'))
return vs_list