367 lines
13 KiB
Python
367 lines
13 KiB
Python
# Copyright (c) 2012 NetApp, Inc.
|
|
# Copyright (c) 2012 OpenStack Foundation
|
|
# 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.
|
|
"""
|
|
Utilities for NetApp drivers.
|
|
|
|
This module contains common utilities to be used by one or more
|
|
NetApp drivers to achieve the desired functionality.
|
|
"""
|
|
|
|
import base64
|
|
import binascii
|
|
import copy
|
|
import decimal
|
|
import socket
|
|
import uuid
|
|
|
|
import six
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder.openstack.common import log as logging
|
|
from cinder.openstack.common import timeutils
|
|
from cinder import utils
|
|
from cinder.volume.drivers.netapp.api import NaApiError
|
|
from cinder.volume.drivers.netapp.api import NaElement
|
|
from cinder.volume.drivers.netapp.api import NaErrors
|
|
from cinder.volume.drivers.netapp.api import NaServer
|
|
from cinder.volume import volume_types
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def provide_ems(requester, server, stats, netapp_backend,
|
|
server_type="cluster"):
|
|
"""Provide ems with volume stats for the requester.
|
|
|
|
:param server_type: cluster or 7mode.
|
|
"""
|
|
def _create_ems(stats, netapp_backend, server_type):
|
|
"""Create ems api request."""
|
|
ems_log = NaElement('ems-autosupport-log')
|
|
host = socket.getfqdn() or 'Cinder_node'
|
|
dest = "cluster node" if server_type == "cluster"\
|
|
else "7 mode controller"
|
|
ems_log.add_new_child('computer-name', host)
|
|
ems_log.add_new_child('event-id', '0')
|
|
ems_log.add_new_child('event-source',
|
|
'Cinder driver %s' % netapp_backend)
|
|
ems_log.add_new_child('app-version', stats.get('driver_version',
|
|
'Undefined'))
|
|
ems_log.add_new_child('category', 'provisioning')
|
|
ems_log.add_new_child('event-description',
|
|
'OpenStack volume created on %s' % dest)
|
|
ems_log.add_new_child('log-level', '6')
|
|
ems_log.add_new_child('auto-support', 'true')
|
|
return ems_log
|
|
|
|
def _create_vs_get():
|
|
"""Create vs_get api request."""
|
|
vs_get = NaElement('vserver-get-iter')
|
|
vs_get.add_new_child('max-records', '1')
|
|
query = NaElement('query')
|
|
query.add_node_with_children('vserver-info',
|
|
**{'vserver-type': 'node'})
|
|
vs_get.add_child_elem(query)
|
|
desired = NaElement('desired-attributes')
|
|
desired.add_node_with_children(
|
|
'vserver-info', **{'vserver-name': '', 'vserver-type': ''})
|
|
vs_get.add_child_elem(desired)
|
|
return vs_get
|
|
|
|
def _get_cluster_node(na_server):
|
|
"""Get the cluster node for ems."""
|
|
na_server.set_vserver(None)
|
|
vs_get = _create_vs_get()
|
|
res = na_server.invoke_successfully(vs_get)
|
|
if (res.get_child_content('num-records') and
|
|
int(res.get_child_content('num-records')) > 0):
|
|
attr_list = res.get_child_by_name('attributes-list')
|
|
vs_info = attr_list.get_child_by_name('vserver-info')
|
|
vs_name = vs_info.get_child_content('vserver-name')
|
|
return vs_name
|
|
return None
|
|
|
|
do_ems = True
|
|
if hasattr(requester, 'last_ems'):
|
|
sec_limit = 604800
|
|
if not (timeutils.is_older_than(requester.last_ems, sec_limit) or
|
|
timeutils.is_older_than(requester.last_ems, sec_limit - 59)):
|
|
do_ems = False
|
|
if do_ems:
|
|
na_server = copy.copy(server)
|
|
na_server.set_timeout(25)
|
|
ems = _create_ems(stats, netapp_backend, server_type)
|
|
try:
|
|
if server_type == "cluster":
|
|
api_version = na_server.get_api_version()
|
|
if api_version:
|
|
major, minor = api_version
|
|
else:
|
|
raise NaApiError(code='Not found',
|
|
message='No api version found')
|
|
if major == 1 and minor > 15:
|
|
node = getattr(requester, 'vserver', None)
|
|
else:
|
|
node = _get_cluster_node(na_server)
|
|
if node is None:
|
|
raise NaApiError(code='Not found',
|
|
message='No vserver found')
|
|
na_server.set_vserver(node)
|
|
else:
|
|
na_server.set_vfiler(None)
|
|
na_server.invoke_successfully(ems, True)
|
|
LOG.debug("ems executed successfully.")
|
|
except NaApiError as e:
|
|
LOG.warn(_("Failed to invoke ems. Message : %s") % e)
|
|
finally:
|
|
requester.last_ems = timeutils.utcnow()
|
|
|
|
|
|
def validate_instantiation(**kwargs):
|
|
"""Checks if a driver is instantiated other than by the unified driver.
|
|
|
|
Helps check direct instantiation of netapp drivers.
|
|
Call this function in every netapp block driver constructor.
|
|
"""
|
|
if kwargs and kwargs.get('netapp_mode') == 'proxy':
|
|
return
|
|
LOG.warn(_("It is not the recommended way to use drivers by NetApp. "
|
|
"Please use NetAppDriver to achieve the functionality."))
|
|
|
|
|
|
def invoke_api(na_server, api_name, api_family='cm', query=None,
|
|
des_result=None, additional_elems=None,
|
|
is_iter=False, records=0, tag=None,
|
|
timeout=0, tunnel=None):
|
|
"""Invokes any given api call to a NetApp server.
|
|
|
|
:param na_server: na_server instance
|
|
:param api_name: api name string
|
|
:param api_family: cm or 7m
|
|
:param query: api query as dict
|
|
:param des_result: desired result as dict
|
|
:param additional_elems: dict other than query and des_result
|
|
:param is_iter: is iterator api
|
|
:param records: limit for records, 0 for infinite
|
|
:param timeout: timeout seconds
|
|
:param tunnel: tunnel entity, vserver or vfiler name
|
|
"""
|
|
record_step = 50
|
|
if not (na_server or isinstance(na_server, NaServer)):
|
|
msg = _("Requires an NaServer instance.")
|
|
raise exception.InvalidInput(reason=msg)
|
|
server = copy.copy(na_server)
|
|
if api_family == 'cm':
|
|
server.set_vserver(tunnel)
|
|
else:
|
|
server.set_vfiler(tunnel)
|
|
if timeout > 0:
|
|
server.set_timeout(timeout)
|
|
iter_records = 0
|
|
cond = True
|
|
while cond:
|
|
na_element = create_api_request(
|
|
api_name, query, des_result, additional_elems,
|
|
is_iter, record_step, tag)
|
|
result = server.invoke_successfully(na_element, True)
|
|
if is_iter:
|
|
if records > 0:
|
|
iter_records = iter_records + record_step
|
|
if iter_records >= records:
|
|
cond = False
|
|
tag_el = result.get_child_by_name('next-tag')
|
|
tag = tag_el.get_content() if tag_el else None
|
|
if not tag:
|
|
cond = False
|
|
else:
|
|
cond = False
|
|
yield result
|
|
|
|
|
|
def create_api_request(api_name, query=None, des_result=None,
|
|
additional_elems=None, is_iter=False,
|
|
record_step=50, tag=None):
|
|
"""Creates a NetApp api request.
|
|
|
|
:param api_name: api name string
|
|
:param query: api query as dict
|
|
:param des_result: desired result as dict
|
|
:param additional_elems: dict other than query and des_result
|
|
:param is_iter: is iterator api
|
|
:param record_step: records at a time for iter api
|
|
:param tag: next tag for iter api
|
|
"""
|
|
api_el = NaElement(api_name)
|
|
if query:
|
|
query_el = NaElement('query')
|
|
query_el.translate_struct(query)
|
|
api_el.add_child_elem(query_el)
|
|
if des_result:
|
|
res_el = NaElement('desired-attributes')
|
|
res_el.translate_struct(des_result)
|
|
api_el.add_child_elem(res_el)
|
|
if additional_elems:
|
|
api_el.translate_struct(additional_elems)
|
|
if is_iter:
|
|
api_el.add_new_child('max-records', str(record_step))
|
|
if tag:
|
|
api_el.add_new_child('tag', tag, True)
|
|
return api_el
|
|
|
|
|
|
def to_bool(val):
|
|
"""Converts true, yes, y, 1 to True, False otherwise."""
|
|
if val:
|
|
strg = str(val).lower()
|
|
if (strg == 'true' or strg == 'y'
|
|
or strg == 'yes' or strg == 'enabled'
|
|
or strg == '1'):
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
|
|
@utils.synchronized("safe_set_attr")
|
|
def set_safe_attr(instance, attr, val):
|
|
"""Sets the attribute in a thread safe manner.
|
|
|
|
Returns if new val was set on attribute.
|
|
If attr already had the value then False.
|
|
"""
|
|
|
|
if not instance or not attr:
|
|
return False
|
|
old_val = getattr(instance, attr, None)
|
|
if val is None and old_val is None:
|
|
return False
|
|
elif val == old_val:
|
|
return False
|
|
else:
|
|
setattr(instance, attr, val)
|
|
return True
|
|
|
|
|
|
def get_volume_extra_specs(volume):
|
|
"""Provides extra specs associated with volume."""
|
|
ctxt = context.get_admin_context()
|
|
type_id = volume.get('volume_type_id')
|
|
specs = None
|
|
if type_id is not None:
|
|
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
|
specs = volume_type.get('extra_specs')
|
|
return specs
|
|
|
|
|
|
def check_apis_on_cluster(na_server, api_list=None):
|
|
"""Checks api availability and permissions on cluster.
|
|
|
|
Checks api availability and permissions for executing user.
|
|
Returns a list of failed apis.
|
|
"""
|
|
api_list = api_list or []
|
|
failed_apis = []
|
|
if api_list:
|
|
api_version = na_server.get_api_version()
|
|
if api_version:
|
|
major, minor = api_version
|
|
if major == 1 and minor < 20:
|
|
for api_name in api_list:
|
|
na_el = NaElement(api_name)
|
|
try:
|
|
na_server.invoke_successfully(na_el)
|
|
except Exception as e:
|
|
if isinstance(e, NaApiError):
|
|
if (e.code == NaErrors['API_NOT_FOUND'].code or
|
|
e.code ==
|
|
NaErrors['INSUFFICIENT_PRIVS'].code):
|
|
failed_apis.append(api_name)
|
|
elif major == 1 and minor >= 20:
|
|
failed_apis = copy.copy(api_list)
|
|
result = invoke_api(
|
|
na_server,
|
|
api_name='system-user-capability-get-iter',
|
|
api_family='cm',
|
|
additional_elems=None,
|
|
is_iter=True)
|
|
for res in result:
|
|
attr_list = res.get_child_by_name('attributes-list')
|
|
if attr_list:
|
|
capabilities = attr_list.get_children()
|
|
for capability in capabilities:
|
|
op_list = capability.get_child_by_name(
|
|
'operation-list')
|
|
if op_list:
|
|
ops = op_list.get_children()
|
|
for op in ops:
|
|
apis = op.get_child_content('api-name')
|
|
if apis:
|
|
api_list = apis.split(',')
|
|
for api_name in api_list:
|
|
if (api_name and
|
|
api_name.strip()
|
|
in failed_apis):
|
|
failed_apis.remove(api_name)
|
|
else:
|
|
continue
|
|
else:
|
|
msg = _("Unsupported Clustered Data ONTAP version.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
msg = _("Api version could not be determined.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
return failed_apis
|
|
|
|
|
|
def resolve_hostname(hostname):
|
|
"""Resolves host name to IP address."""
|
|
res = socket.getaddrinfo(hostname, None)[0]
|
|
family, socktype, proto, canonname, sockaddr = res
|
|
return sockaddr[0]
|
|
|
|
|
|
def encode_hex_to_base32(hex_string):
|
|
"""Encodes hex to base32 bit as per RFC4648."""
|
|
bin_form = binascii.unhexlify(hex_string)
|
|
return base64.b32encode(bin_form)
|
|
|
|
|
|
def decode_base32_to_hex(base32_string):
|
|
"""Decodes base32 string to hex string."""
|
|
bin_form = base64.b32decode(base32_string)
|
|
return binascii.hexlify(bin_form)
|
|
|
|
|
|
def convert_uuid_to_es_fmt(uuid_str):
|
|
"""Converts uuid to e-series compatible name format."""
|
|
uuid_base32 = encode_hex_to_base32(uuid.UUID(str(uuid_str)).hex)
|
|
return uuid_base32.strip('=')
|
|
|
|
|
|
def convert_es_fmt_to_uuid(es_label):
|
|
"""Converts e-series name format to uuid."""
|
|
es_label_b32 = es_label.ljust(32, '=')
|
|
return uuid.UUID(binascii.hexlify(base64.b32decode(es_label_b32)))
|
|
|
|
|
|
def round_down(value, precision):
|
|
return float(decimal.Decimal(six.text_type(value)).quantize(
|
|
decimal.Decimal(precision), rounding=decimal.ROUND_DOWN))
|