Merge "NSX-v3: Initial framework for api-replay-mode"

This commit is contained in:
Jenkins 2016-06-02 20:31:52 +00:00 committed by Gerrit Code Review
commit 7951e8668c
9 changed files with 273 additions and 35 deletions

View File

View File

@ -12,7 +12,7 @@
import argparse
from vmware_nsx.plugins.nsx_v3.api_replay import client
from vmware_nsx.api_replay import client
class ApiReplayCli(object):

View File

@ -47,6 +47,7 @@ class ApiReplayClient(object):
self.migrate_security_groups()
self.migrate_routers()
self.migrate_networks_subnets_ports()
self.migrate_floatingips()
def find_subnet_by_id(self, subnet_id, subnets):
for subnet in subnets:
@ -104,7 +105,7 @@ class ApiReplayClient(object):
dest_sec_group = self.have_id(sg['id'], dest_sec_groups)
# If the security group already exists on the the dest_neutron
if dest_sec_group:
# make sure all the security group rules are theree and
# make sure all the security group rules are there and
# create them if not
for sg_rule in sg['security_group_rules']:
if(self.have_id(sg_rule['id'],
@ -126,18 +127,19 @@ class ApiReplayClient(object):
else:
sg_rules = sg.pop('security_group_rules')
try:
print(self.dest_neutron.create_security_group(
{'security_group': sg}))
new_sg = self.dest_neutron.create_security_group(
{'security_group': sg})
print ("Created security-group %s" % new_sg)
except Exception as e:
# TODO(arosen): improve exception handing here.
print (e)
pass
for sg_rule in sg_rules:
try:
print (self.dest_neutron.create_security_group_rule(
{'security_group_rule': sg_rule}))
except n_exc.Conflict:
rule = self.dest_neutron.create_security_group_rule(
{'security_group_rule': sg_rule})
print ("created security group rule %s " % rule['id'])
except Exception:
# NOTE(arosen): when you create a default
# security group it is automatically populated
# with some rules. When we go to create the rules
@ -155,13 +157,15 @@ class ApiReplayClient(object):
if dest_router is False:
drop_router_fields = ['status',
'routes',
'ha',
'external_gateway_info']
body = self.drop_fields(router, drop_router_fields)
print (self.dest_neutron.create_router(
{'router': body}))
new_router = (self.dest_neutron.create_router(
{'router': body}))
print ("created router %s" % new_router)
def migrate_networks_subnets_ports(self):
"""Migrates routers from source to dest neutron."""
"""Migrates networks/ports/router-uplinks from src to dest neutron."""
source_ports = self.source_neutron.list_ports()['ports']
source_subnets = self.source_neutron.list_subnets()['subnets']
source_networks = self.source_neutron.list_networks()['networks']
@ -185,10 +189,12 @@ class ApiReplayClient(object):
'port_security_enabled',
'binding:vif_details',
'binding:vif_type',
'binding:host_id']
'binding:host_id', 'qos_policy_id']
drop_network_fields = ['status', 'subnets', 'availability_zones',
'created_at', 'updated_at', 'tags']
'created_at', 'updated_at', 'tags',
'qos_policy_id', 'ipv4_address_scope',
'ipv6_address_scope', 'mtu']
for network in source_networks:
body = self.drop_fields(network, drop_network_fields)
@ -202,7 +208,7 @@ class ApiReplayClient(object):
if self.have_id(network['id'], dest_networks) is False:
created_net = self.dest_neutron.create_network(
{'network': body})['network']
print ("Created network: " + created_net['id'])
print ("Created network: %s " % created_net)
for subnet_id in network['subnets']:
subnet = self.find_subnet_by_id(subnet_id, source_subnets)
@ -240,24 +246,49 @@ class ApiReplayClient(object):
# only create port if the dest server doesn't have it
if self.have_id(port['id'], dest_ports) is False:
if port['device_owner'] == 'network:router_gateway':
body = {
"external_gateway_info":
{"network_id": port['network_id']}}
router_uplink = self.dest_neutron.update_router(
port['device_id'], # router_id
{'router': body})
print ("Uplinked router %s" % router_uplink)
continue
if port['device_owner'] in ['network:router_interface',
'network:router_gateway']:
if port['allowed_address_pairs'] == []:
del body['allowed_address_pairs']
created_port = self.dest_neutron.create_port(
{'port': body})['port']
print ("Created port: " + created_port['id'])
# Let the neutron dhcp-agent recreate this on it's own
if port['device_owner'] == 'network:dhcp':
continue
# ignore these as we create them ourselves later
if port['device_owner'] == 'network:floatingip':
continue
if port['device_owner'] == 'network:router_interface':
try:
print (self.dest_neutron.add_interface_router(
# uplink router_interface ports
self.dest_neutron.add_interface_router(
port['device_id'],
{'port_id': port['id']}))
{'subnet_id': created_subnet['id']})
print ("Uplinked router %s to subnet %s" %
(port['device_id'], created_subnet['id']))
continue
except n_exc.BadRequest as e:
# NOTE(arosen): this occurs here if you run the
# script multiple times as we don't track this.
print (e)
raise
# TODO(arosen): handle 'network:router_gateway' uplinking
created_port = self.dest_neutron.create_port(
{'port': body})['port']
print ("Created port: " + created_port['id'])
def migrate_floatingips(self):
"""Migrates floatingips from source to dest neutron."""
source_fips = self.source_neutron.list_floatingips()['floatingips']
drop_fip_fields = ['status', 'router_id', 'id']
for source_fip in source_fips:
body = self.drop_fields(source_fip, drop_fip_fields)
fip = self.dest_neutron.create_floatingip({'floatingip': body})
print ("Created floatingip %s" % fip)

