"Fossies" - the Fresh Open Source Software Archive

Member "manila-8.1.3/manila/share/drivers/service_instance.py" (20 Jul 2020, 48275 Bytes) of package /linux/misc/openstack/manila-8.1.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "service_instance.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 8.1.2_vs_8.1.3.

    1 # Copyright (c) 2014 NetApp, Inc.
    2 # Copyright (c) 2015 Mirantis, Inc.
    3 # All Rights Reserved.
    4 #
    5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 #    not use this file except in compliance with the License. You may obtain
    7 #    a copy of the License at
    8 #
    9 #         http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 #    Unless required by applicable law or agreed to in writing, software
   12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 #    License for the specific language governing permissions and limitations
   15 #    under the License.
   16 
   17 """Module for managing nova instances for share drivers."""
   18 
   19 import abc
   20 import os
   21 import socket
   22 import time
   23 
   24 import netaddr
   25 from oslo_config import cfg
   26 from oslo_log import log
   27 from oslo_utils import importutils
   28 from oslo_utils import netutils
   29 import six
   30 
   31 from manila.common import constants as const
   32 from manila import compute
   33 from manila import context
   34 from manila import exception
   35 from manila.i18n import _
   36 from manila.network.linux import ip_lib
   37 from manila.network.neutron import api as neutron
   38 from manila import utils
   39 
   40 LOG = log.getLogger(__name__)
   41 NEUTRON_NAME = "neutron"
   42 
   43 share_servers_handling_mode_opts = [
   44     cfg.StrOpt(
   45         "service_image_name",
   46         default="manila-service-image",
   47         help="Name of image in Glance, that will be used for service instance "
   48              "creation. Only used if driver_handles_share_servers=True."),
   49     cfg.StrOpt(
   50         "service_instance_name_template",
   51         default="manila_service_instance_%s",
   52         help="Name of service instance. "
   53              "Only used if driver_handles_share_servers=True."),
   54     cfg.StrOpt(
   55         "manila_service_keypair_name",
   56         default="manila-service",
   57         help="Keypair name that will be created and used for service "
   58              "instances. Only used if driver_handles_share_servers=True."),
   59     cfg.StrOpt(
   60         "path_to_public_key",
   61         default="~/.ssh/id_rsa.pub",
   62         help="Path to hosts public key. "
   63              "Only used if driver_handles_share_servers=True."),
   64     cfg.StrOpt(
   65         "service_instance_security_group",
   66         default="manila-service",
   67         help="Security group name, that will be used for "
   68              "service instance creation. "
   69              "Only used if driver_handles_share_servers=True."),
   70     cfg.IntOpt(
   71         "service_instance_flavor_id",
   72         default=100,
   73         help="ID of flavor, that will be used for service instance "
   74              "creation. Only used if driver_handles_share_servers=True."),
   75     cfg.StrOpt(
   76         "service_network_name",
   77         default="manila_service_network",
   78         help="Name of manila service network. Used only with Neutron. "
   79              "Only used if driver_handles_share_servers=True."),
   80     cfg.StrOpt(
   81         "service_network_cidr",
   82         default="10.254.0.0/16",
   83         help="CIDR of manila service network. Used only with Neutron and "
   84              "if driver_handles_share_servers=True."),
   85     cfg.IntOpt(
   86         "service_network_division_mask",
   87         default=28,
   88         help="This mask is used for dividing service network into "
   89              "subnets, IP capacity of subnet with this mask directly "
   90              "defines possible amount of created service VMs "
   91              "per tenant's subnet. Used only with Neutron "
   92              "and if driver_handles_share_servers=True."),
   93     cfg.StrOpt(
   94         "interface_driver",
   95         default="manila.network.linux.interface.OVSInterfaceDriver",
   96         help="Vif driver. Used only with Neutron and "
   97              "if driver_handles_share_servers=True."),
   98     cfg.BoolOpt(
   99         "connect_share_server_to_tenant_network",
  100         default=False,
  101         help="Attach share server directly to share network. "
  102              "Used only with Neutron and "
  103              "if driver_handles_share_servers=True."),
  104     cfg.StrOpt(
  105         "admin_network_id",
  106         help="ID of neutron network used to communicate with admin network,"
  107              " to create additional admin export locations on."),
  108     cfg.StrOpt(
  109         "admin_subnet_id",
  110         help="ID of neutron subnet used to communicate with admin network,"
  111              " to create additional admin export locations on. "
  112              "Related to 'admin_network_id'."),
  113 ]
  114 
  115 no_share_servers_handling_mode_opts = [
  116     cfg.StrOpt(
  117         "service_instance_name_or_id",
  118         help="Name or ID of service instance in Nova to use for share "
  119              "exports. Used only when share servers handling is disabled."),
  120     cfg.HostAddressOpt(
  121         "service_net_name_or_ip",
  122         help="Can be either name of network that is used by service "
  123              "instance within Nova to get IP address or IP address itself "
  124              "(either IPv4 or IPv6) for managing shares there. "
  125              "Used only when share servers handling is disabled."),
  126     cfg.HostAddressOpt(
  127         "tenant_net_name_or_ip",
  128         help="Can be either name of network that is used by service "
  129              "instance within Nova to get IP address or IP address itself "
  130              "(either IPv4 or IPv6) for exporting shares. "
  131              "Used only when share servers handling is disabled."),
  132 ]
  133 
  134 common_opts = [
  135     cfg.StrOpt(
  136         "service_instance_user",
  137         help="User in service instance that will be used for authentication."),
  138     cfg.StrOpt(
  139         "service_instance_password",
  140         secret=True,
  141         help="Password for service instance user."),
  142     cfg.StrOpt(
  143         "path_to_private_key",
  144         help="Path to host's private key."),
  145     cfg.IntOpt(
  146         "max_time_to_build_instance",
  147         default=300,
  148         help="Maximum time in seconds to wait for creating service instance."),
  149     cfg.BoolOpt(
  150         "limit_ssh_access",
  151         default=False,
  152         help="Block SSH connection to the service instance from other "
  153              "networks than service network."),
  154 ]
  155 
  156 CONF = cfg.CONF
  157 
  158 
  159 class ServiceInstanceManager(object):
  160     """Manages nova instances for various share drivers.
  161 
  162     This class provides following external methods:
  163 
  164     1. set_up_service_instance: creates instance and sets up share
  165        infrastructure.
  166     2. ensure_service_instance: ensure service instance is available.
  167     3. delete_service_instance: removes service instance and network
  168        infrastructure.
  169     """
  170     _INSTANCE_CONNECTION_PROTO = "SSH"
  171 
  172     def get_config_option(self, key):
  173         """Returns value of config option.
  174 
  175         :param key: key of config' option.
  176         :returns: str -- value of config's option.
  177                   first priority is driver's config,
  178                   second priority is global config.
  179         """
  180         if self.driver_config:
  181             return self.driver_config.safe_get(key)
  182         return CONF.get(key)
  183 
  184     def _get_network_helper(self):
  185         # Historically, there were multiple types of network helper,
  186         # but currently the only network helper type is Neutron.
  187         return NeutronNetworkHelper(self)
  188 
  189     def __init__(self, driver_config=None):
  190 
  191         super(ServiceInstanceManager, self).__init__()
  192         self.driver_config = driver_config
  193 
  194         if self.driver_config:
  195             self.driver_config.append_config_values(common_opts)
  196             if self.get_config_option("driver_handles_share_servers"):
  197                 self.driver_config.append_config_values(
  198                     share_servers_handling_mode_opts)
  199             else:
  200                 self.driver_config.append_config_values(
  201                     no_share_servers_handling_mode_opts)
  202         else:
  203             CONF.register_opts(common_opts)
  204             if self.get_config_option("driver_handles_share_servers"):
  205                 CONF.register_opts(share_servers_handling_mode_opts)
  206             else:
  207                 CONF.register_opts(no_share_servers_handling_mode_opts)
  208 
  209         if not self.get_config_option("service_instance_user"):
  210             raise exception.ServiceInstanceException(
  211                 _('Service instance user is not specified.'))
  212         self.admin_context = context.get_admin_context()
  213         self._execute = utils.execute
  214 
  215         self.compute_api = compute.API()
  216 
  217         self.path_to_private_key = self.get_config_option(
  218             "path_to_private_key")
  219         self.max_time_to_build_instance = self.get_config_option(
  220             "max_time_to_build_instance")
  221 
  222         self.availability_zone = self.get_config_option(
  223             'backend_availability_zone') or CONF.storage_availability_zone
  224 
  225         if self.get_config_option("driver_handles_share_servers"):
  226             self.path_to_public_key = self.get_config_option(
  227                 "path_to_public_key")
  228             self._network_helper = None
  229 
  230     @property
  231     @utils.synchronized("instantiate_network_helper")
  232     def network_helper(self):
  233         if not self._network_helper:
  234             self._network_helper = self._get_network_helper()
  235             self._network_helper.setup_connectivity_with_service_instances()
  236         return self._network_helper
  237 
  238     def get_common_server(self):
  239         data = {
  240             'public_address': None,
  241             'private_address': None,
  242             'service_net_name_or_ip': self.get_config_option(
  243                 'service_net_name_or_ip'),
  244             'tenant_net_name_or_ip': self.get_config_option(
  245                 'tenant_net_name_or_ip'),
  246         }
  247 
  248         data['instance'] = self.compute_api.server_get_by_name_or_id(
  249             self.admin_context,
  250             self.get_config_option('service_instance_name_or_id'))
  251 
  252         if netutils.is_valid_ip(data['service_net_name_or_ip']):
  253             data['private_address'] = [data['service_net_name_or_ip']]
  254         else:
  255             data['private_address'] = self._get_addresses_by_network_name(
  256                 data['service_net_name_or_ip'], data['instance'])
  257 
  258         if netutils.is_valid_ip(data['tenant_net_name_or_ip']):
  259             data['public_address'] = [data['tenant_net_name_or_ip']]
  260         else:
  261             data['public_address'] = self._get_addresses_by_network_name(
  262                 data['tenant_net_name_or_ip'], data['instance'])
  263 
  264         if not (data['public_address'] and data['private_address']):
  265             raise exception.ManilaException(
  266                 "Can not find one of net addresses for service instance. "
  267                 "Instance: %(instance)s, "
  268                 "private_address: %(private_address)s, "
  269                 "public_address: %(public_address)s." % data)
  270 
  271         share_server = {
  272             'username': self.get_config_option('service_instance_user'),
  273             'password': self.get_config_option('service_instance_password'),
  274             'pk_path': self.path_to_private_key,
  275             'instance_id': data['instance']['id'],
  276         }
  277         for key in ('private_address', 'public_address'):
  278             data[key + '_first'] = None
  279             for address in data[key]:
  280                 if netutils.is_valid_ip(address):
  281                     data[key + '_first'] = address
  282                     break
  283         share_server['ip'] = data['private_address_first']
  284         share_server['public_address'] = data['public_address_first']
  285         return {'backend_details': share_server}
  286 
  287     def _get_addresses_by_network_name(self, net_name, server):
  288         net_ips = []
  289         if 'networks' in server and net_name in server['networks']:
  290             net_ips = server['networks'][net_name]
  291         elif 'addresses' in server and net_name in server['addresses']:
  292             net_ips = [addr['addr'] for addr in server['addresses'][net_name]]
  293         return net_ips
  294 
  295     def _get_service_instance_name(self, share_server_id):
  296         """Returns service vms name."""
  297         if self.driver_config:
  298             # Make service instance name unique for multibackend installation
  299             name = "%s_%s" % (self.driver_config.config_group, share_server_id)
  300         else:
  301             name = share_server_id
  302         return self.get_config_option("service_instance_name_template") % name
  303 
  304     def _get_server_ip(self, server, net_name):
  305         """Returns service IP address of service instance."""
  306         net_ips = self._get_addresses_by_network_name(net_name, server)
  307         if not net_ips:
  308             msg = _("Failed to get service instance IP address. "
  309                     "Service network name is '%(net_name)s' "
  310                     "and provided data are '%(data)s'.")
  311             msg = msg % {'net_name': net_name, 'data': six.text_type(server)}
  312             raise exception.ServiceInstanceException(msg)
  313         return net_ips[0]
  314 
  315     def _get_or_create_security_groups(self, context, name=None,
  316                                        description=None,
  317                                        allow_ssh_subnet=False):
  318         """Get or create security group for service_instance.
  319 
  320         :param context: context, that should be used
  321         :param name: this is used for selection/creation of sec.group
  322         :param description: this is used on sec.group creation step only
  323         :param allow_ssh_subnet: subnet details to allow ssh connection from,
  324          if not supplied ssh will be allowed from any host
  325         :returns: SecurityGroup -- security group instance from Nova
  326         :raises: exception.ServiceInstanceException.
  327         """
  328 
  329         sgs = []
  330         # Common security group
  331         name = name or self.get_config_option(
  332             "service_instance_security_group")
  333         if not name:
  334             LOG.warning("Name for service instance security group is not "
  335                         "provided. Skipping security group step.")
  336             return None
  337         if not description:
  338             description = ("This security group is intended "
  339                            "to be used by share service.")
  340         sec_group_data = const.SERVICE_INSTANCE_SECGROUP_DATA
  341         if not allow_ssh_subnet:
  342             sec_group_data += const.SSH_PORTS
  343 
  344         sgs.append(self._get_or_create_security_group(name, description,
  345                                                       sec_group_data))
  346         if allow_ssh_subnet:
  347             if "cidr" not in allow_ssh_subnet or 'id' not in allow_ssh_subnet:
  348                 raise exception.ManilaException(
  349                     "Unable to limit SSH access")
  350             ssh_sg_name = "manila-service-subnet-{}".format(
  351                 allow_ssh_subnet["id"])
  352             sgs.append(self._get_or_create_security_group(
  353                 ssh_sg_name, description,
  354                 const.SSH_PORTS, allow_ssh_subnet["cidr"]))
  355         return sgs
  356 
  357     @utils.synchronized(
  358         "service_instance_get_or_create_security_group", external=True)
  359     def _get_or_create_security_group(self, name,
  360                                       description, sec_group_data,
  361                                       cidr="0.0.0.0/0"):
  362         s_groups = self.network_helper.neutron_api.security_group_list({
  363             "name": name,
  364         })['security_groups']
  365         s_groups = [s for s in s_groups if s['name'] == name]
  366         if not s_groups:
  367             LOG.debug("Creating security group with name '%s'.", name)
  368             sg = self.network_helper.neutron_api.security_group_create(
  369                 name, description)['security_group']
  370             for protocol, ports in sec_group_data:
  371                 self.network_helper.neutron_api.security_group_rule_create(
  372                     parent_group_id=sg['id'],
  373                     ip_protocol=protocol,
  374                     from_port=ports[0],
  375                     to_port=ports[1],
  376                     cidr=cidr,
  377                 )
  378         elif len(s_groups) > 1:
  379             msg = _("Ambiguous security_groups.")
  380             raise exception.ServiceInstanceException(msg)
  381         else:
  382             sg = s_groups[0]
  383         return sg
  384 
  385     def ensure_service_instance(self, context, server):
  386         """Ensures that server exists and active."""
  387         if 'instance_id' not in server:
  388             LOG.warning("Unable to check server existence since "
  389                         "'instance_id' key is not set in share server "
  390                         "backend details.")
  391             return False
  392         try:
  393             inst = self.compute_api.server_get(self.admin_context,
  394                                                server['instance_id'])
  395         except exception.InstanceNotFound:
  396             LOG.warning("Service instance %s does not exist.",
  397                         server['instance_id'])
  398             return False
  399         if inst['status'] == 'ACTIVE':
  400             return self._check_server_availability(server)
  401         return False
  402 
  403     def _delete_server(self, context, server_id):
  404         """Deletes the server."""
  405         try:
  406             self.compute_api.server_get(context, server_id)
  407         except exception.InstanceNotFound:
  408             LOG.debug("Service instance '%s' was not found. "
  409                       "Nothing to delete, skipping.", server_id)
  410             return
  411 
  412         self.compute_api.server_delete(context, server_id)
  413 
  414         t = time.time()
  415         while time.time() - t < self.max_time_to_build_instance:
  416             try:
  417                 self.compute_api.server_get(context, server_id)
  418             except exception.InstanceNotFound:
  419                 LOG.debug("Service instance '%s' was deleted "
  420                           "successfully.", server_id)
  421                 break
  422             time.sleep(2)
  423         else:
  424             raise exception.ServiceInstanceException(
  425                 _("Instance '%(id)s' has not been deleted in %(s)ss. "
  426                   "Giving up.") % {
  427                       'id': server_id, 's': self.max_time_to_build_instance})
  428 
  429     def set_up_service_instance(self, context, network_info):
  430         """Finds or creates and sets up service vm.
  431 
  432         :param context: defines context, that should be used
  433         :param network_info: network info for getting allocations
  434         :returns: dict with service instance details
  435         :raises: exception.ServiceInstanceException
  436         """
  437         instance_name = network_info['server_id']
  438         server = self._create_service_instance(
  439             context, instance_name, network_info)
  440         instance_details = self._get_new_instance_details(server)
  441 
  442         if not self._check_server_availability(instance_details):
  443             e = exception.ServiceInstanceException(
  444                 _('%(conn_proto)s connection has not been '
  445                   'established to %(server)s in %(time)ss. Giving up.') % {
  446                       'conn_proto': self._INSTANCE_CONNECTION_PROTO,
  447                       'server': server['ip'],
  448                       'time': self.max_time_to_build_instance})
  449             e.detail_data = {'server_details': instance_details}
  450             raise e
  451 
  452         return instance_details
  453 
  454     def _get_new_instance_details(self, server):
  455         instance_details = {
  456             'instance_id': server['id'],
  457             'ip': server['ip'],
  458             'pk_path': server.get('pk_path'),
  459             'subnet_id': server.get('subnet_id'),
  460             'password': self.get_config_option('service_instance_password'),
  461             'username': self.get_config_option('service_instance_user'),
  462             'public_address': server['public_address'],
  463         }
  464         if server.get('admin_ip'):
  465             instance_details['admin_ip'] = server['admin_ip']
  466         if server.get('router_id'):
  467             instance_details['router_id'] = server['router_id']
  468         if server.get('service_port_id'):
  469             instance_details['service_port_id'] = server['service_port_id']
  470         if server.get('public_port_id'):
  471             instance_details['public_port_id'] = server['public_port_id']
  472         if server.get('admin_port_id'):
  473             instance_details['admin_port_id'] = server['admin_port_id']
  474 
  475         for key in ('password', 'pk_path', 'subnet_id'):
  476             if not instance_details[key]:
  477                 instance_details.pop(key)
  478         return instance_details
  479 
  480     @utils.synchronized("service_instance_get_key", external=True)
  481     def _get_key(self, context):
  482         """Get ssh key.
  483 
  484         :param context: defines context, that should be used
  485         :returns: tuple with keypair name and path to private key.
  486         """
  487         if not (self.path_to_public_key and self.path_to_private_key):
  488             return (None, None)
  489         path_to_public_key = os.path.expanduser(self.path_to_public_key)
  490         path_to_private_key = os.path.expanduser(self.path_to_private_key)
  491         if (not os.path.exists(path_to_public_key) or
  492                 not os.path.exists(path_to_private_key)):
  493             return (None, None)
  494         keypair_name = self.get_config_option("manila_service_keypair_name")
  495         keypairs = [k for k in self.compute_api.keypair_list(context)
  496                     if k.name == keypair_name]
  497         if len(keypairs) > 1:
  498             raise exception.ServiceInstanceException(_('Ambiguous keypairs.'))
  499 
  500         public_key, __ = self._execute('cat', path_to_public_key)
  501         if not keypairs:
  502             keypair = self.compute_api.keypair_import(
  503                 context, keypair_name, public_key)
  504         else:
  505             keypair = keypairs[0]
  506             if keypair.public_key != public_key:
  507                 LOG.debug('Public key differs from existing keypair. '
  508                           'Creating new keypair.')
  509                 self.compute_api.keypair_delete(context, keypair.id)
  510                 keypair = self.compute_api.keypair_import(
  511                     context, keypair_name, public_key)
  512 
  513         return keypair.name, path_to_private_key
  514 
  515     def _get_service_image(self, context):
  516         """Returns ID of service image for service vm creating."""
  517         service_image_name = self.get_config_option("service_image_name")
  518         images = [image.id for image in self.compute_api.image_list(context)
  519                   if image.name == service_image_name
  520                   and image.status == 'active']
  521         if len(images) == 1:
  522             return images[0]
  523         elif not images:
  524             raise exception.ServiceInstanceException(
  525                 _("Image with name '%s' was not found or is not in "
  526                   "'active' state.") % service_image_name)
  527         else:
  528             raise exception.ServiceInstanceException(
  529                 _("Found more than one image by name '%s'.") %
  530                 service_image_name)
  531 
  532     def _create_service_instance(self, context, instance_name, network_info):
  533         """Creates service vm and sets up networking for it."""
  534         service_image_id = self._get_service_image(context)
  535         key_name, key_path = self._get_key(context)
  536         if not (self.get_config_option("service_instance_password") or
  537                 key_name):
  538             raise exception.ServiceInstanceException(
  539                 _('Neither service instance password nor key are available.'))
  540         if not key_path:
  541             LOG.warning(
  542                 'No key path is available. May be non-existent key path is '
  543                 'provided. Check path_to_private_key (current value '
  544                 '%(private_path)s) and path_to_public_key (current value '
  545                 '%(public_path)s) in manila configuration file.', dict(
  546                     private_path=self.path_to_private_key,
  547                     public_path=self.path_to_public_key))
  548         network_data = self.network_helper.setup_network(network_info)
  549         fail_safe_data = dict(
  550             router_id=network_data.get('router_id'),
  551             subnet_id=network_data.get('subnet_id'))
  552         if network_data.get('service_port'):
  553             fail_safe_data['service_port_id'] = (
  554                 network_data['service_port']['id'])
  555         if network_data.get('public_port'):
  556             fail_safe_data['public_port_id'] = (
  557                 network_data['public_port']['id'])
  558         if network_data.get('admin_port'):
  559             fail_safe_data['admin_port_id'] = (
  560                 network_data['admin_port']['id'])
  561         try:
  562             create_kwargs = self._get_service_instance_create_kwargs()
  563             service_instance = self.compute_api.server_create(
  564                 context,
  565                 name=instance_name,
  566                 image=service_image_id,
  567                 flavor=self.get_config_option("service_instance_flavor_id"),
  568                 key_name=key_name,
  569                 nics=network_data['nics'],
  570                 availability_zone=self.availability_zone,
  571                 **create_kwargs)
  572 
  573             fail_safe_data['instance_id'] = service_instance['id']
  574 
  575             service_instance = self.wait_for_instance_to_be_active(
  576                 service_instance['id'],
  577                 self.max_time_to_build_instance)
  578 
  579             if self.get_config_option("limit_ssh_access"):
  580                 try:
  581                     service_subnet = network_data['service_subnet']
  582                 except KeyError:
  583                     LOG.error(
  584                         "Unable to limit ssh access to instance id: '%s'!",
  585                         fail_safe_data['instance_id'])
  586                     raise exception.ManilaException(
  587                         "Unable to limit SSH access - "
  588                         "invalid service subnet details provided")
  589             else:
  590                 service_subnet = False
  591 
  592             sec_groups = self._get_or_create_security_groups(
  593                 context, allow_ssh_subnet=service_subnet)
  594 
  595             for sg in sec_groups:
  596                 sg_id = sg['id']
  597                 LOG.debug(
  598                     "Adding security group '%(sg)s' to server '%(si)s'.",
  599                     dict(sg=sg_id, si=service_instance["id"]))
  600                 self.compute_api.add_security_group_to_server(
  601                     context, service_instance["id"], sg_id)
  602 
  603             ip = (network_data.get('service_port',
  604                                    network_data.get(
  605                                        'admin_port'))['fixed_ips'])
  606             service_instance['ip'] = ip[0]['ip_address']
  607             public_ip = (network_data.get('public_port', network_data.get(
  608                 'service_port'))['fixed_ips'])
  609             service_instance['public_address'] = public_ip[0]['ip_address']
  610 
  611         except Exception as e:
  612             e.detail_data = {'server_details': fail_safe_data}
  613             raise
  614 
  615         service_instance.update(fail_safe_data)
  616         service_instance['pk_path'] = key_path
  617         for pair in [('router', 'router_id'), ('service_subnet', 'subnet_id')]:
  618             if pair[0] in network_data and 'id' in network_data[pair[0]]:
  619                 service_instance[pair[1]] = network_data[pair[0]]['id']
  620 
  621         admin_port = network_data.get('admin_port')
  622         if admin_port:
  623             try:
  624                 service_instance['admin_ip'] = (
  625                     admin_port['fixed_ips'][0]['ip_address'])
  626             except Exception:
  627                 msg = _("Admin port is being used but Admin IP was not found.")
  628                 LOG.exception(msg)
  629                 raise exception.AdminIPNotFound(reason=msg)
  630 
  631         return service_instance
  632 
  633     def _get_service_instance_create_kwargs(self):
  634         """Specify extra arguments used when creating the service instance.
  635 
  636         Classes inheriting the service instance manager can use this to easily
  637         pass extra arguments such as user data or metadata.
  638         """
  639         return {}
  640 
  641     def _check_server_availability(self, instance_details):
  642         t = time.time()
  643         while time.time() - t < self.max_time_to_build_instance:
  644             LOG.debug('Checking server availability.')
  645             if not self._test_server_connection(instance_details):
  646                 time.sleep(5)
  647             else:
  648                 return True
  649         return False
  650 
  651     def _test_server_connection(self, server):
  652         try:
  653             socket.socket().connect((server['ip'], 22))
  654             LOG.debug('Server %s is available via SSH.',
  655                       server['ip'])
  656             return True
  657         except socket.error as e:
  658             LOG.debug(e)
  659             LOG.debug("Server %s is not available via SSH. Waiting...",
  660                       server['ip'])
  661             return False
  662 
  663     def delete_service_instance(self, context, server_details):
  664         """Removes share infrastructure.
  665 
  666         Deletes service vm and subnet, associated to share network.
  667         """
  668         instance_id = server_details.get("instance_id")
  669         self._delete_server(context, instance_id)
  670         self.network_helper.teardown_network(server_details)
  671 
  672     def wait_for_instance_to_be_active(self, instance_id, timeout):
  673         t = time.time()
  674         while time.time() - t < timeout:
  675             try:
  676                 service_instance = self.compute_api.server_get(
  677                     self.admin_context,
  678                     instance_id)
  679             except exception.InstanceNotFound as e:
  680                 LOG.debug(e)
  681                 time.sleep(1)
  682                 continue
  683 
  684             instance_status = service_instance['status']
  685             # NOTE(vponomaryov): emptiness of 'networks' field checked as
  686             #                    workaround for nova/neutron bug #1210483.
  687             if (instance_status == 'ACTIVE' and
  688                     service_instance.get('networks', {})):
  689                 return service_instance
  690             elif service_instance['status'] == 'ERROR':
  691                 break
  692 
  693             LOG.debug("Waiting for instance %(instance_id)s to be active. "
  694                       "Current status: %(instance_status)s.",
  695                       dict(instance_id=instance_id,
  696                            instance_status=instance_status))
  697             time.sleep(1)
  698         raise exception.ServiceInstanceException(
  699             _("Instance %(instance_id)s failed to reach active state "
  700               "in %(timeout)s seconds. "
  701               "Current status: %(instance_status)s.") %
  702             dict(instance_id=instance_id,
  703                  timeout=timeout,
  704                  instance_status=instance_status))
  705 
  706     def reboot_server(self, server, soft_reboot=False):
  707         self.compute_api.server_reboot(self.admin_context,
  708                                        server['instance_id'],
  709                                        soft_reboot)
  710 
  711 
  712 @six.add_metaclass(abc.ABCMeta)
  713 class BaseNetworkhelper(object):
  714 
  715     @abc.abstractproperty
  716     def NAME(self):
  717         """Returns code name of network helper."""
  718 
  719     @abc.abstractmethod
  720     def __init__(self, service_instance_manager):
  721         """Instantiates class and its attrs."""
  722 
  723     @abc.abstractmethod
  724     def get_network_name(self, network_info):
  725         """Returns name of network for service instance."""
  726 
  727     @abc.abstractmethod
  728     def setup_connectivity_with_service_instances(self):
  729         """Sets up connectivity between Manila host and service instances."""
  730 
  731     @abc.abstractmethod
  732     def setup_network(self, network_info):
  733         """Sets up network for service instance."""
  734 
  735     @abc.abstractmethod
  736     def teardown_network(self, server_details):
  737         """Teardowns network resources provided for service instance."""
  738 
  739 
  740 class NeutronNetworkHelper(BaseNetworkhelper):
  741 
  742     def __init__(self, service_instance_manager):
  743         self.get_config_option = service_instance_manager.get_config_option
  744         self.vif_driver = importutils.import_class(
  745             self.get_config_option("interface_driver"))()
  746 
  747         if service_instance_manager.driver_config:
  748             self._network_config_group = (
  749                 service_instance_manager.driver_config.network_config_group or
  750                 service_instance_manager.driver_config.config_group)
  751         else:
  752             self._network_config_group = None
  753 
  754         self.use_admin_port = False
  755         self.use_service_network = True
  756         self._neutron_api = None
  757         self._service_network_id = None
  758         self.connect_share_server_to_tenant_network = (
  759             self.get_config_option('connect_share_server_to_tenant_network'))
  760 
  761         self.admin_network_id = self.get_config_option('admin_network_id')
  762         self.admin_subnet_id = self.get_config_option('admin_subnet_id')
  763 
  764         if self.admin_network_id and self.admin_subnet_id:
  765             self.use_admin_port = True
  766         if self.use_admin_port and self.connect_share_server_to_tenant_network:
  767             self.use_service_network = False
  768 
  769     @property
  770     def NAME(self):
  771         return NEUTRON_NAME
  772 
  773     @property
  774     def admin_project_id(self):
  775         return self.neutron_api.admin_project_id
  776 
  777     @property
  778     @utils.synchronized("instantiate_neutron_api_neutron_net_helper")
  779     def neutron_api(self):
  780         if not self._neutron_api:
  781             self._neutron_api = neutron.API(
  782                 config_group_name=self._network_config_group)
  783         return self._neutron_api
  784 
  785     @property
  786     @utils.synchronized("service_network_id_neutron_net_helper")
  787     def service_network_id(self):
  788         if not self._service_network_id:
  789             self._service_network_id = self._get_service_network_id()
  790         return self._service_network_id
  791 
  792     def get_network_name(self, network_info):
  793         """Returns name of network for service instance."""
  794         net = self.neutron_api.get_network(network_info['neutron_net_id'])
  795         return net['name']
  796 
  797     @utils.synchronized("service_instance_get_service_network", external=True)
  798     def _get_service_network_id(self):
  799         """Finds existing or creates new service network."""
  800         service_network_name = self.get_config_option("service_network_name")
  801         networks = []
  802         for network in self.neutron_api.get_all_admin_project_networks():
  803             if network['name'] == service_network_name:
  804                 networks.append(network)
  805         if len(networks) > 1:
  806             raise exception.ServiceInstanceException(
  807                 _('Ambiguous service networks.'))
  808         elif not networks:
  809             return self.neutron_api.network_create(
  810                 self.admin_project_id, service_network_name)['id']
  811         else:
  812             return networks[0]['id']
  813 
  814     @utils.synchronized(
  815         "service_instance_setup_and_teardown_network_for_instance",
  816         external=True)
  817     def teardown_network(self, server_details):
  818         subnet_id = server_details.get("subnet_id")
  819         router_id = server_details.get("router_id")
  820 
  821         service_port_id = server_details.get("service_port_id")
  822         public_port_id = server_details.get("public_port_id")
  823         admin_port_id = server_details.get("admin_port_id")
  824         for port_id in (service_port_id, public_port_id, admin_port_id):
  825             if port_id:
  826                 try:
  827                     self.neutron_api.delete_port(port_id)
  828                 except exception.NetworkException as e:
  829                     if e.kwargs.get('code') != 404:
  830                         raise
  831                     LOG.debug("Failed to delete port %(port_id)s with error: "
  832                               "\n %(exc)s", {"port_id": port_id, "exc": e})
  833 
  834         if router_id and subnet_id:
  835             ports = self.neutron_api.list_ports(
  836                 fields=['device_id', 'device_owner'],
  837                 fixed_ips=['subnet_id=%s' % subnet_id])
  838             # NOTE(vponomaryov): iterate ports to get to know whether current
  839             # subnet is used or not. We will not remove it from router if it
  840             # is used.
  841             for port in ports:
  842                 # NOTE(vponomaryov): if device_id is present, then we know that
  843                 # this port is used. Also, if device owner is 'compute:*', then
  844                 # we know that it is VM. We continue only if both are 'True'.
  845                 if (port['device_id'] and
  846                         port['device_owner'].startswith('compute:')):
  847                     # NOTE(vponomaryov): There are other share servers
  848                     # exist that use this subnet. So, do not remove it
  849                     # from router.
  850                     return
  851             try:
  852                 # NOTE(vponomaryov): there is no other share servers or
  853                 # some VMs that use this subnet. So, remove it from router.
  854                 self.neutron_api.router_remove_interface(
  855                     router_id, subnet_id)
  856             except exception.NetworkException as e:
  857                 if e.kwargs['code'] != 404:
  858                     raise
  859                 LOG.debug('Subnet %(subnet_id)s is not attached to the '
  860                           'router %(router_id)s.',
  861                           {'subnet_id': subnet_id, 'router_id': router_id})
  862             self.neutron_api.update_subnet(subnet_id, '')
  863 
  864     @utils.synchronized(
  865         "service_instance_setup_and_teardown_network_for_instance",
  866         external=True)
  867     def setup_network(self, network_info):
  868         neutron_net_id = network_info['neutron_net_id']
  869         neutron_subnet_id = network_info['neutron_subnet_id']
  870         network_data = dict()
  871         subnet_name = ('service_subnet_for_handling_of_share_server_for_'
  872                        'tenant_subnet_%s' % neutron_subnet_id)
  873 
  874         if self.use_service_network:
  875             network_data['service_subnet'] = self._get_service_subnet(
  876                 subnet_name)
  877             if not network_data['service_subnet']:
  878                 network_data['service_subnet'] = (
  879                     self.neutron_api.subnet_create(
  880                         self.admin_project_id, self.service_network_id,
  881                         subnet_name, self._get_cidr_for_subnet()))
  882 
  883         network_data['ports'] = []
  884 
  885         if not self.connect_share_server_to_tenant_network:
  886             network_data['router'] = self._get_private_router(
  887                 neutron_net_id, neutron_subnet_id)
  888             try:
  889                 self.neutron_api.router_add_interface(
  890                     network_data['router']['id'],
  891                     network_data['service_subnet']['id'])
  892             except exception.NetworkException as e:
  893                 if e.kwargs['code'] != 400:
  894                     raise
  895                 LOG.debug('Subnet %(subnet_id)s is already attached to the '
  896                           'router %(router_id)s.',
  897                           {'subnet_id': network_data['service_subnet']['id'],
  898                            'router_id': network_data['router']['id']})
  899         else:
  900             network_data['public_port'] = self.neutron_api.create_port(
  901                 self.admin_project_id, neutron_net_id,
  902                 subnet_id=neutron_subnet_id, device_owner='manila')
  903             network_data['ports'].append(network_data['public_port'])
  904 
  905         if self.use_service_network:
  906             network_data['service_port'] = self.neutron_api.create_port(
  907                 self.admin_project_id, self.service_network_id,
  908                 subnet_id=network_data['service_subnet']['id'],
  909                 device_owner='manila')
  910             network_data['ports'].append(network_data['service_port'])
  911 
  912         if self.use_admin_port:
  913             network_data['admin_port'] = self.neutron_api.create_port(
  914                 self.admin_project_id, self.admin_network_id,
  915                 subnet_id=self.admin_subnet_id, device_owner='manila')
  916             network_data['ports'].append(network_data['admin_port'])
  917 
  918         try:
  919             self.setup_connectivity_with_service_instances()
  920         except Exception:
  921             for port in network_data['ports']:
  922                 self.neutron_api.delete_port(port['id'])
  923             raise
  924 
  925         network_data['nics'] = [
  926             {'port-id': port['id']} for port in network_data['ports']]
  927         public_ip = network_data.get(
  928             'public_port', network_data.get('service_port'))
  929         network_data['ip_address'] = public_ip['fixed_ips'][0]['ip_address']
  930 
  931         return network_data
  932 
  933     def _get_cidr_for_subnet(self):
  934         """Returns not used cidr for service subnet creating."""
  935         subnets = self._get_all_service_subnets()
  936         used_cidrs = set(subnet['cidr'] for subnet in subnets)
  937         serv_cidr = netaddr.IPNetwork(
  938             self.get_config_option("service_network_cidr"))
  939         division_mask = self.get_config_option("service_network_division_mask")
  940         for subnet in serv_cidr.subnet(division_mask):
  941             cidr = six.text_type(subnet.cidr)
  942             if cidr not in used_cidrs:
  943                 return cidr
  944         else:
  945             raise exception.ServiceInstanceException(_('No available cidrs.'))
  946 
  947     def setup_connectivity_with_service_instances(self):
  948         """Sets up connectivity with service instances.
  949 
  950         Creates host port in service network and/or admin network, creating
  951         and setting up required network devices.
  952         """
  953         if self.use_service_network:
  954             LOG.debug("Plugging service instance into service network %s.",
  955                       self.service_network_id)
  956             port = self._get_service_port(
  957                 self.service_network_id, None, 'manila-share')
  958             port = self._add_fixed_ips_to_service_port(port)
  959             interface_name = self.vif_driver.get_device_name(port)
  960             device = ip_lib.IPDevice(interface_name)
  961             self._plug_interface_in_host(interface_name, device, port)
  962 
  963         if self.use_admin_port:
  964             LOG.debug("Plugging service instance into admin network %s.",
  965                       self.admin_network_id)
  966             port = self._get_service_port(
  967                 self.admin_network_id, self.admin_subnet_id,
  968                 'manila-admin-share')
  969             interface_name = self.vif_driver.get_device_name(port)
  970             device = ip_lib.IPDevice(interface_name)
  971             for fixed_ip in port['fixed_ips']:
  972                 subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id'])
  973                 device.route.clear_outdated_routes(subnet['cidr'])
  974             self._plug_interface_in_host(interface_name, device, port)
  975 
  976     @utils.synchronized("service_instance_plug_interface_in_host",
  977                         external=True)
  978     def _plug_interface_in_host(self, interface_name, device, port):
  979 
  980         LOG.debug("Plug interface into host - interface_name: %s, "
  981                   "device: %s, port: %s", interface_name, device, port)
  982         self.vif_driver.plug(interface_name, port['id'], port['mac_address'])
  983         ip_cidrs = []
  984         for fixed_ip in port['fixed_ips']:
  985             subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id'])
  986             net = netaddr.IPNetwork(subnet['cidr'])
  987             ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen)
  988             ip_cidrs.append(ip_cidr)
  989 
  990         self.vif_driver.init_l3(interface_name, ip_cidrs)
  991 
  992         # ensure that interface is first in the list
  993         device.route.pullup_route(interface_name)
  994 
  995         # here we are checking for garbage devices from removed service port
  996         self._remove_outdated_interfaces(device)
  997 
  998     def _remove_outdated_interfaces(self, device):
  999         """Finds and removes unused network device."""
 1000         device_cidr_set = self._get_set_of_device_cidrs(device)
 1001         for dev in ip_lib.IPWrapper().get_devices():
 1002             if dev.name != device.name and dev.name[:3] == device.name[:3]:
 1003                 cidr_set = self._get_set_of_device_cidrs(dev)
 1004                 if device_cidr_set & cidr_set:
 1005                     self.vif_driver.unplug(dev.name)
 1006 
 1007     def _get_set_of_device_cidrs(self, device):
 1008         cidrs = set()
 1009         addr_list = []
 1010         try:
 1011             # NOTE(ganso): I could call ip_lib.device_exists here, but since
 1012             # this is a concurrency problem, it would not fix the problem.
 1013             addr_list = device.addr.list()
 1014         except Exception as e:
 1015             if 'does not exist' in six.text_type(e):
 1016                 LOG.warning(
 1017                     "Device %s does not exist anymore.", device.name)
 1018             else:
 1019                 raise
 1020         for addr in addr_list:
 1021             if addr['ip_version'] == 4:
 1022                 cidrs.add(six.text_type(netaddr.IPNetwork(addr['cidr']).cidr))
 1023         return cidrs
 1024 
 1025     @utils.synchronized("service_instance_get_service_port", external=True)
 1026     def _get_service_port(self, network_id, subnet_id, device_id):
 1027         """Find or creates service neutron port.
 1028 
 1029         This port will be used for connectivity with service instances.
 1030         """
 1031         host = socket.gethostname()
 1032         search_opts = {'device_id': device_id,
 1033                        'binding:host_id': host}
 1034         ports = [port for port in self.neutron_api.
 1035                  list_ports(**search_opts)]
 1036         if len(ports) > 1:
 1037             raise exception.ServiceInstanceException(
 1038                 _('Error. Ambiguous service ports.'))
 1039         elif not ports:
 1040             port = self.neutron_api.create_port(
 1041                 self.admin_project_id, network_id, subnet_id=subnet_id,
 1042                 device_id=device_id, device_owner='manila:share', host_id=host,
 1043                 port_security_enabled=False)
 1044         else:
 1045             port = ports[0]
 1046         return port
 1047 
 1048     @utils.synchronized(
 1049         "service_instance_add_fixed_ips_to_service_port", external=True)
 1050     def _add_fixed_ips_to_service_port(self, port):
 1051         network = self.neutron_api.get_network(self.service_network_id)
 1052         subnets = set(network['subnets'])
 1053         port_fixed_ips = []
 1054         for fixed_ip in port['fixed_ips']:
 1055             port_fixed_ips.append({'subnet_id': fixed_ip['subnet_id'],
 1056                                    'ip_address': fixed_ip['ip_address']})
 1057             if fixed_ip['subnet_id'] in subnets:
 1058                 subnets.remove(fixed_ip['subnet_id'])
 1059 
 1060         # If there are subnets here that means that
 1061         # we need to add those to the port and call update.
 1062         if subnets:
 1063             port_fixed_ips.extend([dict(subnet_id=s) for s in subnets])
 1064             port = self.neutron_api.update_port_fixed_ips(
 1065                 port['id'], {'fixed_ips': port_fixed_ips})
 1066 
 1067         return port
 1068 
 1069     @utils.synchronized("service_instance_get_private_router", external=True)
 1070     def _get_private_router(self, neutron_net_id, neutron_subnet_id):
 1071         """Returns router attached to private subnet gateway."""
 1072         private_subnet = self.neutron_api.get_subnet(neutron_subnet_id)
 1073         if not private_subnet['gateway_ip']:
 1074             raise exception.ServiceInstanceException(
 1075                 _('Subnet must have gateway.'))
 1076         private_network_ports = [p for p in self.neutron_api.list_ports(
 1077                                  network_id=neutron_net_id)]
 1078         for p in private_network_ports:
 1079             fixed_ip = p['fixed_ips'][0]
 1080             if (fixed_ip['subnet_id'] == private_subnet['id'] and
 1081                     fixed_ip['ip_address'] == private_subnet['gateway_ip']):
 1082                 private_subnet_gateway_port = p
 1083                 break
 1084         else:
 1085             raise exception.ServiceInstanceException(
 1086                 _('Subnet gateway is not attached to the router.'))
 1087         private_subnet_router = self.neutron_api.show_router(
 1088             private_subnet_gateway_port['device_id'])
 1089         return private_subnet_router
 1090 
 1091     @utils.synchronized("service_instance_get_service_subnet", external=True)
 1092     def _get_service_subnet(self, subnet_name):
 1093         all_service_subnets = self._get_all_service_subnets()
 1094         service_subnets = [subnet for subnet in all_service_subnets
 1095                            if subnet['name'] == subnet_name]
 1096         if len(service_subnets) == 1:
 1097             return service_subnets[0]
 1098         elif not service_subnets:
 1099             unused_service_subnets = [subnet for subnet in all_service_subnets
 1100                                       if subnet['name'] == '']
 1101             if unused_service_subnets:
 1102                 service_subnet = unused_service_subnets[0]
 1103                 self.neutron_api.update_subnet(
 1104                     service_subnet['id'], subnet_name)
 1105                 return service_subnet
 1106             return None
 1107         else:
 1108             raise exception.ServiceInstanceException(
 1109                 _('Ambiguous service subnets.'))
 1110 
 1111     @utils.synchronized(
 1112         "service_instance_get_all_service_subnets", external=True)
 1113     def _get_all_service_subnets(self):
 1114         service_network = self.neutron_api.get_network(self.service_network_id)
 1115         subnets = []
 1116         for subnet_id in service_network['subnets']:
 1117             subnets.append(self.neutron_api.get_subnet(subnet_id))
 1118         return subnets