Change dhcp port creation mechanism
1. What is the problem Our current mechanism to handle dhcp port creation is that we first create the top dhcp port, then use the allocated ip to create the bottom dhcp port. If we get an "IpAddressInUse" error, meaning that bottom dhcp agent has already created the dhcp port with the same ip, we just reuse the port; otherwise, we successfully create the bottom dhcp port, we remove other dhcp ports with different ips, so dhcp agent can be notified and use the bottom dhcp port we create. However, we find that this mechanism doesn't work in the following situation. Dhcp agent may create the bottom dhcp port after we do the check about whether other dhcp ports exist, so we don't remove the dhcp port created by dhcp agent, thus we end up with two bottom dhcp ports, one is created by dhcp agent, one is created by us. 2. What is the solution to the problem Create bottom subnet with dhcp disabled, so dhcp agent will not be scheduled, then create bottom dhcp port and update bottom subnet to enable dhcp. Finding that there is already a reserved dhcp port, dhcp agent will not create another dhcp port and directly use the existing one. 3. What the features need to be implemented to the Tricircle to realize the solution No new feature introuduced. Change-Id: I8bb8622b34b709edef230d1f1c985e3fabd5adb0
This commit is contained in:
parent
2b6324bf56
commit
5f0978ef7b
@ -438,6 +438,7 @@ class Client(object):
|
||||
resource -> args -> kwargs
|
||||
--------------------------
|
||||
router -> body -> none
|
||||
subnet -> body -> none
|
||||
--------------------------
|
||||
:return: a dict containing resource information
|
||||
:raises: EndpointNotAvailable
|
||||
|
@ -118,7 +118,7 @@ class GlanceResourceHandle(ResourceHandle):
|
||||
class NeutronResourceHandle(ResourceHandle):
|
||||
service_type = cons.ST_NEUTRON
|
||||
support_resource = {'network': LIST | CREATE | DELETE | GET,
|
||||
'subnet': LIST | CREATE | DELETE | GET,
|
||||
'subnet': LIST | CREATE | DELETE | GET | UPDATE,
|
||||
'port': LIST | CREATE | DELETE | GET,
|
||||
'router': LIST | CREATE | ACTION | GET | UPDATE,
|
||||
'security_group': LIST | CREATE | GET,
|
||||
|
@ -21,8 +21,6 @@ import tricircle.common.context as t_context
|
||||
import tricircle.common.lock_handle as t_lock
|
||||
from tricircle.common import utils
|
||||
import tricircle.db.api as db_api
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
# manually define these constants to avoid depending on neutron repos
|
||||
@ -228,7 +226,7 @@ class NetworkHelper(object):
|
||||
'cidr': t_subnet['cidr'],
|
||||
'gateway_ip': t_subnet['gateway_ip'],
|
||||
'allocation_pools': t_subnet['allocation_pools'],
|
||||
'enable_dhcp': t_subnet['enable_dhcp'],
|
||||
'enable_dhcp': False,
|
||||
'tenant_id': project_id
|
||||
}
|
||||
}
|
||||
@ -294,6 +292,8 @@ class NetworkHelper(object):
|
||||
|
||||
# subnet
|
||||
subnet_map = {}
|
||||
subnet_dhcp_map = {}
|
||||
|
||||
for subnet in t_subnets:
|
||||
subnet_body = self.get_create_subnet_body(
|
||||
project_id, subnet, b_net_id)
|
||||
@ -301,11 +301,17 @@ class NetworkHelper(object):
|
||||
t_ctx, project_id, pod, subnet, t_constants.RT_SUBNET,
|
||||
subnet_body)
|
||||
subnet_map[subnet['id']] = b_subnet_id
|
||||
subnet_dhcp_map[subnet['id']] = subnet['enable_dhcp']
|
||||
|
||||
# dhcp port
|
||||
for t_subnet_id, b_subnet_id in subnet_map.iteritems():
|
||||
if not subnet_dhcp_map[t_subnet_id]:
|
||||
continue
|
||||
self.prepare_dhcp_port(t_ctx, project_id, pod, t_net['id'],
|
||||
t_subnet_id, b_net_id, b_subnet_id)
|
||||
b_client = self._get_client(pod['pod_name'])
|
||||
b_client.update_subnets(t_ctx, b_subnet_id,
|
||||
{'subnet': {'enable_dhcp': True}})
|
||||
|
||||
return b_net_id, subnet_map
|
||||
|
||||
@ -409,7 +415,6 @@ class NetworkHelper(object):
|
||||
:return: None
|
||||
"""
|
||||
t_client = self._get_client()
|
||||
b_client = self._get_client(b_pod['pod_name'])
|
||||
|
||||
t_dhcp_name = t_constants.dhcp_port_name % t_subnet_id
|
||||
t_dhcp_port_body = {
|
||||
@ -436,71 +441,7 @@ class NetworkHelper(object):
|
||||
ctx, None, project_id, db_api.get_top_pod(ctx),
|
||||
{'id': t_dhcp_name}, t_constants.RT_PORT, t_dhcp_port_body)
|
||||
t_dhcp_port = t_client.get_ports(ctx, t_dhcp_port_id)
|
||||
|
||||
mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
ctx, t_dhcp_port['id'], t_constants.RT_PORT)
|
||||
pod_list = [mapping[0]['pod_id'] for mapping in mappings]
|
||||
if b_pod['pod_id'] in pod_list:
|
||||
# mapping exists, skip this subnet
|
||||
return
|
||||
|
||||
dhcp_port_filters = [
|
||||
{'key': 'device_owner', 'comparator': 'eq',
|
||||
'value': 'network:dhcp'},
|
||||
{'key': 'network_id', 'comparator': 'eq',
|
||||
'value': b_net_id},
|
||||
]
|
||||
dhcp_port_body = self._get_create_dhcp_port_body(
|
||||
project_id, t_dhcp_port, b_subnet_id, b_net_id)
|
||||
t_dhcp_ip = t_dhcp_port['fixed_ips'][0]['ip_address']
|
||||
|
||||
b_dhcp_port = None
|
||||
try:
|
||||
b_dhcp_port = b_client.create_ports(ctx, dhcp_port_body)
|
||||
except Exception:
|
||||
# examine if we conflicted with a dhcp port which was
|
||||
# automatically created by bottom pod
|
||||
b_dhcp_ports = b_client.list_ports(ctx,
|
||||
dhcp_port_filters)
|
||||
dhcp_port_match = False
|
||||
for dhcp_port in b_dhcp_ports:
|
||||
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
|
||||
ip = dhcp_port['fixed_ips'][0]['ip_address']
|
||||
if b_subnet_id == subnet_id and t_dhcp_ip == ip:
|
||||
with ctx.session.begin():
|
||||
core.create_resource(
|
||||
ctx, models.ResourceRouting,
|
||||
{'top_id': t_dhcp_port['id'],
|
||||
'bottom_id': dhcp_port['id'],
|
||||
'pod_id': b_pod['pod_id'],
|
||||
'project_id': project_id,
|
||||
'resource_type': t_constants.RT_PORT})
|
||||
dhcp_port_match = True
|
||||
break
|
||||
if not dhcp_port_match:
|
||||
# so we didn't conflict with a dhcp port, raise exception
|
||||
raise
|
||||
|
||||
if b_dhcp_port:
|
||||
with ctx.session.begin():
|
||||
core.create_resource(ctx, models.ResourceRouting,
|
||||
{'top_id': t_dhcp_port['id'],
|
||||
'bottom_id': b_dhcp_port['id'],
|
||||
'pod_id': b_pod['pod_id'],
|
||||
'project_id': project_id,
|
||||
'resource_type': t_constants.RT_PORT})
|
||||
# there is still one thing to do, there may be other dhcp ports
|
||||
# created by bottom pod, we need to delete them
|
||||
b_dhcp_ports = b_client.list_ports(ctx,
|
||||
dhcp_port_filters)
|
||||
remove_port_list = []
|
||||
for dhcp_port in b_dhcp_ports:
|
||||
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
|
||||
ip = dhcp_port['fixed_ips'][0]['ip_address']
|
||||
if b_subnet_id == subnet_id and t_dhcp_ip != ip:
|
||||
remove_port_list.append(dhcp_port['id'])
|
||||
for dhcp_port_id in remove_port_list:
|
||||
# NOTE(zhiyuan) dhcp agent will receive this port-delete
|
||||
# notification and re-configure dhcp so our newly created
|
||||
# dhcp port can be used
|
||||
b_client.delete_ports(ctx, dhcp_port_id)
|
||||
self.prepare_bottom_element(ctx, project_id, b_pod, t_dhcp_port,
|
||||
t_constants.RT_PORT, dhcp_port_body)
|
||||
|
@ -21,7 +21,9 @@ from oslotest import base
|
||||
CONFLICT_OPT_NAMES = [
|
||||
'api_extensions_path',
|
||||
'bind_port',
|
||||
'bind_host'
|
||||
'bind_host',
|
||||
'allow_pagination',
|
||||
'allow_sorting'
|
||||
]
|
||||
|
||||
|
||||
|
@ -277,6 +277,9 @@ class FakeClient(object):
|
||||
'comparator': 'eq',
|
||||
'value': subnet_id}])[0]
|
||||
|
||||
def update_subnets(self, ctx, subnet_id, body):
|
||||
pass
|
||||
|
||||
def create_ports(self, ctx, body):
|
||||
return self.create_resources('port', ctx, body)
|
||||
|
||||
@ -615,6 +618,8 @@ class FakeSession(object):
|
||||
if model_obj.__tablename__ not in RES_MAP:
|
||||
return
|
||||
model_dict = DotDict(model_obj._as_dict())
|
||||
if 'project_id' in model_dict:
|
||||
model_dict['tenant_id'] = model_dict['project_id']
|
||||
|
||||
if model_obj.__tablename__ == 'networks':
|
||||
model_dict['subnets'] = []
|
||||
@ -758,7 +763,10 @@ class FakePlugin(plugin.TricirclePlugin):
|
||||
|
||||
def _make_network_dict(self, network, fields=None,
|
||||
process_extensions=True, context=None):
|
||||
return _transform_az(network)
|
||||
network = _transform_az(network)
|
||||
if 'project_id' in network:
|
||||
network['tenant_id'] = network['project_id']
|
||||
return network
|
||||
|
||||
def _make_subnet_dict(self, subnet, fields=None, context=None):
|
||||
return subnet
|
||||
@ -781,7 +789,11 @@ class FakePlugin(plugin.TricirclePlugin):
|
||||
'ip_address': allocation['ip_address']}]
|
||||
else:
|
||||
ret[key] = value
|
||||
if 'project_id' in ret:
|
||||
ret['tenant_id'] = ret['project_id']
|
||||
return ret
|
||||
if 'project_id' in port:
|
||||
port['tenant_id'] = port['project_id']
|
||||
return port
|
||||
|
||||
def _make_security_group_dict(self, security_group, fields=None):
|
||||
|
@ -233,6 +233,11 @@ class FakeClient(object):
|
||||
'subnet', ctx,
|
||||
[{'key': 'id', 'comparator': 'eq', 'value': subnet_id}])[0]
|
||||
|
||||
def update_subnets(self, ctx, subnet_id, body):
|
||||
subnet = self.get_subnets(ctx, subnet_id)
|
||||
for key, value in body['subnet'].iteritems():
|
||||
subnet[key] = value
|
||||
|
||||
def get_ports(self, ctx, port_id):
|
||||
return self.list_resources(
|
||||
'port', ctx,
|
||||
@ -416,33 +421,48 @@ class ServerTest(unittest.TestCase):
|
||||
def _check_routes(self):
|
||||
for res in (TOP_NETS, TOP_SUBNETS, BOTTOM_NETS, BOTTOM_SUBNETS):
|
||||
self.assertEqual(1, len(res))
|
||||
self.assertEqual(2, len(TOP_PORTS))
|
||||
self.assertEqual(2, len(BOTTOM_PORTS))
|
||||
enable_dhcp = TOP_SUBNETS[0]['enable_dhcp']
|
||||
self.assertEqual(enable_dhcp, BOTTOM_SUBNETS[0]['enable_dhcp'])
|
||||
port_num = 2 if enable_dhcp else 1
|
||||
self.assertEqual(port_num, len(TOP_PORTS))
|
||||
self.assertEqual(port_num, len(BOTTOM_PORTS))
|
||||
|
||||
with self.context.session.begin():
|
||||
routes = core.query_resource(self.context,
|
||||
models.ResourceRouting, [], [])
|
||||
# bottom network, bottom subnet, bottom port, top dhcp, bottom dhcp
|
||||
self.assertEqual(5, len(routes))
|
||||
actual = [[], [], [], [], []]
|
||||
actual[4].append(constants.dhcp_port_name % TOP_SUBNETS[0]['id'])
|
||||
# bottom network, bottom subnet, bottom port, no top dhcp and bottom
|
||||
# dhcp if dhcp disabled
|
||||
entry_num = 5 if enable_dhcp else 3
|
||||
self.assertEqual(entry_num, len(routes))
|
||||
actual = [[], [], []]
|
||||
if entry_num > 3:
|
||||
actual.extend([[], []])
|
||||
|
||||
for region in ('t_region', 'b_region'):
|
||||
actual[0].append(self.controller._get_client(
|
||||
region).list_resources('network', self.context, [])[0]['id'])
|
||||
actual[1].append(self.controller._get_client(
|
||||
region).list_resources('subnet', self.context, [])[0]['id'])
|
||||
t_ports = self.controller._get_client(
|
||||
ports = self.controller._get_client(
|
||||
region).list_resources('port', self.context, [])
|
||||
if 'device_id' in t_ports[0]:
|
||||
actual[2].append(t_ports[0]['id'])
|
||||
actual[3].append(t_ports[1]['id'])
|
||||
if region == 't_region':
|
||||
actual[4].append(t_ports[0]['id'])
|
||||
if 'device_id' not in ports[0]:
|
||||
actual[2].append(ports[0]['id'])
|
||||
else:
|
||||
actual[2].append(t_ports[1]['id'])
|
||||
actual[3].append(t_ports[0]['id'])
|
||||
if region == 't_region':
|
||||
actual[4].append(t_ports[1]['id'])
|
||||
actual[2].append(ports[1]['id'])
|
||||
|
||||
if entry_num > 3:
|
||||
actual[4].append(constants.dhcp_port_name % TOP_SUBNETS[0]['id'])
|
||||
for region in ('t_region', 'b_region'):
|
||||
ports = self.controller._get_client(
|
||||
region).list_resources('port', self.context, [])
|
||||
if 'device_id' in ports[0]:
|
||||
actual[3].append(ports[0]['id'])
|
||||
if region == 't_region':
|
||||
actual[4].append(ports[0]['id'])
|
||||
else:
|
||||
actual[3].append(ports[1]['id'])
|
||||
if region == 't_region':
|
||||
actual[4].append(ports[1]['id'])
|
||||
|
||||
expect = [[route['top_id'], route['bottom_id']] for route in routes]
|
||||
self.assertItemsEqual(expect, actual)
|
||||
@ -463,6 +483,22 @@ class ServerTest(unittest.TestCase):
|
||||
self.controller._handle_network(self.context, b_pod, net, [subnet])
|
||||
self._check_routes()
|
||||
|
||||
def test_handle_network_dhcp_disable(self):
|
||||
t_pod, b_pod = self._prepare_pod()
|
||||
net = {'id': 'top_net_id', 'name': 'net'}
|
||||
subnet = {'id': 'top_subnet_id',
|
||||
'network_id': 'top_net_id',
|
||||
'ip_version': 4,
|
||||
'cidr': '10.0.0.0/24',
|
||||
'gateway_ip': '10.0.0.1',
|
||||
'allocation_pools': {'start': '10.0.0.2',
|
||||
'end': '10.0.0.254'},
|
||||
'enable_dhcp': False}
|
||||
TOP_NETS.append(net)
|
||||
TOP_SUBNETS.append(subnet)
|
||||
self.controller._handle_network(self.context, b_pod, net, [subnet])
|
||||
self._check_routes()
|
||||
|
||||
def test_handle_port(self):
|
||||
t_pod, b_pod = self._prepare_pod()
|
||||
net = {'id': 'top_net_id', 'name': 'net'}
|
||||
@ -487,67 +523,6 @@ class ServerTest(unittest.TestCase):
|
||||
self.controller._handle_port(self.context, b_pod, port)
|
||||
self._check_routes()
|
||||
|
||||
def _test_handle_network_dhcp_port(self, dhcp_ip):
|
||||
t_pod, b_pod = self._prepare_pod()
|
||||
|
||||
top_net_id = 'top_net_id'
|
||||
bottom_net_id = 'bottom_net_id'
|
||||
top_subnet_id = 'top_subnet_id'
|
||||
bottom_subnet_id = 'bottom_subnet_id'
|
||||
t_net = {'id': top_net_id, 'name': 'net'}
|
||||
b_net = {'id': bottom_net_id}
|
||||
t_subnet = {'id': top_subnet_id,
|
||||
'network_id': top_net_id,
|
||||
'ip_version': 4,
|
||||
'cidr': '10.0.0.0/24',
|
||||
'gateway_ip': '10.0.0.1',
|
||||
'allocation_pools': {'start': '10.0.0.2',
|
||||
'end': '10.0.0.254'},
|
||||
'enable_dhcp': True}
|
||||
b_subnet = {'id': bottom_subnet_id,
|
||||
'network_id': bottom_net_id,
|
||||
'ip_version': 4,
|
||||
'cidr': '10.0.0.0/24',
|
||||
'gateway_ip': '10.0.0.1',
|
||||
'allocation_pools': {'start': '10.0.0.2',
|
||||
'end': '10.0.0.254'},
|
||||
'enable_dhcp': True}
|
||||
b_dhcp_port = {'id': 'bottom_dhcp_port_id',
|
||||
'network_id': bottom_net_id,
|
||||
'fixed_ips': [
|
||||
{'subnet_id': bottom_subnet_id,
|
||||
'ip_address': dhcp_ip}
|
||||
],
|
||||
'mac_address': 'fa:16:3e:96:41:0a',
|
||||
'binding:profile': {},
|
||||
'device_id': 'reserved_dhcp_port',
|
||||
'device_owner': 'network:dhcp'}
|
||||
TOP_NETS.append(t_net)
|
||||
TOP_SUBNETS.append(t_subnet)
|
||||
BOTTOM_NETS.append(b_net)
|
||||
BOTTOM_SUBNETS.append(b_subnet)
|
||||
BOTTOM_PORTS.append(b_dhcp_port)
|
||||
with self.context.session.begin():
|
||||
core.create_resource(
|
||||
self.context, models.ResourceRouting,
|
||||
{'top_id': top_net_id, 'bottom_id': bottom_net_id,
|
||||
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
|
||||
'resource_type': 'network'})
|
||||
core.create_resource(
|
||||
self.context, models.ResourceRouting,
|
||||
{'top_id': top_subnet_id, 'bottom_id': bottom_subnet_id,
|
||||
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
|
||||
'resource_type': 'subnet'})
|
||||
self.controller._handle_network(self.context,
|
||||
b_pod, t_net, [t_subnet])
|
||||
self._check_routes()
|
||||
|
||||
def test_handle_network_dhcp_port_same_ip(self):
|
||||
self._test_handle_network_dhcp_port('10.0.0.2')
|
||||
|
||||
def test_handle_network_dhcp_port_exist_diff_ip(self):
|
||||
self._test_handle_network_dhcp_port('10.0.0.4')
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(FakeClient, 'create_servers')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
|
@ -44,6 +44,8 @@ _TIMER_INTERVAL = 30
|
||||
_TIMER_INTERVAL_MAX = 60
|
||||
|
||||
common_opts = [
|
||||
cfg.StrOpt('host', default='tricircle.xhost',
|
||||
help=_("The host name for RPC server")),
|
||||
cfg.IntOpt('workers', default=1,
|
||||
help=_("Number of workers")),
|
||||
cfg.IntOpt('worker_handle_timeout', default=1800,
|
||||
|
Loading…
x
Reference in New Issue
Block a user