View File

@ -0,0 +1,41 @@
# Copyright 2016 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.api.v2 import attributes
from oslo_config import cfg
from oslo_utils import uuidutils
import webob.exc
def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True):
# This method is a replacement of _fixup_res_dict which is used in
# neutron.plugin.common.utils. All this mock does is insert a uuid
# for the id field if one is not found ONLY if running in api_replay_mode.
if cfg.CONF.api_replay_mode and 'id' not in res_dict:
res_dict['id'] = uuidutils.generate_uuid()
attr_info = attributes.RESOURCE_ATTRIBUTE_MAP[attr_name]
try:
attributes.populate_tenant_id(context, res_dict, attr_info, True)
attributes.verify_attributes(res_dict, attr_info)
except webob.exc.HTTPBadRequest as e:
# convert webob exception into ValueError as these functions are
# for internal use. webob exception doesn't make sense.
raise ValueError(e.detail)
attributes.fill_default_value(attr_info, res_dict,
check_allow_post=check_allow_post)
attributes.convert_value(attr_info, res_dict)
return res_dict

View File

@ -230,6 +230,12 @@ nsx_common_opts = [
"parameter to tooz coordinator. By default, value is "
"None and oslo_concurrency is used for single-node "
"lock management.")),
cfg.BoolOpt('api_replay_mode',
default=False,
help=_("If true, the server then allows the caller to "
"specify the id of resources. This should only "
"be enabled in order to allow one to migrate an "
"existing install of neutron to the nsx-v3 plugin.")),
]
nsx_v3_opts = [

View File

@ -0,0 +1,73 @@
# Copyright 2016 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.api import extensions
RESOURCE_ATTRIBUTE_MAP = {
'ports': {
'id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
},
'networks': {
'id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
},
'security_groups': {
'id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
},
'security_group_rules': {
'id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
},
'routers': {
'id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
},
}
class Api_replay(extensions.ExtensionDescriptor):
"""Extension for api replay which allows us to specify ids of resources."""
@classmethod
def get_name(cls):
return "Api Replay"
@classmethod
def get_alias(cls):
return 'api-replay'
@classmethod
def get_description(cls):
return "Enables mode to allow api to be replayed"
@classmethod
def get_updated(cls):
return "2016-05-05T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -12,6 +12,8 @@
# 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 mock
import netaddr
import six
@ -66,6 +68,7 @@ from oslo_utils import importutils
from oslo_utils import uuidutils
from vmware_nsx._i18n import _, _LE, _LI, _LW
from vmware_nsx.api_replay import utils as api_replay_utils
from vmware_nsx.common import config # noqa
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import locking
@ -196,6 +199,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
"switching profile: %s") % NSX_V3_DHCP_PROFILE_NAME
raise nsx_exc.NsxPluginException(msg)
self._unsubscribe_callback_events()
if cfg.CONF.api_replay_mode:
self.supported_extension_aliases.append('api-replay')
# translate configured transport zones/rotuers names to uuid
self._translate_configured_names_2_uuids()
@ -1658,8 +1663,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
name = utils.get_name_and_uuid(
router_name, port['id'], tag='port')
self._port_client.update(nsx_port_id, None, name=name)
return super(NsxV3Plugin, self).update_router(
context, router_id, router)
# NOTE(arosen): the mock.patch here is needed for api_replay_mode
with mock.patch("neutron.plugins.common.utils._fixup_res_dict",
side_effect=api_replay_utils._fixup_res_dict):
return super(NsxV3Plugin, self).update_router(
context, router_id, router)
except nsx_exc.ResourceNotFound:
with context.session.begin(subtransactions=True):
router_db = self._get_router(context, router_id)
@ -1742,9 +1751,11 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# to routers
self._validate_multiple_subnets_routers(context,
router_id, interface_info)
info = super(NsxV3Plugin, self).add_router_interface(
context, router_id, interface_info)
# NOTE(arosen): the mock.patch here is needed for api_replay_mode
with mock.patch("neutron.plugins.common.utils._fixup_res_dict",
side_effect=api_replay_utils._fixup_res_dict):
info = super(NsxV3Plugin, self).add_router_interface(
context, router_id, interface_info)
try:
subnet = self.get_subnet(context, info['subnet_ids'][0])
port = self.get_port(context, info['port_id'])
@ -1860,11 +1871,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return info
def create_floatingip(self, context, floatingip):
new_fip = super(NsxV3Plugin, self).create_floatingip(
context, floatingip, initial_status=(
const.FLOATINGIP_STATUS_ACTIVE
if floatingip['floatingip']['port_id']
else const.FLOATINGIP_STATUS_DOWN))
# NOTE(arosen): the mock.patch here is needed for api_replay_mode
with mock.patch("neutron.plugins.common.utils._fixup_res_dict",
side_effect=api_replay_utils._fixup_res_dict):
new_fip = super(NsxV3Plugin, self).create_floatingip(
context, floatingip, initial_status=(
const.FLOATINGIP_STATUS_ACTIVE
if floatingip['floatingip']['port_id']
else const.FLOATINGIP_STATUS_DOWN))
router_id = new_fip['router_id']
if not router_id:
return new_fip
@ -1973,6 +1988,33 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
super(NsxV3Plugin, self).disassociate_floatingips(
context, port_id, do_notify=False)
def _ensure_default_security_group(self, context, tenant_id):
# NOTE(arosen): if in replay mode we'll create all the default
# security groups for the user with their data so we don't
# want this to be called.
if (cfg.CONF.api_replay_mode is False):
return super(NsxV3Plugin, self)._ensure_default_security_group(
context, tenant_id)
def _stub__validate_name_not_default(self):
# NOTE(arosen): if in replay mode we need stub out this validator to
# all default security groups to be created via the api
if cfg.CONF.api_replay_mode:
def _pass(data, foo=None):
pass
ext_sg.validators.validators['type:name_not_default'] = _pass
def get_security_groups(self, context, filters=None, fields=None,
sorts=None, limit=None,
marker=None, page_reverse=False, default_sg=False):
self._stub__validate_name_not_default()
return super(NsxV3Plugin, self).get_security_groups(
context, filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker=marker, page_reverse=page_reverse,
default_sg=default_sg)
def create_security_group(self, context, security_group, default_sg=False):
secgroup = security_group['security_group']
secgroup['id'] = secgroup.get('id') or uuidutils.generate_uuid()

