manila/manila/share/drivers/service_instance.py

979 lines
41 KiB
Python

# Copyright (c) 2014 NetApp, Inc.
# Copyright (c) 2015 Mirantis, 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.
"""Module for managing nova instances for share drivers."""
import abc
import os
import socket
import time
import netaddr
from oslo_config import cfg
from oslo_log import log
from oslo_utils import importutils
import six
from manila.common import constants as const
from manila import compute
from manila import context
from manila import exception
from manila.i18n import _
from manila.i18n import _LW
from manila.network.linux import ip_lib
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 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 = [
cfg.StrOpt(
"service_instance_name_or_id",
help="Name or ID of service instance in Nova to use for share "
"exports. Used only when share servers handling is disabled."),
cfg.StrOpt(
"service_net_name_or_ip",
help="Can be either name of network that is used by service "
"instance within Nova to get IP address or IP address itself "
"for managing shares there. "
"Used only when share servers handling is disabled."),
cfg.StrOpt(
"tenant_net_name_or_ip",
help="Can be either name of network that is used by service "
"instance within Nova to get IP address or IP address itself "
"for exporting shares. "
"Used only when share servers handling is disabled."),
]
common_opts = [
cfg.StrOpt(
"service_instance_user",
help="User in service instance that will be used for authentication."),
cfg.StrOpt(
"service_instance_password",
default=None,
secret=True,
help="Password for service instance user."),
cfg.StrOpt(
"path_to_private_key",
default="~/.ssh/id_rsa",
help="Path to host's private key."),
cfg.IntOpt(
"max_time_to_build_instance",
default=300,
help="Maximum time in seconds to wait for creating service instance."),
]
CONF = cfg.CONF
class ServiceInstanceManager(object):
"""Manages nova instances for various share drivers.
This class provides following external methods:
1. set_up_service_instance: creates instance and sets up share
infrastructure.
2. ensure_service_instance: ensure service instance is available.
3. delete_service_instance: removes service instance and network
infrastructure.
"""
def get_config_option(self, key):
"""Returns value of config option.
:param key: key of config' option.
:returns: str -- value of config's option.
first priority is driver's config,
second priority is global config.
"""
if self.driver_config:
return self.driver_config.safe_get(key)
return CONF.get(key)
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, driver_config=None):
super(ServiceInstanceManager, self).__init__()
self.driver_config = driver_config
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:
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.path_to_public_key = self.get_config_option(
"path_to_public_key")
self.network_helper = self._get_network_helper()
self.network_helper.setup_connectivity_with_service_instances()
def get_common_server(self):
data = {
'public_address': None,
'private_address': None,
'service_net_name_or_ip': self.get_config_option(
'service_net_name_or_ip'),
'tenant_net_name_or_ip': self.get_config_option(
'tenant_net_name_or_ip'),
}
data['instance'] = self.compute_api.server_get_by_name_or_id(
self.admin_context,
self.get_config_option('service_instance_name_or_id'))
if netaddr.valid_ipv4(data['service_net_name_or_ip']):
data['private_address'] = [data['service_net_name_or_ip']]
else:
data['private_address'] = self._get_addresses_by_network_name(
data['service_net_name_or_ip'], data['instance'])
if netaddr.valid_ipv4(data['tenant_net_name_or_ip']):
data['public_address'] = [data['tenant_net_name_or_ip']]
else:
data['public_address'] = self._get_addresses_by_network_name(
data['tenant_net_name_or_ip'], data['instance'])
if not (data['public_address'] and data['private_address']):
raise exception.ManilaException(
"Can not find one of net addresses for service instance. "
"Instance: %(instance)s, "
"private_address: %(private_address)s, "
"public_address: %(public_address)s." % data)
share_server = {
'username': self.get_config_option('service_instance_user'),
'password': self.get_config_option('service_instance_password'),
'pk_path': self.path_to_private_key,
'instance_id': data['instance']['id'],
}
for key in ('private_address', 'public_address'):
data[key + '_v4'] = None
for address in data[key]:
if netaddr.valid_ipv4(address):
data[key + '_v4'] = address
break
share_server['ip'] = data['private_address_v4']
share_server['public_address'] = data['public_address_v4']
return {'backend_details': share_server}
def _get_addresses_by_network_name(self, net_name, server):
net_ips = []
if 'networks' in server and net_name in server['networks']:
net_ips = server['networks'][net_name]
elif 'addresses' in server and net_name in server['addresses']:
net_ips = [addr['addr'] for addr in server['addresses'][net_name]]
return net_ips
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)
else:
name = share_server_id
return self.get_config_option("service_instance_name_template") % 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. "
"Service network name is '%(net_name)s' "
"and provided data are '%(data)s'.")
msg = msg % {'net_name': net_name, 'data': six.text_type(server)}
raise exception.ServiceInstanceException(msg)
return net_ips[0]
@utils.synchronized(
"service_instance_get_or_create_security_group", external=True)
def _get_or_create_security_group(self, context, name=None,
description=None):
"""Get or create security group for service_instance.
:param context: context, that should be used
:param name: this is used for selection/creation of sec.group
:param description: this is used on sec.group creation step only
:returns: SecurityGroup -- security group instance from Nova
:raises: exception.ServiceInstanceException.
"""
name = name or self.get_config_option(
"service_instance_security_group")
if not name:
LOG.warning(_LW("Name for service instance security group is not "
"provided. Skipping security group step."))
return None
s_groups = [s for s in self.compute_api.security_group_list(context)
if s.name == name]
if not s_groups:
# Creating security group
if not description:
description = "This security group is intended "\
"to be used by share service."
LOG.debug("Creating security group with name '%s'.", name)
sg = self.compute_api.security_group_create(
context, name, description)
for protocol, ports in const.SERVICE_INSTANCE_SECGROUP_DATA:
self.compute_api.security_group_rule_create(
context,
parent_group_id=sg.id,
ip_protocol=protocol,
from_port=ports[0],
to_port=ports[1],
cidr="0.0.0.0/0",
)
elif len(s_groups) > 1:
msg = _("Ambiguous security_groups.")
raise exception.ServiceInstanceException(msg)
else:
sg = s_groups[0]
return sg
def ensure_service_instance(self, context, server):
"""Ensures that server exists and active."""
try:
inst = self.compute_api.server_get(self.admin_context,
server['instance_id'])
except exception.InstanceNotFound:
LOG.warning(_LW("Service instance %s does not exist."),
server['instance_id'])
return False
if inst['status'] == 'ACTIVE':
return self._check_server_availability(server)
return False
def _delete_server(self, context, server_id):
"""Deletes the server."""
try:
self.compute_api.server_get(context, server_id)
except exception.InstanceNotFound:
LOG.debug("Service instance '%s' was not found. "
"Nothing to delete, skipping.", server_id)
return
self.compute_api.server_delete(context, server_id)
t = time.time()
while time.time() - t < self.max_time_to_build_instance:
try:
self.compute_api.server_get(context, server_id)
except exception.InstanceNotFound:
LOG.debug("Service instance '%s' was deleted "
"successfully.", server_id)
break
time.sleep(2)
else:
raise exception.ServiceInstanceException(
_("Instance '%(id)s' has not been deleted in %(s)ss. "
"Giving up.") % {
'id': server_id, 's': self.max_time_to_build_instance})
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 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, network_info)
instance_details = {
'instance_id': server['id'],
'ip': server['ip'],
'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 server.get('router_id'):
instance_details['router_id'] = server['router_id']
if server.get('service_port_id'):
instance_details['service_port_id'] = server['service_port_id']
if server.get('public_port_id'):
instance_details['public_port_id'] = server['public_port_id']
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)
def _get_key(self, context):
"""Get ssh key.
:param context: defines context, that should be used
:returns: tuple with keypair name and path to private key.
"""
if not (self.path_to_public_key and self.path_to_private_key):
return (None, None)
path_to_public_key = os.path.expanduser(self.path_to_public_key)
path_to_private_key = os.path.expanduser(self.path_to_private_key)
if (not os.path.exists(path_to_public_key) or
not os.path.exists(path_to_private_key)):
return (None, None)
keypair_name = self.get_config_option("manila_service_keypair_name")
keypairs = [k for k in self.compute_api.keypair_list(context)
if k.name == keypair_name]
if len(keypairs) > 1:
raise exception.ServiceInstanceException(_('Ambiguous keypairs.'))
public_key, __ = self._execute('cat', path_to_public_key)
if not keypairs:
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)
return keypair.name, path_to_private_key
def _get_service_image(self, context):
"""Returns ID of service image for service vm creating."""
service_image_name = self.get_config_option("service_image_name")
images = [image.id for image in self.compute_api.image_list(context)
if image.name == service_image_name]
if len(images) == 1:
return images[0]
elif not images:
raise exception.ServiceInstanceException(
_("Image with name '%s' not found.") % service_image_name)
else:
raise exception.ServiceInstanceException(
_("Found more than one image by name '%s'.") %
service_image_name)
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'))
if network_data.get('service_port'):
fail_safe_data['service_port_id'] = (
network_data['service_port']['id'])
if network_data.get('public_port'):
fail_safe_data['public_port_id'] = (
network_data['public_port']['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'])
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(
_("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)
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)
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']
except Exception as e:
e.detail_data = {'server_details': fail_safe_data}
raise
service_instance.update(fail_safe_data)
service_instance['pk_path'] = key_path
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 availability.')
try:
socket.socket().connect((server['ip'], 22))
LOG.debug('Service VM is available via SSH.')
return True
except socket.error as e:
LOG.debug(e)
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"))()
if service_instance_manager.driver_config:
network_config_group = (
service_instance_manager.driver_config.network_config_group or
service_instance_manager.driver_config.config_group)
else:
network_config_group = None
self.neutron_api = neutron.API(config_group_name=network_config_group)
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']
@utils.synchronized(
"service_instance_setup_and_teardown_network_for_instance",
external=True)
def teardown_network(self, server_details):
subnet_id = server_details.get("subnet_id")
router_id = server_details.get("router_id")
service_port_id = server_details.get("service_port_id")
public_port_id = server_details.get("public_port_id")
for port_id in (service_port_id, public_port_id):
if port_id:
try:
self.neutron_api.delete_port(port_id)
except exception.NetworkException as e:
if e.kwargs.get('code') != 404:
raise
LOG.debug("Failed to delete port %(port_id)s with error: "
"\n %(exc)s", {"port_id": port_id, "exc": e})
if router_id and subnet_id:
ports = self.neutron_api.list_ports(
fields=['fixed_ips', 'device_id', 'device_owner'])
# NOTE(vponomaryov): iterate ports to get to know whether current
# subnet is used or not. We will not remove it from router if it
# is used.
for port in ports:
# NOTE(vponomaryov): if device_id is present, then we know that
# this port is used. Also, if device owner is 'compute:*', then
# we know that it is VM. We continue only if both are 'True'.
if (port['device_id'] and
port['device_owner'].startswith('compute:')):
for fixed_ip in port['fixed_ips']:
if fixed_ip['subnet_id'] == subnet_id:
# NOTE(vponomaryov): There are other share servers
# exist that use this subnet. So, do not remove it
# from router.
return
try:
# NOTE(vponomaryov): there is no other share servers or
# some VMs that use this subnet. So, remove it from router.
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_and_teardown_network_for_instance",
external=True)
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)
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.admin_project_id, self.service_network_id, subnet_name,
self._get_cidr_for_subnet())
if not self.connect_share_server_to_tenant_network:
network_data['router'] = self._get_private_router(
neutron_net_id, neutron_subnet_id)
try:
self.neutron_api.router_add_interface(
network_data['router']['id'],
network_data['service_subnet']['id'])
except exception.NetworkException as e:
if e.kwargs['code'] != 400:
raise
LOG.debug('Subnet %(subnet_id)s is already attached to the '
'router %(router_id)s.',
{'subnet_id': network_data['service_subnet']['id'],
'router_id': network_data['router']['id']})
network_data['service_port'] = self.neutron_api.create_port(
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.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
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(_('No available cidrs.'))
def setup_connectivity_with_service_instances(self):
"""Sets up connectivity with service instances.
Creates creating port in service network, creating and setting up
required network devices.
"""
port = self._get_service_port()
port = self._add_fixed_ips_to_service_port(port)
interface_name = self.vif_driver.get_device_name(port)
self.vif_driver.plug(interface_name, port['id'], port['mac_address'])
ip_cidrs = []
for fixed_ip in port['fixed_ips']:
subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id'])
net = netaddr.IPNetwork(subnet['cidr'])
ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen)
ip_cidrs.append(ip_cidr)
self.vif_driver.init_l3(interface_name, ip_cidrs)
# ensure that interface is first in the list
device = ip_lib.IPDevice(interface_name)
device.route.pullup_route(interface_name)
# here we are checking for garbage devices from removed service port
self._remove_outdated_interfaces(device)
@utils.synchronized(
"service_instance_remove_outdated_interfaces", external=True)
def _remove_outdated_interfaces(self, device):
"""Finds and removes unused network device."""
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 = self._get_set_of_device_cidrs(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):
"""Find or creates service neutron port.
This port will be used for connectivity with service instances.
"""
ports = [port for port in self.neutron_api.
list_ports(device_id='manila-share')]
if len(ports) > 1:
raise exception.ServiceInstanceException(
_('Error. Ambiguous service ports.'))
elif not ports:
host = socket.gethostname()
port = self.neutron_api.create_port(
self.admin_project_id, self.service_network_id,
device_id='manila-share', device_owner='manila:share',
host_id=host)
else:
port = ports[0]
return port
@utils.synchronized(
"service_instance_add_fixed_ips_to_service_port", external=True)
def _add_fixed_ips_to_service_port(self, port):
network = self.neutron_api.get_network(self.service_network_id)
subnets = set(network['subnets'])
port_fixed_ips = []
for fixed_ip in port['fixed_ips']:
port_fixed_ips.append({'subnet_id': fixed_ip['subnet_id'],
'ip_address': fixed_ip['ip_address']})
if fixed_ip['subnet_id'] in subnets:
subnets.remove(fixed_ip['subnet_id'])
# If there are subnets here that means that
# we need to add those to the port and call update.
if subnets:
port_fixed_ips.extend([dict(subnet_id=s) for s in subnets])
port = self.neutron_api.update_port_fixed_ips(
port['id'], {'fixed_ips': port_fixed_ips})
return port
@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(
_('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):
all_service_subnets = self._get_all_service_subnets()
service_subnets = [subnet for subnet in all_service_subnets
if subnet['name'] == subnet_name]
if len(service_subnets) == 1:
return service_subnets[0]
elif not service_subnets:
unused_service_subnets = [subnet for subnet in all_service_subnets
if subnet['name'] == '']
if unused_service_subnets:
service_subnet = unused_service_subnets[0]
self.neutron_api.update_subnet(
service_subnet['id'], subnet_name)
return service_subnet
return None
else:
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