Move some code out of utils.py

Move code into more specific locations where it is
applicable rather than the Cinder-wide utils.py.

Change-Id: I87f80f041cec255d51145e39bc0c0781a81e6db8
This commit is contained in:
Eric Harney 2019-08-19 11:44:05 -04:00
parent 48f9425d2c
commit 8a4dbf3eef
15 changed files with 92 additions and 89 deletions

View File

@ -130,3 +130,16 @@ def validate_integer(value, name, min_value=None, max_value=None):
return value return value
except ValueError as e: except ValueError as e:
raise webob.exc.HTTPBadRequest(explanation=six.text_type(e)) raise webob.exc.HTTPBadRequest(explanation=six.text_type(e))
def walk_class_hierarchy(clazz, encountered=None):
"""Walk class hierarchy, yielding most derived classes first."""
if not encountered:
encountered = []
for subclass in clazz.__subclasses__():
if subclass not in encountered:
encountered.append(subclass)
# drill down to leaves first
for subsubclass in walk_class_hierarchy(subclass, encountered):
yield subsubclass
yield subclass

View File

@ -20,10 +20,10 @@ from six.moves import http_client
import webob.dec import webob.dec
import webob.exc import webob.exc
from cinder.api import api_utils
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import utils
from cinder.wsgi import common as base_wsgi from cinder.wsgi import common as base_wsgi
@ -38,7 +38,7 @@ class FaultWrapper(base_wsgi.Middleware):
@staticmethod @staticmethod
def status_to_type(status): def status_to_type(status):
if not FaultWrapper._status_to_type: if not FaultWrapper._status_to_type:
for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError): for clazz in api_utils.walk_class_hierarchy(webob.exc.HTTPError):
FaultWrapper._status_to_type[clazz.code] = clazz FaultWrapper._status_to_type[clazz.code] = clazz
return FaultWrapper._status_to_type.get( return FaultWrapper._status_to_type.get(
status, webob.exc.HTTPInternalServerError)() status, webob.exc.HTTPInternalServerError)()

View File

@ -29,7 +29,7 @@ from cinder import db
from cinder import exception from cinder import exception
from cinder import test from cinder import test
from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_constants as fake
from cinder import utils from cinder.tests.unit import utils as test_utils
from cinder.volume import qos_specs from cinder.volume import qos_specs
from cinder.volume import volume_types from cinder.volume import volume_types
@ -374,7 +374,7 @@ class QoSSpecsTestCase(test.TestCase):
qos_specs_dict['specs']) qos_specs_dict['specs'])
qos_specs_dict['id'] = qos_specs_id qos_specs_dict['id'] = qos_specs_id
specs = db.qos_specs_get(self.ctxt, qos_specs_id) specs = db.qos_specs_get(self.ctxt, qos_specs_id)
qos_specs_list[index]['created_at'] = utils.time_format( qos_specs_list[index]['created_at'] = test_utils.time_format(
specs['created_at']) specs['created_at'])
res = qos_specs.get_all_specs(self.ctxt) res = qos_specs.get_all_specs(self.ctxt)

View File

