Port cinder EMS and ASUP support to manila

In Cinder, NetApp drivers collect useful information about
deployments - release, version, distribution, etc. -- and
post it to Event Management System (EMS) logs on backend
filers, where it can then be used for NetApp AutoSupport
(ASUP).  This commit ports this code to Manila NetApp
drivers.

Implements blueprint: netapp-asup-support
Change-Id: Ibaffdd9712cd5363b09718c3113c5cb68ebd1cbe
This commit is contained in:
Tom Barron 2014-11-26 13:25:34 -08:00 committed by Ben Swartzlander
parent f16267d878
commit 7651b002c2
4 changed files with 567 additions and 0 deletions

View File

@ -1,4 +1,5 @@
# Copyright (c) 2014 NetApp, Inc.
# Copyright (c) 2015 Tom Barron.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -36,6 +37,7 @@ from manila.i18n import _LI
from manila.openstack.common import log
from manila.share import driver
from manila.share.drivers.netapp import api as naapi
from manila.share.drivers.netapp import utils as na_utils
from manila import utils
@ -135,6 +137,9 @@ class NetAppClusteredShareDriver(driver.ShareDriver):
def __init__(self, db, *args, **kwargs):
super(NetAppClusteredShareDriver, self).__init__(True, *args, **kwargs)
self._app_version = na_utils.OpenStackInfo().info()
LOG.info(_LI("OpenStack OS Version Info: %(info)s") % {
'info': self._app_version})
self.db = db
self._helpers = None
self._licenses = []
@ -198,6 +203,8 @@ class NetAppClusteredShareDriver(driver.ShareDriver):
total_capacity_gb=(total / units.Gi),
free_capacity_gb=(free / units.Gi))
super(NetAppClusteredShareDriver, self)._update_share_stats(data)
na_utils.provide_ems(self, self._client._client, self.backend_name,
self._app_version)
def check_for_setup_error(self):
"""Raises error if prerequisites are not met."""

View File

@ -0,0 +1,285 @@
# Copyright (c) 2015 Bob Callaway.
# Copyright (c) 2015 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.
"""
Utilities for NetApp drivers.
"""
import copy
import platform
import socket
from oslo.concurrency import processutils as putils
from oslo.utils import timeutils
from manila.i18n import _LI, _LW
from manila.openstack.common import log as logging
from manila.share.drivers.netapp import api as na_api
from manila import version
LOG = logging.getLogger(__name__)
def provide_ems(requester, server, netapp_backend, app_version,
server_type="cluster"):
"""Provide ems with volume stats for the requester.
"""
# TODO(tbarron): rework provide_ems to not store timestamp in the caller.
# This requires upcoming Manila NetApp refactoring work.
def _create_ems(netapp_backend, app_version, server_type):
"""Create ems api request."""
ems_log = na_api.NaElement('ems-autosupport-log')
host = socket.getfqdn() or 'Manila_node'
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',
'Manila driver %s' % netapp_backend)
ems_log.add_new_child('app-version', app_version)
ems_log.add_new_child('category', 'provisioning')
ems_log.add_new_child('event-description',
'OpenStack Manila connected to %s' % dest)
ems_log.add_new_child('log-level', '6')
ems_log.add_new_child('auto-support', 'false')
return ems_log
def _create_vs_get():
"""Create vs_get api request."""
vs_get = na_api.NaElement('vserver-get-iter')
vs_get.add_new_child('max-records', '1')
query = na_api.NaElement('query')
query.add_node_with_children('vserver-info',
**{'vserver-type': 'node'})
vs_get.add_child_elem(query)
desired = na_api.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 = 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(netapp_backend, app_version, server_type)
try:
if server_type == "cluster":
api_version = na_server.get_api_version()
if api_version:
major, minor = api_version
else:
raise na_api.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 na_api.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 na_api.NaApiError as e:
LOG.warn(_LW("Failed to invoke ems. Message : %s") % e)
finally:
requester.last_ems = timeutils.utcnow()
class OpenStackInfo(object):
"""OS/distribution, release, and version.
NetApp uses these fields as content for EMS log entry.
"""
PACKAGE_NAME = 'python-manila'
def __init__(self):
self._version = 'unknown version'
self._release = 'unknown release'
self._vendor = 'unknown vendor'
self._platform = 'unknown platform'
@property
def version(self):
return self._version
@version.setter
def version(self, value):
self._version = value
@property
def release(self):
return self._release
@release.setter
def release(self, value):
self._release = value
@property
def vendor(self):
return self._vendor
@vendor.setter
def vendor(self, value):
self._vendor = value
@property
def platform(self):
return self._platform
@platform.setter
def platform(self, value):
self._platform = value
# Because of the variety of platforms and OpenStack distributions that we
# may run against, it is by no means a sure thing that these update methods
# will work. We collect what information we can and deliberately ignore
# exceptions of any kind in order to avoid fillling the manila share log
# with noise.
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", "-q", "--queryformat",
"'%{version}\t%{release}\t%{vendor}'",
self.PACKAGE_NAME)
if not out:
LOG.info(_LI('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(_LI('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(_LI(
'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(_LI('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}

View File

@ -1,4 +1,5 @@
# Copyright (c) 2014 NetApp, Inc.
# Copyright (c) 2015 Tom Barron.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -23,6 +24,7 @@ from manila import exception
from manila.share import configuration
from manila.share.drivers.netapp import api as naapi
from manila.share.drivers.netapp import cluster_mode as driver
from manila.share.drivers.netapp import utils as na_utils
from manila import test
from manila import utils
@ -30,6 +32,10 @@ from manila import utils
class NetAppClusteredDrvTestCase(test.TestCase):
"""Test suite for NetApp Cluster Mode driver."""
@mock.patch.object(na_utils.OpenStackInfo, '_update_info_from_rpm',
mock.Mock())
@mock.patch.object(na_utils.OpenStackInfo, '_update_info_from_dpkg',
mock.Mock())
def setUp(self):
super(NetAppClusteredDrvTestCase, self).setUp()
self._context = context.get_admin_context()
@ -114,6 +120,7 @@ class NetAppClusteredDrvTestCase(test.TestCase):
]
)
@mock.patch.object(na_utils, 'provide_ems', mock.Mock())
def test_update_share_stats(self):
"""Retrieve status info from share volume group."""
fake_aggr1_struct = {

View File

@ -0,0 +1,268 @@
# Copyright 2015 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 oslo.concurrency import processutils as putils
from manila.share.drivers.netapp import utils as na_utils
from manila import test
from manila import version
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_exception_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_exception_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_exception_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_exception_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_exception_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_exception_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)