Fix NetApp AutoSupport Shortcomings.

This patch addresses several problems with the current implementation.

1. Appending a record to EMS should not in itself trigger ASUP
delivery. These should be separately scheduled and Openstack cinder
should have no role in ASUP scheduling or delivery, only a role in
logging via EMS.

2. Log frequency should be adjusted from weekly to hourly.

3. The log message should be useful for support. It should include
release (Havana, Icehouse, Juno, etc.) version (2014.1.1), and
distribution information (RHEL-OSP, etc.) rather than simply noting that
the message came from "Openstack."

Closes-Bug: 1367676
Change-Id: I2f81fed18342fe384e3c61184948f1e4052765d5
This commit is contained in:
Bob Callaway 2014-06-20 07:31:29 -04:00 committed by Tom Barron
parent 35b6b55ff3
commit b3be30b14c
5 changed files with 424 additions and 18 deletions

View File

@ -0,0 +1,268 @@
# Copyright 2014 Tom Barron. 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.
import platform
import mock
from cinder.openstack.common import processutils as putils
from cinder import test
from cinder import version
from cinder.volume.drivers.netapp import utils as na_utils
class OpenstackInfoTestCase(test.TestCase):
UNKNOWN_VERSION = 'unknown version'
UNKNOWN_RELEASE = 'unknown release'
UNKNOWN_VENDOR = 'unknown vendor'
UNKNOWN_PLATFORM = 'unknown platform'
VERSION_STRING_RET_VAL = 'fake_version_1'
RELEASE_STRING_RET_VAL = 'fake_release_1'
PLATFORM_RET_VAL = 'fake_platform_1'
VERSION_INFO_VERSION = 'fake_version_2'
VERSION_INFO_RELEASE = 'fake_release_2'
RPM_INFO_VERSION = 'fake_version_3'
RPM_INFO_RELEASE = 'fake_release_3'
RPM_INFO_VENDOR = 'fake vendor 3'
PUTILS_RPM_RET_VAL = ('fake_version_3 fake_release_3 fake vendor 3', '')
NO_PKG_FOUND = ('', 'whatever')
PUTILS_DPKG_RET_VAL = ('epoch:upstream_version-debian_revision', '')
DEB_RLS = 'upstream_version-debian_revision'
DEB_VENDOR = 'debian_revision'
def setUp(self):
super(OpenstackInfoTestCase, self).setUp()
def test_openstack_info_init(self):
info = na_utils.OpenStackInfo()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(version.version_info, 'version_string',
mock.Mock(return_value=VERSION_STRING_RET_VAL))
def test_update_version_from_version_string(self):
info = na_utils.OpenStackInfo()
info._update_version_from_version_string()
self.assertEqual(self.VERSION_STRING_RET_VAL, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(version.version_info, 'version_string',
mock.Mock(side_effect=Exception))
def test_xcption_in_update_version_from_version_string(self):
info = na_utils.OpenStackInfo()
info._update_version_from_version_string()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(version.version_info, 'release_string',
mock.Mock(return_value=RELEASE_STRING_RET_VAL))
def test_update_release_from_release_string(self):
info = na_utils.OpenStackInfo()
info._update_release_from_release_string()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.RELEASE_STRING_RET_VAL, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(version.version_info, 'release_string',
mock.Mock(side_effect=Exception))
def test_xcption_in_update_release_from_release_string(self):
info = na_utils.OpenStackInfo()
info._update_release_from_release_string()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(platform, 'platform',
mock.Mock(return_value=PLATFORM_RET_VAL))
def test_update_platform(self):
info = na_utils.OpenStackInfo()
info._update_platform()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.PLATFORM_RET_VAL, info._platform)
@mock.patch.object(platform, 'platform',
mock.Mock(side_effect=Exception))
def test_xcption_in_update_platform(self):
info = na_utils.OpenStackInfo()
info._update_platform()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_version',
mock.Mock(return_value=VERSION_INFO_VERSION))
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_release',
mock.Mock(return_value=VERSION_INFO_RELEASE))
def test_update_info_from_version_info(self):
info = na_utils.OpenStackInfo()
info._update_info_from_version_info()
self.assertEqual(self.VERSION_INFO_VERSION, info._version)
self.assertEqual(self.VERSION_INFO_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_version',
mock.Mock(return_value=''))
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_release',
mock.Mock(return_value=None))
def test_no_info_from_version_info(self):
info = na_utils.OpenStackInfo()
info._update_info_from_version_info()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_version',
mock.Mock(return_value=VERSION_INFO_VERSION))
@mock.patch.object(na_utils.OpenStackInfo, '_get_version_info_release',
mock.Mock(side_effect=Exception))
def test_xcption_in_info_from_version_info(self):
info = na_utils.OpenStackInfo()
info._update_info_from_version_info()
self.assertEqual(self.VERSION_INFO_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
@mock.patch.object(putils, 'execute',
mock.Mock(return_value=PUTILS_RPM_RET_VAL))
def test_update_info_from_rpm(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_rpm()
self.assertEqual(self.RPM_INFO_VERSION, info._version)
self.assertEqual(self.RPM_INFO_RELEASE, info._release)
self.assertEqual(self.RPM_INFO_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertTrue(found_package)
@mock.patch.object(putils, 'execute',
mock.Mock(return_value=NO_PKG_FOUND))
def test_update_info_from_rpm_no_pkg_found(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_rpm()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertFalse(found_package)
@mock.patch.object(putils, 'execute',
mock.Mock(side_effect=Exception))
def test_xcption_in_update_info_from_rpm(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_rpm()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertFalse(found_package)
@mock.patch.object(putils, 'execute',
mock.Mock(return_value=PUTILS_DPKG_RET_VAL))
def test_update_info_from_dpkg(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_dpkg()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.DEB_RLS, info._release)
self.assertEqual(self.DEB_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertTrue(found_package)
@mock.patch.object(putils, 'execute',
mock.Mock(return_value=NO_PKG_FOUND))
def test_update_info_from_dpkg_no_pkg_found(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_dpkg()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertFalse(found_package)
@mock.patch.object(putils, 'execute',
mock.Mock(side_effect=Exception))
def test_xcption_in_update_info_from_dpkg(self):
info = na_utils.OpenStackInfo()
found_package = info._update_info_from_dpkg()
self.assertEqual(self.UNKNOWN_VERSION, info._version)
self.assertEqual(self.UNKNOWN_RELEASE, info._release)
self.assertEqual(self.UNKNOWN_VENDOR, info._vendor)
self.assertEqual(self.UNKNOWN_PLATFORM, info._platform)
self.assertFalse(found_package)
@mock.patch.object(na_utils.OpenStackInfo,
'_update_version_from_version_string', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_release_from_release_string', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_platform', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_version_info', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_rpm', mock.Mock(return_value=True))
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_dpkg')
def test_update_openstack_info_rpm_pkg_found(self, mock_updt_from_dpkg):
info = na_utils.OpenStackInfo()
info._update_openstack_info()
self.assertFalse(mock_updt_from_dpkg.called)
@mock.patch.object(na_utils.OpenStackInfo,
'_update_version_from_version_string', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_release_from_release_string', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_platform', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_version_info', mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_rpm', mock.Mock(return_value=False))
@mock.patch.object(na_utils.OpenStackInfo,
'_update_info_from_dpkg')
def test_update_openstack_info_rpm_pkg_not_found(self,
mock_updt_from_dpkg):
info = na_utils.OpenStackInfo()
info._update_openstack_info()
self.assertTrue(mock_updt_from_dpkg.called)

View File

@ -25,6 +25,7 @@ from cinder.openstack.common import importutils
from cinder.openstack.common import log as logging
from cinder.volume import driver
from cinder.volume.drivers.netapp.options import netapp_proxy_opts
from cinder.volume.drivers.netapp import utils
LOG = logging.getLogger(__name__)
@ -75,12 +76,16 @@ class NetAppDriver(object):
def __init__(self, *args, **kwargs):
super(NetAppDriver, self).__init__()
app_version = utils.OpenStackInfo().info()
LOG.info(_('OpenStack OS Version Info: %(info)s') % {
'info': app_version})
self.configuration = kwargs.get('configuration', None)
if self.configuration:
self.configuration.append_config_values(netapp_proxy_opts)
else:
raise exception.InvalidInput(
reason=_("Required configuration not found"))
kwargs['app_version'] = app_version
self.driver = NetAppDriverFactory.create_driver(
self.configuration.netapp_storage_family,
self.configuration.netapp_storage_protocol,

View File

@ -84,6 +84,7 @@ class NetAppLun(object):
class NetAppDirectISCSIDriver(driver.ISCSIDriver):
"""NetApp Direct iSCSI volume driver."""
# do not increment this as it may be used in volume type definitions
VERSION = "1.0.0"
IGROUP_PREFIX = 'openstack-'
@ -92,6 +93,7 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
'netapp_server_port']
def __init__(self, *args, **kwargs):
self._app_version = kwargs.pop("app_version", "unknown")
super(NetAppDirectISCSIDriver, self).__init__(*args, **kwargs)
validate_instantiation(**kwargs)
self.configuration.append_config_values(netapp_connection_opts)
@ -1081,7 +1083,8 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
data['storage_protocol'] = 'iSCSI'
data['pools'] = self._get_pool_stats()
na_utils.provide_ems(self, self.client, data, netapp_backend)
na_utils.provide_ems(self, self.client, netapp_backend,
self._app_version)
self._stats = data
def _get_pool_stats(self):
@ -1494,8 +1497,8 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
data['storage_protocol'] = 'iSCSI'
data['pools'] = self._get_pool_stats()
na_utils.provide_ems(self, self.client, data, netapp_backend,
server_type='7mode')
na_utils.provide_ems(self, self.client, netapp_backend,
self._app_version, server_type='7mode')
self._stats = data
def _get_pool_stats(self):

View File

@ -59,6 +59,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
Executes commands relating to Volumes.
"""
# do not increment this as it may be used in volume type definitions
VERSION = "1.0.0"
def __init__(self, *args, **kwargs):
@ -66,6 +67,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
validate_instantiation(**kwargs)
self._execute = None
self._context = None
self._app_version = kwargs.pop("app_version", "unknown")
super(NetAppNFSDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(netapp_connection_opts)
self.configuration.append_config_values(netapp_basicauth_opts)
@ -961,7 +963,8 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
data['pools'] = self._get_pool_stats()
self._spawn_clean_cache_job()
na_utils.provide_ems(self, self._client, data, netapp_backend)
na_utils.provide_ems(self, self._client, netapp_backend,
self._app_version)
self._stats = data
def _get_pool_stats(self):
@ -1510,8 +1513,8 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
data['pools'] = self._get_pool_stats()
self._spawn_clean_cache_job()
na_utils.provide_ems(self, self._client, data, netapp_backend,
server_type="7mode")
na_utils.provide_ems(self, self._client, netapp_backend,
self._app_version, server_type="7mode")
self._stats = data
def _get_pool_stats(self):

View File

@ -24,6 +24,7 @@ import base64
import binascii
import copy
import decimal
import platform
import socket
import uuid
@ -33,8 +34,10 @@ 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 processutils as putils
from cinder.openstack.common import timeutils
from cinder import utils
from cinder import version
from cinder.volume.drivers.netapp.api import NaApiError
from cinder.volume.drivers.netapp.api import NaElement
from cinder.volume.drivers.netapp.api import NaErrors
@ -53,29 +56,31 @@ DEPRECATED_SSC_SPECS = {'netapp_unmirrored': 'netapp_mirrored',
'netapp_thick_provisioned': 'netapp_thin_provisioned'}
def provide_ems(requester, server, stats, netapp_backend,
def provide_ems(requester, server, netapp_backend, app_version,
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):
def _create_ems(netapp_backend, app_version, 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"
if server_type == "cluster":
dest = "cluster node"
else:
dest = "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('app-version', app_version)
ems_log.add_new_child('category', 'provisioning')
ems_log.add_new_child('event-description',
'OpenStack volume created on %s' % dest)
'OpenStack Cinder connected to %s' % dest)
ems_log.add_new_child('log-level', '6')
ems_log.add_new_child('auto-support', 'true')
ems_log.add_new_child('auto-support', 'false')
return ems_log
def _create_vs_get():
@ -107,14 +112,13 @@ def provide_ems(requester, server, stats, netapp_backend,
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)):
sec_limit = 3559
if not (timeutils.is_older_than(requester.last_ems, sec_limit)):
do_ems = False
if do_ems:
na_server = copy.copy(server)
na_server.set_timeout(25)
ems = _create_ems(stats, netapp_backend, server_type)
ems = _create_ems(netapp_backend, app_version, server_type)
try:
if server_type == "cluster":
api_version = na_server.get_api_version()
@ -385,3 +389,126 @@ def log_extra_spec_warnings(extra_specs):
msg = _('Extra spec %(old)s is deprecated. Use %(new)s instead.')
args = {'old': spec, 'new': DEPRECATED_SSC_SPECS[spec]}
LOG.warn(msg % args)
class OpenStackInfo(object):
"""OS/distribution, release, and version.
NetApp uses these fields as content for EMS log entry.
"""
PACKAGE_NAME = 'python-cinder'
def __init__(self):
self._version = 'unknown version'
self._release = 'unknown release'
self._vendor = 'unknown vendor'
self._platform = 'unknown platform'
def _update_version_from_version_string(self):
try:
self._version = version.version_info.version_string()
except Exception:
pass
def _update_release_from_release_string(self):
try:
self._release = version.version_info.release_string()
except Exception:
pass
def _update_platform(self):
try:
self._platform = platform.platform()
except Exception:
pass
@staticmethod
def _get_version_info_version():
return version.version_info.version
@staticmethod
def _get_version_info_release():
return version.version_info.release
def _update_info_from_version_info(self):
try:
ver = self._get_version_info_version()
if ver:
self._version = ver
except Exception:
pass
try:
rel = self._get_version_info_release()
if rel:
self._release = rel
except Exception:
pass
# RDO, RHEL-OSP, Mirantis on Redhat, SUSE
def _update_info_from_rpm(self):
LOG.debug('Trying rpm command.')
try:
out, err = putils.execute("rpm", "-qa", "--queryformat",
"'%{version}\t%{release}\t%{vendor}'",
self.PACKAGE_NAME)
if not out:
LOG.info(_('No rpm info found for %(pkg)s package.') % {
'pkg': self.PACKAGE_NAME})
return False
parts = out.split()
self._version = parts[0]
self._release = parts[1]
self._vendor = ' '.join(parts[2::])
return True
except Exception as e:
LOG.info(_('Could not run rpm command: %(msg)s.') % {
'msg': e})
return False
# ubuntu, mirantis on ubuntu
def _update_info_from_dpkg(self):
LOG.debug('Trying dpkg-query command.')
try:
_vendor = None
out, err = putils.execute("dpkg-query", "-W", "-f='${Version}'",
self.PACKAGE_NAME)
if not out:
LOG.info(_('No dpkg-query info found for %(pkg)s package.') % {
'pkg': self.PACKAGE_NAME})
return False
# debian format: [epoch:]upstream_version[-debian_revision]
deb_version = out
# in case epoch or revision is missing, copy entire string
_release = deb_version
if ':' in deb_version:
deb_epoch, upstream_version = deb_version.split(':')
_release = upstream_version
if '-' in deb_version:
deb_revision = deb_version.split('-')[1]
_vendor = deb_revision
self._release = _release
if _vendor:
self._vendor = _vendor
return True
except Exception as e:
LOG.info(_('Could not run dpkg-query command: %(msg)s.') % {
'msg': e})
return False
def _update_openstack_info(self):
self._update_version_from_version_string()
self._update_release_from_release_string()
self._update_platform()
# some distributions override with more meaningful information
self._update_info_from_version_info()
# see if we have still more targeted info from rpm or apt
found_package = self._update_info_from_rpm()
if not found_package:
self._update_info_from_dpkg()
def info(self):
self._update_openstack_info()
return '%(version)s|%(release)s|%(vendor)s|%(platform)s' % {
'version': self._version, 'release': self._release,
'vendor': self._vendor, 'platform': self._platform}