RETIRED, Tricircle is to provide networking automation across Neutron.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

352 lines
14 KiB

# Copyright 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from oslo_log import log
from neutron.services.trunk import exceptions as trunk_exc
from neutron.services.trunk import plugin as trunk_plugin
from neutron_lib.db import utils as db_utils
from neutron_lib.plugins import directory
import tricircle.common.client as t_client
import tricircle.common.constants as t_constants
import tricircle.common.context as t_context
from tricircle.common import xrpcapi
import tricircle.db.api as db_api
from tricircle.network import central_plugin
from tricircle.network import helper
LOG = log.getLogger(__name__)
class TricircleTrunkDriver(trunk_plugin.TrunkPlugin):
def __init__(self):
super(TricircleTrunkDriver, self).__init__()
self.xjob_handler = xrpcapi.XJobAPI()
self.helper = helper.NetworkHelper(self)
self.clients = {}
def is_rpc_enabled(self):
return False
def _get_client(self, region_name):
if region_name not in self.clients:
self.clients[region_name] = t_client.Client(region_name)
return self.clients[region_name]
@property
def registered_drivers(self):
return super(TricircleTrunkDriver, self).registered_drivers()
@property
def supported_interfaces(self):
"""A set of supported interfaces."""
return super(TricircleTrunkDriver, self).supported_interfaces()
@property
def supported_agent_types(self):
"""A set of supported agent types."""
return super(TricircleTrunkDriver, self).supported_agent_types()
def update_trunk(self, context, trunk_id, trunk):
# update trunk
t_ctx = t_context.get_context_from_neutron_context(context)
with context.session.begin():
res = super(TricircleTrunkDriver, self).update_trunk(
context, trunk_id, trunk)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, trunk_id, t_constants.RT_TRUNK)
if mappings:
b_pod = mappings[0][0]
self.xjob_handler.sync_trunk(t_ctx, res['project_id'],
trunk_id, b_pod['pod_id'])
return res
def delete_trunk(self, context, trunk_id):
t_ctx = t_context.get_context_from_neutron_context(context)
res = super(TricircleTrunkDriver, self).get_trunk(
context, trunk_id)
with context.session.begin():
super(TricircleTrunkDriver, self).delete_trunk(
context, trunk_id)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, trunk_id, t_constants.RT_TRUNK)
if mappings:
b_pod = mappings[0][0]
self.xjob_handler.sync_trunk(t_ctx, res['project_id'],
trunk_id, b_pod['pod_id'])
def get_trunk(self, context, trunk_id, fields=None):
t_ctx = t_context.get_context_from_neutron_context(context)
t_trunk = super(TricircleTrunkDriver, self).get_trunk(
context, trunk_id, fields)
if not fields or 'status' in fields:
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, trunk_id, t_constants.RT_TRUNK)
if mappings:
b_pod, b_trunk_id = mappings[0]
b_region_name = b_pod['region_name']
b_client = self._get_client(region_name=b_region_name)
b_trunk = b_client.get_trunks(t_ctx, b_trunk_id)
if not b_trunk:
LOG.error('trunk: %(trunk_id)s not found '
'pod name: %(name)s',
{'trunk_id': b_trunk_id, 'name': b_region_name})
else:
t_trunk['status'] = b_trunk['status']
return t_trunk
def _get_trunks_from_top(self, context, top_bottom_map, filters):
top_trunks = super(TricircleTrunkDriver, self).get_trunks(
context, filters)
return [trunk for trunk in top_trunks if
trunk['id'] not in top_bottom_map]
def _get_min_search_step(self):
# this method is for unit test mock purpose
return 100
def _get_trunks_from_top_with_limit(self, context, top_bottom_map,
filters, limit, marker):
ret = []
total = 0
# set step as two times of number to have better chance to obtain all
# trunks we need
search_step = limit * 2
min_search_step = self._get_min_search_step()
if search_step < min_search_step:
search_step = min_search_step
# None means sort direction is desc
sorts = [('id', None)]
top_trunks = super(TricircleTrunkDriver, self).get_trunks(
context, filters, sorts=sorts, limit=search_step, marker=marker)
for trunk in top_trunks:
total += 1
if trunk['id'] not in top_bottom_map:
ret.append(trunk)
if len(ret) == limit:
return ret
# NOTE(xiulin) we have traversed all the trunks
if total < search_step:
return ret
else:
ret.extend(self._get_trunks_from_top_with_limit(
context, top_bottom_map, filters, limit - len(ret),
ret[-1]['id']))
return ret
def _get_trunks_from_pod_with_limit(self, context, current_pod,
bottom_top_map, top_bottom_map,
filters, limit, marker):
ret = []
t_ctx = t_context.get_context_from_neutron_context(context)
q_client = self._get_client(
current_pod['region_name']).get_native_client('trunk', t_ctx)
params = {'limit': 0 if not limit else limit}
if marker:
b_marker = top_bottom_map[marker]
params.update({'marker': b_marker})
if filters:
if 'id' in filters:
map_ids = self._get_map_trunk_ids(filters['id'],
top_bottom_map)
filters['id'] = map_ids
params.update(filters)
bottom_trunks = q_client.get(q_client.trunks_path,
params=params)['trunks']
for bottom_trunk in bottom_trunks:
top_id = bottom_top_map.get(bottom_trunk['id'])
# TODO(xiulin): handle unmapped trunk
if top_id:
bottom_trunk['id'] = top_id
ret.append(bottom_trunk)
if len(ret) == limit:
return ret
remainder = limit - len(ret)
next_pod = db_api.get_next_bottom_pod(
t_ctx, current_pod_id=current_pod['pod_id'])
if next_pod:
# get from next bottom pod
next_ret = self._get_trunks_from_pod_with_limit(
context, next_pod, bottom_top_map, top_bottom_map,
filters, remainder, None)
ret.extend(next_ret)
return ret
else:
# get from top pod
top_ret = self._get_trunks_from_top_with_limit(
context, top_bottom_map, filters, remainder, None)
ret.extend(top_ret)
return ret
def _map_trunks_from_bottom_to_top(self, trunks, bottom_top_map):
trunk_list = []
for trunk in trunks:
# TODO(xiulin): handle unmapped trunk
if trunk['id'] not in bottom_top_map:
continue
trunk['id'] = bottom_top_map[trunk['id']]
trunk_list.append(trunk)
return trunk_list
def _get_map_trunk_ids(self, top_ids, top_bottom_map):
b_trunk_ids = []
for _id in top_ids:
if _id in top_bottom_map:
b_trunk_ids.append(top_bottom_map[_id])
else:
b_trunk_ids.append(_id)
return b_trunk_ids
def _transform_trunk_filters(self, filters, top_bottom_map):
_filters = []
if filters:
for key, value in six.iteritems(filters):
if key == 'id':
value = self._get_map_trunk_ids(value, top_bottom_map)
_filters.append({'key': key,
'comparator': 'eq',
'value': value})
return _filters
def get_trunks(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None, page_reverse=False):
ret = []
bottom_top_map = {}
top_bottom_map = {}
t_ctx = t_context.get_context_from_neutron_context(context)
route_filters = [{'key': 'resource_type',
'comparator': 'eq',
'value': t_constants.RT_TRUNK}]
routes = db_api.list_resource_routings(t_ctx, route_filters)
for route in routes:
bottom_top_map[route['bottom_id']] = route['top_id']
top_bottom_map[route['top_id']] = route['bottom_id']
if limit:
if marker:
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, marker, t_constants.RT_TRUNK)
# if mapping exists, we retrieve trunk information
# from bottom, otherwise from top
if mappings:
pod_id = mappings[0][0]['pod_id']
current_pod = db_api.get_pod(t_ctx, pod_id)
ret = self._get_trunks_from_pod_with_limit(
context, current_pod, bottom_top_map, top_bottom_map,
filters, limit, marker)
else:
ret = self._get_trunks_from_top_with_limit(
context, top_bottom_map, filters, limit, marker)
else:
current_pod = db_api.get_next_bottom_pod(t_ctx)
# if current_pod exists, we retrieve trunk information
# from bottom, otherwise from top
if current_pod:
ret = self._get_trunks_from_pod_with_limit(
context, current_pod, bottom_top_map, top_bottom_map,
filters, limit, None)
else:
ret = self._get_trunks_from_top_with_limit(
context, top_bottom_map, filters, limit, None)
else:
pods = db_api.list_pods(t_ctx)
_filters = self._transform_trunk_filters(filters, top_bottom_map)
for pod in pods:
if not pod['az_name']:
continue
client = self._get_client(pod['region_name'])
pod_trunks = client.list_trunks(t_ctx, filters=_filters)
ret.extend(pod_trunks)
ret = self._map_trunks_from_bottom_to_top(ret, bottom_top_map)
top_trunks = self._get_trunks_from_top(context,
top_bottom_map, filters)
ret.extend(top_trunks)
return [db_utils.resource_fields(trunk, fields) for trunk in ret]
def get_trunk_subports(self, context, filters=None):
ret = None
if not filters or len(filters) != 2:
return ret
device_ids = filters.get('device_id', [])
device_owners = filters.get('device_owner', [])
if (len(device_owners) != 1
or len(device_ids) != 1
or device_owners[0] != t_constants.DEVICE_OWNER_SUBPORT):
return ret
try:
super(TricircleTrunkDriver, self).get_trunk(context, device_ids[0])
except trunk_exc.TrunkNotFound:
return ret
core_plugin = directory.get_plugin()
ret = super(central_plugin.TricirclePlugin, core_plugin).get_ports(
context, filters)
return ret
def update_subports_device_id(self, context,
subports, device_id, device_owner):
if not subports['sub_ports']:
return
core_plugin = directory.get_plugin()
body = {'port': {
'device_id': device_id,
'device_owner': device_owner}}
for subport in subports['sub_ports']:
super(central_plugin.TricirclePlugin, core_plugin).update_port(
context, subport['port_id'], body)
def add_subports(self, context, trunk_id, subports):
t_ctx = t_context.get_context_from_neutron_context(context)
with context.session.begin():
self.update_subports_device_id(context, subports, trunk_id,
t_constants.DEVICE_OWNER_SUBPORT)
res = super(TricircleTrunkDriver, self).add_subports(
context, trunk_id, subports)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, trunk_id, t_constants.RT_TRUNK)
if mappings:
b_pod = mappings[0][0]
self.xjob_handler.sync_trunk(
t_ctx, res['project_id'], trunk_id, b_pod['pod_id'])
return res
def remove_subports(self, context, trunk_id, subports):
t_ctx = t_context.get_context_from_neutron_context(context)
with context.session.begin():
self.update_subports_device_id(context, subports, '', '')
res = super(TricircleTrunkDriver, self).remove_subports(
context, trunk_id, subports)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, trunk_id, t_constants.RT_TRUNK)
if mappings:
b_pod = mappings[0][0]
self.xjob_handler.sync_trunk(
t_ctx, res['project_id'], trunk_id, b_pod['pod_id'])
return res