summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@mdehaan.rdu.redhat.com>2007-09-10 16:23:18 -0400
committerMichael DeHaan <mdehaan@mdehaan.rdu.redhat.com>2007-09-10 16:23:18 -0400
commitd875f3bf66625fd5548e57fd30574b9a9623bb63 (patch)
tree025457743661b8ea90f9a34a5d324a415be1e83e /cobbler
parentc57ccfadd1b244769187bf2346861a7917b6a281 (diff)
downloadthird_party-cobbler-d875f3bf66625fd5548e57fd30574b9a9623bb63.tar.gz
third_party-cobbler-d875f3bf66625fd5548e57fd30574b9a9623bb63.tar.xz
third_party-cobbler-d875f3bf66625fd5548e57fd30574b9a9623bb63.zip
Providing for a read-write XMLRPC API, off by default, set xmlrpc_rw_enabled to 1 in settings
to enable. Currently no methods implemented other than test, user validation is a stub.
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/cobblerd.py200
-rw-r--r--cobbler/remote.py361
-rw-r--r--cobbler/settings.py2
3 files changed, 416 insertions, 147 deletions
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py
index 2375dee..74570dc 100644
--- a/cobbler/cobblerd.py
+++ b/cobbler/cobblerd.py
@@ -23,30 +23,52 @@ import api as cobbler_api
import yaml # Howell Clark version
import utils
import sub_process
+import remote
def main():
core(logger=None)
def core(logger=None):
- bootapi = cobbler_api.BootAPI()
- settings = bootapi.settings()
- syslog_port = settings.syslog_port
- xmlrpc_port = settings.xmlrpc_port
+ bootapi = cobbler_api.BootAPI()
+ settings = bootapi.settings()
+ syslog_port = settings.syslog_port
+ xmlrpc_port = settings.xmlrpc_port
+ xmlrpc_port2 = settings.xmlrpc_rw_port
pid = os.fork()
if pid == 0:
- do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
+ # part one: XMLRPC -- which may be just read-only or both read-only and read-write
+ do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger)
else:
- if os.path.exists("/usr/bin/avahi-publish-service"):
- pid2 = os.fork()
- if pid2 == 0:
- do_syslog(bootapi, settings, syslog_port, logger)
- else:
- do_avahi(bootapi, settings, logger)
+ # part two: syslog, or syslog+avahi if avahi is installed
+ do_other_tasks(bootapi, settings, syslog_port, logger)
+
+def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger):
+ if str(settings.xmlrpc_rw_enabled) != "0":
+ pid2 = os.fork()
+ if pid2 == 0:
+ do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
else:
+ do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger)
+ else:
+ do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
+
+
+def do_other_tasks(bootapi, settings, syslog_port, logger):
+
+ # FUTURE: this should also start the Web UI, if the dependencies
+ # are available.
+
+ if os.path.exists("/usr/bin/avahi-publish-service"):
+ pid2 = os.fork()
+ if pid2 == 0:
do_syslog(bootapi, settings, syslog_port, logger)
+ else:
+ do_avahi(bootapi, settings, logger)
+ else:
+ do_syslog(bootapi, settings, syslog_port, logger)
def log(logger,msg):
@@ -69,8 +91,11 @@ def do_avahi(bootapi, settings, logger):
def do_xmlrpc(bootapi, settings, port, logger):
- xinterface = CobblerXMLRPCInterface(bootapi,logger)
- server = CobblerXMLRPCServer(('', port))
+ # This is the simple XMLRPC API we provide to koan and other
+ # apps that do not need to manage Cobbler's config
+
+ xinterface = remote.CobblerXMLRPCInterface(bootapi,logger)
+ server = remote.CobblerXMLRPCServer(('', port))
server.logRequests = 0 # don't print stuff
log(logger, "XMLRPC running on %s" % port)
server.register_instance(xinterface)
@@ -81,7 +106,21 @@ def do_xmlrpc(bootapi, settings, port, logger):
except IOError:
# interrupted? try to serve again
time.sleep(0.5)
-
+
+def do_xmlrpc_rw(bootapi,settings,port,logger):
+
+ xinterface = remote.CobblerReadWriteXMLRPCInterface(bootapi,logger)
+ server = remote.CobblerReadWriteXMLRPCServer(('', port))
+ server.logRequests = 0 # don't print stuff
+ log(logger, "XMLRPC (read-write variant) running on %s" % port)
+ server.register_instance(xinterface)
+
+ while True:
+ try:
+ server.serve_forever()
+ except IOError:
+ # interrupted? try to serve again
+ time.sleep(0.5)
def do_syslog(bootapi, settings, port, logger):
@@ -116,139 +155,6 @@ def do_syslog(bootapi, settings, port, logger):
logfile.write("\n")
logfile.close()
-# FIXME: somewhat inefficient as it reloads the configs each time
-# better to watch files for changes?
-
-class CobblerXMLRPCInterface:
-
- def __init__(self,api,logger):
- self.api = api
- self.logger = logger
-
- def __sorter(self,a,b):
- return cmp(a["name"],b["name"])
-
- def get_settings(self):
- self.api.clear()
- self.api.deserialize()
- data = self.api.settings().to_datastruct()
- return self.fix_none(data)
-
- def disable_netboot(self,name):
- # used by nopxe.cgi
- self.api.clear()
- self.api.deserialize()
- if not self.api.settings().pxe_just_once:
- # feature disabled!
- return False
- systems = self.api.systems()
- obj = systems.find(name=name)
- if obj == None:
- # system not found!
- return False
- obj.set_netboot_enabled(0)
- systems.add(obj,with_copy=True)
- return True
-
- def __get_all(self,collection):
- self.api.clear()
- self.api.deserialize()
- data = collection.to_datastruct()
- data.sort(self.__sorter)
- return self.fix_none(data)
-
- def version(self):
- return self.api.version()
-
- def get_distros(self):
- return self.__get_all(self.api.distros())
-
- def get_profiles(self):
- return self.__get_all(self.api.profiles())
-
- def get_systems(self):
- return self.__get_all(self.api.systems())
-
- def __get_specific(self,collection,name):
- self.api.clear()
- self.api.deserialize()
- item = collection.find(name=name)
- if item is None:
- return self.fix_none({})
- return self.fix_none(item.to_datastruct())
-
- def get_distro(self,name):
- return self.__get_specific(self.api.distros(),name)
-
- def get_profile(self,name):
- return self.__get_specific(self.api.profiles(),name)
-
- def get_system(self,name):
- name = self.fix_system_name(name)
- return self.__get_specific(self.api.systems(),name)
-
- def get_repo(self,name):
- return self.__get_specific(self.api.repos(),name)
-
- def get_distro_for_koan(self,name):
- self.api.clear()
- self.api.deserialize()
- obj = self.api.distros().find(name=name)
- if obj is not None:
- return self.fix_none(utils.blender(True, obj))
- return self.fix_none({})
-
- def get_profile_for_koan(self,name):
- self.api.clear()
- self.api.deserialize()
- obj = self.api.profiles().find(name=name)
- if obj is not None:
- return self.fix_none(utils.blender(True, obj))
- return self.fix_none({})
-
- def get_system_for_koan(self,name):
- self.api.clear()
- self.api.deserialize()
- obj = self.api.systems().find(name=name)
- if obj is not None:
- return self.fix_none(utils.blender(True, obj))
- return self.fix_none({})
-
- def get_repo_for_koan(self,name):
- self.api.clear()
- self.api.deserialize()
- obj = self.api.repos().find(name=name)
- if obj is not None:
- return self.fix_none(utils.blender(True, obj))
- return self.fix_none({})
-
- def fix_none(self,data,recurse=False):
- """
- Convert None in XMLRPC to just '~'. Above hack should
- do this, but let's make extra sure.
- """
-
- if data is None:
- data = '~'
-
- elif type(data) == list:
- data = [ self.fix_none(x,recurse=True) for x in data ]
-
- elif type(data) == dict:
- for key in data.keys():
- data[key] = self.fix_none(data[key],recurse=True)
-
- return data
-
-
-
-class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
-
- def __init__(self, args):
- self.allow_reuse_address = True
- SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
-
-
if __name__ == "__main__":
main()
diff --git a/cobbler/remote.py b/cobbler/remote.py
new file mode 100644
index 0000000..7fb73a3
--- /dev/null
+++ b/cobbler/remote.py
@@ -0,0 +1,361 @@
+# Interface for Cobbler's XMLRPC API(s).
+# there are two:
+# a read-only API that koan uses
+# a read-write API that requires logins
+
+# Copyright 2007, Red Hat, Inc
+# Michael DeHaan <mdehaan@redhat.com>
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import sys
+import socket
+import time
+import os
+import SimpleXMLRPCServer
+import glob
+from rhpl.translate import _, N_, textdomain, utf8
+import xmlrpclib
+import logging
+import base64
+
+import api as cobbler_api
+import yaml # Howell Clark version
+import utils
+from cexceptions import *
+import sub_process
+
+# FIXME: make configurable?
+TOKEN_TIMEOUT = 60*60 # 60 minutes
+
+# *********************************************************************************
+# *********************************************************************************
+
+class CobblerXMLRPCInterface:
+
+ # note: public methods take an optional parameter token that is just
+ # here for consistancy with the ReadWrite API. The tokens for the read only
+ # interface are intentionally /not/ validated. It's a public API.
+
+ def __init__(self,api,logger):
+ self.api = api
+ self.logger = logger
+
+ def __sorter(self,a,b):
+ return cmp(a["name"],b["name"])
+
+ def get_settings(self,token=None):
+ """
+ Return the contents of /var/lib/cobbler/settings, which is a hash.
+ """
+ self.api.clear()
+ self.api.deserialize()
+ data = self.api.settings().to_datastruct()
+ return self._fix_none(data)
+
+ def disable_netboot(self,name,token=None):
+ """
+ This is a feature used by the pxe_just_once support, see manpage.
+ Sets system named "name" to no-longer PXE. Disabled by default as
+ this requires public API access and is technically a read-write operation.
+ """
+ # used by nopxe.cgi
+ self.api.clear()
+ self.api.deserialize()
+ if not self.api.settings().pxe_just_once:
+ # feature disabled!
+ return False
+ systems = self.api.systems()
+ obj = systems.find(name=name)
+ if obj == None:
+ # system not found!
+ return False
+ obj.set_netboot_enabled(0)
+ systems.add(obj,with_copy=True)
+ return True
+
+ def __get_all(self,collection):
+ self.api.clear()
+ self.api.deserialize()
+ data = collection.to_datastruct()
+ data.sort(self.__sorter)
+ return self._fix_none(data)
+
+ def version(self,token=None):
+ """
+ Return the cobbler version for compatibility testing with remote applications.
+ Returns as a float, 0.6.1-2 should result in (int) "0.612".
+ """
+ return self.api.version()
+
+ def get_distros(self,token=None):
+ """
+ Returns all cobbler distros as an array of hashes.
+ """
+ return self.__get_all(self.api.distros())
+
+ def get_profiles(self,token=None):
+ """
+ Returns all cobbler profiles as an array of hashes.
+ """
+ return self.__get_all(self.api.profiles())
+
+ def get_systems(self,token=None):
+ """
+ Returns all cobbler systems as an array of hashes.
+ """
+ return self.__get_all(self.api.systems())
+
+ def __get_specific(self,collection,name):
+ self.api.clear()
+ self.api.deserialize()
+ item = collection.find(name=name)
+ if item is None:
+ return self._fix_none({})
+ return self._fix_none(item.to_datastruct())
+
+ def get_distro(self,name,token=None):
+ """
+ Returns the distro named "name" as a hash.
+ """
+ return self.__get_specific(self.api.distros(),name)
+
+ def get_profile(self,name,token=None):
+ """
+ Returns the profile named "name" as a hash.
+ """
+ return self.__get_specific(self.api.profiles(),name)
+
+ def get_system(self,name,token=None):
+ """
+ Returns the system named "name" as a hash.
+ """
+ name = self.fix_system_name(name)
+ return self.__get_specific(self.api.systems(),name)
+
+ def get_repo(self,name,token=None):
+ """
+ Returns the repo named "name" as a hash.
+ """
+ return self.__get_specific(self.api.repos(),name)
+
+ def get_distro_as_rendered(self,name,token=None):
+ """
+ Return the distribution as passed through cobbler's
+ inheritance/graph engine. Shows what would be installed, not
+ the input data.
+ """
+ return self.get_distro_for_koan(self,name,token)
+
+ def get_distro_for_koan(self,name,token=None):
+ """
+ Same as get_distro_as_rendered.
+ """
+ self.api.clear()
+ self.api.deserialize()
+ obj = self.api.distros().find(name=name)
+ if obj is not None:
+ return self._fix_none(utils.blender(True, obj))
+ return self._fix_none({})
+
+ def get_profile_as_rendered(self,name,token=None):
+ """
+ Return the profile as passed through cobbler's
+ inheritance/graph engine. Shows what would be installed, not
+ the input data.
+ """
+ return self.get_profile_for_koan(name,token)
+
+ def get_profile_for_koan(self,name,token=None):
+ """
+ Same as get_profile_as_rendered
+ """
+ self.api.clear()
+ self.api.deserialize()
+ obj = self.api.profiles().find(name=name)
+ if obj is not None:
+ return self._fix_none(utils.blender(True, obj))
+ return self._fix_none({})
+
+ def get_system_as_rendered(self,name,token=None):
+ """
+ Return the system as passed through cobbler's
+ inheritance/graph engine. Shows what would be installed, not
+ the input data.
+ """
+ return self.get_system_for_koan(self,name,token)
+
+ def get_system_for_koan(self,name,token=None):
+ """
+ Same as get_system_as_rendered.
+ """
+ self.api.clear()
+ self.api.deserialize()
+ obj = self.api.systems().find(name=name)
+ if obj is not None:
+ return self._fix_none(utils.blender(True, obj))
+ return self._fix_none({})
+
+ def get_repo_as_rendered(self,name,token=None):
+ """
+ Return the repo as passed through cobbler's
+ inheritance/graph engine. Shows what would be installed, not
+ the input data.
+ """
+ return self.get_repo_for_koan(self,name,token)
+
+ def get_repo_for_koan(self,name,token=None):
+ """
+ Same as get_repo_as_rendered.
+ """
+ self.api.clear()
+ self.api.deserialize()
+ obj = self.api.repos().find(name=name)
+ if obj is not None:
+ return self._fix_none(utils.blender(True, obj))
+ return self._fix_none({})
+
+ def _fix_none(self,data,recurse=False):
+ """
+ Convert None in XMLRPC to just '~'. The above
+ XMLRPC module hack should do this, but let's make extra sure.
+ """
+
+ if data is None:
+ data = '~'
+
+ elif type(data) == list:
+ data = [ self._fix_none(x,recurse=True) for x in data ]
+
+ elif type(data) == dict:
+ for key in data.keys():
+ data[key] = self._fix_none(data[key],recurse=True)
+
+ return data
+
+# *********************************************************************************
+# *********************************************************************************
+
+class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
+ def __init__(self, args):
+ self.allow_reuse_address = True
+ SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
+
+# *********************************************************************************
+# *********************************************************************************
+
+class CobblerReadWriteXMLRPCInterface:
+
+ def __init__(self,api,logger):
+ self.api = api
+ self.logger = logger
+ self.token_cache = {}
+
+ def __make_token(self):
+ """
+ Returns a new random token.
+ """
+ urandom = open("/dev/urandom")
+ b64 = base64.b64encode(urandom.read(100))
+ self.token_cache[b64] = time.time()
+ return b64
+
+ def __invalidate_expired_tokens(self):
+ """
+ Deletes any login tokens that might have expired.
+ """
+ timenow = time.time()
+ for token in self.token_cache:
+ tokentime = self.token_cache[token]
+ if (timenow > tokentime + TOKEN_TIMEOUT):
+ self.logger.debug("expiring token: %s" % token)
+ del self.token_cache[token]
+
+ def __validate_user(self,user,password):
+ """
+ Returns whether this user/pass combo should be given
+ access to the cobbler read-write API.
+
+ FIXME: always returns True, implement this.
+ """
+ if user == "exampleuser":
+ return True
+ else:
+ return False
+
+ def __validate_token(self,token):
+ """
+ Checks to see if an API method can be called when
+ the given token is passed in. Updates the timestamp
+ of the token automatically to prevent the need to
+ repeatedly call login(). Any method that needs
+ access control should call this before doing anything
+ else.
+ """
+ ok = False
+ self.__invalidate_expired_tokens()
+ if self.token_cache.has_key(token):
+ ok = True
+ self.token_cache[token] = time.time() # update to prevent timeout
+ else:
+ self.logger.debug("invalid token: %s" % token)
+ raise CX(_("invalid token: %s" % token))
+
+ def login(self,user,password):
+ if self.__validate_user(user,password):
+ token = self.__make_token()
+ self.logger.info("login succeeded: %s" % user)
+ return token
+ else:
+ self.logger.info("login failed: %s" % user)
+ raise CX(_("login failed: %s") % user)
+
+ def test(self,token=None):
+ self.__validate_token(token)
+ return "passed"
+
+
+# *********************************************************************************
+# *********************************************************************************
+
+class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
+
+ def __init__(self, args):
+ self.allow_reuse_address = True
+ SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
+
+# *********************************************************************************
+# *********************************************************************************
+
+if __name__ == "__main__":
+
+ logger = logging.getLogger("cobbler.cobblerd")
+ logger.setLevel(logging.DEBUG)
+ ch = logging.FileHandler("/var/log/cobbler/cobblerd.log")
+ ch.setLevel(logging.DEBUG)
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ ch.setFormatter(formatter)
+ logger.addHandler(ch)
+ api = cobbler_api.BootAPI()
+
+ remote = CobblerReadWriteXMLRPCInterface(api,logger)
+ token = remote.login("exampleuser","examplepass")
+ print token
+ rc = remote.test(token)
+ print "test result: %s" % rc
+
+ remote = CobblerReadWriteXMLRPCInterface(api,logger)
+ try:
+ token = remote.login("exampleuser2","examplepass")
+ except:
+ token = "fake_token"
+ print token
+ rc = remote.test(token)
+ print "test result: %s" % rc
+
+ print "cache: %s" % remote.token_cache
diff --git a/cobbler/settings.py b/cobbler/settings.py
index e8b713f..3356bbd 100644
--- a/cobbler/settings.py
+++ b/cobbler/settings.py
@@ -47,6 +47,8 @@ DEFAULTS = {
"tftpd_conf" : "/etc/xinetd.d/tftp",
"webdir" : "/var/www/cobbler",
"xmlrpc_port" : 25151,
+ "xmlrpc_rw_enabled" : 0,
+ "xmlrpc_rw_port" : 25152,
"yum_core_mirror_from_server" : 0
}