Add Nova-network support to service_instance module

Till that moment it was possible to use Generic driver only with Neutron.
Nova network still is used in various deployments, so add support for it.

List of changes:
- Split service_instance to two parts - compute and networking.
- Implement networking part as 'network helpers'.
- Add new config option 'service_instance_network_helper_type' that supports
only two values - 'nova' and 'neutron' respectively.
It is required option. Default value is 'neutron'.

Details on Nova-network approach:
- Due to resctrictions of Nova network, where security group rules are applied
to all VM network interfaces, no need to create service network, because it
will be just duplication. Service instance will use only one network that is
provided within share-network entity.
- Host of manila-share service should have network connectivity for all
networks, that are going to be used for share creations.
- Nova network helper does not create resources, so, network is not configured
by Manila in that case. So, network infrastructure can be either flat or using
VLAN segmentation as well.

Implements BP nova-network-support-in-generic-driver
Implements BP split-service-instance
Change-Id: I7462fe3d6fa944d391628b8e2b374cf5593924ea
This commit is contained in:
Ponomaryov Valeriy 2015-02-10 21:28:11 +02:00 committed by Valeriy Ponomaryov
parent 46857b6ffc
commit 79949476ed
11 changed files with 2101 additions and 940 deletions

View File

@ -135,6 +135,15 @@ def _untranslate_server_summary_view(server):
return d
def _to_dict(obj):
if isinstance(obj, dict):
return obj
elif hasattr(obj, 'to_dict'):
return obj.to_dict()
else:
return obj.__dict__
def translate_server_exception(method):
"""Transforms the exception for the instance.
@ -308,3 +317,7 @@ class API(base.Base):
def security_group_rule_delete(self, context, rule):
return novaclient(context).security_group_rules.delete(rule)
def network_get(self, context, network_id):
"""Return network data by its ID."""
return _to_dict(novaclient(context).networks.get(network_id))

View File

@ -47,10 +47,11 @@ neutron_opts = [
deprecated_group='DEFAULT',
secret=True),
cfg.StrOpt(
'neutron_admin_tenant_name',
'neutron_admin_project_name',
default='service',
deprecated_group='DEFAULT',
help='Tenant name for connecting to neutron in admin context.'),
deprecated_name='neutron_admin_tenant_name',
help='Project name for connecting to Neutron in admin context.'),
cfg.StrOpt(
'neutron_admin_auth_url',
default='http://localhost:5000/v2.0',
@ -105,7 +106,7 @@ class API(base.Base):
else:
params['username'] = self.configuration.neutron_admin_username
params['tenant_name'] = (
self.configuration.neutron_admin_tenant_name)
self.configuration.neutron_admin_project_name)
params['password'] = self.configuration.neutron_admin_password
params['auth_url'] = self.configuration.neutron_admin_auth_url
params['auth_strategy'] = self.configuration.neutron_auth_strategy
@ -121,7 +122,7 @@ class API(base.Base):
return self._get_client(token=token)
@property
def admin_tenant_id(self):
def admin_project_id(self):
if self.client.httpclient.auth_token is None:
try:
self.client.httpclient.authenticate()
@ -130,8 +131,8 @@ class API(base.Base):
message=e.message)
return self.client.httpclient.auth_tenant_id
def get_all_tenant_networks(self, tenant_id):
search_opts = {'tenant_id': tenant_id, 'shared': False}
def get_all_admin_project_networks(self):
search_opts = {'tenant_id': self.admin_project_id, 'shared': False}
nets = self.client.list_networks(**search_opts).get('networks', [])
return nets

View File

@ -618,11 +618,7 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
msg = "Creating share server '%s'."
LOG.debug(msg % network_info['server_id'])
server = self.service_instance_manager.set_up_service_instance(
self.admin_context,
network_info['server_id'],
network_info['neutron_net_id'],
network_info['neutron_subnet_id'],
)
self.admin_context, network_info)
for helper in self._helpers.values():
helper.init_helper(server)
return server
@ -632,8 +628,7 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
LOG.debug("Removing share infrastructure for service instance '%s'.",
instance_id)
self.service_instance_manager.delete_service_instance(
self.admin_context, instance_id, server_details['subnet_id'],
server_details.get('router_id'))
self.admin_context, server_details)
class NASHelperBase(object):

View File

@ -16,9 +16,9 @@
"""Module for managing nova instances for share drivers."""
import abc
import os
import socket
import threading
import time
import netaddr
@ -38,48 +38,66 @@ from manila.network.neutron import api as neutron
from manila import utils
LOG = log.getLogger(__name__)
NEUTRON_NAME = "neutron"
NOVA_NAME = "nova"
share_servers_handling_mode_opts = [
cfg.StrOpt('service_image_name',
default='manila-service-image',
help="Name of image in glance, that will be used to create "
"service instance."),
cfg.StrOpt('service_instance_name_template',
default='manila_service_instance_%s',
help="Name of service instance."),
cfg.StrOpt('manila_service_keypair_name',
default='manila-service',
help="Name of keypair that will be created and used "
"for service instance."),
cfg.StrOpt('path_to_public_key',
default='~/.ssh/id_rsa.pub',
help="Path to hosts public key."),
cfg.StrOpt('service_instance_security_group',
default="manila-service",
help="Name of security group, that will be used for "
"service instance creation."),
cfg.IntOpt('service_instance_flavor_id',
default=100,
help="ID of flavor, that will be used for service instance "
"creation."),
cfg.StrOpt('service_network_name',
default='manila_service_network',
help="Name of manila service network."),
cfg.StrOpt('service_network_cidr',
default='10.254.0.0/16',
help="CIDR of manila service network."),
cfg.IntOpt('service_network_division_mask',
default=28,
help="This mask is used for dividing service network into "
"subnets, IP capacity of subnet with this mask directly "
"defines possible amount of created service VMs "
"per tenant's subnet."),
cfg.StrOpt('interface_driver',
default='manila.network.linux.interface.OVSInterfaceDriver',
help="Vif driver."),
cfg.BoolOpt('connect_share_server_to_tenant_network',
default=False,
help='Attach share server directly to share network.'),
cfg.StrOpt(
"service_image_name",
default="manila-service-image",
help="Name of image in Glance, that will be used for service instance "
"creation."),
cfg.StrOpt(
"service_instance_name_template",
default="manila_service_instance_%s",
help="Name of service instance."),
cfg.StrOpt(
"manila_service_keypair_name",
default="manila-service",
help="Keypair name that will be created and used for service "
"instances."),
cfg.StrOpt(
"path_to_public_key",
default="~/.ssh/id_rsa.pub",
help="Path to hosts public key."),
cfg.StrOpt(
"service_instance_security_group",
default="manila-service",
help="Security group name, that will be used for "
"service instance creation."),
cfg.IntOpt(
"service_instance_flavor_id",
default=100,
help="ID of flavor, that will be used for service instance "
"creation."),
cfg.StrOpt(
"service_network_name",
default="manila_service_network",
help="Name of manila service network. Used only with Neutron."),
cfg.StrOpt(
"service_network_cidr",
default="10.254.0.0/16",
help="CIDR of manila service network. Used only with Neutron."),
cfg.IntOpt(
"service_network_division_mask",
default=28,
help="This mask is used for dividing service network into "
"subnets, IP capacity of subnet with this mask directly "
"defines possible amount of created service VMs "
"per tenant's subnet. Used only with Neutron."),
cfg.StrOpt(
"interface_driver",
default="manila.network.linux.interface.OVSInterfaceDriver",
help="Vif driver. Used only with Neutron."),
cfg.BoolOpt(
"connect_share_server_to_tenant_network",
default=False,
help="Attach share server directly to share network. "
"Used only with Neutron."),
cfg.StrOpt(
"service_instance_network_helper_type",
default=NEUTRON_NAME,
help="Allowed values are %s." % [NOVA_NAME, NEUTRON_NAME])
]
no_share_servers_handling_mode_opts = [
@ -121,11 +139,6 @@ common_opts = [
]
CONF = cfg.CONF
CONF.register_opts(common_opts)
CONF.register_opts(no_share_servers_handling_mode_opts)
CONF.register_opts(share_servers_handling_mode_opts)
lock = threading.Lock()
class ServiceInstanceManager(object):
@ -148,64 +161,64 @@ class ServiceInstanceManager(object):
first priority is driver's config,
second priority is global config.
"""
value = None
if self.driver_config:
value = self.driver_config.safe_get(key)
else:
value = CONF.get(key)
return value
return self.driver_config.safe_get(key)
return CONF.get(key)
def __init__(self, db, driver_config):
"""Do initialization."""
def _get_network_helper(self):
network_helper_type = (
self.get_config_option(
"service_instance_network_helper_type").lower())
if network_helper_type == NEUTRON_NAME:
return NeutronNetworkHelper(self)
elif network_helper_type == NOVA_NAME:
return NovaNetworkHelper(self)
else:
raise exception.ManilaException(
_("Wrong value '%(provided)s' for config opt "
"'service_instance_network_helper_type'. "
"Allowed values are %(allowed)s.") % dict(
provided=network_helper_type,
allowed=[NOVA_NAME, NEUTRON_NAME]))
def __init__(self, db, driver_config=None):
super(ServiceInstanceManager, self).__init__()
self.db = db
self.driver_config = driver_config
self.driver_config.append_config_values(common_opts)
if self.get_config_option("driver_handles_share_servers"):
self.driver_config.append_config_values(
share_servers_handling_mode_opts)
if self.driver_config:
self.driver_config.append_config_values(common_opts)
if self.get_config_option("driver_handles_share_servers"):
self.driver_config.append_config_values(
share_servers_handling_mode_opts)
else:
self.driver_config.append_config_values(
no_share_servers_handling_mode_opts)
else:
self.driver_config.append_config_values(
no_share_servers_handling_mode_opts)
CONF.register_opts(common_opts)
if self.get_config_option("driver_handles_share_servers"):
CONF.register_opts(share_servers_handling_mode_opts)
else:
CONF.register_opts(no_share_servers_handling_mode_opts)
if not self.get_config_option("service_instance_user"):
raise exception.ServiceInstanceException(
_('Service instance user is not specified.'))
self.admin_context = context.get_admin_context()
self._execute = utils.execute
self.compute_api = compute.API()
self.path_to_private_key = self.get_config_option(
"path_to_private_key")
self.max_time_to_build_instance = self.get_config_option(
"max_time_to_build_instance")
if self.get_config_option("driver_handles_share_servers"):
self.neutron_api = neutron.API()
self.path_to_public_key = self.get_config_option(
"path_to_public_key")
self.connect_share_server_to_tenant_network = (
self.get_config_option(
'connect_share_server_to_tenant_network'))
self._get_service_tenant_id()
self.service_network_id = self._get_service_network()
self.vif_driver = importutils.import_class(
self.get_config_option("interface_driver"))()
self._setup_connectivity_with_service_instances()
def _get_service_tenant_id(self):
attempts = 5
while attempts:
try:
self.service_tenant_id = self.neutron_api.admin_tenant_id
break
except exception.NetworkException:
LOG.debug('Connection to neutron failed.')
attempts -= 1
time.sleep(3)
else:
raise exception.ServiceInstanceException(_('Can not receive '
'service tenant id.'))
self.network_helper = self._get_network_helper()
self.network_helper.setup_connectivity_with_service_instances()
def get_common_server(self):
data = {
@ -258,38 +271,20 @@ class ServiceInstanceManager(object):
net_ips = [addr['addr'] for addr in server['addresses'][net_name]]
return net_ips
@utils.synchronized("service_instance_get_service_network", external=True)
def _get_service_network(self):
"""Finds existing or creates new service network."""
service_network_name = self.get_config_option("service_network_name")
networks = [network for network in self.neutron_api.
get_all_tenant_networks(self.service_tenant_id)
if network['name'] == service_network_name]
if len(networks) > 1:
raise exception.ServiceInstanceException(_('Ambiguous service '
'networks.'))
elif not networks:
return self.neutron_api.network_create(self.service_tenant_id,
service_network_name)['id']
else:
return networks[0]['id']
def _get_service_instance_name(self, share_server_id):
"""Returns service vms name."""
if self.driver_config:
# Make service instance name unique for multibackend installation
name = "%s_%s" % (self.driver_config.config_group,
share_server_id)
name = "%s_%s" % (self.driver_config.config_group, share_server_id)
else:
name = share_server_id
return self.get_config_option("service_instance_name_template") % name
def _get_server_ip(self, server):
"""Returns service vms ip address."""
net_name = self.get_config_option("service_network_name")
def _get_server_ip(self, server, net_name):
"""Returns service IP address of service instance."""
net_ips = self._get_addresses_by_network_name(net_name, server)
if not net_ips:
msg = _("Failed to get service instance ip address. "
msg = _("Failed to get service instance IP address. "
"Service network name is '%(net_name)s' "
"and provided data are '%(data)s'.")
msg = msg % {'net_name': net_name, 'data': six.text_type(server)}
@ -379,35 +374,39 @@ class ServiceInstanceManager(object):
"Giving up.") % {
'id': server_id, 's': self.max_time_to_build_instance})
def set_up_service_instance(self, context, instance_name, neutron_net_id,
neutron_subnet_id):
def set_up_service_instance(self, context, network_info):
"""Finds or creates and sets up service vm.
:param context: defines context, that should be used
:param instance_name: provides name for service VM
:param neutron_net_id: provides network id for service VM
:param neutron_subnet_id: provides subnet id for service VM
:param network_info: network info for getting allocations
:returns: dict with service instance details
:raises: exception.ServiceInstanceException
"""
instance_name = network_info['server_id']
server = self._create_service_instance(
context, instance_name, neutron_net_id, neutron_subnet_id)
context, instance_name, network_info)
instance_details = {
'instance_id': server['id'],
'ip': server['ip'],
'pk_path': server['pk_path'],
'subnet_id': server['subnet_id'],
'pk_path': server.get('pk_path'),
'subnet_id': server.get('subnet_id'),
'password': self.get_config_option('service_instance_password'),
'username': self.get_config_option('service_instance_user'),
'public_address': server['public_address'],
}
if 'router_id' in server:
if server.get('router_id'):
instance_details['router_id'] = server['router_id']
for key in ('password', 'pk_path'):
for key in ('password', 'pk_path', 'subnet_id'):
if not instance_details[key]:
instance_details.pop(key)
if not self._check_server_availability(server):
raise exception.ServiceInstanceException(
_('SSH connection has not been '
'established to %(server)s in %(time)ss. Giving up.') % {
'server': server['ip'],
'time': self.max_time_to_build_instance})
return instance_details
@utils.synchronized("service_instance_get_key", external=True)
@ -432,18 +431,17 @@ class ServiceInstanceManager(object):
public_key, __ = self._execute('cat', path_to_public_key)
if not keypairs:
keypair = self.compute_api.keypair_import(context,
keypair_name,
public_key)
keypair = self.compute_api.keypair_import(
context, keypair_name, public_key)
else:
keypair = keypairs[0]
if keypair.public_key != public_key:
LOG.debug('Public key differs from existing keypair. '
'Creating new keypair.')
self.compute_api.keypair_delete(context, keypair.id)
keypair = self.compute_api.keypair_import(context,
keypair_name,
public_key)
keypair = self.compute_api.keypair_import(
context, keypair_name, public_key)
return keypair.name, path_to_private_key
def _get_service_image(self, context):
@ -454,130 +452,221 @@ class ServiceInstanceManager(object):
if len(images) == 1:
return images[0]
elif not images:
raise exception.ServiceInstanceException(_('No appropriate '
'image was found.'))
raise exception.ServiceInstanceException(
_("Image with name '%s' not found.") % service_image_name)
else:
raise exception.ServiceInstanceException(
_('Ambiguous image name.'))
_("Found more than one image by name '%s'.") %
service_image_name)
def _create_service_instance(self, context, instance_name, neutron_net_id,
neutron_subnet_id):
def _create_service_instance(self, context, instance_name, network_info):
"""Creates service vm and sets up networking for it."""
service_image_id = self._get_service_image(context)
key_name, key_path = self._get_key(context)
if not (self.get_config_option("service_instance_password") or
key_name):
raise exception.ServiceInstanceException(
_('Neither service instance password nor key are available.'))
if not key_path:
LOG.warning(_LW(
'No key path is available. May be non-existent key path is '
'provided. Check path_to_private_key (current value '
'%(private_path)s) and path_to_public_key (current value '
'%(public_path)s) in manila configuration file.'), dict(
private_path=self.path_to_private_key,
public_path=self.path_to_public_key))
network_data = self.network_helper.setup_network(network_info)
fail_safe_data = dict(
router_id=network_data.get('router_id'),
subnet_id=network_data.get('subnet_id'))
try:
service_instance = self.compute_api.server_create(
context,
name=instance_name,
image=service_image_id,
flavor=self.get_config_option("service_instance_flavor_id"),
key_name=key_name,
nics=network_data['nics'])
with lock:
key_name, key_path = self._get_key(context)
if not (self.get_config_option("service_instance_password") or
key_name):
fail_safe_data['instance_id'] = service_instance['id']
t = time.time()
while time.time() - t < self.max_time_to_build_instance:
# NOTE(vponomaryov): emptiness of 'networks' field checked as
# workaround for nova/neutron bug #1210483.
if (service_instance['status'] == 'ACTIVE' and
service_instance.get('networks', {})):
break
if service_instance['status'] == 'ERROR':
raise exception.ServiceInstanceException(
_("Failed to build service instance '%s'.") %
service_instance['id'])
time.sleep(1)
try:
service_instance = self.compute_api.server_get(
context,
service_instance['id'])
except exception.InstanceNotFound as e:
LOG.debug(e)
else:
raise exception.ServiceInstanceException(
_('Neither service '
'instance password nor key are available.'))
if not key_path:
str_params = {
'private_path': self.path_to_private_key,
'public_path': self.path_to_public_key,
}
LOG.warning(_LW('No key path is available. May be '
'non-existent key path is provided. Check '
'path_to_private_key (current value '
'%(private_path)s) and '
'path_to_public_key (current value '
'%(public_path)s) in manila '
'configuration file.'), str_params)
_("Instance '%(ins)s' has not been spawned in %(timeout)s "
"seconds. Giving up.") % dict(
ins=service_instance['id'],
timeout=self.max_time_to_build_instance))
security_group = self._get_or_create_security_group(context)
network_data = self._setup_network_for_instance(neutron_net_id,
neutron_subnet_id)
try:
self._setup_connectivity_with_service_instances()
except Exception as e:
LOG.debug(e)
for port in network_data['ports']:
self.neutron_api.delete_port(port['id'])
raise
if security_group:
if self.network_helper.NAME == NOVA_NAME:
# NOTE(vponomaryov): Nova-network allows to assign
# secgroups only by names.
sg_id = security_group.name
else:
sg_id = security_group.id
LOG.debug(
"Adding security group '%(sg)s' to server '%(si)s'.",
dict(sg=sg_id, si=service_instance["id"]))
self.compute_api.add_security_group_to_server(
context, service_instance["id"], sg_id)
service_instance = self.compute_api.server_create(
context,
name=instance_name,
image=service_image_id,
flavor=self.get_config_option("service_instance_flavor_id"),
key_name=key_name,
nics=[{'port-id': port['id']} for port in network_data['ports']])
if self.network_helper.NAME == NEUTRON_NAME:
service_instance['ip'] = self._get_server_ip(
service_instance,
self.get_config_option("service_network_name"))
public_ip = network_data.get(
'public_port', network_data['service_port'])['fixed_ips']
service_instance['public_address'] = public_ip[0]['ip_address']
else:
net_name = self.network_helper.get_network_name(network_info)
service_instance['ip'] = self._get_server_ip(
service_instance, net_name)
service_instance['public_address'] = service_instance['ip']
t = time.time()
while time.time() - t < self.max_time_to_build_instance:
# NOTE(vponomaryov): emptiness of 'networks' field is checked as
# workaround for nova/neutron bug #1210483.
if (service_instance['status'] == 'ACTIVE' and
service_instance.get('networks', {})):
break
if service_instance['status'] == 'ERROR':
raise exception.ServiceInstanceException(
_('Failed to build service instance.'))
time.sleep(1)
try:
service_instance = self.compute_api.server_get(
context,
service_instance['id'])
except exception.InstanceNotFound as e:
LOG.debug(e)
else:
raise exception.ServiceInstanceException(
_('Instance have not been spawned in %ss. Giving up.') %
self.max_time_to_build_instance)
except Exception as e:
e.detail_data = {'server_details': fail_safe_data}
raise
if security_group:
LOG.debug("Adding security group "
"'%s' to server '%s'." % (security_group.id,
service_instance["id"]))
self.compute_api.add_security_group_to_server(
context,
service_instance["id"], security_group.id)
service_instance['ip'] = self._get_server_ip(service_instance)
service_instance['pk_path'] = key_path
if 'router' in network_data and 'id' in network_data['router']:
service_instance['router_id'] = network_data['router']['id']
service_instance['subnet_id'] = network_data['service_subnet']['id']
service_instance['port_id'] = network_data['service_port']['id']
try:
public_ip = network_data['public_port']
except KeyError:
public_ip = network_data['service_port']
public_ip = public_ip['fixed_ips']
public_ip = public_ip[0]
public_ip = public_ip['ip_address']
service_instance['public_address'] = public_ip
if not self._check_server_availability(service_instance):
raise exception.ServiceInstanceException(
_('SSH connection have not been '
'established in %ss. Giving up.') %
self.max_time_to_build_instance)
for pair in [('router', 'router_id'), ('service_subnet', 'subnet_id')]:
if pair[0] in network_data and 'id' in network_data[pair[0]]:
service_instance[pair[1]] = network_data[pair[0]]['id']
return service_instance
def _check_server_availability(self, server):
t = time.time()
while time.time() - t < self.max_time_to_build_instance:
LOG.debug('Checking service vm availablity.')
LOG.debug('Checking service VM availablity.')
try:
socket.socket().connect((server['ip'], 22))
LOG.debug('Service vm is available via ssh.')
LOG.debug('Service VM is available via SSH.')
return True
except socket.error as e:
LOG.debug(e)
LOG.debug('Server is not available through ssh. Waiting...')
LOG.debug("Server %s is not available via SSH. Waiting...",
server['ip'])
time.sleep(5)
return False
def delete_service_instance(self, context, server_details):
"""Removes share infrastructure.
Deletes service vm and subnet, associated to share network.
"""
instance_id = server_details.get("instance_id")
self._delete_server(context, instance_id)
self.network_helper.teardown_network(server_details)
@six.add_metaclass(abc.ABCMeta)
class BaseNetworkhelper(object):
@abc.abstractproperty
def NAME(self):
"""Returns code name of network helper."""
@abc.abstractmethod
def __init__(self, service_instance_manager):
"""Instantiates class and its attrs."""
@abc.abstractmethod
def get_network_name(self, network_info):
"""Returns name of network for service instance."""
@abc.abstractmethod
def setup_connectivity_with_service_instances(self):
"""Sets up connectivity between Manila host and service instances."""
@abc.abstractmethod
def setup_network(self, network_info):
"""Sets up network for service instance."""
@abc.abstractmethod
def teardown_network(self, server_details):
"""Teardowns network resources provided for service instance."""
class NeutronNetworkHelper(BaseNetworkhelper):
def __init__(self, service_instance_manager):
self.get_config_option = service_instance_manager.get_config_option
self.vif_driver = importutils.import_class(
self.get_config_option("interface_driver"))()
self.neutron_api = neutron.API()
self.service_network_id = self.get_service_network_id()
self.connect_share_server_to_tenant_network = (
self.get_config_option('connect_share_server_to_tenant_network'))
@property
def NAME(self):
return NEUTRON_NAME
@property
def admin_project_id(self):
return self.neutron_api.admin_project_id
def get_network_name(self, network_info):
"""Returns name of network for service instance."""
net = self.neutron_api.get_network(network_info['neutron_net_id'])
return net['name']
@utils.synchronized("service_instance_get_service_network", external=True)
def get_service_network_id(self):
"""Finds existing or creates new service network."""
service_network_name = self.get_config_option("service_network_name")
networks = []
for network in self.neutron_api.get_all_admin_project_networks():
if network['name'] == service_network_name:
networks.append(network)
if len(networks) > 1:
raise exception.ServiceInstanceException(
_('Ambiguous service networks.'))
elif not networks:
return self.neutron_api.network_create(
self.admin_project_id, service_network_name)['id']
else:
return networks[0]['id']
def teardown_network(self, server_details):
subnet_id = server_details.get("subnet_id")
router_id = server_details.get("router_id")
if router_id and subnet_id:
try:
self.neutron_api.router_remove_interface(
router_id, subnet_id)
except exception.NetworkException as e:
if e.kwargs['code'] != 404:
raise
LOG.debug('Subnet %(subnet_id)s is not attached to the '
'router %(router_id)s.',
{'subnet_id': subnet_id, 'router_id': router_id})
self.neutron_api.update_subnet(subnet_id, '')
@utils.synchronized(
"service_instance_setup_network_for_instance", external=True)
def _setup_network_for_instance(self, neutron_net_id, neutron_subnet_id):
"""Sets up network for service vm."""
def setup_network(self, network_info):
neutron_net_id = network_info['neutron_net_id']
neutron_subnet_id = network_info['neutron_subnet_id']
network_data = dict()
subnet_name = ('service_subnet_for_handling_of_share_server_for_'
'tenant_subnet_%s' % neutron_subnet_id)
@ -585,9 +674,7 @@ class ServiceInstanceManager(object):
network_data['service_subnet'] = self._get_service_subnet(subnet_name)
if not network_data['service_subnet']:
network_data['service_subnet'] = self.neutron_api.subnet_create(
self.service_tenant_id,
self.service_network_id,
subnet_name,
self.admin_project_id, self.service_network_id, subnet_name,
self._get_cidr_for_subnet())
if not self.connect_share_server_to_tenant_network:
@ -606,43 +693,47 @@ class ServiceInstanceManager(object):
'router_id': network_data['router']['id']})
network_data['service_port'] = self.neutron_api.create_port(
self.service_tenant_id,
self.service_network_id,
self.admin_project_id, self.service_network_id,
subnet_id=network_data['service_subnet']['id'],
device_owner='manila')
network_data['ports'] = [network_data['service_port']]
if self.connect_share_server_to_tenant_network:
network_data['public_port'] = self.neutron_api.create_port(
self.service_tenant_id, neutron_net_id,
self.admin_project_id, neutron_net_id,
subnet_id=neutron_subnet_id, device_owner='manila')
network_data['ports'].append(network_data['public_port'])
try:
self.setup_connectivity_with_service_instances()
except Exception as e:
for port in network_data['ports']:
self.neutron_api.delete_port(port['id'])
raise
network_data['nics'] = [
{'port-id': port['id']} for port in network_data['ports']]
public_ip = network_data.get(
'public_port', network_data['service_port'])
network_data['ip_address'] = public_ip['fixed_ips'][0]['ip_address']
return network_data
@utils.synchronized("service_instance_get_private_router", external=True)
def _get_private_router(self, neutron_net_id, neutron_subnet_id):
"""Returns router attached to private subnet gateway."""
private_subnet = self.neutron_api.get_subnet(neutron_subnet_id)
if not private_subnet['gateway_ip']:
raise exception.ServiceInstanceException(
_('Subnet must have gateway.'))
private_network_ports = [p for p in self.neutron_api.list_ports(
network_id=neutron_net_id)]
for p in private_network_ports:
fixed_ip = p['fixed_ips'][0]
if (fixed_ip['subnet_id'] == private_subnet['id'] and
fixed_ip['ip_address'] == private_subnet['gateway_ip']):
private_subnet_gateway_port = p
break
def _get_cidr_for_subnet(self):
"""Returns not used cidr for service subnet creating."""
subnets = self._get_all_service_subnets()
used_cidrs = set(subnet['cidr'] for subnet in subnets)
serv_cidr = netaddr.IPNetwork(
self.get_config_option("service_network_cidr"))
division_mask = self.get_config_option("service_network_division_mask")
for subnet in serv_cidr.subnet(division_mask):
cidr = six.text_type(subnet.cidr)
if cidr not in used_cidrs:
return cidr
else:
raise exception.ServiceInstanceException(
_('Subnet gateway is not attached the router.'))
private_subnet_router = self.neutron_api.show_router(
private_subnet_gateway_port['device_id'])
return private_subnet_router
raise exception.ServiceInstanceException(_('No available cidrs.'))
def _setup_connectivity_with_service_instances(self):
def setup_connectivity_with_service_instances(self):
"""Sets up connectivity with service instances.
Creates creating port in service network, creating and setting up
@ -672,21 +763,19 @@ class ServiceInstanceManager(object):
"service_instance_remove_outdated_interfaces", external=True)
def _remove_outdated_interfaces(self, device):
"""Finds and removes unused network device."""
list_dev = []
device_cidr_set = self._get_set_of_device_cidrs(device)
for dev in ip_lib.IPWrapper().get_devices():
if dev.name != device.name and dev.name[:3] == device.name[:3]:
cidr_set = set()
for a in dev.addr.list():
if a['ip_version'] == 4:
cidr_set.add(str(netaddr.IPNetwork(a['cidr']).cidr))
list_dev.append((dev.name, cidr_set))
device_cidr_set = set(str(netaddr.IPNetwork(a['cidr']).cidr)
for a in device.addr.list()
if a['ip_version'] == 4)
cidr_set = self._get_set_of_device_cidrs(dev)
if device_cidr_set & cidr_set:
self.vif_driver.unplug(dev.name)
for dev_name, cidr_set in list_dev:
if device_cidr_set & cidr_set:
self.vif_driver.unplug(dev_name)
def _get_set_of_device_cidrs(self, device):
cidrs = set()
for addr in device.addr.list():
if addr['ip_version'] == 4:
cidrs.add(six.text_type(netaddr.IPNetwork(addr['cidr']).cidr))
return cidrs
@utils.synchronized("service_instance_get_service_port", external=True)
def _get_service_port(self):
@ -700,17 +789,10 @@ class ServiceInstanceManager(object):
raise exception.ServiceInstanceException(
_('Error. Ambiguous service ports.'))
elif not ports:
try:
stdout, stderr = self._execute('hostname')
host = stdout.strip()
except exception.ProcessExecutionError as e:
msg = _('Unable to get host. %s') % e.stderr
raise exception.ManilaException(msg)
host = socket.gethostname()
port = self.neutron_api.create_port(
self.service_tenant_id,
self.service_network_id,
device_id='manila-share',
device_owner='manila:share',
self.admin_project_id, self.service_network_id,
device_id='manila-share', device_owner='manila:share',
host_id=host)
else:
port = ports[0]
@ -737,46 +819,27 @@ class ServiceInstanceManager(object):
return port
def _get_cidr_for_subnet(self):
"""Returns not used cidr for service subnet creating."""
subnets = self._get_all_service_subnets()
used_cidrs = set(subnet['cidr'] for subnet in subnets)
serv_cidr = netaddr.IPNetwork(
self.get_config_option("service_network_cidr"))
division_mask = self.get_config_option("service_network_division_mask")
for subnet in serv_cidr.subnet(division_mask):
cidr = str(subnet.cidr)
if cidr not in used_cidrs:
return cidr
@utils.synchronized("service_instance_get_private_router", external=True)
def _get_private_router(self, neutron_net_id, neutron_subnet_id):
"""Returns router attached to private subnet gateway."""
private_subnet = self.neutron_api.get_subnet(neutron_subnet_id)
if not private_subnet['gateway_ip']:
raise exception.ServiceInstanceException(
_('Subnet must have gateway.'))
private_network_ports = [p for p in self.neutron_api.list_ports(
network_id=neutron_net_id)]
for p in private_network_ports:
fixed_ip = p['fixed_ips'][0]
if (fixed_ip['subnet_id'] == private_subnet['id'] and
fixed_ip['ip_address'] == private_subnet['gateway_ip']):
private_subnet_gateway_port = p
break
else:
raise exception.ServiceInstanceException(_('No available cidrs.'))
def delete_service_instance(self, context, instance_id, subnet_id,
router_id=None):
"""Removes share infrastructure.
Deletes service vm and subnet, associated to share network.
"""
self._delete_server(context, instance_id)
if router_id and subnet_id:
try:
self.neutron_api.router_remove_interface(router_id,
subnet_id)
except exception.NetworkException as e:
if e.kwargs['code'] != 404:
raise
LOG.debug('Subnet %(subnet_id)s is not attached to the '
'router %(router_id)s.',
{'subnet_id': subnet_id,
'router_id': router_id})
self.neutron_api.update_subnet(subnet_id, '')
@utils.synchronized(
"service_instance_get_all_service_subnets", external=True)
def _get_all_service_subnets(self):
service_network = self.neutron_api.get_network(self.service_network_id)
return [self.neutron_api.get_subnet(subnet_id)
for subnet_id in service_network['subnets']]
raise exception.ServiceInstanceException(
_('Subnet gateway is not attached to the router.'))
private_subnet_router = self.neutron_api.show_router(
private_subnet_gateway_port['device_id'])
return private_subnet_router
@utils.synchronized("service_instance_get_service_subnet", external=True)
def _get_service_subnet(self, subnet_name):
@ -790,10 +853,66 @@ class ServiceInstanceManager(object):
if subnet['name'] == '']
if unused_service_subnets:
service_subnet = unused_service_subnets[0]
self.neutron_api.update_subnet(service_subnet['id'],
subnet_name)
self.neutron_api.update_subnet(
service_subnet['id'], subnet_name)
return service_subnet
return None
else:
raise exception.ServiceInstanceException(_('Ambiguous service '
'subnets.'))
raise exception.ServiceInstanceException(
_('Ambiguous service subnets.'))
@utils.synchronized(
"service_instance_get_all_service_subnets", external=True)
def _get_all_service_subnets(self):
service_network = self.neutron_api.get_network(self.service_network_id)
subnets = []
for subnet_id in service_network['subnets']:
subnets.append(self.neutron_api.get_subnet(subnet_id))
return subnets
class NovaNetworkHelper(BaseNetworkhelper):
"""Nova network helper for Manila service instances.
All security-group rules are applied to all interfaces of Nova VM
using Nova-network. In that case there is no need to create additional
service network. Only one thing should be satisfied - Manila host
should have access to all tenant networks.
This network helper does not create resources.
"""
def __init__(self, service_instance_manager):
self.compute_api = service_instance_manager.compute_api
self.admin_context = service_instance_manager.admin_context
@property
def NAME(self):
return NOVA_NAME
def setup_network(self, network_info):
net = self._get_nova_network(network_info['nova_net_id'])
network_info['nics'] = [{'net-id': net['id']}]
return network_info
def get_network_name(self, network_info):
"""Returns name of network for service instance."""
return self._get_nova_network(network_info['nova_net_id'])['label']
def teardown_network(self, server_details):
"""Nothing to do. Placeholder."""
def setup_connectivity_with_service_instances(self):
"""Nothing to do. Placeholder."""
def _get_nova_network(self, nova_network_id):
"""Returns network to be used for service instance.
:param nova_network_id: string with id of network.
:returns: dict -- network data as dict
:raises: exception.ManilaException
"""
if not nova_network_id:
raise exception.ManilaException(
_('Nova network for service instance is not provided.'))
net = self.compute_api.network_get(self.admin_context, nova_network_id)
return net

View File

@ -469,6 +469,7 @@ class ShareManager(manager.SchedulerDependentManager):
'cidr': share_network['cidr'],
'neutron_net_id': share_network['neutron_net_id'],
'neutron_subnet_id': share_network['neutron_subnet_id'],
'nova_net_id': share_network['nova_net_id'],
'security_services': share_network['security_services'],
'network_allocations': network_allocations,
'backend_details': share_server.get('backend_details'),
@ -485,14 +486,13 @@ class ShareManager(manager.SchedulerDependentManager):
share_network = self.db.share_network_get(
context, share_server['share_network_id'])
network_info = self._form_server_setup_info(context, share_server,
share_network)
server_info = self.driver.setup_server(network_info,
metadata=metadata)
network_info = self._form_server_setup_info(
context, share_server, share_network)
server_info = self.driver.setup_server(
network_info, metadata=metadata)
if server_info and isinstance(server_info, dict):
self.db.share_server_backend_details_set(context,
share_server['id'],
server_info)
self.db.share_server_backend_details_set(
context, share_server['id'], server_info)
return self.db.share_server_update(
context, share_server['id'],
{'status': constants.STATUS_ACTIVE})

View File

@ -30,6 +30,12 @@ class Volume(object):
self.name = volume_id
class Network(object):
def __init__(self, net_id):
self.id = net_id
self.label = 'fake_label_%s' % net_id
class FakeNovaClient(object):
class Servers(object):
def get(self, instance_id):
@ -57,10 +63,15 @@ class FakeNovaClient(object):
def __getattr__(self, item):
return None
class Networks(object):
def get(self, net_id):
return Network(net_id)
def __init__(self):
self.servers = self.Servers()
self.volumes = self.Volumes()
self.keypairs = self.servers
self.networks = self.Networks()
class NovaApiTestCase(test.TestCase):
@ -209,3 +220,42 @@ class NovaApiTestCase(test.TestCase):
def test_keypair_list(self):
self.assertEqual([{'id': 'id1'}, {'id': 'id2'}],
self.api.keypair_list(self.ctx))
def test_network_get(self):
net_id = 'fake_net_id'
net = self.api.network_get(self.ctx, net_id)
self.assertTrue(isinstance(net, dict))
self.assertEqual(net_id, net['id'])
class ToDictTestCase(test.TestCase):
def test_dict_provided(self):
fake_dict = {'foo_key': 'foo_value', 'bar_key': 'bar_value'}
result = nova._to_dict(fake_dict)
self.assertEqual(fake_dict, result)
def test_obj_provided_with_to_dict_method(self):
expected = {'foo': 'bar'}
class FakeObj(object):
def __init__(self):
self.fake_attr = 'fake_attr_value'
def to_dict(self):
return expected
fake_obj = FakeObj()
result = nova._to_dict(fake_obj)
self.assertEqual(expected, result)
def test_obj_provided_without_to_dict_method(self):
expected = {'foo': 'bar'}
class FakeObj(object):
def __init__(self):
self.foo = expected['foo']
fake_obj = FakeObj()
result = nova._to_dict(fake_obj)
self.assertEqual(expected, result)

View File

@ -72,9 +72,27 @@ class FakeRouter(object):
setattr(self, attr, value)
class FakeDeviceAddr(object):
def __init__(self, list_of_addresses=None):
self.addresses = list_of_addresses or [
dict(ip_version=4, cidr='1.0.0.0/27'),
dict(ip_version=4, cidr='2.0.0.0/27'),
dict(ip_version=6, cidr='3.0.0.0/27'),
]
def list(self):
return self.addresses
class FakeDevice(object):
def __init__(self, name=None, list_of_addresses=None):
self.addr = FakeDeviceAddr(list_of_addresses)
self.name = name or 'fake_device_name'
class API(object):
"""Fake Network API."""
admin_tenant_id = 'fake admin tenant id'
admin_project_id = 'fake_admin_project_id'
network = {
"status": "ACTIVE",
@ -105,13 +123,13 @@ class API(object):
"device_id": "fake_device_id"
}
def get_all_tenant_networks(self, tenant_id):
def get_all_admin_project_networks(self):
net1 = self.network.copy()
net1['tenant_id'] = tenant_id
net1['tenant_id'] = self.admin_project_id
net1['id'] = str(uuid.uuid4())
net2 = self.network.copy()
net2['tenant_id'] = tenant_id
net2['tenant_id'] = self.admin_project_id
net2['id'] = str(uuid.uuid4())
return [net1, net2]

View File

@ -566,3 +566,65 @@ class NeutronApiTest(test.TestCase):
self.neutron_api.client.add_interface_router.assert_called_once_with(
router_id, {'subnet_id': subnet_id, 'port_id': port_id})
self.assertTrue(clientv20.Client.called)
def test_admin_project_id_exist(self):
fake_admin_project_id = 'fake_admin_project_id_value'
self.neutron_api.client.httpclient = mock.Mock()
self.neutron_api.client.httpclient.auth_token = mock.Mock()
self.neutron_api.client.httpclient.auth_tenant_id = (
fake_admin_project_id)
admin_project_id = self.neutron_api.admin_project_id
self.assertEqual(fake_admin_project_id, admin_project_id)
self.neutron_api.client.httpclient.auth_token.called
def test_admin_project_id_not_exist(self):
fake_admin_project_id = 'fake_admin_project_id_value'
self.neutron_api.client.httpclient = mock.Mock()
self.neutron_api.client.httpclient.auth_token = mock.Mock(
return_value=None)
self.neutron_api.client.httpclient.authenticate = mock.Mock()
self.neutron_api.client.httpclient.auth_tenant_id = (
fake_admin_project_id)
admin_project_id = self.neutron_api.admin_project_id
self.assertEqual(fake_admin_project_id, admin_project_id)
self.neutron_api.client.httpclient.auth_token.called
self.neutron_api.client.httpclient.authenticate.called
def test_admin_project_id_not_exist_with_failure(self):
self.neutron_api.client.httpclient = mock.Mock()
self.neutron_api.client.httpclient.auth_token = None
self.neutron_api.client.httpclient.authenticate = mock.Mock(
side_effect=neutron_client_exc.NeutronClientException)
self.neutron_api.client.httpclient.auth_tenant_id = mock.Mock()
try:
self.neutron_api.admin_project_id
except exception.NetworkException:
pass
else:
raise Exception('Expected error was not raised')
self.assertTrue(self.neutron_api.client.httpclient.authenticate.called)
self.assertFalse(
self.neutron_api.client.httpclient.auth_tenant_id.called)
def test_get_all_admin_project_networks(self):
fake_networks = {'networks': ['fake_net_1', 'fake_net_2']}
self.mock_object(
self.neutron_api.client, 'list_networks',
mock.Mock(return_value=fake_networks))
self.neutron_api.client.httpclient = mock.Mock()
self.neutron_api.client.httpclient.auth_token = mock.Mock()
self.neutron_api.client.httpclient.auth_tenant_id = mock.Mock()
networks = self.neutron_api.get_all_admin_project_networks()
self.assertEqual(fake_networks['networks'], networks)
self.neutron_api.client.httpclient.auth_token.called
self.neutron_api.client.httpclient.auth_tenant_id.called
self.neutron_api.client.list_networks.assert_called_once_with(
tenant_id=self.neutron_api.admin_project_id, shared=False)

View File

@ -861,15 +861,14 @@ class GenericShareDriverTestCase(test.TestCase):
def test__setup_server(self):
sim = self._driver.instance_manager
net_info = {'server_id': 'fake',
'neutron_net_id': 'fake-net-id',
'neutron_subnet_id': 'fake-subnet-id'}
net_info = {
'server_id': 'fake',
'neutron_net_id': 'fake-net-id',
'neutron_subnet_id': 'fake-subnet-id',
}
self._driver.setup_server(net_info)
sim.set_up_service_instance.assert_called_once_with(
self._context,
'fake',
'fake-net-id',
'fake-subnet-id')
self._context, net_info)
def test__setup_server_revert(self):
@ -895,8 +894,7 @@ class GenericShareDriverTestCase(test.TestCase):
self._driver.teardown_server(server_details)
self._driver.service_instance_manager.delete_service_instance.\
assert_called_once_with(
self._driver.admin_context, server_details['instance_id'],
server_details['subnet_id'], server_details['router_id'])
self._driver.admin_context, server_details)
def test_ssh_exec_connection_not_exist(self):
ssh_output = 'fake_ssh_output'

File diff suppressed because it is too large Load Diff

View File

@ -1001,3 +1001,35 @@ class ShareManagerTestCase(test.TestCase):
get_admin_context(),
fake_share)
self.assertEqual(1, mock_LOG.error.call_count)
def test__form_server_setup_info(self):
fake_network_allocations = ['foo', 'bar']
self.mock_object(
self.share_manager.db, 'network_allocations_get_for_share_server',
mock.Mock(return_value=fake_network_allocations))
fake_share_server = dict(
id='fake_share_server_id', backend_details=dict(foo='bar'))
fake_share_network = dict(
segmentation_id='fake_segmentation_id',
cidr='fake_cidr',
neutron_net_id='fake_neutron_net_id',
neutron_subnet_id='fake_neutron_subnet_id',
nova_net_id='fake_nova_net_id',
security_services='fake_security_services')
expected = dict(
server_id=fake_share_server['id'],
segmentation_id=fake_share_network['segmentation_id'],
cidr=fake_share_network['cidr'],
neutron_net_id=fake_share_network['neutron_net_id'],
neutron_subnet_id=fake_share_network['neutron_subnet_id'],
nova_net_id=fake_share_network['nova_net_id'],
security_services=fake_share_network['security_services'],
network_allocations=fake_network_allocations,
backend_details=fake_share_server['backend_details'])
network_info = self.share_manager._form_server_setup_info(
self.context, fake_share_server, fake_share_network)
self.assertEqual(expected, network_info)
self.share_manager.db.network_allocations_get_for_share_server.\
assert_called_once_with(self.context, fake_share_server['id'])