@ -33,6 +33,7 @@ from cinder import exception
from cinder import test from cinder import test
from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_constants as fake
from cinder import utils from cinder import utils
from cinder.volume import utils as volume_utils
POOL_CAPS = {'total_capacity_gb': 0, POOL_CAPS = {'total_capacity_gb': 0,
'free_capacity_gb': 0, 'free_capacity_gb': 0,
@ -124,26 +125,26 @@ class GenericUtilsTestCase(test.TestCase):
def test_hostname_unicode_sanitization(self): def test_hostname_unicode_sanitization(self):
hostname = u"\u7684.test.example.com" hostname = u"\u7684.test.example.com"
self.assertEqual("test.example.com", self.assertEqual("test.example.com",
utils.sanitize_hostname(hostname)) volume_utils.sanitize_hostname(hostname))
def test_hostname_sanitize_periods(self): def test_hostname_sanitize_periods(self):
hostname = "....test.example.com..." hostname = "....test.example.com..."
self.assertEqual("test.example.com", self.assertEqual("test.example.com",
utils.sanitize_hostname(hostname)) volume_utils.sanitize_hostname(hostname))
def test_hostname_sanitize_dashes(self): def test_hostname_sanitize_dashes(self):
hostname = "----test.example.com---" hostname = "----test.example.com---"
self.assertEqual("test.example.com", self.assertEqual("test.example.com",
utils.sanitize_hostname(hostname)) volume_utils.sanitize_hostname(hostname))
def test_hostname_sanitize_characters(self): def test_hostname_sanitize_characters(self):
hostname = "(#@&$!(@*--#&91)(__=+--test-host.example!!.com-0+" hostname = "(#@&$!(@*--#&91)(__=+--test-host.example!!.com-0+"
self.assertEqual("91----test-host.example.com-0", self.assertEqual("91----test-host.example.com-0",
utils.sanitize_hostname(hostname)) volume_utils.sanitize_hostname(hostname))
def test_hostname_translate(self): def test_hostname_translate(self):
hostname = "<}\x1fh\x10e\x08l\x02l\x05o\x12!{>" hostname = "<}\x1fh\x10e\x08l\x02l\x05o\x12!{>"
self.assertEqual("hello", utils.sanitize_hostname(hostname)) self.assertEqual("hello", volume_utils.sanitize_hostname(hostname))
@mock.patch('os.path.join', side_effect=lambda x, y: '/'.join((x, y))) @mock.patch('os.path.join', side_effect=lambda x, y: '/'.join((x, y)))
def test_make_dev_path(self, mock_join): def test_make_dev_path(self, mock_join):
@ -385,11 +386,11 @@ class WalkClassHierarchyTestCase(test.TestCase):
pass pass
class_pairs = zip((D, B, E), class_pairs = zip((D, B, E),
utils.walk_class_hierarchy(A, encountered=[C])) api_utils.walk_class_hierarchy(A, encountered=[C]))
for actual, expected in class_pairs: for actual, expected in class_pairs:
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
class_pairs = zip((D, B, C, E), utils.walk_class_hierarchy(A)) class_pairs = zip((D, B, C, E), api_utils.walk_class_hierarchy(A))
for actual, expected in class_pairs: for actual, expected in class_pairs:
self.assertEqual(expected, actual) self.assertEqual(expected, actual)

View File

@ -556,3 +556,19 @@ def set_timeout(timeout):
return _wrapper return _wrapper
return _decorator return _decorator
def time_format(at=None):
"""Format datetime string to date.
:param at: Type is datetime.datetime (example
'datetime.datetime(2017, 12, 24, 22, 11, 32, 6086)')
:returns: Format date (example '2017-12-24T22:11:32Z').
"""
if not at:
at = timeutils.utcnow()
date_string = at.strftime("%Y-%m-%dT%H:%M:%S")
tz = at.tzname(None) if at.tzinfo else 'UTC'
# Need to handle either iso8601 or python UTC format
date_string += ('Z' if tz in ['UTC', 'UTC+00:00'] else tz)
return date_string

View File

@ -1055,7 +1055,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
mock.call('qos-policy-group-delete-iter', api_args, False)]) mock.call('qos-policy-group-delete-iter', api_args, False)])
self.assertEqual(1, mock_log.call_count) self.assertEqual(1, mock_log.call_count)
@mock.patch('cinder.utils.resolve_hostname', @mock.patch('cinder.volume.utils.resolve_hostname',
return_value='192.168.1.101') return_value='192.168.1.101')
def test_get_if_info_by_ip_not_found(self, mock_resolve_hostname): def test_get_if_info_by_ip_not_found(self, mock_resolve_hostname):
fake_ip = '192.168.1.101' fake_ip = '192.168.1.101'
@ -1070,7 +1070,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertRaises(exception.NotFound, self.client.get_if_info_by_ip, self.assertRaises(exception.NotFound, self.client.get_if_info_by_ip,
fake_ip) fake_ip)
@mock.patch('cinder.utils.resolve_hostname', @mock.patch('cinder.volume.utils.resolve_hostname',
return_value='192.168.1.101') return_value='192.168.1.101')
def test_get_if_info_by_ip(self, mock_resolve_hostname): def test_get_if_info_by_ip(self, mock_resolve_hostname):
fake_ip = '192.168.1.101' fake_ip = '192.168.1.101'

View File

@ -41,6 +41,7 @@ from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume.drivers import nfs from cinder.volume.drivers import nfs
from cinder.volume.drivers import remotefs from cinder.volume.drivers import remotefs
from cinder.volume import utils as volume_utils
@ddt.ddt @ddt.ddt
@ -651,7 +652,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
fake.NFS_SHARE) fake.NFS_SHARE)
def test_get_share_mount_and_vol_from_vol_ref(self): def test_get_share_mount_and_vol_from_vol_ref(self):
self.mock_object(utils, 'resolve_hostname', self.mock_object(volume_utils, 'resolve_hostname',
return_value='10.12.142.11') return_value='10.12.142.11')
self.mock_object(os.path, 'isfile', return_value=True) self.mock_object(os.path, 'isfile', return_value=True)
self.driver._mounted_shares = [self.fake_nfs_export_1] self.driver._mounted_shares = [self.fake_nfs_export_1]
@ -669,7 +670,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
self.assertEqual('test_file_name', file_path) self.assertEqual('test_file_name', file_path)
def test_get_share_mount_and_vol_from_vol_ref_with_bad_ref(self): def test_get_share_mount_and_vol_from_vol_ref_with_bad_ref(self):
self.mock_object(utils, 'resolve_hostname', self.mock_object(volume_utils, 'resolve_hostname',
return_value='10.12.142.11') return_value='10.12.142.11')
self.driver._mounted_shares = [self.fake_nfs_export_1] self.driver._mounted_shares = [self.fake_nfs_export_1]
vol_ref = {'source-id': '1234546'} vol_ref = {'source-id': '1234546'}
@ -683,7 +684,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
vol_ref) vol_ref)
def test_get_share_mount_and_vol_from_vol_ref_where_not_found(self): def test_get_share_mount_and_vol_from_vol_ref_where_not_found(self):
self.mock_object(utils, 'resolve_hostname', self.mock_object(volume_utils, 'resolve_hostname',
return_value='10.12.142.11') return_value='10.12.142.11')
self.driver._mounted_shares = [self.fake_nfs_export_1] self.driver._mounted_shares = [self.fake_nfs_export_1]
vol_path = "%s/%s" % (self.fake_nfs_export_2, 'test_file_name') vol_path = "%s/%s" % (self.fake_nfs_export_2, 'test_file_name')
@ -698,7 +699,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
vol_ref) vol_ref)
def test_get_share_mount_and_vol_from_vol_ref_where_is_dir(self): def test_get_share_mount_and_vol_from_vol_ref_where_is_dir(self):
self.mock_object(utils, 'resolve_hostname', self.mock_object(volume_utils, 'resolve_hostname',
return_value='10.12.142.11') return_value='10.12.142.11')
self.driver._mounted_shares = [self.fake_nfs_export_1] self.driver._mounted_shares = [self.fake_nfs_export_1]
vol_ref = {'source-name': self.fake_nfs_export_2} vol_ref = {'source-name': self.fake_nfs_export_2}

View File

@ -307,7 +307,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.driver.zapi_client, 'get_operational_lif_addresses', self.driver.zapi_client, 'get_operational_lif_addresses',
return_value=[fake.SHARE_IP]) return_value=[fake.SHARE_IP])
mock_resolve_hostname = self.mock_object( mock_resolve_hostname = self.mock_object(
utils, 'resolve_hostname', return_value=fake.SHARE_IP) volume_utils, 'resolve_hostname', return_value=fake.SHARE_IP)
mock_get_flexvol = self.mock_object( mock_get_flexvol = self.mock_object(
self.driver.zapi_client, 'get_flexvol', self.driver.zapi_client, 'get_flexvol',
return_value={'name': fake.NETAPP_VOLUME}) return_value={'name': fake.NETAPP_VOLUME})
@ -330,7 +330,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver.zapi_client, self.mock_object(self.driver.zapi_client,
'get_operational_lif_addresses', 'get_operational_lif_addresses',
return_value=[]) return_value=[])
self.mock_object(utils, self.mock_object(volume_utils,
'resolve_hostname', 'resolve_hostname',
return_value=fake.SHARE_IP) return_value=fake.SHARE_IP)
@ -344,7 +344,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver.zapi_client, self.mock_object(self.driver.zapi_client,
'get_operational_lif_addresses', 'get_operational_lif_addresses',
return_value=[fake.SHARE_IP]) return_value=[fake.SHARE_IP])
self.mock_object(utils, self.mock_object(volume_utils,
'resolve_hostname', 'resolve_hostname',
return_value=fake.SHARE_IP) return_value=fake.SHARE_IP)
side_effect = exception.VolumeBackendAPIException(data='fake_data') side_effect = exception.VolumeBackendAPIException(data='fake_data')

View File

@ -32,7 +32,6 @@ import pyclbr
import random import random
import re import re
import shutil import shutil
import socket
import stat import stat
import sys import sys
import tempfile import tempfile
@ -275,22 +274,6 @@ def last_completed_audit_period(unit=None):
return (begin, end) return (begin, end)
def time_format(at=None):
"""Format datetime string to date.
:param at: Type is datetime.datetime (example
'datetime.datetime(2017, 12, 24, 22, 11, 32, 6086)')
:returns: Format date (example '2017-12-24T22:11:32Z').
"""
if not at:
at = timeutils.utcnow()
date_string = at.strftime("%Y-%m-%dT%H:%M:%S")
tz = at.tzname(None) if at.tzinfo else 'UTC'
# Need to handle either iso8601 or python UTC format
date_string += ('Z' if tz in ['UTC', 'UTC+00:00'] else tz)
return date_string
def monkey_patch(): def monkey_patch():
"""Patches decorators for all functions in a specified module. """Patches decorators for all functions in a specified module.
@ -353,23 +336,6 @@ def make_dev_path(dev, partition=None, base='/dev'):
return path return path
def sanitize_hostname(hostname):
"""Return a hostname which conforms to RFC-952 and RFC-1123 specs."""
if six.PY3:
hostname = hostname.encode('latin-1', 'ignore')
hostname = hostname.decode('latin-1')
else:
if isinstance(hostname, six.text_type):
hostname = hostname.encode('latin-1', 'ignore')
hostname = re.sub(r'[ _]', '-', hostname)
hostname = re.sub(r'[^\w.-]+', '', hostname)
hostname = hostname.lower()
hostname = hostname.strip('.-')
return hostname
def robust_file_write(directory, filename, data): def robust_file_write(directory, filename, data):
"""Robust file write. """Robust file write.
@ -453,19 +419,6 @@ def tempdir(**kwargs):
six.text_type(e)) six.text_type(e))
def walk_class_hierarchy(clazz, encountered=None):
"""Walk class hierarchy, yielding most derived classes first."""
if not encountered:
encountered = []
for subclass in clazz.__subclasses__():
if subclass not in encountered:
encountered.append(subclass)
# drill down to leaves first
for subsubclass in walk_class_hierarchy(subclass, encountered):
yield subsubclass
yield subclass
def get_root_helper(): def get_root_helper():
return 'sudo cinder-rootwrap %s' % CONF.rootwrap_config return 'sudo cinder-rootwrap %s' % CONF.rootwrap_config
@ -922,22 +875,6 @@ def setup_tracing(trace_flags):
TRACE_API = 'api' in trace_flags TRACE_API = 'api' in trace_flags
def resolve_hostname(hostname):
"""Resolves host name to IP address.
Resolves a host name (my.data.point.com) to an IP address (10.12.143.11).
This routine also works if the data passed in hostname is already an IP.
In this case, the same IP address will be returned.
:param hostname: Host name to resolve.
:returns: IP Address for Host name.
"""
ip = socket.getaddrinfo(hostname, None)[0][4][0]
LOG.debug('Asked to resolve hostname %(host)s and got IP %(ip)s.',
{'host': hostname, 'ip': ip})
return ip
def build_or_str(elements, str_format=None): def build_or_str(elements, str_format=None):
"""Builds a string of elements joined by 'or'. """Builds a string of elements joined by 'or'.

View File

@ -30,6 +30,7 @@ from cinder import utils
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
from oslo_utils import strutils from oslo_utils import strutils
@ -668,7 +669,7 @@ class Client(client_base.Client):
net_if_iter.add_child_elem(query) net_if_iter.add_child_elem(query)
query.add_node_with_children( query.add_node_with_children(
'net-interface-info', 'net-interface-info',
**{'address': utils.resolve_hostname(ip)}) **{'address': volume_utils.resolve_hostname(ip)})
result = self.connection.invoke_successfully(net_if_iter, True) result = self.connection.invoke_successfully(net_if_iter, True)
num_records = result.get_child_content('num-records') num_records = result.get_child_content('num-records')
if num_records and int(num_records) >= 1: if num_records and int(num_records) >= 1:

View File

@ -906,7 +906,7 @@ class NetAppNfsDriver(driver.ManageableVD,
# First strip out share and convert to IP format. # First strip out share and convert to IP format.
share_split = vol_ref.rsplit(':', 1) share_split = vol_ref.rsplit(':', 1)
vol_ref_share_ip = utils.resolve_hostname(share_split[0]) vol_ref_share_ip = volume_utils.resolve_hostname(share_split[0])
# Now place back into volume reference. # Now place back into volume reference.
vol_ref_share = vol_ref_share_ip + ':' + share_split[1] vol_ref_share = vol_ref_share_ip + ':' + share_split[1]

View File

@ -327,7 +327,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
for share in self._mounted_shares: for share in self._mounted_shares:
host, junction_path = na_utils.get_export_host_junction_path(share) host, junction_path = na_utils.get_export_host_junction_path(share)
address = utils.resolve_hostname(host) address = volume_utils.resolve_hostname(host)
if address not in vserver_addresses: if address not in vserver_addresses:
LOG.warning('Address not found for NFS share %s.', share) LOG.warning('Address not found for NFS share %s.', share)
@ -463,7 +463,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
def _get_ip_verify_on_cluster(self, host): def _get_ip_verify_on_cluster(self, host):
"""Verifies if host on same cluster and returns ip.""" """Verifies if host on same cluster and returns ip."""
ip = utils.resolve_hostname(host) ip = volume_utils.resolve_hostname(host)
vserver = self._get_vserver_for_ip(ip) vserver = self._get_vserver_for_ip(ip)
if not vserver: if not vserver:
raise exception.NotFound(_("Unable to locate an SVM that is " raise exception.NotFound(_("Unable to locate an SVM that is "

View File

@ -263,7 +263,7 @@ class ZFSSAISCSIDriver(driver.ISCSIDriver):
# Lookup the zfssa_target_portal DNS name to an IP address # Lookup the zfssa_target_portal DNS name to an IP address
host, port = lcfg.zfssa_target_portal.split(':') host, port = lcfg.zfssa_target_portal.split(':')
host_ip_addr = utils.resolve_hostname(host) host_ip_addr = volume_utils.resolve_hostname(host)
self.zfssa_target_portal = host_ip_addr + ':' + port self.zfssa_target_portal = host_ip_addr + ':' + port
def check_for_setup_error(self): def check_for_setup_error(self):

View File

@ -1305,7 +1305,7 @@ class VolumeManager(manager.CleanableManager,
raise exception.InvalidVolume( raise exception.InvalidVolume(
reason=_("being attached by different mode")) reason=_("being attached by different mode"))
host_name_sanitized = utils.sanitize_hostname( host_name_sanitized = vol_utils.sanitize_hostname(
host_name) if host_name else None host_name) if host_name else None
if instance_uuid: if instance_uuid:
attachments = ( attachments = (

View File

@ -23,6 +23,7 @@ import operator
import os import os
from os import urandom from os import urandom
import re import re
import socket
import tempfile import tempfile
import time import time
import uuid import uuid
@ -1213,3 +1214,36 @@ def sanitize_host(host):
if netutils.is_valid_ipv6(host): if netutils.is_valid_ipv6(host):
return '[%s]' % host return '[%s]' % host
return host return host
def sanitize_hostname(hostname):
"""Return a hostname which conforms to RFC-952 and RFC-1123 specs."""
if six.PY3:
hostname = hostname.encode('latin-1', 'ignore')
hostname = hostname.decode('latin-1')
else:
if isinstance(hostname, six.text_type):
hostname = hostname.encode('latin-1', 'ignore')
hostname = re.sub(r'[ _]', '-', hostname)
hostname = re.sub(r'[^\w.-]+', '', hostname)
hostname = hostname.lower()
hostname = hostname.strip('.-')
return hostname
def resolve_hostname(hostname):
"""Resolves host name to IP address.
Resolves a host name (my.data.point.com) to an IP address (10.12.143.11).
This routine also works if the data passed in hostname is already an IP.
In this case, the same IP address will be returned.
:param hostname: Host name to resolve.
:returns: IP Address for Host name.
"""
ip = socket.getaddrinfo(hostname, None)[0][4][0]
LOG.debug('Asked to resolve hostname %(host)s and got IP %(ip)s.',
{'host': hostname, 'ip': ip})
return ip