Remove external network hooks and auto-added resources

Previously the orchestrator could be configured to create access to the
external network.  This combined with auto external gateway additions
would cause problems with automation tools and did not work reliably.
This change removes this functionality since it was often disabled in
production deployments. This change aslo slightly reduces devstack
runtime by removing the Neutron restart.

Change-Id: I556f1fc2729f1d62a60de24b6d5e9ed473749f9a
This commit is contained in:
Mark McClain 2016-02-03 19:57:50 +00:00 committed by Adam Gandelman
parent c19cef298a
commit f8d7a37abb
11 changed files with 45 additions and 388 deletions

View File

@ -74,7 +74,8 @@ def get_default_v4_gateway(client, router, networks):
"""Find the IPv4 default gateway for the router.
"""
LOG.debug('networks = %r', networks)
LOG.debug('external interface = %s', router.external_port.mac_address)
if router.external_port:
LOG.debug('external interface = %s', router.external_port.mac_address)
# Now find the subnet that our external IP is on, and return its
# gateway.
@ -122,11 +123,6 @@ def load_provider_rules(path):
def generate_network_config(client, router, management_port, iface_map):
retval = [
common.network_config(
client,
router.external_port,
iface_map[router.external_port.network_id],
EXTERNAL_NET),
common.network_config(
client,
management_port,
@ -135,6 +131,14 @@ def generate_network_config(client, router, management_port, iface_map):
)
]
if router.external_port:
retval.extend([
common.network_config(
client,
router.external_port,
iface_map[router.external_port.network_id],
EXTERNAL_NET)])
retval.extend(
common.network_config(
client,

View File

@ -15,14 +15,12 @@
# under the License.
import collections
import itertools
import socket
import time
import uuid
import netaddr
import six
from neutronclient.v2_0 import client
from neutronclient.common import exceptions as neutron_exc
@ -43,11 +41,8 @@ CONF = cfg.CONF
neutron_opts = [
cfg.StrOpt('management_network_id'),
cfg.StrOpt('external_network_id'),
cfg.StrOpt('management_subnet_id'),
cfg.StrOpt('external_subnet_id'),
cfg.StrOpt('management_prefix', default='fdca:3ba5:a17a:acda::/64'),
cfg.StrOpt('external_prefix', default='172.16.77.0/24'),
cfg.IntOpt('astara_mgt_service_port', default=5000),
cfg.StrOpt('default_instance_flavor', default=1),
cfg.StrOpt('interface_driver',
@ -206,10 +201,13 @@ class Router(object):
@property
def ports(self):
return itertools.chain(
[self.external_port],
self.internal_ports
)
if self.external_port:
return itertools.chain(
[self.external_port],
self.internal_ports
)
else:
return self.internal_ports
class Network(DictModelBase):
@ -753,63 +751,6 @@ class Neutron(object):
for port in port_data:
self.api_client.delete_port(port['id'])
def create_router_external_port(self, router):
# FIXME: Need to make this smarter in case the switch is full.
network_args = {'network_id': self.conf.external_network_id}
update_args = {
'name': router.name,
'admin_state_up': router.admin_state_up,
'external_gateway_info': network_args
}
self.api_client.update_router(
router.id,
body=dict(router=update_args)
)
new_port = self.get_router_external_port(router)
# Make sure the port has enough IPs.
subnets = self.get_network_subnets(self.conf.external_network_id)
sn_by_id = {
sn.id: sn
for sn in subnets
}
sn_by_version = collections.defaultdict(list)
for sn in subnets:
sn_by_version[sn.ip_version].append(sn)
versions_needed = set(sn_by_version.keys())
found = set(sn_by_id[fip.subnet_id].ip_version
for fip in new_port.fixed_ips)
if found != versions_needed:
missing_versions = list(sorted(versions_needed - found))
raise MissingIPAllocation(
new_port.id,
[(mv, [sn.id for sn in sn_by_version[mv]])
for mv in missing_versions]
)
return new_port
def get_router_external_port(self, router):
for i in six.moves.range(self.conf.max_retries):
LOG.debug(
'Looking for router external port. Attempt %d of %d',
i,
cfg.CONF.max_retries,
)
query_dict = {
'device_owner': DEVICE_OWNER_ROUTER_GW,
'device_id': router.id,
'network_id': self.conf.external_network_id
}
ports = self.api_client.list_ports(**query_dict)['ports']
if len(ports):
port = Port.from_dict(ports[0])
LOG.debug('Found router external port: %s', port.id)
return port
time.sleep(self.conf.retry_delay)
raise RouterGatewayMissing()
def _ensure_local_port(self, network_id, subnet_id, prefix,
network_type):
driver = importutils.import_object(self.conf.interface_driver,
@ -900,13 +841,6 @@ class Neutron(object):
driver.init_l3(driver.get_device_name(port), [ip_cidr])
return ip_cidr
def ensure_local_external_port(self):
return self._ensure_local_port(
self.conf.external_network_id,
self.conf.external_subnet_id,
self.conf.external_prefix,
'external')
def ensure_local_service_port(self):
return self._ensure_local_port(
self.conf.management_network_id,

View File

@ -164,21 +164,6 @@ class Router(BaseDriver):
self.log.info(_('Config updated for %s after %s seconds'),
self.name, round(delta, 2))
def pre_plug(self, worker_context):
"""pre-plug hook
Sets up the external port.
:param worker_context:
:returs: None
"""
if self._router.external_port is None:
# FIXME: Need to do some work to pick the right external
# network for a tenant.
self.log.debug('Adding external port to router %s')
ext_port = worker_context.neutron.create_router_external_port(
self._router)
self._router.external_port = ext_port
def make_ports(self, worker_context):
"""make ports call back for the nova client.

View File

@ -46,7 +46,6 @@ MAIN_OPTS = [
cfg.StrOpt('host',
default=socket.getfqdn(),
help="The hostname Astara is running on"),
cfg.BoolOpt('plug_external_port', default=True),
]
CONF.register_opts(MAIN_OPTS)
@ -122,10 +121,6 @@ def main(argv=sys.argv[1:]):
# bring the mgt tap interface up
mgt_ip_address = neutron.ensure_local_service_port().split('/')[0]
# bring the external port
if cfg.CONF.plug_external_port:
neutron.ensure_local_external_port()
# Set up the queue to move messages between the eventlet-based
# listening process and the scheduler.
notification_queue = multiprocessing.Queue()

View File

@ -147,15 +147,17 @@ class TestAstaraClient(unittest.TestCase):
self.assertEqual(result, expected)
expected_calls = [
mock.call(
mock_client, fakes.fake_router.external_port,
'ge1', 'external'),
mock.call(
mock_client, fakes.fake_router.management_port,
'ge0', 'management'),
mock.call(
mock_client, fakes.fake_router.external_port,
'ge1', 'external'),
mock.call(
mock_client, fakes.fake_int_port,
'ge2', 'internal', mock.ANY)]
for c in expected_calls:
self.assertIn(c, mock_net_conf.call_args_list)
mock_net_conf.assert_has_calls(expected_calls)
def test_generate_floating_config(self):

View File

@ -15,8 +15,6 @@
# under the License.
import copy
import mock
import netaddr
@ -364,211 +362,9 @@ class TestNeutronWrapper(base.RugTestBase):
self.assertFalse(neutron_wrapper.api_client.delete_port.called)
class TestExternalPort(base.RugTestBase):
EXTERNAL_NET_ID = 'a0c63b93-2c42-4346-909e-39c690f53ba0'
EXTERNAL_PORT_ID = '089ae859-10ec-453c-b264-6c452fc355e5'
ROUTER = {
u'status': u'ACTIVE',
u'external_gateway_info': {
u'network_id': EXTERNAL_NET_ID,
u'enable_snat': True},
u'name': u'ak-b81e555336da4bf48886e5b93ac6186d',
u'admin_state_up': True,
u'tenant_id': u'b81e555336da4bf48886e5b93ac6186d',
u'ports': [
# This is the external port:
{u'status': u'ACTIVE',
u'binding:host_id': u'devstack-develop',
u'name': u'',
u'allowed_address_pairs': [],
u'admin_state_up': True,
u'network_id': EXTERNAL_NET_ID,
u'tenant_id': u'',
u'extra_dhcp_opts': [],
u'binding:vif_type': u'ovs',
u'device_owner': u'network:router_gateway',
u'binding:capabilities': {u'port_filter': True},
u'mac_address': u'fa:16:3e:a1:a6:ac',
u'fixed_ips': [
{u'subnet_id': u'ipv4snid',
u'ip_address': u'172.16.77.2'},
{u'subnet_id': u'ipv6snid',
u'ip_address': u'fdee:9f85:83be::0'}],
u'id': EXTERNAL_PORT_ID,
u'security_groups': [],
u'device_id': u'7770b189-1223-4d85-9bf7-4d7bc2a28cd7'},
# Some other nice ports you might like:
{u'status': u'ACTIVE',
u'binding:host_id': u'devstack-develop',
u'name': u'',
u'allowed_address_pairs': [],
u'admin_state_up': True,
u'network_id': u'adf190e0-b281-4453-bd87-4ae6fd96d5c1',
u'tenant_id': u'a09298ceed154d26b4ea96977e1c7f17',
u'extra_dhcp_opts': [],
u'binding:vif_type': u'ovs',
u'device_owner': u'network:router_management',
u'binding:capabilities': {u'port_filter': True},
u'mac_address': u'fa:16:3e:e5:dd:55',
u'fixed_ips': [
{u'subnet_id': u'ipv6snid2',
u'ip_address': u'fdca:3ba5:a17a:acda::0'}],
u'id': u'2f4e41b2-c923-48e5-ad19-59e4d02c26a4',
u'security_groups': [],
u'device_id': u'7770b189-1223-4d85-9bf7-4d7bc2a28cd7'},
{u'status': u'ACTIVE',
u'binding:host_id': u'devstack-develop',
u'name': u'',
u'allowed_address_pairs': [],
u'admin_state_up': True,
u'network_id': u'0c04f39c-f739-44dd-9e65-dca6ae20e35c',
u'tenant_id': u'b81e555336da4bf48886e5b93ac6186d',
u'extra_dhcp_opts': [],
u'binding:vif_type': u'ovs',
u'device_owner': u'network:router_interface',
u'binding:capabilities': {u'port_filter': True},
u'mac_address': u'fa:16:3e:e7:27:fc',
u'fixed_ips': [{u'subnet_id': u'ipv4snid2',
u'ip_address': u'192.168.0.1'},
{u'subnet_id': u'ipv6snid3',
u'ip_address': u'fdd6:a1fa:cfa8:cd70::1'}],
u'id': u'b24139b8-a3d0-46cf-bc53-f4b70bb33596',
u'security_groups': [],
u'device_id': u'7770b189-1223-4d85-9bf7-4d7bc2a28cd7'}],
u'routes': [],
u'id': u'5366e8ca-b3e4-408a-91d4-e207af48c755',
}
SUBNETS = [
neutron.Subnet(u'ipv4snid', u'ipv4snid', None, None, 4,
'172.16.77.0/24', '172.16.77.1', False,
[], [], None),
neutron.Subnet(u'ipv6snid', u'ipv4snid', None, None, 6,
'fdee:9f85:83be::/48', 'fdee:9f85:83be::1',
False, [], [], None),
]
def setUp(self):
super(TestExternalPort, self).setUp()
self.conf = mock.Mock()
self.conf.external_network_id = 'ext'
self.conf.max_retries = 3
self.conf.retry_delay = 1
self.conf.external_network_id = self.EXTERNAL_NET_ID
self.router = neutron.Router.from_dict(self.ROUTER)
@mock.patch('astara.api.neutron.AstaraExtClientWrapper')
def test_create(self, client_wrapper):
mock_client = mock.Mock()
mock_client.show_router.return_value = {'router': self.ROUTER}
mock_client.list_ports.return_value = {
'ports': [self.ROUTER['ports'][0]]
}
client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS
port = neutron_wrapper.create_router_external_port(self.router)
self.assertEqual(port.id, self.EXTERNAL_PORT_ID)
@mock.patch('astara.api.neutron.AstaraExtClientWrapper')
def test_create_missing_gateway_port(self, client_wrapper):
self.conf.retry_delay = 0
mock_client = mock.Mock()
router = copy.deepcopy(self.ROUTER)
router['ports'] = []
mock_client.show_router.return_value = {'router': router}
mock_client.list_ports.return_value = {'ports': []}
client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS
self.assertRaises(
neutron.RouterGatewayMissing,
neutron_wrapper.create_router_external_port,
self.router
)
@mock.patch('astara.api.neutron.AstaraExtClientWrapper')
def test_missing_v4(self, client_wrapper):
mock_client = mock.Mock()
router = copy.deepcopy(self.ROUTER)
del router['ports'][0]['fixed_ips'][0]
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
mock_client.show_router.return_value = {'router': router}
client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS
try:
neutron_wrapper.create_router_external_port(self.router)
except neutron.MissingIPAllocation as e:
self.assertEqual(4, e.missing[0][0])
else:
self.fail('Should have seen MissingIPAllocation')
@mock.patch('astara.api.neutron.AstaraExtClientWrapper')
def test_missing_v6(self, client_wrapper):
mock_client = mock.Mock()
router = copy.deepcopy(self.ROUTER)
del router['ports'][0]['fixed_ips'][1]
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
mock_client.show_router.return_value = {'router': router}
client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS
try:
neutron_wrapper.create_router_external_port(self.router)
except neutron.MissingIPAllocation as e:
self.assertEqual(6, e.missing[0][0])
else:
self.fail('Should have seen MissingIPAllocation')
@mock.patch('astara.api.neutron.AstaraExtClientWrapper')
def test_missing_both(self, client_wrapper):
mock_client = mock.Mock()
router = copy.deepcopy(self.ROUTER)
router['ports'][0]['fixed_ips'] = []
mock_client.show_router.return_value = {'router': router}
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS
try:
neutron_wrapper.create_router_external_port(self.router)
except neutron.MissingIPAllocation as e:
self.assertEqual(4, e.missing[0][0])
self.assertEqual(6, e.missing[1][0])
else:
self.fail('Should have seen MissingIPAllocation')
class TestLocalServicePorts(base.RugTestBase):
def setUp(self):
super(TestLocalServicePorts, self).setUp()
self.config(external_network_id='fake_extnet_network_id')
self.config(external_subnet_id='fake_extnet_subnet_id')
self.config(external_prefix='172.16.77.0/24')
self.config(management_network_id='fake_mgtnet_network_id')
self.config(management_subnet_id='fake_mgtnet_subnet_id')
self.config(management_prefix='172.16.77.0/24')
@ -579,17 +375,6 @@ class TestLocalServicePorts(base.RugTestBase):
init_l3=mock.Mock(),
get_device_name=mock.Mock())
def test_ensure_local_external_port(self):
with mock.patch.object(self.neutron_wrapper,
'_ensure_local_port') as ep:
self.neutron_wrapper.ensure_local_external_port()
ep.assert_called_with(
'fake_extnet_network_id',
'fake_extnet_subnet_id',
'172.16.77.0/24',
'external',
)
def test_ensure_local_service_port(self):
with mock.patch.object(self.neutron_wrapper,
'_ensure_local_port') as ep:

View File

@ -117,25 +117,6 @@ class RouterDriverTest(base.RugTestBase):
rtr.mgt_port,
'fake_config',)
def test_pre_plug_no_external_port(self):
rtr = self._init_driver()
fake_router_obj = fakes.fake_router()
fake_router_obj.external_port = None
rtr._router = fake_router_obj
self.ctx.neutron.create_router_external_port.return_value = 'fake_port'
rtr.pre_plug(self.ctx)
self.ctx.neutron.create_router_external_port.assert_called_with(
fake_router_obj,
)
self.assertEqual(rtr._router.external_port, 'fake_port')
def test_pre_plug_with_external_port(self):
rtr = self._init_driver()
fake_router_obj = fakes.fake_router()
fake_router_obj.external_port = 'fake_port'
rtr.pre_plug(self.ctx)
self.assertFalse(self.ctx.neutron.create_router_external_port.called)
@mock.patch('astara.drivers.router.Router._ensure_cache')
def test_make_ports(self, mock_ensure_cache):
rtr = self._init_driver()

View File

@ -88,6 +88,7 @@ function configure_astara_neutron() {
# We need the RUG to be able to get neutron's events notification like port.create.start/end
# or router.interface.start/end to make it able to boot astara routers
iniset $NEUTRON_CONF DEFAULT notification_driver "neutron.openstack.common.notifier.rpc_notifier"
iniset $NEUTRON_CONF DEFAULT astara_auto_add_resources False
}
function configure_astara_horizon() {
@ -146,15 +147,6 @@ function create_astara_nova_flavor() {
iniset $ASTARA_CONF router instance_flavor $ROUTER_INSTANCE_FLAVOR_ID
}
function _remove_subnets() {
# Attempt to delete subnets associated with a network.
# We have to modify the output of net-show to allow it to be
# parsed properly as shell variables, and we run both commands in
# a subshell to avoid polluting the local namespace.
(eval $(neutron $auth_args net-show -f shell $1 | sed 's/:/_/g');
neutron $auth_args subnet-delete $subnets || true)
}
function pre_start_astara() {
# Create and init the database
recreate_database astara
@ -166,33 +158,13 @@ function pre_start_astara() {
# CLI.
unset OS_TENANT_NAME OS_PROJECT_NAME
if ! neutron $auth_args net-show $PUBLIC_NETWORK_NAME; then
neutron $auth_args net-create $PUBLIC_NETWORK_NAME --router:external
fi
# Remove the ipv6 subnet created automatically before adding our own.
# NOTE(adam_g): For some reason this fails the first time and needs to be repeated?
_remove_subnets $PUBLIC_NETWORK_NAME ; _remove_subnets $PUBLIC_NETWORK_NAME
typeset public_subnet_id=$(neutron $auth_args subnet-create --ip-version 4 $PUBLIC_NETWORK_NAME 172.16.77.0/24 | grep ' id ' | awk '{ print $4 }')
iniset $ASTARA_CONF DEFAULT external_subnet_id $public_subnet_id
neutron $auth_args subnet-create --ip-version 6 $PUBLIC_NETWORK_NAME fdee:9f85:83be::/48
# setup masq rule for public network
sudo iptables -t nat -A POSTROUTING -s 172.16.77.0/24 -o $PUBLIC_INTERFACE_DEFAULT -j MASQUERADE
neutron $auth_args net-show $PUBLIC_NETWORK_NAME | grep ' id ' | awk '{ print $4 }'
typeset public_network_id=$(neutron $auth_args net-show $PUBLIC_NETWORK_NAME | grep ' id ' | awk '{ print $4 }')
iniset $ASTARA_CONF DEFAULT external_network_id $public_network_id
#sudo iptables -t nat -A POSTROUTING -s 172.16.77.0/24 -o $PUBLIC_INTERFACE_DEFAULT -j MASQUERADE
neutron $auth_args net-create mgt
typeset mgt_network_id=$(neutron $auth_args net-show mgt | grep ' id ' | awk '{ print $4 }')
iniset $ASTARA_CONF DEFAULT management_network_id $mgt_network_id
# Remove the ipv6 subnet created automatically before adding our own.
_remove_subnets mgt
local subnet_create_args=""
if [[ "$ASTARA_MANAGEMENT_PREFIX" =~ ':' ]]; then
subnet_create_args="--ip-version=6 --ipv6_address_mode=slaac --enable_dhcp"
@ -200,10 +172,6 @@ function pre_start_astara() {
typeset mgt_subnet_id=$(neutron $auth_args subnet-create mgt $ASTARA_MANAGEMENT_PREFIX $subnet_create_args | grep ' id ' | awk '{ print $4 }')
iniset $ASTARA_CONF DEFAULT management_subnet_id $mgt_subnet_id
# Remove the private network created by devstack
neutron $auth_args subnet-delete $PRIVATE_SUBNET_NAME
neutron $auth_args net-delete $PRIVATE_NETWORK_NAME
local astara_dev_image_src=""
local lb_element=""
@ -251,15 +219,6 @@ function pre_start_astara() {
fi
create_astara_nova_flavor
# Restart neutron so that `astara.floatingip_subnet` is properly set
if [[ "$USE_SCREEN" == "True" ]]; then
screen_stop_service q-svc
else
stop_process q-svc
fi
start_neutron_service_and_check
sleep 10
}
function start_astara() {
@ -271,13 +230,8 @@ function start_astara() {
}
function post_start_astara() {
echo "Creating demo user network and subnet"
local auth_args="$(_auth_args demo $OS_PASSWORD demo)"
neutron $auth_args net-create thenet
neutron $auth_args subnet-create thenet $FIXED_RANGE
# Open all traffic on the private CIDR
set_demo_tenant_sec_group_private_traffic
set_demo_tenant_sec_group_private_traffic
}
function stop_astara() {
@ -293,8 +247,6 @@ function set_neutron_user_permission() {
# public networks, we need to modify the policy and allow users with the service
# to do that too.
echo "Updating Nova policy file"
local old_value='"network:attach_external_network": "rule:admin_api"'
local new_value='"network:attach_external_network": "rule:admin_api or role:service"'
sed -i "s/$old_value/$new_value/g" "$NOVA_CONF_DIR/policy.json"
@ -331,7 +283,6 @@ if is_service_enabled astara; then
elif [[ "$1" == "stack" && "$2" == "install" ]]; then
echo_summary "Installing Astara"
if is_service_enabled n-api; then
set_neutron_user_permission
fi

View File

@ -58,4 +58,6 @@ ASTARA_COORDINATION_URL=${ASTARA_COORDINATION_URL:-memcached://localhost:11211}
if [[ "$ASTARA_ENABLED_DRIVERS" =~ "router" ]]; then
ML2_L3_PLUGIN="astara_neutron.plugins.ml2_neutron_plugin.L3RouterPlugin"
Q_L3_ENABLED=True
Q_L3_ROUTER_PER_TENANT=True
fi

View File

@ -0,0 +1,17 @@
---
prelude: >
Astara has dropped a number of legacy convenience hooks available in
earlier releases. The hooks complicated automation and created
potential for mismatch of end state and the desired state.
fixes:
- Bug `1539345 <https://bugs.launchpad.net/astara/+bug/1539345>`_
auto added resources break interoperability
upgrade:
- Astara will no longer automatically add the external gateway to a router.
Previous usage was causing issues with automation tooling.
- Astara no longer requires the external network and subnet id to be known.
In production deployments this step was handled externally and the
internal hooks were often disabled.
critical:
- The devstack plugin no longer creates the external network as before and
instead follows the setup used for reference implementation.

View File

@ -26,6 +26,7 @@ os_tenant_name=$OS_TENANT_NAME
service_tenant_name=$SERVICE_TENANT_NAME
service_tenant_id=$SERVICE_TENANT_ID
appliance_api_port=$APPLIANCE_API_PORT
astara_auto_add_resources=False
# Defaults for the gate
health_check_timeout=10