NSX Migration: support keystone v3 & other fixes

- support for keystone V3, and adding domain names as arguments
- add argument to still use keystone v2 for the source connection
  (--use-old-keystone)
- in case network creation fails - print the source network
- skip routers & floatingips creation in case that the source setup
  does not have L3 support
- rename tenant arguments to project

Change-Id: I374dc9eb5ace18804fafcff31dfd96e917db0261
This commit is contained in:
Adit Sarfaty 2017-05-28 09:24:26 +03:00
parent 740a2ec3f5
commit efc6a2d25b
2 changed files with 118 additions and 38 deletions

View File

@ -14,20 +14,27 @@ import argparse
from vmware_nsx.api_replay import client from vmware_nsx.api_replay import client
DEFAULT_DOMAIN_ID = 'default'
class ApiReplayCli(object): class ApiReplayCli(object):
def __init__(self): def __init__(self):
args = self._setup_argparse() args = self._setup_argparse()
client.ApiReplayClient( client.ApiReplayClient(
source_os_tenant_name=args.source_os_tenant_name, source_os_tenant_name=args.source_os_project_name,
source_os_tenant_domain_id=args.source_os_project_domain_id,
source_os_username=args.source_os_username, source_os_username=args.source_os_username,
source_os_user_domain_id=args.source_os_user_domain_id,
source_os_password=args.source_os_password, source_os_password=args.source_os_password,
source_os_auth_url=args.source_os_auth_url, source_os_auth_url=args.source_os_auth_url,
dest_os_tenant_name=args.dest_os_project_name,
dest_os_tenant_domain_id=args.dest_os_project_domain_id,
dest_os_username=args.dest_os_username, dest_os_username=args.dest_os_username,
dest_os_tenant_name=args.dest_os_tenant_name, dest_os_user_domain_id=args.dest_os_user_domain_id,
dest_os_password=args.dest_os_password, dest_os_password=args.dest_os_password,
dest_os_auth_url=args.dest_os_auth_url) dest_os_auth_url=args.dest_os_auth_url,
use_old_keystone=args.use_old_keystone)
def _setup_argparse(self): def _setup_argparse(self):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -40,9 +47,19 @@ class ApiReplayCli(object):
help="The source os-username to use to " help="The source os-username to use to "
"gather neutron resources with.") "gather neutron resources with.")
parser.add_argument( parser.add_argument(
"--source-os-tenant-name", "--source-os-user-domain-id",
default=DEFAULT_DOMAIN_ID,
help="The source os-user-domain-id to use to "
"gather neutron resources with.")
parser.add_argument(
"--source-os-project-name",
required=True, required=True,
help="The source os-tenant-name to use to " help="The source os-project-name to use to "
"gather neutron resource with.")
parser.add_argument(
"--source-os-project-domain-id",
default=DEFAULT_DOMAIN_ID,
help="The source os-project-domain-id to use to "
"gather neutron resource with.") "gather neutron resource with.")
parser.add_argument( parser.add_argument(
"--source-os-password", "--source-os-password",
@ -61,9 +78,19 @@ class ApiReplayCli(object):
help="The dest os-username to use to" help="The dest os-username to use to"
"gather neutron resources with.") "gather neutron resources with.")
parser.add_argument( parser.add_argument(
"--dest-os-tenant-name", "--dest-os-user-domain-id",
default=DEFAULT_DOMAIN_ID,
help="The dest os-user-domain-id to use to"
"gather neutron resources with.")
parser.add_argument(
"--dest-os-project-name",
required=True, required=True,
help="The dest os-tenant-name to use to " help="The dest os-project-name to use to "
"gather neutron resource with.")
parser.add_argument(
"--dest-os-project-domain-id",
default=DEFAULT_DOMAIN_ID,
help="The dest os-project-domain-id to use to "
"gather neutron resource with.") "gather neutron resource with.")
parser.add_argument( parser.add_argument(
"--dest-os-password", "--dest-os-password",
@ -74,6 +101,12 @@ class ApiReplayCli(object):
required=True, required=True,
help="They keystone api endpoint for this user.") help="They keystone api endpoint for this user.")
parser.add_argument(
"--use-old-keystone",
default=False,
action='store_true',
help="Use old keystone client for source authentication.")
# NOTE: this will return an error message if any of the # NOTE: this will return an error message if any of the
# require options are missing. # require options are missing.
return parser.parse_args() return parser.parse_args()

View File

