]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fix NetApp AutoSupport Shortcomings.
authorBob Callaway <bob.callaway@netapp.com>
Fri, 20 Jun 2014 11:31:29 +0000 (07:31 -0400)
committerTom Barron <tpb@dyncloud.net>
Fri, 10 Oct 2014 18:06:25 +0000 (14:06 -0400)
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

cinder/tests/test_netapp_utils.py [new file with mode: 0644]
cinder/volume/drivers/netapp/common.py
cinder/volume/drivers/netapp/iscsi.py
cinder/volume/drivers/netapp/nfs.py
cinder/volume/drivers/netapp/utils.py

diff --git a/cinder/tests/test_netapp_utils.py b/cinder/tests/test_netapp_utils.py
new file mode 100644 (file)
index 0000000..0c9ac42
--- /dev/null
@@ -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)
index 938a218c087a85bd87b6891dec778d0a51e44170..ff233a07048ab32f8ad5aa6eaf1ce39b8e259980 100644 (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,
index e08c0dc10300f85cd6f3db11aa70ef83040cdf2e..6897932d24e534b8727b9a0301d647633b179a19 100644 (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):
index c74f8551effbbda5bf9c68d57789fc8edac3aee1..8a7062e3a831d5c7c320a019f38274ec0f4e2f8c 100644 (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):
index 6a6bfc0e1d5eac74b4b0c21511e856ecd67ae613..85d24ca00e61d848f441fd5664fd4f27b7bd2331 100644 (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}