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:
parent
740a2ec3f5
commit
efc6a2d25b
@ -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()
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user