@ -12,8 +12,11 @@
import six import six
from keystoneauth1 import identity
from keystoneauth1 import session
from neutronclient.common import exceptions as n_exc from neutronclient.common import exceptions as n_exc
from neutronclient.v2_0 import client from neutronclient.v2_0 import client
from oslo_utils import excutils
class ApiReplayClient(object): class ApiReplayClient(object):
@ -24,33 +27,42 @@ class ApiReplayClient(object):
'revision', 'revision',
'revision_number'] 'revision_number']
def __init__(self, source_os_username, source_os_tenant_name, def __init__(self,
source_os_username, source_os_user_domain_id,
source_os_tenant_name, source_os_tenant_domain_id,
source_os_password, source_os_auth_url, source_os_password, source_os_auth_url,
dest_os_username, dest_os_tenant_name, dest_os_username, dest_os_user_domain_id,
dest_os_password, dest_os_auth_url): dest_os_tenant_name, dest_os_tenant_domain_id,
dest_os_password, dest_os_auth_url,
self._source_os_username = source_os_username use_old_keystone):
self._source_os_tenant_name = source_os_tenant_name
self._source_os_password = source_os_password
self._source_os_auth_url = source_os_auth_url
self._dest_os_username = dest_os_username
self._dest_os_tenant_name = dest_os_tenant_name
self._dest_os_password = dest_os_password
self._dest_os_auth_url = dest_os_auth_url
# connect to both clients
if use_old_keystone:
# Since we are not sure what keystone version will be used on the
# source setup, we add an option to use the v2 client
self.source_neutron = client.Client( self.source_neutron = client.Client(
username=self._source_os_username, username=source_os_username,
tenant_name=self._source_os_tenant_name, tenant_name=source_os_tenant_name,
password=self._source_os_password, password=source_os_password,
auth_url=self._source_os_auth_url) auth_url=source_os_auth_url)
else:
self.source_neutron = self.connect_to_client(
username=source_os_username,
user_domain_id=source_os_user_domain_id,
tenant_name=source_os_tenant_name,
tenant_domain_id=source_os_tenant_domain_id,
password=source_os_password,
auth_url=source_os_auth_url)
self.dest_neutron = client.Client( self.dest_neutron = self.connect_to_client(
username=self._dest_os_username, username=dest_os_username,
tenant_name=self._dest_os_tenant_name, user_domain_id=dest_os_user_domain_id,
password=self._dest_os_password, tenant_name=dest_os_tenant_name,
auth_url=self._dest_os_auth_url) tenant_domain_id=dest_os_tenant_domain_id,
password=dest_os_password,
auth_url=dest_os_auth_url)
# Migrate all the objects
self.migrate_security_groups() self.migrate_security_groups()
self.migrate_qos_policies() self.migrate_qos_policies()
routers_routes = self.migrate_routers() routers_routes = self.migrate_routers()
@ -58,6 +70,19 @@ class ApiReplayClient(object):
self.migrate_floatingips() self.migrate_floatingips()
self.migrate_routers_routes(routers_routes) self.migrate_routers_routes(routers_routes)
def connect_to_client(self, username, user_domain_id,
tenant_name, tenant_domain_id,
password, auth_url):
auth = identity.Password(username=username,
user_domain_id=user_domain_id,
password=password,
project_name=tenant_name,
project_domain_id=tenant_domain_id,
auth_url=auth_url)
sess = session.Session(auth=auth)
neutron = client.Client(session=sess)
return neutron
def find_subnet_by_id(self, subnet_id, subnets): def find_subnet_by_id(self, subnet_id, subnets):
for subnet in subnets: for subnet in subnets:
if subnet['id'] == subnet_id: if subnet['id'] == subnet_id:
@ -257,7 +282,12 @@ class ApiReplayClient(object):
each router. Static routes must be added later, after the router each router. Static routes must be added later, after the router
ports are set. ports are set.
""" """
try:
source_routers = self.source_neutron.list_routers()['routers'] source_routers = self.source_neutron.list_routers()['routers']
except Exception:
# L3 might be disabled in the source
source_routers = []
dest_routers = self.dest_neutron.list_routers()['routers'] dest_routers = self.dest_neutron.list_routers()['routers']
update_routes = {} update_routes = {}
@ -328,17 +358,22 @@ class ApiReplayClient(object):
def fix_network(self, body, dest_default_public_net): def fix_network(self, body, dest_default_public_net):
# neutron doesn't like some fields being None even though its # neutron doesn't like some fields being None even though its
# what it returns to us. # what it returns to us.
for field in ['provider:physical_network']: for field in ['provider:physical_network',
'provider:segmentation_id']:
if field in body and body[field] is None: if field in body and body[field] is None:
del body[field] del body[field]
# vxlan network with segmentation id should be translated to regular # vxlan network with segmentation id should be translated to a regular
# networks in nsx-v3. # network in nsx-v3.
if (body.get('provider:network_type') == 'vxlan' and if (body.get('provider:network_type') == 'vxlan' and
body.get('provider:segmentation_id') is not None): body.get('provider:segmentation_id') is not None):
del body['provider:network_type'] del body['provider:network_type']
del body['provider:segmentation_id'] del body['provider:segmentation_id']
# flat network should be translated to a regular network in nsx-v3.
if (body.get('provider:network_type') == 'flat'):
del body['provider:network_type']
# external networks needs some special care # external networks needs some special care
if body.get('router:external'): if body.get('router:external'):
fields_reset = False fields_reset = False
@ -408,9 +443,16 @@ class ApiReplayClient(object):
# only create network if the dest server doesn't have it # only create network if the dest server doesn't have it
if self.have_id(network['id'], dest_networks) is False: if self.have_id(network['id'], dest_networks) is False:
try:
created_net = self.dest_neutron.create_network( created_net = self.dest_neutron.create_network(
{'network': body})['network'] {'network': body})['network']
print("Created network: %s " % created_net) print("Created network: %s " % created_net)
except Exception as e:
# Print the network and exception to help debugging
with excutils.save_and_reraise_exception():
print("Failed to create network: " + str(body))
print("Source network: " + str(network))
raise e
created_subnet = None created_subnet = None
for subnet_id in network['subnets']: for subnet_id in network['subnets']:
@ -497,7 +539,12 @@ class ApiReplayClient(object):
def migrate_floatingips(self): def migrate_floatingips(self):
"""Migrates floatingips from source to dest neutron.""" """Migrates floatingips from source to dest neutron."""
try:
source_fips = self.source_neutron.list_floatingips()['floatingips'] source_fips = self.source_neutron.list_floatingips()['floatingips']
except Exception:
# L3 might be disabled in the source
source_fips = []
drop_fip_fields = ['status', 'router_id', 'id', 'revision'] drop_fip_fields = ['status', 'router_id', 'id', 'revision']
for source_fip in source_fips: for source_fip in source_fips: