manila/manila/share/drivers/hp/hp_3par_driver.py

391 lines
14 KiB
Python

# Copyright 2015 Hewlett Packard Development Company, L.P.
#
# 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.
"""HP 3PAR Driver for OpenStack Manila."""
import datetime
import hashlib
import inspect
import logging
import os
import re
from oslo_config import cfg
from oslo_log import log
import six
from manila import exception
from manila.i18n import _
from manila.i18n import _LI
from manila.share import driver
from manila.share.drivers.hp import hp_3par_mediator
from manila.share import share_types
from manila import utils
HP3PAR_OPTS = [
cfg.StrOpt('hp3par_api_url',
default='',
help="3PAR WSAPI Server Url like "
"https://<3par ip>:8080/api/v1"),
cfg.StrOpt('hp3par_username',
default='',
help="3PAR username with the 'edit' role"),
cfg.StrOpt('hp3par_password',
default='',
help="3PAR password for the user specified in hp3par_username",
secret=True),
cfg.StrOpt('hp3par_san_ip',
default='',
help="IP address of SAN controller"),
cfg.StrOpt('hp3par_san_login',
default='',
help="Username for SAN controller"),
cfg.StrOpt('hp3par_san_password',
default='',
help="Password for SAN controller",
secret=True),
cfg.PortOpt('hp3par_san_ssh_port',
default=22,
help='SSH port to use with SAN'),
cfg.StrOpt('hp3par_fpg',
default="OpenStack",
help="The File Provisioning Group (FPG) to use"),
cfg.StrOpt('hp3par_share_ip_address',
default='',
help="The IP address for shares not using a share server"),
cfg.BoolOpt('hp3par_fstore_per_share',
default=False,
help="Use one filestore per share"),
cfg.BoolOpt('hp3par_debug',
default=False,
help="Enable HTTP debugging to 3PAR"),
]
CONF = cfg.CONF
CONF.register_opts(HP3PAR_OPTS)
LOG = log.getLogger(__name__)
class HP3ParShareDriver(driver.ShareDriver):
"""HP 3PAR driver for Manila.
Supports NFS and CIFS protocols on arrays with File Persona.
Version history:
1.0.00 - Begin Liberty development (post-Kilo)
1.0.01 - Report thin/dedup/hp_flash_cache capabilities
1.0.02 - Add share server/share network support
"""
VERSION = "1.0.02"
def __init__(self, *args, **kwargs):
super(HP3ParShareDriver, self).__init__((True, False), *args, **kwargs)
self.configuration = kwargs.get('configuration', None)
self.configuration.append_config_values(HP3PAR_OPTS)
self.configuration.append_config_values(driver.ssh_opts)
self.fpg = None
self.vfs = None
self.share_ip_address = None
self._hp3par = None # mediator between driver and client
def do_setup(self, context):
"""Any initialization the share driver does while starting."""
LOG.info(_LI("Starting share driver %(driver_name)s (%(version)s)"),
{'driver_name': self.__class__.__name__,
'version': self.VERSION})
if not self.driver_handles_share_servers:
self.share_ip_address = self.configuration.hp3par_share_ip_address
if not self.share_ip_address:
raise exception.HP3ParInvalid(
_("Unsupported configuration. "
"hp3par_share_ip_address must be set when "
"driver_handles_share_servers is False."))
mediator = hp_3par_mediator.HP3ParMediator(
hp3par_username=self.configuration.hp3par_username,
hp3par_password=self.configuration.hp3par_password,
hp3par_api_url=self.configuration.hp3par_api_url,
hp3par_debug=self.configuration.hp3par_debug,
hp3par_san_ip=self.configuration.hp3par_san_ip,
hp3par_san_login=self.configuration.hp3par_san_login,
hp3par_san_password=self.configuration.hp3par_san_password,
hp3par_san_ssh_port=self.configuration.hp3par_san_ssh_port,
hp3par_fstore_per_share=self.configuration.hp3par_fstore_per_share,
ssh_conn_timeout=self.configuration.ssh_conn_timeout,
)
mediator.do_setup()
# FPG must be configured and must exist.
self.fpg = self.configuration.safe_get('hp3par_fpg')
# Validate the FPG and discover the VFS
# This also validates the client, connection, firmware, WSAPI, FPG...
self.vfs = mediator.get_vfs_name(self.fpg)
# Don't set _hp3par until it is ready. Otherwise _update_stats fails.
self._hp3par = mediator
def check_for_setup_error(self):
try:
# Log the source SHA for support. Only do this with DEBUG.
if LOG.isEnabledFor(logging.DEBUG):
LOG.debug('HP3ParShareDriver SHA1: %s',
self.sha1_hash(HP3ParShareDriver))
LOG.debug('HP3ParMediator SHA1: %s',
self.sha1_hash(hp_3par_mediator.HP3ParMediator))
except Exception as e:
# Don't let any exceptions during the SHA1 logging interfere
# with startup. This is just debug info to identify the source
# code. If it doesn't work, just log a debug message.
LOG.debug('Source code SHA1 not logged due to: %s',
six.text_type(e))
@staticmethod
def sha1_hash(clazz):
"""Get the SHA1 hash for the source of a class."""
source_file = inspect.getsourcefile(clazz)
file_size = os.path.getsize(source_file)
sha1 = hashlib.sha1()
sha1.update(("blob %u\0" % file_size).encode('utf-8'))
with open(source_file, 'rb') as f:
sha1.update(f.read())
return sha1.hexdigest()
def get_network_allocations_number(self):
return 1
@staticmethod
def _validate_network_type(network_type):
if network_type not in ('flat', 'vlan', None):
reason = _('Invalid network type. %s is not supported by the '
'3PAR driver.')
raise exception.NetworkBadConfigurationException(
reason=reason % network_type)
def _setup_server(self, network_info, metadata=None):
LOG.debug("begin _setup_server with %s", network_info)
self._validate_network_type(network_info['network_type'])
ip = network_info['network_allocations'][0]['ip_address']
subnet = utils.cidr_to_netmask(network_info['cidr'])
vlantag = network_info['segmentation_id']
self._hp3par.create_fsip(ip, subnet, vlantag, self.fpg, self.vfs)
return {
'share_server_name': network_info['server_id'],
'share_server_id': network_info['server_id'],
'ip': ip,
'subnet': subnet,
'vlantag': vlantag if vlantag else 0,
'fpg': self.fpg,
'vfs': self.vfs,
}
def _teardown_server(self, server_details, security_services=None):
LOG.debug("begin _teardown_server with %s", server_details)
self._hp3par.remove_fsip(server_details.get('ip'),
server_details.get('fpg'),
server_details.get('vfs'))
def _get_share_ip(self, share_server):
return share_server['backend_details'].get('ip') if share_server else (
self.share_ip_address)
@staticmethod
def _build_export_location(protocol, ip, path):
if not ip:
message = _('Failed to build export location due to missing IP.')
raise exception.InvalidInput(message)
if not path:
message = _('Failed to build export location due to missing path.')
raise exception.InvalidInput(message)
if protocol == 'NFS':
location = ':'.join((ip, path))
elif protocol == 'CIFS':
location = '\\\\%s\%s' % (ip, path)
else:
message = _('Invalid protocol. Expected NFS or CIFS. '
'Got %s.') % protocol
raise exception.InvalidInput(message)
return location
@staticmethod
def build_share_comment(share):
"""Create an informational only comment to help admins and testers."""
info = {
'name': share['display_name'],
'host': share['host'],
'now': datetime.datetime.now().strftime('%H%M%S'),
}
acceptable = re.compile('[^a-zA-Z0-9_=:@# \-]+', re.UNICODE)
comment = ("OpenStack Manila - host=%(host)s orig_name=%(name)s "
"created=%(now)s" % info)
return acceptable.sub('_', comment)[:254] # clean and truncate
def create_share(self, context, share, share_server=None):
"""Is called to create share."""
ip = self._get_share_ip(share_server)
protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share)
path = self._hp3par.create_share(
share['project_id'],
share['id'],
protocol,
extra_specs,
self.fpg, self.vfs,
size=share['size'],
comment=self.build_share_comment(share)
)
return self._build_export_location(protocol, ip, path)
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Is called to create share from snapshot."""
ip = self._get_share_ip(share_server)
protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share)
path = self._hp3par.create_share_from_snapshot(
share['id'],
protocol,
extra_specs,
snapshot['share']['project_id'],
snapshot['share']['id'],
snapshot['share']['share_proto'],
snapshot['id'],
self.fpg,
self.vfs,
comment=self.build_share_comment(share)
)
return self._build_export_location(protocol, ip, path)
def delete_share(self, context, share, share_server=None):
"""Deletes share and its fstore."""
self._hp3par.delete_share(share['project_id'],
share['id'],
share['share_proto'],
self.fpg,
self.vfs)
def create_snapshot(self, context, snapshot, share_server=None):
"""Creates a snapshot of a share."""
self._hp3par.create_snapshot(snapshot['share']['project_id'],
snapshot['share']['id'],
snapshot['share']['share_proto'],
snapshot['id'],
self.fpg,
self.vfs)
def delete_snapshot(self, context, snapshot, share_server=None):
"""Deletes a snapshot of a share."""
self._hp3par.delete_snapshot(snapshot['share']['project_id'],
snapshot['share']['id'],
snapshot['share']['share_proto'],
snapshot['id'],
self.fpg,
self.vfs)
def ensure_share(self, context, share, share_server=None):
pass
def allow_access(self, context, share, access, share_server=None):
"""Allow access to the share."""
self._hp3par.allow_access(share['project_id'],
share['id'],
share['share_proto'],
access['access_type'],
access['access_to'],
self.fpg,
self.vfs)
def deny_access(self, context, share, access, share_server=None):
"""Deny access to the share."""
self._hp3par.deny_access(share['project_id'],
share['id'],
share['share_proto'],
access['access_type'],
access['access_to'],
self.fpg,
self.vfs)
def _update_share_stats(self):
"""Retrieve stats info from share group."""
backend_name = self.configuration.safe_get(
'share_backend_name') or "HP_3PAR"
max_over_subscription_ratio = self.configuration.safe_get(
'max_over_subscription_ratio')
reserved_share_percentage = self.configuration.safe_get(
'reserved_share_percentage')
if reserved_share_percentage is None:
reserved_share_percentage = 0
stats = {
'share_backend_name': backend_name,
'driver_handles_share_servers': self.driver_handles_share_servers,
'vendor_name': 'HP',
'driver_version': self.VERSION,
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': 0,
'free_capacity_gb': 0,
'provisioned_capacity_gb': 0,
'reserved_percentage': reserved_share_percentage,
'max_over_subscription_ratio': max_over_subscription_ratio,
'QoS_support': False,
'thin_provisioning': True, # 3PAR default is thin
}
if not self._hp3par:
LOG.info(
_LI("Skipping capacity and capabilities update. Setup has not "
"completed."))
else:
fpg_status = self._hp3par.get_fpg_status(self.fpg)
LOG.debug("FPG status = %s.", fpg_status)
stats.update(fpg_status)
super(HP3ParShareDriver, self)._update_share_stats(stats)