Repository: ambari Updated Branches: refs/heads/trunk a8fc9a263 -> b570fc1f7
AMBARI-11419. Checks for package existence are too slow. (aonishuk) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/b570fc1f Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/b570fc1f Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/b570fc1f Branch: refs/heads/trunk Commit: b570fc1f73785106f3dff59dd8c02b1c42613b88 Parents: a8fc9a2 Author: Andrew Onishuk <[email protected]> Authored: Wed May 27 14:48:24 2015 +0300 Committer: Andrew Onishuk <[email protected]> Committed: Wed May 27 14:48:24 2015 +0300 ---------------------------------------------------------------------- .../resource_management/TestPackageResource.py | 92 +++++++++++--------- .../core/providers/package/apt.py | 46 +++++----- .../core/providers/package/yumrpm.py | 48 +++++++--- .../core/providers/package/zypper.py | 46 +++++++--- .../python/resource_management/core/shell.py | 2 +- .../python/resource_management/core/utils.py | 10 +++ 6 files changed, 152 insertions(+), 92 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-agent/src/test/python/resource_management/TestPackageResource.py ---------------------------------------------------------------------- diff --git a/ambari-agent/src/test/python/resource_management/TestPackageResource.py b/ambari-agent/src/test/python/resource_management/TestPackageResource.py index e585826..d69f278 100644 --- a/ambari-agent/src/test/python/resource_management/TestPackageResource.py +++ b/ambari-agent/src/test/python/resource_management/TestPackageResource.py @@ -16,6 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. ''' +import sys from unittest import TestCase from mock.mock import patch, MagicMock, call @@ -35,9 +36,9 @@ class TestPackageResource(TestCase): with Environment('/') as env: Package("some_package", ) - call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep '^some-package$'"), - call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package'], logoutput=False, sudo=True, env={'DEBIAN_FRONTEND': 'noninteractive'}), - call(['/usr/bin/apt-get', 'update', '-qq'], logoutput=False, sudo=True)]) + call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep ^some-package$"), + call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package'], logoutput=False, sudo=True, env={'DEBIAN_FRONTEND': 'noninteractive'}), + call(['/usr/bin/apt-get', 'update', '-qq'], logoutput=False, sudo=True)]) shell_mock.assert_has_calls([call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package'], logoutput=False, sudo=True)]) @@ -50,8 +51,9 @@ class TestPackageResource(TestCase): with Environment('/') as env: Package("some_package", ) - call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep '^some-package$'"), - call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package'], logoutput=False, sudo=True, env={'DEBIAN_FRONTEND': 'noninteractive'})]) + call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep ^some-package$"), + call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package'], logoutput=False, sudo=True, env={'DEBIAN_FRONTEND': 'noninteractive'})]) + self.assertEqual(shell_mock.call_count, 0, "shell.checked_call shouldn't be called") @@ -59,19 +61,11 @@ class TestPackageResource(TestCase): @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'ubuntu') def test_action_install_regex_ubuntu(self, shell_mock, call_mock): - call_mock.side_effect = [(0, None), - (0, "some-package1\nsome-package2"), - (0, "Some text.\nStatus: install ok installed\nSome text"), - (0, "Some text.\nStatus: not installed\nSome text"), - (0, None)] + call_mock.side_effect = [(0, None)] with Environment('/') as env: Package("some_package.*", ) - call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep '^some-package.*$'"), - call("apt-cache --names-only search '^some-package.*$' | awk '{print $1}'"), - call("dpkg --status 'some-package1'"), - call("dpkg --status 'some-package2'"), - call(['/usr/bin/apt-get', '-q', '-o', 'Dpkg::Options::=--force-confdef', '--allow-unauthenticated', '--assume-yes', 'install', 'some-package.*'], logoutput=False, sudo=True, env={'DEBIAN_FRONTEND': 'noninteractive'})]) + call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep ^some-package.*$")]) self.assertEqual(shell_mock.call_count, 0, "shell.checked_call shouldn't be called") @patch.object(shell, "call") @@ -86,57 +80,56 @@ class TestPackageResource(TestCase): with Environment('/') as env: Package("some_package.*", ) - call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep '^some-package.*$'"), - call("apt-cache --names-only search '^some-package.*$' | awk '{print $1}'"), - call("dpkg --status 'some-package1'"), - call("dpkg --status 'some-package2'")]) - self.assertEqual(call_mock.call_count, 4, "Package should not be installed") + call_mock.assert_has_calls([call("dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep ^some-package.*$")]) + self.assertEqual(call_mock.call_count, 1, "Package should not be installed") self.assertEqual(shell_mock.call_count, 0, "shell.checked_call shouldn't be called") - @patch.object(shell, "call") @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'redhat') - def test_action_install_rhel(self, shell_mock, call_mock): - call_mock.return_value= (1, None) + def test_action_install_rhel(self, shell_mock): + sys.modules['yum'] = MagicMock() + sys.modules['yum'].YumBase.return_value = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.return_value = [('some_packag',)] with Environment('/') as env: Package("some_package", ) - call_mock.assert_called_with("installed_pkgs=`rpm -qa 'some_package'` ; [ ! -z \"$installed_pkgs\" ]") + self.assertTrue(sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.called) shell_mock.assert_called_with(['/usr/bin/yum', '-d', '0', '-e', '0', '-y', 'install', 'some_package'], logoutput=False, sudo=True) - @patch.object(shell, "call") @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'redhat') - def test_action_install_pattern_rhel(self, shell_mock, call_mock): - call_mock.side_effect=[(0, None), (1, "Some text")] + def test_action_install_pattern_rhel(self, shell_mock): + sys.modules['yum'] = MagicMock() + sys.modules['yum'].YumBase.return_value = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.return_value = [('some_packag',)] with Environment('/') as env: Package("some_package*", ) - call_mock.assert_has_calls([call("installed_pkgs=`rpm -qa 'some_package*'` ; [ ! -z \"$installed_pkgs\" ]"), - call("! yum list available 'some_package*'")]) shell_mock.assert_called_with(['/usr/bin/yum', '-d', '0', '-e', '0', '-y', 'install', 'some_package*'], logoutput=False, sudo=True) - @patch.object(shell, "call") @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'redhat') - def test_action_install_pattern_installed_rhel(self, shell_mock, call_mock): - call_mock.side_effect=[(0, None), (0, "Some text")] + def test_action_install_pattern_installed_rhel(self, shell_mock): + sys.modules['yum'] = MagicMock() + sys.modules['yum'].YumBase.return_value = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.return_value = [('some_package_1_2_3',)] with Environment('/') as env: Package("some_package*", ) - call_mock.assert_has_calls([call("installed_pkgs=`rpm -qa 'some_package*'` ; [ ! -z \"$installed_pkgs\" ]"), - call("! yum list available 'some_package*'")]) self.assertEqual(shell_mock.call_count, 0, "shell.checked_call shouldn't be called") - @patch.object(shell, "call") @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'suse') - def test_action_install_suse(self, shell_mock, call_mock): - call_mock.return_value= (1, None) + def test_action_install_suse(self, shell_mock): + sys.modules['rpm'] = MagicMock() + sys.modules['rpm'].TransactionSet.return_value = MagicMock() + sys.modules['rpm'].TransactionSet.return_value.dbMatch.return_value = [{'name':'some_packages'}] with Environment('/') as env: Package("some_package", ) - call_mock.assert_called_with("installed_pkgs=`rpm -qa 'some_package'` ; [ ! -z \"$installed_pkgs\" ]") shell_mock.assert_called_with(['/usr/bin/zypper', '--quiet', 'install', '--auto-agree-with-licenses', '--no-confirm', 'some_package'], logoutput=False, sudo=True) @patch.object(shell, "call") @@ -151,22 +144,25 @@ class TestPackageResource(TestCase): call("zypper --non-interactive search --type package --uninstalled-only --match-exact 'some_package*'")]) shell_mock.assert_called_with(['/usr/bin/zypper', '--quiet', 'install', '--auto-agree-with-licenses', '--no-confirm', 'some_package*'], logoutput=False, sudo=True) - @patch.object(shell, "call") @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'suse') - def test_action_install_pattern_suse(self, shell_mock, call_mock): - call_mock.side_effect=[(0, None), (0, "Loading repository data...\nReading installed packages...\nNo packages found.\n")] + def test_action_install_pattern_suse(self, shell_mock): + sys.modules['rpm'] = MagicMock() + sys.modules['rpm'].TransactionSet.return_value = MagicMock() + sys.modules['rpm'].TransactionSet.return_value.dbMatch.return_value = [{'name':'some_packagetest'}] with Environment('/') as env: Package("some_package*", ) - call_mock.assert_has_calls([call("installed_pkgs=`rpm -qa 'some_package*'` ; [ ! -z \"$installed_pkgs\" ]"), - call("zypper --non-interactive search --type package --uninstalled-only --match-exact 'some_package*'")]) self.assertEqual(shell_mock.call_count, 0, "shell.checked_call shouldn't be called") @patch.object(shell, "call", new = MagicMock(return_value=(0, None))) @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'redhat') def test_action_install_existent_rhel(self, shell_mock): + sys.modules['yum'] = MagicMock() + sys.modules['yum'].YumBase.return_value = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.return_value = [('some_package',)] with Environment('/') as env: Package("some_package", ) @@ -188,6 +184,9 @@ class TestPackageResource(TestCase): @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'suse') def test_action_install_existent_suse(self, shell_mock): + sys.modules['rpm'] = MagicMock() + sys.modules['rpm'].TransactionSet.return_value = MagicMock() + sys.modules['rpm'].TransactionSet.return_value.dbMatch.return_value = [{'name':'some_package'}] with Environment('/') as env: Package("some_package", ) @@ -197,6 +196,10 @@ class TestPackageResource(TestCase): @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'redhat') def test_action_remove_rhel(self, shell_mock): + sys.modules['yum'] = MagicMock() + sys.modules['yum'].YumBase.return_value = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb = MagicMock() + sys.modules['yum'].YumBase.return_value.rpmdb.simplePkgList.return_value = [('some_package',)] with Environment('/') as env: Package("some_package", action = "remove" @@ -207,6 +210,9 @@ class TestPackageResource(TestCase): @patch.object(shell, "checked_call") @patch.object(System, "os_family", new = 'suse') def test_action_remove_suse(self, shell_mock): + sys.modules['rpm'] = MagicMock() + sys.modules['rpm'].TransactionSet.return_value = MagicMock() + sys.modules['rpm'].TransactionSet.return_value.dbMatch.return_value = [{'name':'some_package'}] with Environment('/') as env: Package("some_package", action = "remove" http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-common/src/main/python/resource_management/core/providers/package/apt.py ---------------------------------------------------------------------- diff --git a/ambari-common/src/main/python/resource_management/core/providers/package/apt.py b/ambari-common/src/main/python/resource_management/core/providers/package/apt.py index 76db791..c4cae2a 100644 --- a/ambari-common/src/main/python/resource_management/core/providers/package/apt.py +++ b/ambari-common/src/main/python/resource_management/core/providers/package/apt.py @@ -22,6 +22,7 @@ Ambari Agent import os import tempfile import shutil +import re from resource_management.core.providers.package import PackageProvider from resource_management.core import shell @@ -39,15 +40,10 @@ REMOVE_CMD = { } REPO_UPDATE_CMD = ['/usr/bin/apt-get', 'update','-qq'] -CHECK_EXISTENCE_CMD = "dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep '^%s$'" -GET_PACKAGES_BY_PATTERN_CMD = "apt-cache --names-only search '^%s$' | awk '{print $1}'" -GET_PACKAGE_STATUS_CMD = "dpkg --status '%s'" - -PACKAGE_INSTALLED_STATUS = 'Status: install ok installed' - -EMPTY_FILE = "/dev/null" APT_SOURCES_LIST_DIR = "/etc/apt/sources.list.d" +CHECK_CMD = "dpkg --get-selections | grep -v deinstall | awk '{print $1}' | grep ^%s$" + def replace_underscores(function_to_decorate): def wrapper(*args): self = args[0] @@ -60,7 +56,7 @@ class AptProvider(PackageProvider): @replace_underscores def install_package(self, name, use_repos=[]): - if not self._check_existence(name) or use_repos: + if use_repos or not self._check_existence(name): cmd = INSTALL_CMD[self.get_logoutput()] copied_sources_files = [] is_tmp_dir_created = False @@ -117,16 +113,24 @@ class AptProvider(PackageProvider): Logger.info("Skipping removal of non-existing package %s" % (name)) @replace_underscores - def _check_existence(self, name): - code, out = shell.call(CHECK_EXISTENCE_CMD % name) - if bool(code): - return False - elif '*' in name or '.' in name: # Check if all packages matching regexp are installed - code1, out1 = shell.call(GET_PACKAGES_BY_PATTERN_CMD % name) - for package_name in out1.splitlines(): - code2, out2 = shell.call(GET_PACKAGE_STATUS_CMD % package_name) - if PACKAGE_INSTALLED_STATUS not in out2.splitlines(): - return False - return True - else: - return True + def _check_existence(self, name): + """ + For regexp names: + If only part of packages were installed during early canceling. + Let's say: + 1. install hbase-2-3-.* + 2. Only hbase-2-3-1234 is installed, but is not hbase-2-3-1234-regionserver yet. + 3. We cancel the apt-get + + In that case this is bug of packages we require. + And hbase-2-3-*-regionserver should be added to metainfo.xml. + + Checking existence should never fail in such a case for hbase-2-3-.*, otherwise it + gonna break things like removing packages and some other things. + + Note: this method SHOULD NOT use apt-get (apt.cache is using dpkg not apt). Because a lot of issues we have, when customer have + apt-get in inconsistant state (locked, used, having invalid repo). Once packages are installed + we should not rely on that. + """ + code, out = shell.call(CHECK_CMD % name) + return not bool(code) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-common/src/main/python/resource_management/core/providers/package/yumrpm.py ---------------------------------------------------------------------- diff --git a/ambari-common/src/main/python/resource_management/core/providers/package/yumrpm.py b/ambari-common/src/main/python/resource_management/core/providers/package/yumrpm.py index bfe7fa4..2d39c4d 100644 --- a/ambari-common/src/main/python/resource_management/core/providers/package/yumrpm.py +++ b/ambari-common/src/main/python/resource_management/core/providers/package/yumrpm.py @@ -24,7 +24,9 @@ from resource_management.core.providers.package import PackageProvider from resource_management.core import shell from resource_management.core.shell import string_cmd_from_args_list from resource_management.core.logger import Logger +from resource_management.core.utils import suppress_stdout import os +import re INSTALL_CMD = { True: ['/usr/bin/yum', '-y', 'install'], @@ -36,12 +38,9 @@ REMOVE_CMD = { False: ['/usr/bin/yum', '-d', '0', '-e', '0', '-y', 'erase'], } -CHECK_CMD = "installed_pkgs=`rpm -qa '%s'` ; [ ! -z \"$installed_pkgs\" ]" -CHECK_AVAILABLE_PACKAGES_CMD = "! yum list available '%s'" - class YumProvider(PackageProvider): def install_package(self, name, use_repos=[]): - if not self._check_existence(name) or use_repos: + if use_repos or not self._check_existence(name): cmd = INSTALL_CMD[self.get_logoutput()] if use_repos: enable_repo_option = '--enablerepo=' + ",".join(use_repos) @@ -64,13 +63,34 @@ class YumProvider(PackageProvider): Logger.info("Skipping removal of non-existing package %s" % (name)) def _check_existence(self, name): - if '.' in name: # To work with names like 'zookeeper_2_2_1_0_2072.noarch' - name = os.path.splitext(name)[0] - code, out = shell.call(CHECK_CMD % name) - if bool(code): - return False - elif '*' in name or '?' in name: # Check if all packages matching pattern are installed - code1, out1 = shell.call(CHECK_AVAILABLE_PACKAGES_CMD % name) - return not bool(code1) - else: - return True + """ + For regexp names: + If only part of packages were installed during early canceling. + Let's say: + 1. install hbase_2_3_* + 2. Only hbase_2_3_1234 is installed, but is not hbase_2_3_1234_regionserver yet. + 3. We cancel the yum + + In that case this is bug of packages we require. + And hbase_2_3_*_regionserver should be added to metainfo.xml. + + Checking existence should never fail in such a case for hbase_2_3_*, otherwise it + gonna break things like removing packages and some others. + + Note: this method SHOULD NOT use yum directly (yum.rpmdb doesn't use it). Because a lot of issues we have, when customer have + yum in inconsistant state (locked, used, having invalid repo). Once packages are installed + we should not rely on that. + """ + import yum # Python Yum API is much faster then other check methods. (even then "import rpm") + yb = yum.YumBase() + name_regex = re.escape(name).replace("\\?", ".").replace("\\*", ".*") + '$' + regex = re.compile(name_regex) + + with suppress_stdout(): + package_list = yb.rpmdb.simplePkgList() + + for package in package_list: + if regex.match(package[0]): + return True + + return False \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-common/src/main/python/resource_management/core/providers/package/zypper.py ---------------------------------------------------------------------- diff --git a/ambari-common/src/main/python/resource_management/core/providers/package/zypper.py b/ambari-common/src/main/python/resource_management/core/providers/package/zypper.py index 367d3cc..b7f33f4 100644 --- a/ambari-common/src/main/python/resource_management/core/providers/package/zypper.py +++ b/ambari-common/src/main/python/resource_management/core/providers/package/zypper.py @@ -24,6 +24,9 @@ from resource_management.core.providers.package import PackageProvider from resource_management.core import shell from resource_management.core.shell import string_cmd_from_args_list from resource_management.core.logger import Logger +from resource_management.core.utils import suppress_stdout + +import re INSTALL_CMD = { True: ['/usr/bin/zypper', 'install', '--auto-agree-with-licenses', '--no-confirm'], @@ -33,10 +36,6 @@ REMOVE_CMD = { True: ['/usr/bin/zypper', 'remove', '--no-confirm'], False: ['/usr/bin/zypper', '--quiet', 'remove', '--no-confirm'], } -CHECK_CMD = "installed_pkgs=`rpm -qa '%s'` ; [ ! -z \"$installed_pkgs\" ]" -GET_NOT_INSTALLED_CMD = "zypper --non-interactive search --type package --uninstalled-only --match-exact '%s'" - -NO_PACKAGES_FOUND_STATUS = 'No packages found.' LIST_ACTIVE_REPOS_CMD = ['/usr/bin/zypper', 'repos'] @@ -55,7 +54,7 @@ def get_active_base_repos(): class ZypperProvider(PackageProvider): def install_package(self, name, use_repos=[]): - if not self._check_existence(name) or use_repos: + if use_repos or not self._check_existence(name): cmd = INSTALL_CMD[self.get_logoutput()] if use_repos: active_base_repos = get_active_base_repos() @@ -85,11 +84,32 @@ class ZypperProvider(PackageProvider): Logger.info("Skipping removal of non-existing package %s" % (name)) def _check_existence(self, name): - code, out = shell.call(CHECK_CMD % name) - if bool(code): - return False - elif '*' in name or '?' in name: # Check if all packages matching pattern are installed - code1, out1 = shell.call(GET_NOT_INSTALLED_CMD % name) - return NO_PACKAGES_FOUND_STATUS in out1.splitlines() - else: - return True + """ + For regexp names: + If only part of packages were installed during early canceling. + Let's say: + 1. install hbase_2_3_* + 2. Only hbase_2_3_1234 is installed, but is not hbase_2_3_1234_regionserver yet. + 3. We cancel the zypper + + In that case this is bug of packages we require. + And hbase_2_3_*_regionserver should be added to metainfo.xml. + + Checking existence should never fail in such a case for hbase_2_3_*, otherwise it + gonna break things like removing packages and some other things. + + Note: this method SHOULD NOT use zypper. Because a lot of issues we have, when customer have + zypper in inconsistant state (locked, used, having invalid repo). Once packages are installed + we should not rely on that. + """ + import rpm # this is faster then calling 'rpm'-binary externally. + ts = rpm.TransactionSet() + packages = ts.dbMatch() + + name_regex = re.escape(name).replace("\\?", ".").replace("\\*", ".*") + '$' + regex = re.compile(name_regex) + + for package in packages: + if regex.match(package['name']): + return True + return False http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-common/src/main/python/resource_management/core/shell.py ---------------------------------------------------------------------- diff --git a/ambari-common/src/main/python/resource_management/core/shell.py b/ambari-common/src/main/python/resource_management/core/shell.py index f2b5000..71b6501 100644 --- a/ambari-common/src/main/python/resource_management/core/shell.py +++ b/ambari-common/src/main/python/resource_management/core/shell.py @@ -170,7 +170,7 @@ def _call(command, logoutput=None, throw_on_failure=True, if ready: line = os.read(master_fd, 512) if not line: - break + continue out += line try: http://git-wip-us.apache.org/repos/asf/ambari/blob/b570fc1f/ambari-common/src/main/python/resource_management/core/utils.py ---------------------------------------------------------------------- diff --git a/ambari-common/src/main/python/resource_management/core/utils.py b/ambari-common/src/main/python/resource_management/core/utils.py index 52a12b3..d9f678b 100644 --- a/ambari-common/src/main/python/resource_management/core/utils.py +++ b/ambari-common/src/main/python/resource_management/core/utils.py @@ -20,6 +20,9 @@ Ambari Agent """ +import contextlib +import sys +import cStringIO from resource_management.core.exceptions import Fail class AttributeDictionary(object): @@ -106,3 +109,10 @@ def checked_unite(dict1, dict2): result.update(dict2) return result + [email protected] +def suppress_stdout(): + save_stdout = sys.stdout + sys.stdout = cStringIO.StringIO() + yield + sys.stdout = save_stdout