View File

@ -0,0 +1,45 @@
# Copyright (c) 2015 OpenStack Foundation.
#
# 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 vmware_nsx.tests.unit.nsx_v3 import test_plugin
# FIXME(arosen): - these tests pass but seem to break the other tests
# as the attribute map doesn't get reset after each test class. I tried
# backing it up and restoring it here though that doesn't seem to be doing
# the trick either...
class TestApiReplay(test_plugin.NsxV3PluginTestCaseMixin):
def setUp(self, plugin=None, ext_mgr=None, service_plugins=None):
# enables api_replay_mode for these tests
super(TestApiReplay, self).setUp()
def test_create_port_specify_id(self):
self.skipTest("...fixme...")
specified_network_id = '555e762b-d7a1-4b44-b09b-2a34ada56c9f'
specified_port_id = 'e55e762b-d7a1-4b44-b09b-2a34ada56c9f'
network_res = self._create_network(self.fmt,
'test-network',
True,
arg_list=('id',),
id=specified_network_id)
network = self.deserialize(self.fmt, network_res)
self.assertEqual(specified_network_id, network['network']['id'])
port_res = self._create_port(self.fmt,
network['network']['id'],
arg_list=('id',),
id=specified_port_id)
port = self.deserialize(self.fmt, port_res)
self.assertEqual(specified_port_id, port['port']['id'])