From f12e173649f0538e18f45fefdd9606a362ddab12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Nussbaumer?= <rn@google.com> Date: Thu, 12 Aug 2010 10:28:12 +0200 Subject: [PATCH] Adding a runtime configuration library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is used to expand the users/group names just once at initial call. Signed-off-by: RenΓ© Nussbaumer <rn@google.com> Reviewed-by: Michael Hanselmann <hansmi@google.com> --- Makefile.am | 8 +++ lib/constants.py | 6 ++ lib/runtime.py | 120 ++++++++++++++++++++++++++++++++ test/ganeti.runtime_unittest.py | 109 +++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 lib/runtime.py create mode 100755 test/ganeti.runtime_unittest.py diff --git a/Makefile.am b/Makefile.am index a9ef6379f..b89e24170 100644 --- a/Makefile.am +++ b/Makefile.am @@ -124,6 +124,7 @@ pkgpython_PYTHON = \ lib/objects.py \ lib/opcodes.py \ lib/rpc.py \ + lib/runtime.py \ lib/serializer.py \ lib/ssconf.py \ lib/ssh.py \ @@ -393,6 +394,7 @@ python_tests = \ test/ganeti.rapi.resources_unittest.py \ test/ganeti.rapi.rlib2_unittest.py \ test/ganeti.rpc_unittest.py \ + test/ganeti.runtime_unittest.py \ test/ganeti.serializer_unittest.py \ test/ganeti.ssh_unittest.py \ test/ganeti.uidpool_unittest.py \ @@ -565,8 +567,14 @@ lib/_autoconf.py: Makefile stamp-directories vcs-version echo "DRBD_BARRIERS = $(DRBD_BARRIERS)"; \ echo "SYSLOG_USAGE = '$(SYSLOG_USAGE)'"; \ echo "DAEMONS_GROUP = '$(DAEMONS_GROUP)'"; \ + echo "ADMIN_GROUP = '$(ADMIN_GROUP)'"; \ echo "MASTERD_USER = '$(MASTERD_USER)'"; \ + echo "MASTERD_GROUP = '$(MASTERD_GROUP)'"; \ echo "RAPI_USER = '$(RAPI_USER)'"; \ + echo "RAPI_GROUP = '$(RAPI_GROUP)'"; \ + echo "CONFD_USER = '$(CONFD_USER)'"; \ + echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \ + echo "NODED_USER = '$(NODED_USER)'"; \ echo "VCS_VERSION = '$$VCSVER'"; \ } > $@ diff --git a/lib/constants.py b/lib/constants.py index ca76eef63..402549859 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -86,8 +86,14 @@ CONFIG_VERSION = BuildVersion(CONFIG_MAJOR, CONFIG_MINOR, CONFIG_REVISION) # user separation DAEMONS_GROUP = _autoconf.DAEMONS_GROUP +ADMIN_GROUP = _autoconf.ADMIN_GROUP MASTERD_USER = _autoconf.MASTERD_USER +MASTERD_GROUP = _autoconf.MASTERD_GROUP RAPI_USER = _autoconf.RAPI_USER +RAPI_GROUP = _autoconf.RAPI_GROUP +CONFD_USER = _autoconf.CONFD_USER +CONFD_GROUP = _autoconf.CONFD_GROUP +NODED_USER = _autoconf.NODED_USER # file paths DATA_DIR = _autoconf.LOCALSTATEDIR + "/lib/ganeti" diff --git a/lib/runtime.py b/lib/runtime.py new file mode 100644 index 000000000..2d65ef0ba --- /dev/null +++ b/lib/runtime.py @@ -0,0 +1,120 @@ +# + +# Copyright (C) 2010 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""Module implementing configuration details at runtime. + +""" + + +import grp +import pwd +import threading + +from ganeti import constants +from ganeti import errors + + +_priv = None +_priv_lock = threading.Lock() + + +def GetUid(user, _getpwnam): + """Retrieve the uid from the database. + + @type user: string + @param user: The username to retrieve + @return: The resolved uid + + """ + try: + return _getpwnam(user).pw_uid + except KeyError, err: + raise errors.ConfigurationError("User '%s' not found (%s)" % (user, err)) + + +def GetGid(group, _getgrnam): + """Retrieve the gid from the database. + + @type group: string + @param group: The group name to retrieve + @return: The resolved gid + + """ + try: + return _getgrnam(group).gr_gid + except KeyError, err: + raise errors.ConfigurationError("Group '%s' not found (%s)" % (group, err)) + + +class GetentResolver: + """Resolves Ganeti uids and gids by name. + + @ivar masterd_uid: The resolved uid of the masterd user + @ivar masterd_gid: The resolved gid of the masterd group + @ivar confd_uid: The resolved uid of the confd user + @ivar confd_gid: The resolved gid of the confd group + @ivar rapi_uid: The resolved uid of the rapi user + @ivar rapi_gid: The resolved gid of the rapi group + @ivar noded_uid: The resolved uid of the noded user + + @ivar daemons_gid: The resolved gid of the daemons group + @ivar admin_gid: The resolved gid of the admin group + """ + def __init__(self, _getpwnam=pwd.getpwnam, _getgrnam=grp.getgrnam): + """Initialize the resolver. + + """ + # Daemon pairs + self.masterd_uid = GetUid(constants.MASTERD_USER, _getpwnam) + self.masterd_gid = GetGid(constants.MASTERD_GROUP, _getgrnam) + + self.confd_uid = GetUid(constants.CONFD_USER, _getpwnam) + self.confd_gid = GetGid(constants.CONFD_GROUP, _getgrnam) + + self.rapi_uid = GetUid(constants.RAPI_USER, _getpwnam) + self.rapi_gid = GetGid(constants.RAPI_GROUP, _getgrnam) + + self.noded_uid = GetUid(constants.NODED_USER, _getpwnam) + + # Misc Ganeti groups + self.daemons_gid = GetGid(constants.DAEMONS_GROUP, _getgrnam) + self.admin_gid = GetGid(constants.ADMIN_GROUP, _getgrnam) + + +def GetEnts(resolver=GetentResolver): + """Singleton wrapper around resolver instance. + + As this method is accessed by multiple threads at the same time + we need to take thread-safty carefully + + """ + # We need to use the global keyword here + global _priv # pylint: disable-msg=W0603 + + if not _priv: + _priv_lock.acquire() + try: + if not _priv: + # W0621: Redefine '_priv' from outer scope (used for singleton) + _priv = resolver() # pylint: disable-msg=W0621 + finally: + _priv_lock.release() + + return _priv + diff --git a/test/ganeti.runtime_unittest.py b/test/ganeti.runtime_unittest.py new file mode 100755 index 000000000..3ba9ac873 --- /dev/null +++ b/test/ganeti.runtime_unittest.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# + +# Copyright (C) 2010 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""Script for testing ganeti.runtime""" + +from ganeti import constants +from ganeti import errors +from ganeti import runtime + +import testutils + + +class _EntStub: + def __init__(self, uid=None, gid=None): + self.pw_uid = uid + self.gr_gid = gid + + +def _StubGetpwnam(user): + users = { + constants.MASTERD_USER: _EntStub(uid=0), + constants.CONFD_USER: _EntStub(uid=1), + constants.RAPI_USER: _EntStub(uid=2), + constants.NODED_USER: _EntStub(uid=3), + } + return users[user] + + +def _StubGetgrnam(group): + groups = { + constants.MASTERD_GROUP: _EntStub(gid=0), + constants.CONFD_GROUP: _EntStub(gid=1), + constants.RAPI_GROUP: _EntStub(gid=2), + constants.DAEMONS_GROUP: _EntStub(gid=3), + constants.ADMIN_GROUP: _EntStub(gid=4), + } + return groups[group] + + +def _RaisingStubGetpwnam(user): + raise KeyError("user not found") + + +def _RaisingStubGetgrnam(group): + raise KeyError("group not found") + + +class ResolverStubRaising(object): + def __init__(self): + raise errors.ConfigurationError("No entries") + + +class TestErrors(testutils.GanetiTestCase): + def testEverythingSuccessful(self): + resolver = runtime.GetentResolver(_getpwnam=_StubGetpwnam, + _getgrnam=_StubGetgrnam) + + self.assertEqual(resolver.masterd_uid, + _StubGetpwnam(constants.MASTERD_USER).pw_uid) + self.assertEqual(resolver.masterd_gid, + _StubGetgrnam(constants.MASTERD_GROUP).gr_gid) + self.assertEqual(resolver.confd_uid, + _StubGetpwnam(constants.CONFD_USER).pw_uid) + self.assertEqual(resolver.confd_gid, + _StubGetgrnam(constants.CONFD_GROUP).gr_gid) + self.assertEqual(resolver.rapi_uid, + _StubGetpwnam(constants.RAPI_USER).pw_uid) + self.assertEqual(resolver.rapi_gid, + _StubGetgrnam(constants.RAPI_GROUP).gr_gid) + self.assertEqual(resolver.noded_uid, + _StubGetpwnam(constants.NODED_USER).pw_uid) + + self.assertEqual(resolver.daemons_gid, + _StubGetgrnam(constants.DAEMONS_GROUP).gr_gid) + self.assertEqual(resolver.admin_gid, + _StubGetgrnam(constants.ADMIN_GROUP).gr_gid) + + def testUserNotFound(self): + self.assertRaises(errors.ConfigurationError, runtime.GetentResolver, + _getpwnam=_RaisingStubGetpwnam, _getgrnam=_StubGetgrnam) + + def testGroupNotFound(self): + self.assertRaises(errors.ConfigurationError, runtime.GetentResolver, + _getpwnam=_StubGetpwnam, _getgrnam=_RaisingStubGetgrnam) + + def testUserNotFoundGetEnts(self): + self.assertRaises(errors.ConfigurationError, runtime.GetEnts, + resolver=ResolverStubRaising) + + +if __name__ == "__main__": + testutils.GanetiTestProgram() -- GitLab