diff options
author | Seth Vidal <skvidal@fedoraproject.org> | 2007-09-20 21:28:51 -0400 |
---|---|---|
committer | Seth Vidal <skvidal@fedoraproject.org> | 2007-09-20 21:28:51 -0400 |
commit | 8d168259f1cb0af25a7ee342bd1c32cd5bfdd424 (patch) | |
tree | 9c1d80b6da18a902b03ba7b21ec6bd0a60aabbfa | |
parent | a83c4bcc40aae7c8b8058d831667ee1e07a969dc (diff) | |
parent | 98010f591948fb4bf297c1c0c32def42f766edca (diff) | |
download | func-8d168259f1cb0af25a7ee342bd1c32cd5bfdd424.tar.gz func-8d168259f1cb0af25a7ee342bd1c32cd5bfdd424.tar.xz func-8d168259f1cb0af25a7ee342bd1c32cd5bfdd424.zip |
Merge branch 'master' of ssh://git.fedoraproject.org/git/hosted/func
* 'master' of ssh://git.fedoraproject.org/git/hosted/func: (27 commits)
just a friendly reminder
we are not vf_server, change I!*N domain
Add virt module.
Add test code for virt.
add a very simple, very dumb commandline client:
Remove messages.pot from po dir, since its automatically generated
Get rid of extra / in module loading error
pychecker cleanups
Add po dir to git
Prevent XMLRPC server from printing to console.
Catch FuncException when the config file is missing and exit gracefully
Implement a quickie service control module
Removing VF items + misc cleanup
Clean up some speclint warnings
Baseobj bites the dust.
remove all the --debug "try to run from the src tree" crap
debug spew cleanup to protect the unwashed masses from foo poisoning
fix up config_data to use ConfigParser correctly
attempt to let us run with --debug flag to run from src checkout
attempts at letting us run from a installed, or local modules
...
36 files changed, 853 insertions, 2129 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5f30f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +\#*\# +*.pyc +*.pyo +*.swp +MANIFEST +rpm-build +dist @@ -0,0 +1,16 @@ + +func is written by (alphabetically) ... + + Michael DeHaan <mdehaan@redhat.com> + Adrian Likins <alikins@redhat.com> + Scott Sseago <sseago@redhat.com> + Seth Vidal <skvidal@redhat.com> + ... + (committers: please add yourself) + +Additional patches and contributions by ... + + ... + [ send in patches to get your name here ] + + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0c3c56d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include settings +include version +recursive-include docs * +recursive-include init-scripts * +recursive-include po *.po +recursive-include po *.pot +include AUTHORS diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..b831591 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +VERSION = $(shell echo `awk '{ print $$1 }' version`) +RELEASE = $(shell echo `awk '{ print $$2 }' version`) +NEWRELEASE = $(shell echo $$(($(RELEASE) + 1))) + +MESSAGESPOT=po/messages.pot + +all: rpms + +clean: + -rm -f MANIFEST + -rm -rf dist/ build/ + -rm -rf *~ + -rm -rf rpm-build/ + +#manpage: +# pod2man --center="cobbler" --release="" cobbler.pod | gzip -c > cobbler.1.gz +# pod2html cobbler.pod > cobbler.html + +#test: +# python tests/tests.py +# -rm -rf /tmp/_cobbler-* + +messages: server/*.py + xgettext -k_ -kN_ -o $(MESSAGESPOT) server/*.py + sed -i'~' -e 's/SOME DESCRIPTIVE TITLE/func/g' -e 's/YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/2007 Red Hat, inc. /g' -e 's/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR/Adrian Likins <alikins@redhat.com>, 2007/g' -e 's/PACKAGE VERSION/func $(VERSION)-$(RELEASE)/g' -e 's/PACKAGE/func/g' $(MESSAGESPOT) + + +bumprelease: + -echo "$(VERSION) $(NEWRELEASE)" > version + +setversion: + -echo "$(VERSION) $(RELEASE)" > version + +build: clean + python setup.py build -f + +install: build + python setup.py install -f + +sdist: clean messages + python setup.py sdist + +new-rpms: bumprelease rpms + + +rpms: sdist + mkdir -p rpm-build + cp dist/*.gz rpm-build/ + cp version rpm-build/ + rpmbuild --define "_topdir %(pwd)/rpm-build" \ + --define "_builddir %{_topdir}" \ + --define "_rpmdir %{_topdir}" \ + --define "_srcrpmdir %{_topdir}" \ + --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \ + --define "_specdir %{_topdir}" \ + --define "_sourcedir %{_topdir}" \ + -ba func.spec diff --git a/func/slave-keys.py b/certs/slave-keys.py index 5ac3227..5ac3227 100644 --- a/func/slave-keys.py +++ b/certs/slave-keys.py diff --git a/client/__init__.py b/client/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/client/__init__.py diff --git a/client/dumb_client.py b/client/dumb_client.py new file mode 100644 index 0000000..173b3a3 --- /dev/null +++ b/client/dumb_client.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + + +# all the cool kids would use optparse instead +import getopt +import sys +import xmlrpclib + + +verbose = 0 + +try: + opts, args = getopt.getopt(sys.argv, "hvs:", + ["help", + "verbose", + "server="]) +except getopt.error, e: + print _("Error parsing list arguments: %s") % e + self.print_help() + # FIXME: error handling + + +server = "http://127.0.0.1:51234" +for (opt, val) in opts: + if opt in ["-h", "--help"]: + self.print_help() + sys.exit() + if opt in ["-v", "--verbose"]: + verbose = verbose + 1 + if opt in ["-s", "--server"]: + server = val + +s = xmlrpclib.ServerProxy(server) + +args = args[1:] +method = args[0] +print "calling %s with args: %s" % (method, args[1:]) + +# thats some pretty code right there aint it? -akl +# we can't call "call" on s, since thats a rpc, so +# we call gettatr around it. +print getattr(s, method)(*args[1:]) diff --git a/client/test_func.py b/client/test_func.py index 35ce100..a108c47 100644 --- a/client/test_func.py +++ b/client/test_func.py @@ -1,11 +1,37 @@ #!/usr/bin/python +# FIXME: should import the client lib, not XMLRPC lib, when we are done + import xmlrpclib +TEST_VIRT = True +TEST_SERVICES = True + +# get a connecton (to be replaced by client lib logic) s = xmlrpclib.ServerProxy("http://127.0.0.1:51234") +# here's the basic test... print s.test_add(1, 2) +# here's the service module testing +if TEST_SERVICES: + print s.service_restart("httpd") + +# this is so I can remember how the virt module works +if TEST_VIRT: + + # example of using koan to install a virtual machine + # s.virt_install("mdehaan.rdu.redhat.com","fc7webserver",False) + + # wait ... + vms = s.virt_list_vms() + # example of stopping all stopped virtual machines + print "list of virtual instances = %s" % vms + for vm in vms: + status = s.virt_status(vm) + if status == "stopped": + s.virt_start(vm) +# add more tests here diff --git a/func.spec b/func.spec new file mode 100644 index 0000000..71c2dcf --- /dev/null +++ b/func.spec @@ -0,0 +1,66 @@ + +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +Summary: Remote config, monitoring, and management api +Name: func +Source1: version +Version: %(echo `awk '{ print $1 }' %{SOURCE1}`) +Release: %(echo `awk '{ print $2 }' %{SOURCE1}`)%{?dist} +Source0: %{name}-%{version}.tar.gz +License: GPL+ +Group: Applications/System +Requires: python >= 2.3 +Requires: rhpl +Requires: yum-utils +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: https://hosted.fedoraproject.org/projects/func/ + +%description + +func is a remote api for mangement, configation, and monitoring of systems. + +%prep +%setup -q + +%build +%{__python} setup.py build + +%install +test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install --root=$RPM_BUILD_ROOT + +%clean +rm -fr $RPM_BUILD_ROOT + +%files +%{_bindir}/funcd +/etc/init.d/funcd +%config(noreplace) /etc/func/settings +%dir %{python_sitelib}/func +%dir %{python_sitelib}/func/server +%dir %{python_sitelib}/func/client +%{python_sitelib}/func/server/*.py* +%{python_sitelib}/func/client/*.py* + +%dir %{python_sitelib}/func/server/modules +%{python_sitelib}/func/server/modules/*.py* +%dir /var/log/func + +%post +/sbin/chkconfig --add funcd +exit 0 + +%preun +if [ "$1" = 0 ] ; then + /sbin/service funcd stop > /dev/null 2>&1 + /sbin/chkconfig --del funcd +fi + + +%changelog +* Thu Sep 20 2007 James Bowes <jbowes@redhat.com> - 0.0.11-2 +- Clean up some speclint warnings + +* Thu Sep 20 2007 Adrian Likins <alikins@redhat.com> - 0.0.11-1 +- initial release (this one goes to .11) diff --git a/init-scripts/funcd b/init-scripts/funcd new file mode 100755 index 0000000..4971feb --- /dev/null +++ b/init-scripts/funcd @@ -0,0 +1,82 @@ +#!/bin/sh +# +# funcd Fedora Unified Network Control +################################### + +# LSB header + +### BEGIN INIT INFO +# Provides: funcd +# Required-Start: network, xinetd, httpd +# Default-Start: 3 4 5 +# Short-Description: Fedora Unified Network Control +# Description: Crazy simple, secure remote management. +### END INIT INFO + +# chkconfig header + +# chkconfig: 345 99 99 +# description: Crazy simple, secure remote management. +# +# processname: /usr/bin/funcd + +# Sanity checks. +[ -x /usr/bin/funcd ] || exit 0 + +# Source function library. +. /etc/rc.d/init.d/functions + +SERVICE=funcd +PROCESS=funcd +CONFIG_ARGS=" " + +RETVAL=0 + +start() { + echo -n $"Starting func daemon: " + daemon --check $SERVICE $PROCESS --daemon $CONFIG_ARGS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + return $RETVAL +} + +stop() { + echo -n $"Stopping func daemon: " + killproc $PROCESS + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/$SERVICE + rm -f /var/run/$SERVICE.pid + fi +} + +restart() { + stop + start +} + +# See how we were called. +case "$1" in + start|stop|restart) + $1 + ;; + status) + status $PROCESS + RETVAL=$? + ;; + condrestart) + [ -f /var/lock/subsys/$SERVICE ] && restart || : + ;; + reload) + echo "can't reload configuration, you have to restart it" + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" + exit 1 + ;; +esac +exit $RETVAL + diff --git a/modules/baseobj.py b/modules/baseobj.py deleted file mode 100755 index 702f0a7..0000000 --- a/modules/baseobj.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Virt-factory backend code. - -Copyright 2006, Red Hat, Inc -Michael DeHaan <mdehaan@redhat.com> -Scott Seago <sseago@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 string -import exceptions -import os - -class BaseObject(object): - - FIELDS = [] # subclasses should define a list of db column names here - - def load(self,hash,key,default=None): - """ - Access a hash element safely... - """ - # FIXME: it would be cool if load starts with a copy of the hash - # and clears off entries as recieved, such that we can tell if any - # entries are not loaded. This should result in a warning in the return - # object. - assert hash is not None, "hash is None" - assert key is not None, "key is None" - if hash.has_key(key): - return hash[key] - else: - return default - - def to_datastruct(self,to_caller=False): - """ - Return a hash representation of this object. - Defers to self.to_datastruct_internal which subclasses must implement. - """ - ds = self.to_datastruct_internal() - if to_caller: - # don't send NULLs - ds = self.remove_nulls(ds) - return ds - - def to_datastruct_internal(self): - """ - Subclasses: implement this. - """ - raise exceptions.NotImplementedError - - def deserialize(self, args): - for x in self.FIELDS: - if args.has_key(x): - setattr(self, x, args[x]) - else: - setattr(self, x, None) - - def serialize(self): - result = {} - for x in self.FIELDS: - result[x] = getattr(self, x, None) - return result - - def remove_nulls(self, x): - """ - If any entries are None in the datastructure, prune them. - XMLRPC can't marshall None and this is our workaround. Objects - that are None are removed from the hash -- including hash keys that - are not None and have None for the value. The WUI or other SW - should know how to deal with these returns. - """ - assert x is not None, "datastructure is None" - if type(x) == list: - newx = [] - for i in x: - if type(i) == list or type(i) == dict: - newx.append(self.remove_nulls(i)) - elif i is not None: - newx.append(i) - x = newx - elif type(x) == dict: - newx = {} - for i,j in x.iteritems(): - if type(j) == list or type(j) == dict: - newx[i] = self.remove_nulls(x) - elif j is not None: - newx[i] = j - x = newx - return x - - # ======================== - # random utility functions - - def is_printable(self, stringy): - # FIXME: use regex package - - if stringy == None: - return False - if type(stringy) != str: - stringy = "%s" % stringy - try: - for letter in stringy: - if letter not in string.printable: - return False - return True - except: - return False - - -
\ No newline at end of file diff --git a/modules/service.py b/modules/service.py new file mode 100755 index 0000000..f0693b0 --- /dev/null +++ b/modules/service.py @@ -0,0 +1,60 @@ +#!/usr/bin/python + +## func +## +## 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. +## +## + + +from codes import * +from modules import web_svc + +import subprocess +import os + +class Service(web_svc.WebSvc): + + def __init__(self): + self.methods = { + "service_start" : self.start, + "service_stop" : self.stop, + "service_restart" : self.restart, + "service_reload" : self.reload, + "service_status" : self.status + } + web_svc.WebSvc.__init__(self) + + def __command(self, service_name, command): + + filename = os.path.join("/etc/rc.d/init.d/",service_name) + if os.path.exists(filename): + return subprocess.call(["/sbin/service", service_name, command]) + else: + raise FuncException("Service not installed: %s" % service_name) + + def start(self, service_name): + return self.__command(service_name, "start") + + def stop(self, service_name): + return self.__command(service_name, "start") + + def restart(self, service_name): + return self.__command(service_name, "restart") + + def reload(self, service_name): + return self.__command(service_name, "reload") + + def status(self, service_name): + return self.__command(service_name, "status") + +methods = Service() +register_rpc = methods.register_rpc diff --git a/modules/test.py b/modules/test.py index 7783b4e..eb2e3f4 100755 --- a/modules/test.py +++ b/modules/test.py @@ -1,26 +1,17 @@ #!/usr/bin/python - from codes import * from modules import web_svc - - class Test(web_svc.WebSvc): def __init__(self): self.methods = { - "test_add": self.add, - "test_blippy": self.blippy, + "test_add": self.add } web_svc.WebSvc.__init__(self) def add(self, numb1, numb2): - return success(int(numb1) + int(numb2)) - - def blippy(self, foo): - fh = open("/tmp/blippy","w+") - fh.close() - return success(foo) + return numb1 + numb2 methods = Test() register_rpc = methods.register_rpc diff --git a/modules/virt.py b/modules/virt.py new file mode 100755 index 0000000..7e642cf --- /dev/null +++ b/modules/virt.py @@ -0,0 +1,276 @@ +#!/usr/bin/python + +""" +Virt management features + +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. +""" + +# warning: virt management is rather complicated +# to see a simple example of func, look at the +# service control module. API docs on how +# to use this to come. + +# other modules +import sys +import os +import subprocess +import libvirt + +# our modules +import codes +import web_svc + +VIRT_STATE_NAME_MAP = { + 0 : "running", + 1 : "running", + 2 : "running", + 3 : "paused", + 4 : "shutdown", + 5 : "shutdown", + 6 : "crashed" +} + +class FuncLibvirtConnection(): + + def __init__(self): + + + cmd = subprocess.Popen("uname -r", shell=True, stdout=subprocess.PIPE) + output = cmd.communicate()[0] + + if output.find("xen") != -1: + conn = libvirt.open(None) + else: + conn = libvirt.open("qemu:///system") + + if not conn: + raise FuncException(comment="hypervisor connection failure") + + self.conn = conn + + def find_vm(self, vmid): + """ + Extra bonus feature: vmid = -1 returns a list of everything + """ + conn = self.conn + + vms = [] + + # this block of code borrowed from virt-manager: + # get working domain's name + ids = conn.listDomainsID(); + for id in ids: + vm = conn.lookupByID(id) + vms.append(vm) + # get defined domain + names = conn.listDefinedDomains() + for name in names: + vm = conn.lookupByName(name) + vms.append(vm) + + if vmid == -1: + return vms + + for vm in vms: + if vm.name() == needle: + return vm + + raise FuncException(comment="virtual machine %s not found" % needle) + + def shutdown(self, vmid): + return self.find_vm(vmid).shutdown() + + def pause(self, vmid): + return suspend(self.conn,vmid) + + def unpause(self, vmid): + return resume(self.conn,vmid) + + def suspend(self, vmid): + return self.find_vm(vmid).suspend() + + def resume(self, vmid): + return self.find_vm(vmid).resume() + + def create(self, vmid): + return self.find_vm(vmid).create() + + def destroy(self, vmid): + return self.find_vm(vmid).destroy() + + def undefine(self, vmid): + return self.find_vm(vmid).undefine() + + def get_status2(self, vm): + state = vm.info()[0] + # print "DEBUG: state: %s" % state + return VIRT_STATE_NAME_MAP.get(state,"unknown") + + def get_status(self, vmid): + state = self.find_vm(vmid).info()[0] + return VIRT_STATE_NAME_MAP.get(state,"unknown") + + + +class Virt(web_svc.WebSvc): + + + def __init__(self): + + """ + Constructor. Register methods and make them available. + """ + + self.methods = { + "virt_install" : self.install, + "virt_shutdown" : self.shutdown, + "virt_destroy" : self.destroy, + "virt_start" : self.create, + "virt_pause" : self.pause, + "virt_unpause" : self.unpause, + "virt_delete" : self.undefine, + "virt_status" : self.get_status, + "virt_list_vms" : self.list_vms, + } + + web_svc.WebSvc.__init__(self) + + def get_conn(self): + self.conn = FuncLibvirtConnection() + return self.conn + + def list_vms(self): + self.conn = self.get_conn() + vms = self.conn.find_vm(-1) + results = [] + for x in vms: + try: + results.append(x.name()) + except: + pass + return results + + def install(self, server_name, target_name, system=False): + + """ + Install a new virt system by way of a named cobbler profile. + """ + + # Example: + # install("bootserver.example.org", "fc7webserver", True) + + conn = self.get_conn() + + if conn is None: + raise FuncException(comment="no connection") + + if not os.path.exists("/usr/bin/koan"): + raise FuncException(comment="no /usr/bin/koan") + target = "profile" + if system: + target = "system" + + # TODO: FUTURE: set --virt-path in cobbler or here + koan_args = [ + "/usr/bin/koan", + "--virt", + "--virt-graphics", # enable VNC + "--%s=%s" % (target, target_name), + "--server=%s" % server_name + ] + + rc = subprocess.call(koan_args,shell=False) + if rc == 0: + return success(0) + else: + raise FuncException(comment="koan returned %d" % rc) + + + def shutdown(self, vmid): + """ + Make the machine with the given vmid stop running. + Whatever that takes. + """ + self.get_conn() + self.conn.shutdown(vmid) + return success() + + + def pause(self, vmid): + + """ + Pause the machine with the given vmid. + """ + self.get_conn() + self.conn.suspend(vmid) + return success() + + + def unpause(self, vmid): + + """ + Unpause the machine with the given vmid. + """ + + self.get_conn() + self.conn.resume(vmid) + return success() + + + def create(self, vmid): + + """ + Start the machine via the given mac address. + """ + self.get_conn() + self.conn.create(vmid) + return success() + + + def destroy(self, vmid): + + """ + Pull the virtual power from the virtual domain, giving it virtually no + time to virtually shut down. + """ + self.get_conn() + self.conn.destroy(vmid) + return success() + + + + def undefine(self, vmid): + + """ + Stop a domain, and then wipe it from the face of the earth. + by deleting the disk image and it's configuration file. + """ + + self.get_conn() + self.conn.undefine(vmid) + return success() + + + def get_status(self, vmid): + + """ + Return a state suitable for server consumption. Aka, codes.py values, not XM output. + """ + + self.get_conn() + return success("STATE=%s" % self.conn.get_status(vmid)) + + +methods = Virt() +register_rpc = methods.register_rpc + + diff --git a/modules/web_svc.py b/modules/web_svc.py index ed4ec19..134af37 100755 --- a/modules/web_svc.py +++ b/modules/web_svc.py @@ -15,7 +15,6 @@ from codes import * -import baseobj from server import config_data from server import logger diff --git a/po/.gitignore b/po/.gitignore new file mode 100644 index 0000000..1495005 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1 @@ +messages.pot diff --git a/server/codes.py b/server/codes.py index 82bfb0a..dc0ceac 100755 --- a/server/codes.py +++ b/server/codes.py @@ -1,10 +1,9 @@ #!/usr/bin/python """ -Virt-factory backend code. +func -Copyright 2006, Red Hat, Inc -Michael DeHaan <mdehaan@redhat.com> -Scott Seago <sseago@redhat.com> +Copyright 2007, Red Hat, Inc +See AUTHORS This software may be freely redistributed under the terms of the GNU general public license. @@ -20,233 +19,10 @@ import sys import traceback - -# internal codes for the types of operations (used by validation logic) -OP_ADD = "add" -OP_EDIT = "edit" -OP_DELETE = "delete" -OP_LIST = "list" -OP_METHOD = "method" -OP_GET = "get" - -# error codes for the web service. -SUCCESS = ERR_SUCCESS = 0 -ERR_TOKEN_EXPIRED = 1 -ERR_TOKEN_INVALID = 2 -ERR_USER_INVALID = 3 -ERR_PASSWORD_INVALID = 4 -ERR_INTERNAL_ERROR = 5 -ERR_INVALID_ARGUMENTS = 6 -ERR_NO_SUCH_OBJECT = 7 -ERR_ORPHANED_OBJECT = 8 -ERR_SQL = 9 -ERR_MISCONFIGURED = 10 -ERR_UNCAUGHT = 11 -ERR_INVALID_METHOD = 12 -ERR_TASK = 13 -ERR_REG_TOKEN_INVALID = 14 -ERR_REG_TOKEN_EXHAUSTED = 15 -ERR_PUPPET_NODE_NOT_SIGNED = 16 - - - class FuncException(exceptions.Exception): - error_code = ERR_INTERNAL_ERROR - - def __init__(self, **kwargs): - self.job_id = self.load(kwargs,"job_id") - self.stacktrace = self.load(kwargs,"stacktrace") - self.invalid_fields = self.load(kwargs,"invalid_fields") - self.data = self.load(kwargs,"data") - self.comment = self.load(kwargs,"comment") - self.tb_data = traceback.extract_stack() - exceptions.Exception.__init__(self) - - - def format(self): - msg = """ -Exception Name: %s -Exception Comment: %s -Exception Data: %s -Stack Trace: -%s""" % (self.__class__, self.comment, self.data, - string.join(traceback.format_list(self.tb_data))) - - return msg - - def ok(self): - return self.error_code == 0 - - def load(self,hash,key,default=None): - if hash.has_key(key): - return hash[key] - else: - return default - - def __get_additional_data(self): - data = {} - if not self.job_id is None: - data["job_id"] = self.job_id - if not self.stacktrace is None: - data["stacktrace"] = self.stacktrace - if not self.invalid_fields is None: - data["invalid_fields"] = self.invalid_fields - if not self.data is None: - data["data"] = self.data - if not self.comment is None: - data["comment"] = self.comment - return data - - def to_datastruct(self): - return (self.error_code, self.__get_additional_data()) - -#FIXME: hack -VirtFactoryException = FuncException - -class SuccessException(VirtFactoryException): - """ - Not an error / return success and data to caller. - """ - error_code = ERR_SUCCESS - -class TokenExpiredException(VirtFactoryException): - """ - The user token that was passed in has been logged out - due to inactivity. Call user_login again. - """ - error_code = ERR_TOKEN_EXPIRED - -class TokenInvalidException(VirtFactoryException): - """ - The user token doesn't exist, so this function call isn't - permitted. Call user_login to get a valid token. - """ - error_code = ERR_TOKEN_INVALID - -class RegTokenInvalidException(VirtFactoryException): - """ - The registration token doesn't exist, so this function call isn't - permitted. - """ - error_code = ERR_REG_TOKEN_INVALID - -class RegTokenExhaustedException(VirtFactoryException): - """ - The registration token that was passed in has been used - it allowed number of uses. - """ - error_code = ERR_REG_TOKEN_EXHAUSTED - -class UserInvalidException(VirtFactoryException): - """ - Can't log in this user since the user account doesn't - exist in the database. - """ - error_code = ERR_USER_INVALID - -class PasswordInvalidException(VirtFactoryException): - """ - Wrong password. Bzzzt. Try again. - """ - error_code = ERR_PASSWORD_INVALID - -class InternalErrorException(VirtFactoryException): - """ - FIXME: This is a generic error code, and if something is - throwing that error, it probably - should be changed to throw something more specific. - """ - error_code = ERR_INTERNAL_ERROR - -class InvalidArgumentsException(VirtFactoryException): - """ - The arguments passed in to this function failed to pass - validation. See additional_data for the - names of which arguments were rejected. - """ - error_code = ERR_INVALID_ARGUMENTS - -class NoSuchObjectException(VirtFactoryException): - """ - The id passed in doesn't refer to an object. - """ - error_code = ERR_NO_SUCH_OBJECT - -class InvalidMethodException(VirtFactoryException): - """The method called does not exist""" - error_code = ERR_INVALID_METHOD - -class UncaughtException(VirtFactoryException): - """ - The python code choked. additional_data contains the - stacktrace, and it's ok to give the stacktrace - since the user is already logged in. user_login shouldn't - give stacktraces for security reasons. - """ - error_code = ERR_UNCAUGHT - -class OrphanedObjectException(VirtFactoryException): - """ - A delete can't proceed because another object references - this one, or an add can't proceed because a - referenced object doesn't exist. - """ - error_code = ERR_ORPHANED_OBJECT - -class SQLException(VirtFactoryException): - """ - The code died inside a SQL call. This is probably - a sign that the validation prior to making - the call needs to be improved, or maybe SQL was just - more efficient (i.e. referential integrity). - """ - error_code = ERR_SQL - -class TaskException(VirtFactoryException): - """ - Something went wrong with the background task engine - """ - error_code = ERR_TASK - -class MisconfiguredException(VirtFactoryException): - """ - The virt-factory service isn't properly configured and - no calls can be processed until this is corrected on the - server side. The UI/WUI/etc is non-functional and should - display a splash screen telling the user to finish their - setup of the virt-factory service by running "vf_server init", edit - /var/lib/virt-factory/settings, and then run "vf_server import". - """ - error_code = ERR_MISCONFIGURED - - -class PuppetNodeNotSignedException(VirtFactoryException): - """ - The puppet node certificate could not be signed, either - because there was no matching certificate requrest or - due to another puppetca error. - """ - error_code = ERR_PUPPET_NODE_NOT_SIGNED - -def success(data=None,job_id=None): - """ - Shortcut around success exception that returns equivalent data - w/o raise. - """ - ret = SuccessException(data=data, job_id=job_id) - return ret - - -if __name__ == "__main__": - # run this module as main to generate a ruby compatible constants - # file. - module = sys.modules[__name__] - for x in sorted(module.__dict__.keys()): - obj = module.__dict__[x] - if (type(obj) == int or type(obj) == str) and not x.startswith("__"): - if type(obj) == int: - print "%s = %s" % (x, obj) - else: - print "%s = \"%s\"" % (x, obj) + pass +class InvalidMethodException(FuncException): + pass +# FIXME: more sub-exceptions maybe diff --git a/server/config_data.py b/server/config_data.py index 9ccca75..7ace8ca 100755 --- a/server/config_data.py +++ b/server/config_data.py @@ -1,11 +1,9 @@ #!/usr/bin/python -# Virt-factory backend code. +# func # # Copyright 2006, Red Hat, Inc -# Michael DeHaan <mdehaan@redhat.com> -# Scott Seago <sseago@redhat.com> -# Adrian Likins <alikins@redhat.com> +# see AUTHORS # # This software may be freely redistributed under the terms of the GNU # general public license. @@ -16,19 +14,10 @@ from codes import * - import os -import yaml - -CONFIG_FILE = "/etc/virt-factory/settings" - -# from the comments in http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531 -#class Singleton(object): -# def __new__(type): -# if not '_the_instance' in type.__dict__: -# type._the_instance = object.__new__(type) -# return type._the_instance +import ConfigParser +CONFIG_FILE = "/etc/func/settings" class Config: @@ -38,18 +27,23 @@ class Config: def __init__(self): self.__dict__ = self.__shared_state + self.ds = {} if not self.has_read: self.read() - print "***** CONFIG RELOAD *****" Config.has_read = True def read(self): + if not os.path.exists(CONFIG_FILE): - raise MisconfiguredException(comment="Missing %s" % CONFIG_FILE) - config_file = open(CONFIG_FILE) - data = config_file.read() - self.ds = yaml.load(data).next() - + raise FuncException("Missing %s" % CONFIG_FILE) + + cp = ConfigParser.ConfigParser() + + cp.read([CONFIG_FILE]) + + self.ds["is_master"] = int(cp.get("general","is_master")) + self.ds["is_minion"] = int(cp.get("general","is_minion")) + self.ds["master_server"] = cp.get("general","master_server") def get(self): return self.ds diff --git a/server/logger.py b/server/logger.py index 0b9d791..fa56a3a 100755 --- a/server/logger.py +++ b/server/logger.py @@ -1,10 +1,9 @@ #!/usr/bin/python -## Virt-factory backend code. +## func ## -## Copyright 2006, Red Hat, Inc -## Michael DeHaan <mdehaan@redhat.com -## Adrian Likins <alikins@redhat.com +## Copyright 2007, Red Hat, Inc +## See AUTHORS ## ## This software may be freely redistributed under the terms of the GNU ## general public license. @@ -29,13 +28,15 @@ class Singleton(object): # logging is weird, we don't want to setup multiple handlers # so make sure we do that mess only once + class Logger(Singleton): __no_handlers = True - def __init__(self, logfilepath ="/var/log/virt-factory/svclog"): + + def __init__(self, logfilepath ="/var/log/func/func.log"): self.config = config_data.Config().get() - if self.config.has_key("loglevel"): - self.loglevel = logging._levelNames[self.config["loglevel"]] + if self.config.has_key("log_level"): + self.loglevel = logging._levelNames[self.config["log_level"]] else: self.loglevel = logging.INFO self.__setup_logging() @@ -45,7 +46,7 @@ class Logger(Singleton): def __setup_logging(self): self.logger = logging.getLogger("svc") - def __setup_handlers(self, logfilepath="/var/log/virt-factory/svclog"): + def __setup_handlers(self, logfilepath="/var/log/func/func.log"): handler = logging.FileHandler(logfilepath, "a") self.logger.setLevel(self.loglevel) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") diff --git a/server/module_loader.py b/server/module_loader.py index 10631fe..f189623 100755 --- a/server/module_loader.py +++ b/server/module_loader.py @@ -1,5 +1,19 @@ #!/usr/bin/python +## func +## +## Copyright 2007, Red Hat, Inc +## See AUTHORS +## +## 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 distutils.sysconfig import os @@ -7,18 +21,18 @@ import sys import glob from rhpl.translate import _, N_, textdomain, utf8 -module_file_path="modules/" -mod_path="server/" -sys.path.insert(0, mod_path) -def load_modules(module_path=module_file_path, blacklist=None): - filenames = glob.glob("%s/*.py" % module_file_path) - filenames = filenames + glob.glob("%s/*.pyc" % module_file_path) - filesnames = filenames + glob.glob("%s/*.pyo" % module_file_path) +def load_modules(blacklist=None): + + module_file_path="%s/func/server/modules/" % distutils.sysconfig.get_python_lib() + mod_path="%s/func/server/" % distutils.sysconfig.get_python_lib() + sys.path.insert(0, mod_path) mods = {} - print sys.path + filenames = glob.glob("%s/*.py" % module_file_path) + filenames = filenames + glob.glob("%s/*.pyc" % module_file_path) + filesnames = filenames + glob.glob("%s/*.pyo" % module_file_path) for fn in filenames: basename = os.path.basename(fn) @@ -33,8 +47,8 @@ def load_modules(module_path=module_file_path, blacklist=None): try: blip = __import__("modules.%s" % ( modname), globals(), locals(), [modname]) if not hasattr(blip, "register_rpc"): - errmsg = _("%(module_path)s/%(modname)s module not a proper module") - print errmsg % {'module_path': module_path, 'modname':modname} + errmsg = _("%(module_path)s%(modname)s module not a proper module") + print errmsg % {'module_path': module_file_path, 'modname':modname} continue mods[modname] = blip except ImportError, e: @@ -46,8 +60,4 @@ def load_modules(module_path=module_file_path, blacklist=None): - -if __name__ == "__main__": - print load_modules(module_path) - diff --git a/server/server.py b/server/server.py index d297b06..aa8bdef 100755 --- a/server/server.py +++ b/server/server.py @@ -1,11 +1,10 @@ #!/usr/bin/python + """ -Virt-factory backend code. +func -Copyright 2006, Red Hat, Inc -Michael DeHaan <mdehaan@redhat.com> -Scott Seago <sseago@redhat.com> -Adrian Likins <alikins@redhat.com> +Copyright 2007, Red Hat, Inc +see AUTHORS This software may be freely redistributed under the terms of the GNU general public license. @@ -15,198 +14,174 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ +# standard modules import SimpleXMLRPCServer -import os -import subprocess +import string import socket +import sys +import traceback -SERVE_ON = (None,None) - -# FIXME: logrotate - -from codes import * +from rhpl.translate import _, N_, textdomain, utf8 +I18N_DOMAIN = "func" +# our modules +import codes import config_data import logger import module_loader import utils +# ====================================================================================== -MODULE_PATH="modules/" -modules = module_loader.load_modules(MODULE_PATH) -print "modules", modules - +class XmlRpcInterface(object): -#from busrpc.services import RPCDispatcher -#from busrpc.config import DeploymentConfig + def __init__(self, modules={}): -from rhpl.translate import _, N_, textdomain, utf8 -I18N_DOMAIN = "vf_server" - - -class Singleton(object): - def __new__(type, *args, **kwargs): - if not '_the_instance' in type.__dict__: - type._the_instance = object.__new__(type, *args, **kwargs) - type._the_instance.init(*args, **kwargs) - return type._the_instance - -class XmlRpcInterface(Singleton): - - def init(self): """ - Constructor sets up SQLAlchemy (database ORM) and logging. + Constructor. """ + config_obj = config_data.Config() self.config = config_obj.get() - - self.tables = {} - self.tokens = [] - + self.modules = modules self.logger = logger.Logger().logger - self.__setup_handlers() def __setup_handlers(self): + """ Add RPC functions from each class to the global list so they can be called. - FIXME: eventually calling most functions should go from here through getattr. """ + self.handlers = {} - print "ffffffffffff", modules.keys() - for x in modules.keys(): - print "x", x + for x in self.modules.keys(): try: - modules[x].register_rpc(self.handlers) + self.modules[x].register_rpc(self.handlers) self.logger.debug("adding %s" % x) except AttributeError, e: - self.logger.warning("module %s could not be loaded, it did not have a register_rpc method" % modules[x]) + self.logger.warning("module %s not loaded, missing register_rpc method" % self.modules[x]) - # FIXME: find some more elegant way to surface the handlers? - # FIXME: aforementioned login/session token requirement - def get_dispatch_method(self, method): + if method in self.handlers: - return FuncApiMethod(self.logger, method, - self.handlers[method]) + return FuncApiMethod(self.logger, method, self.handlers[method]) else: self.logger.info("Unhandled method call for method: %s " % method) - raise InvalidMethodException + raise codes.InvalidMethodException def _dispatch(self, method, params): + """ the SimpleXMLRPCServer class will call _dispatch if it doesn't find a handler method """ + return self.get_dispatch_method(method)(*params) -class BusRpcWrapper: - - def __init__(self, config): - self.rpc_interface = None +# ====================================================================================== - def __getattr__(self, name): - if self.rpc_interface == None: - self.rpc_interface = XmlRpcInterface() - return self.rpc_interface.get_dispatch_method(name) +class FuncApiMethod: - def __repr__(self): - return ("<BusRpcWrapper>") + """ + Used to hold a reference to all of the registered functions. + """ -class FuncApiMethod: def __init__(self, logger, name, method): + self.logger = logger self.__method = method self.__name = name def __log_exc(self): + """ Log an exception. """ + (t, v, tb) = sys.exc_info() self.logger.info("Exception occured: %s" % t ) self.logger.info("Exception value: %s" % v) self.logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb)))) def __call__(self, *args): + self.logger.debug("(X) -------------------------------------------") + try: rc = self.__method(*args) - except FuncException, e: + except codes.FuncException, e: self.__log_exc() rc = e except: - self.logger.debug("Not a virt-factory specific exception") + self.logger.debug("Not a Func-specific exception") self.__log_exc() raise - rc = rc.to_datastruct() self.logger.debug("Return code for %s: %s" % (self.__name, rc)) + return rc +# ====================================================================================== def serve(websvc): + """ Code for starting the XMLRPC service. FIXME: make this HTTPS (see RRS code) and make accompanying Rails changes.. """ + server =FuncXMLRPCServer(('', 51234)) + server.logRequests = 0 # don't print stuff to console server.register_instance(websvc) server.serve_forever() -def serve_qpid(config_path, register_with_bridge=False, is_bridge_server=False): - """ - Code for starting the QPID RPC service. - """ - config = DeploymentConfig(config_path) - dispatcher = RPCDispatcher(config, register_with_bridge, is_bridge_server=is_bridge_server) - - try: - dispatcher.start() - except KeyboardInterrupt: - dispatcher.stop() - print "Exiting..." +# ====================================================================================== class FuncXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): + def __init__(self, args): + self.allow_reuse_address = True SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args) + +# ====================================================================================== def main(argv): + """ Start things up. """ - websvc = XmlRpcInterface() - - for arg in sys.argv: - if arg == "import" or arg == "--import": - prov_obj = provisioning.Provisioning() - prov_obj.init(None, {}) - return - elif arg == "sync" or arg == "--sync": - prov_obj = provisioning.Provisioning() - prov_obj.sync(None, {}) # just for testing - return - if "qpid" in sys.argv or "--qpid" in sys.argv: - if "daemon" in sys.argv or "--daemon" in sys.argv: - utils.daemonize("/var/run/vf_server_qpid.pid") - else: - print "serving...\n" - serve_qpid("/etc/virt-factory/qpid.conf") + modules = module_loader.load_modules() + + print "\n\n\n\n\n" + print " WARNING WARNING WARNING" + print "DANGER DANGER DANGER" + print "\n\n\n\n" + print "THERE IS NO AUTHENTICATION IN THIS VERSION" + print "DO NOT RUN ON A MACHINE EXPOSED TO ANYONE YOU DO NOT TRUST" + print " THEY CAN DO VERY BAD THINGS" + print "\n\n\n\n\n" + print "Really, don't do that. It is not at all secure at the moment" + print "like, at all." + print "" + print "Seriously.\n\n" + + try: + websvc = XmlRpcInterface(modules=modules) + except FuncException, e: + print >> sys.stderr, 'error: %s' % e + sys.exit(1) + + if "daemon" in sys.argv or "--daemon" in sys.argv: + utils.daemonize("/var/run/vf_server.pid") else: - if "daemon" in sys.argv or "--daemon" in sys.argv: - utils.daemonize("/var/run/vf_server.pid") - else: - print "serving...\n" - # daemonize only if --daemonize, because I forget to type "debug" -- MPD - serve(websvc) - -# FIXME: upgrades? database upgrade logic would be nice to have here, as would general creation (?) -# FIXME: command line way to add a distro would be nice to have in the future, rsync import is a bit heavy handed. -# (and might not be enough for RHEL, but is good for Fedora/Centos) + print "serving...\n" + serve(websvc) + +# ====================================================================================== if __name__ == "__main__": textdomain(I18N_DOMAIN) diff --git a/server/utils.py b/server/utils.py index 552db54..724c847 100755 --- a/server/utils.py +++ b/server/utils.py @@ -1,11 +1,8 @@ #!/usr/bin/python -""" -Virt-factory backend code. -Copyright 2006, Red Hat, Inc -Michael DeHaan <mdehaan@redhat.com> -Scott Seago <sseago@redhat.com> -Adrian Likins <alikins@redhat.com> +""" +Copyright 2007, Red Hat, Inc +see AUTHORS This software may be freely redistributed under the terms of the GNU general public license. diff --git a/server/yaml/__init__.py b/server/yaml/__init__.py deleted file mode 100755 index 419d1f3..0000000 --- a/server/yaml/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -__version__ = "0.32"
-from load import loadFile, load, Parser, l
-from dump import dump, dumpToFile, Dumper, d
-from stream import YamlLoaderException, StringStream, FileStream
-from timestamp import timestamp
-import sys
-if sys.hexversion >= 0x02020000:
- from redump import loadOrdered
-
-try:
- from ypath import ypath
-except NameError:
- def ypath(expr,target='',cntx=''):
- raise NotImplementedError("ypath requires Python 2.2")
-
-if sys.hexversion < 0x02010000:
- raise 'YAML is not tested for pre-2.1 versions of Python'
diff --git a/server/yaml/dump.py b/server/yaml/dump.py deleted file mode 100755 index c55dbfe..0000000 --- a/server/yaml/dump.py +++ /dev/null @@ -1,296 +0,0 @@ -import types
-import string
-from types import StringType, UnicodeType, IntType, FloatType
-from types import DictType, ListType, TupleType, InstanceType
-from klass import hasMethod, isDictionary
-import re
-
-"""
- The methods from this module that are exported to the top
- level yaml package should remain stable. If you call
- directly into other methods of this module, be aware that
- they may change or go away in future implementations.
- Contact the authors if there are methods in this file
- that you wish to remain stable.
-"""
-
-def dump(*data):
- return Dumper().dump(*data)
-
-def d(data): return dump(data)
-
-def dumpToFile(file, *data):
- return Dumper().dumpToFile(file, *data)
-
-class Dumper:
- def __init__(self):
- self.currIndent = "\n"
- self.indent = " "
- self.keysrt = None
- self.alphaSort = 1 # legacy -- on by default
-
- def setIndent(self, indent):
- self.indent = indent
- return self
-
- def setSort(self, sort_hint):
- self.keysrt = sortMethod(sort_hint)
- return self
-
- def dump(self, *data):
- self.result = []
- self.output = self.outputToString
- self.dumpDocuments(data)
- return string.join(self.result,"")
-
- def outputToString(self, data):
- self.result.append(data)
-
- def dumpToFile(self, file, *data):
- self.file = file
- self.output = self.outputToFile
- self.dumpDocuments(data)
-
- def outputToFile(self, data):
- self.file.write(data)
-
- def dumpDocuments(self, data):
- for obj in data:
- self.anchors = YamlAnchors(obj)
- self.output("---")
- self.dumpData(obj)
- self.output("\n")
-
- def indentDump(self, data):
- oldIndent = self.currIndent
- self.currIndent += self.indent
- self.dumpData(data)
- self.currIndent = oldIndent
-
- def dumpData(self, data):
- anchor = self.anchors.shouldAnchor(data)
- if anchor:
- self.output(" &%d" % anchor )
- else:
- anchor = self.anchors.isAlias(data)
- if anchor:
- self.output(" *%d" % anchor )
- return
- if (data is None):
- self.output(' ~')
- elif hasMethod(data, 'to_yaml'):
- self.dumpTransformedObject(data)
- elif hasMethod(data, 'to_yaml_implicit'):
- self.output(" " + data.to_yaml_implicit())
- elif type(data) is InstanceType:
- self.dumpRawObject(data)
- elif isDictionary(data):
- self.dumpDict(data)
- elif type(data) in [ListType, TupleType]:
- self.dumpList(data)
- else:
- self.dumpScalar(data)
-
- def dumpTransformedObject(self, data):
- obj_yaml = data.to_yaml()
- if type(obj_yaml) is not TupleType:
- self.raiseToYamlSyntaxError()
- (data, typestring) = obj_yaml
- if typestring:
- self.output(" " + typestring)
- self.dumpData(data)
-
- def dumpRawObject(self, data):
- self.output(' !!%s.%s' % (data.__module__, data.__class__.__name__))
- self.dumpData(data.__dict__)
-
- def dumpDict(self, data):
- keys = data.keys()
- if len(keys) == 0:
- self.output(" {}")
- return
- if self.keysrt:
- keys = sort_keys(keys,self.keysrt)
- else:
- if self.alphaSort:
- keys.sort()
- for key in keys:
- self.output(self.currIndent)
- self.dumpKey(key)
- self.output(":")
- self.indentDump(data[key])
-
- def dumpKey(self, key):
- if type(key) is TupleType:
- self.output("?")
- self.indentDump(key)
- self.output("\n")
- else:
- self.output(quote(key))
-
- def dumpList(self, data):
- if len(data) == 0:
- self.output(" []")
- return
- for item in data:
- self.output(self.currIndent)
- self.output("-")
- self.indentDump(item)
-
- def dumpScalar(self, data):
- if isUnicode(data):
- self.output(' "%s"' % repr(data)[2:-1])
- elif isMulti(data):
- self.dumpMultiLineScalar(data.splitlines())
- else:
- self.output(" ")
- self.output(quote(data))
-
- def dumpMultiLineScalar(self, lines):
- self.output(" |")
- if lines[-1] == "":
- self.output("+")
- for line in lines:
- self.output(self.currIndent)
- self.output(line)
-
- def raiseToYamlSyntaxError(self):
- raise """
-to_yaml should return tuple w/object to dump
-and optional YAML type. Example:
-({'foo': 'bar'}, '!!foobar')
-"""
-
-#### ANCHOR-RELATED METHODS
-
-def accumulate(obj,occur):
- typ = type(obj)
- if obj is None or \
- typ is IntType or \
- typ is FloatType or \
- ((typ is StringType or typ is UnicodeType) \
- and len(obj) < 32): return
- obid = id(obj)
- if 0 == occur.get(obid,0):
- occur[obid] = 1
- if typ is ListType:
- for x in obj:
- accumulate(x,occur)
- if typ is DictType:
- for (x,y) in obj.items():
- accumulate(x,occur)
- accumulate(y,occur)
- else:
- occur[obid] = occur[obid] + 1
-
-class YamlAnchors:
- def __init__(self,data):
- occur = {}
- accumulate(data,occur)
- anchorVisits = {}
- for (obid, occur) in occur.items():
- if occur > 1:
- anchorVisits[obid] = 0
- self._anchorVisits = anchorVisits
- self._currentAliasIndex = 0
- def shouldAnchor(self,obj):
- ret = self._anchorVisits.get(id(obj),None)
- if 0 == ret:
- self._currentAliasIndex = self._currentAliasIndex + 1
- ret = self._currentAliasIndex
- self._anchorVisits[id(obj)] = ret
- return ret
- return 0
- def isAlias(self,obj):
- return self._anchorVisits.get(id(obj),0)
-
-### SORTING METHODS
-
-def sort_keys(keys,fn):
- tmp = []
- for key in keys:
- val = fn(key)
- if val is None: val = '~'
- tmp.append((val,key))
- tmp.sort()
- return [ y for (x,y) in tmp ]
-
-def sortMethod(sort_hint):
- typ = type(sort_hint)
- if DictType == typ:
- return sort_hint.get
- elif ListType == typ or TupleType == typ:
- indexes = {}; idx = 0
- for item in sort_hint:
- indexes[item] = idx
- idx += 1
- return indexes.get
- else:
- return sort_hint
-
-### STRING QUOTING AND SCALAR HANDLING
-def isStr(data):
- # XXX 2.1 madness
- if type(data) == type(''):
- return 1
- if type(data) == type(u''):
- return 1
- return 0
-
-def doubleUpQuotes(data):
- return data.replace("'", "''")
-
-def quote(data):
- if not isStr(data):
- return str(data)
- single = "'"
- double = '"'
- quote = ''
- if len(data) == 0:
- return "''"
- if hasSpecialChar(data) or data[0] == single:
- data = `data`[1:-1]
- data = string.replace(data, r"\x08", r"\b")
- quote = double
- elif needsSingleQuote(data):
- quote = single
- data = doubleUpQuotes(data)
- return "%s%s%s" % (quote, data, quote)
-
-def needsSingleQuote(data):
- if re.match(r"^-?\d", data):
- return 1
- if re.match(r"\*\S", data):
- return 1
- if data[0] in ['&', ' ']:
- return 1
- if data[0] == '"':
- return 1
- if data[-1] == ' ':
- return 1
- return (re.search(r'[:]', data) or re.search(r'(\d\.){2}', data))
-
-def hasSpecialChar(data):
- # need test to drive out '#' from this
- return re.search(r'[\t\b\r\f#]', data)
-
-def isMulti(data):
- if not isStr(data):
- return 0
- if hasSpecialChar(data):
- return 0
- return re.search("\n", data)
-
-def isUnicode(data):
- return type(data) == unicode
-
-def sloppyIsUnicode(data):
- # XXX - hack to make tests pass for 2.1
- return repr(data)[:2] == "u'" and repr(data) != data
-
-import sys
-if sys.hexversion < 0x20200000:
- isUnicode = sloppyIsUnicode
-
-
-
diff --git a/server/yaml/implicit.py b/server/yaml/implicit.py deleted file mode 100755 index 6172564..0000000 --- a/server/yaml/implicit.py +++ /dev/null @@ -1,46 +0,0 @@ -import re
-import string
-from timestamp import timestamp, matchTime
-
-DATETIME_REGEX = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")
-FLOAT_REGEX = re.compile("^[-+]?[0-9][0-9,]*\.[0-9]*$")
-SCIENTIFIC_REGEX = re.compile("^[-+]?[0-9]+(\.[0-9]*)?[eE][-+][0-9]+$")
-OCTAL_REGEX = re.compile("^[-+]?([0][0-7,]*)$")
-HEX_REGEX = re.compile("^[-+]?0x[0-9a-fA-F,]+$")
-INT_REGEX = re.compile("^[-+]?(0|[1-9][0-9,]*)$")
-
-def convertImplicit(val):
- if val == '~':
- return None
- if val == '+':
- return 1
- if val == '-':
- return 0
- if val[0] == "'" and val[-1] == "'":
- val = val[1:-1]
- return string.replace(val, "''", "\'")
- if val[0] == '"' and val[-1] == '"':
- if re.search(r"\u", val):
- val = "u" + val
- unescapedStr = eval (val)
- return unescapedStr
- if matchTime.match(val):
- return timestamp(val)
- if INT_REGEX.match(val):
- return int(cleanseNumber(val))
- if OCTAL_REGEX.match(val):
- return int(val, 8)
- if HEX_REGEX.match(val):
- return int(val, 16)
- if FLOAT_REGEX.match(val):
- return float(cleanseNumber(val))
- if SCIENTIFIC_REGEX.match(val):
- return float(cleanseNumber(val))
- return val
-
-def cleanseNumber(str):
- if str[0] == '+':
- str = str[1:]
- str = string.replace(str,',','')
- return str
-
diff --git a/server/yaml/inline.py b/server/yaml/inline.py deleted file mode 100755 index 8e647de..0000000 --- a/server/yaml/inline.py +++ /dev/null @@ -1,38 +0,0 @@ -import re
-import string
-
-class InlineTokenizer:
- def __init__(self, data):
- self.data = data
-
- def punctuation(self):
- puncts = [ '[', ']', '{', '}' ]
- for punct in puncts:
- if self.data[0] == punct:
- self.data = self.data[1:]
- return punct
-
- def up_to_comma(self):
- match = re.match('(.*?)\s*, (.*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def up_to_end_brace(self):
- match = re.match('(.*?)(\s*[\]}].*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def next(self):
- self.data = string.strip(self.data)
- productions = [
- self.punctuation,
- self.up_to_comma,
- self.up_to_end_brace
- ]
- for production in productions:
- token = production()
- if token:
- return token
-
diff --git a/server/yaml/klass.py b/server/yaml/klass.py deleted file mode 100755 index edcf5a8..0000000 --- a/server/yaml/klass.py +++ /dev/null @@ -1,48 +0,0 @@ -import new
-import re
-
-class DefaultResolver:
- def resolveType(self, data, typestring):
- match = re.match('!!(.*?)\.(.*)', typestring)
- if not match:
- raise "Invalid private type specifier"
- (modname, classname) = match.groups()
- return makeClass(modname, classname, data)
-
-def makeClass(module, classname, dict):
- exec('import %s' % (module))
- klass = eval('%s.%s' % (module, classname))
- obj = new.instance(klass)
- if hasMethod(obj, 'from_yaml'):
- return obj.from_yaml(dict)
- obj.__dict__ = dict
- return obj
-
-def hasMethod(object, method_name):
- try:
- klass = object.__class__
- except:
- return 0
- if not hasattr(klass, method_name):
- return 0
- method = getattr(klass, method_name)
- if not callable(method):
- return 0
- return 1
-
-def isDictionary(data):
- return isinstance(data, dict)
-
-try:
- isDictionary({})
-except:
- def isDictionary(data): return type(data) == type({}) # XXX python 2.1
-
-if __name__ == '__main__':
- print isDictionary({'foo': 'bar'})
- try:
- print isDictionary(dict())
- from ordered_dict import OrderedDict
- print isDictionary(OrderedDict())
- except:
- pass
diff --git a/server/yaml/load.py b/server/yaml/load.py deleted file mode 100755 index 259178d..0000000 --- a/server/yaml/load.py +++ /dev/null @@ -1,327 +0,0 @@ -import re, string
-from implicit import convertImplicit
-from inline import InlineTokenizer
-from klass import DefaultResolver
-from stream import YamlLoaderException, FileStream, StringStream, NestedDocs
-
-try:
- iter(list()) # is iter supported by this version of Python?
-except:
- # XXX - Python 2.1 does not support iterators
- class StopIteration: pass
- class iter:
- def __init__(self,parser):
- self._docs = []
- try:
- while 1:
- self._docs.append(parser.next())
- except StopIteration: pass
- self._idx = 0
- def __len__(self): return len(self._docs)
- def __getitem__(self,idx): return self._docs[idx]
- def next(self):
- if self._idx < len(self._docs):
- ret = self._docs[self._idx]
- self._idx = self._idx + 1
- return ret
- raise StopIteration
-
-def loadFile(filename, typeResolver=None):
- return loadStream(FileStream(filename),typeResolver)
-
-def load(str, typeResolver=None):
- return loadStream(StringStream(str), typeResolver)
-
-def l(str): return load(str).next()
-
-def loadStream(stream, typeResolver):
- return iter(Parser(stream, typeResolver))
-
-def tryProductions(productions, value):
- for production in productions:
- results = production(value)
- if results:
- (ok, result) = results
- if ok:
- return (1, result)
-
-def dumpDictionary(): return {}
-
-class Parser:
- def __init__(self, stream, typeResolver=None):
- try:
- self.dictionary = dict
- except:
- self.dictionary = dumpDictionary
- self.nestedDocs = NestedDocs(stream)
- self.aliases = {}
- if typeResolver:
- self.typeResolver = typeResolver
- else:
- self.typeResolver = DefaultResolver()
-
- def error(self, msg):
- self.nestedDocs.error(msg, self.line)
-
- def nestPop(self):
- line = self.nestedDocs.pop()
- if line is not None:
- self.line = line
- return 1
-
- def value(self, indicator):
- return getToken(indicator+"\s*(.*)", self.line)
-
- def getNextDocument(self): raise "getNextDocument() deprecated--use next()"
-
- def next(self):
- line = self.nestedDocs.popDocSep()
- indicator = getIndicator(line)
- if indicator:
- return self.parse_value(indicator)
- if line:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
- raise StopIteration
-
- def __iter__(self): return self
-
- def parseLines(self):
- peekLine = self.nestedDocs.peek()
- if peekLine:
- if re.match("\s*-", peekLine):
- return self.parse_collection([], self.parse_seq_line)
- else:
- return self.parse_collection(self.dictionary(), self.parse_map_line)
- raise StopIteration
-
- def parse_collection(self, items, lineParser):
- while self.nestPop():
- if self.line:
- lineParser(items)
- return items
-
- def parse_seq_line(self, items):
- value = self.value("-")
- if value is not None:
- items.append(self.parse_seq_value(value))
- else:
- self.error("missing '-' for seq")
-
- def parse_map_line(self, items):
- if (self.line == '?'):
- self.parse_map_line_nested(items)
- else:
- self.parse_map_line_simple(items, self.line)
-
- def parse_map_line_nested(self, items):
- self.nestedDocs.nestToNextLine()
- key = self.parseLines()
- if self.nestPop():
- value = self.value(':')
- if value is not None:
- items[tuple(key)] = self.parse_value(value)
- return
- self.error("key has no value for nested map")
-
- def parse_map_line_simple(self, items, line):
- map_item = self.key_value(line)
- if map_item:
- (key, value) = map_item
- key = convertImplicit(key)
- if items.has_key(key):
- self.error("Duplicate key "+key)
- items[key] = self.parse_value(value)
- else:
- self.error("bad key for map")
-
- def is_map(self, value):
- # XXX - need real tokenizer
- if len(value) == 0:
- return 0
- if value[0] == "'":
- return 0
- if re.search(':(\s|$)', value):
- return 1
-
- def parse_seq_value(self, value):
- if self.is_map(value):
- return self.parse_compressed_map(value)
- else:
- return self.parse_value(value)
-
- def parse_compressed_map(self, value):
- items = self.dictionary()
- line = self.line
- token = getToken("(\s*-\s*)", line)
- self.nestedDocs.nestBySpecificAmount(len(token))
- self.parse_map_line_simple(items, value)
- return self.parse_collection(items, self.parse_map_line)
-
- def parse_value(self, value):
- (alias, value) = self.testForRepeatOfAlias(value)
- if alias:
- return value
- (alias, value) = self.testForAlias(value)
- value = self.parse_unaliased_value(value)
- if alias:
- self.aliases[alias] = value
- return value
-
- def parse_unaliased_value(self, value):
- match = re.match(r"(!\S*)(.*)", value)
- if match:
- (url, value) = match.groups()
- value = self.parse_untyped_value(value)
- if url[:2] == '!!':
- return self.typeResolver.resolveType(value, url)
- else:
- # XXX - allows syntax, but ignores it
- return value
- return self.parse_untyped_value(value)
-
- def parseInlineArray(self, value):
- if re.match("\s*\[", value):
- return self.parseInline([], value, ']',
- self.parseInlineArrayItem)
-
- def parseInlineHash(self, value):
- if re.match("\s*{", value):
- return self.parseInline(self.dictionary(), value, '}',
- self.parseInlineHashItem)
-
- def parseInlineArrayItem(self, result, token):
- return result.append(convertImplicit(token))
-
- def parseInlineHashItem(self, result, token):
- (key, value) = self.key_value(token)
- result[key] = value
-
- def parseInline(self, result, value, end_marker, itemMethod):
- tokenizer = InlineTokenizer(value)
- tokenizer.next()
- while 1:
- token = tokenizer.next()
- if token == end_marker:
- break
- itemMethod(result, token)
- return (1, result)
-
- def parseSpecial(self, value):
- productions = [
- self.parseMultiLineScalar,
- self.parseInlineHash,
- self.parseInlineArray,
- ]
- return tryProductions(productions, value)
-
- def parse_untyped_value(self, value):
- parse = self.parseSpecial(value)
- if parse:
- (ok, data) = parse
- return data
- token = getToken("(\S.*)", value)
- if token:
- lines = [token] + \
- pruneTrailingEmpties(self.nestedDocs.popNestedLines())
- return convertImplicit(joinLines(lines))
- else:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
-
- def parseNative(self, value):
- return (1, convertImplicit(value))
-
- def parseMultiLineScalar(self, value):
- if value == '>':
- return (1, self.parseFolded())
- elif value == '|':
- return (1, joinLiteral(self.parseBlock()))
- elif value == '|+':
- return (1, joinLiteral(self.unprunedBlock()))
-
- def parseFolded(self):
- data = self.parseBlock()
- i = 0
- resultString = ''
- while i < len(data)-1:
- resultString = resultString + data[i]
- resultString = resultString + foldChar(data[i], data[i+1])
- i = i + 1
- return resultString + data[-1] + "\n"
-
- def unprunedBlock(self):
- self.nestedDocs.nestToNextLine()
- data = []
- while self.nestPop():
- data.append(self.line)
- return data
-
- def parseBlock(self):
- return pruneTrailingEmpties(self.unprunedBlock())
-
- def testForAlias(self, value):
- match = re.match("&(\S*)\s*(.*)", value)
- if match:
- return match.groups()
- return (None, value)
-
- def testForRepeatOfAlias(self, value):
- match = re.match("\*(\S+)", value)
- if match:
- alias = match.groups()[0]
- if self.aliases.has_key(alias):
- return (alias, self.aliases[alias])
- else:
- self.error("Unknown alias")
- return (None, value)
-
- def key_value(self, str):
- if str[-1] == ' ':
- self.error("Trailing spaces not allowed without quotes.")
- # XXX This allows mis-balanced " vs. ' stuff
- match = re.match("[\"'](.+)[\"']\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- return (key, value)
- match = re.match("(.+?)\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- if len(value) and value[0] == '#':
- value = ''
- return (key, value)
-
-def getToken(regex, value):
- match = re.search(regex, value)
- if match:
- return match.groups()[0]
-
-def pruneTrailingEmpties(data):
- while len(data) > 0 and data[-1] == '':
- data = data[:-1]
- return data
-
-def foldChar(line1, line2):
- if re.match("^\S", line1) and re.match("^\S", line2):
- return " "
- return "\n"
-
-def getIndicator(line):
- if line:
- header = r"(#YAML:\d+\.\d+\s*){0,1}"
- match = re.match("--- "+header+"(\S*.*)", line)
- if match:
- return match.groups()[-1]
-
-def joinLines(lines):
- result = ''
- for line in lines[:-1]:
- if line[-1] == '\\':
- result = result + line[:-1]
- else:
- result = result + line + " "
- return result + lines[-1]
-
-def joinLiteral(data):
- return string.join(data,"\n") + "\n"
-
diff --git a/server/yaml/ordered_dict.py b/server/yaml/ordered_dict.py deleted file mode 100755 index b3788b7..0000000 --- a/server/yaml/ordered_dict.py +++ /dev/null @@ -1,31 +0,0 @@ -# This is extremely crude implementation of an OrderedDict. -# If you know of a better implementation, please send it to -# the author Steve Howell. You can find my email via -# the YAML mailing list or wiki. - -class OrderedDict(dict): - def __init__(self): - self._keys = [] - - def __setitem__(self, key, val): - self._keys.append(key) - dict.__setitem__(self, key, val) - - def keys(self): - return self._keys - - def items(self): - return [(key, self[key]) for key in self._keys] - -if __name__ == '__main__': - data = OrderedDict() - data['z'] = 26 - data['m'] = 13 - data['a'] = 1 - for key in data.keys(): - print "The value for %s is %s" % (key, data[key]) - print data - - - - diff --git a/server/yaml/redump.py b/server/yaml/redump.py deleted file mode 100755 index 56ea958..0000000 --- a/server/yaml/redump.py +++ /dev/null @@ -1,16 +0,0 @@ -from ordered_dict import OrderedDict -from load import Parser -from dump import Dumper -from stream import StringStream - -def loadOrdered(stream): - parser = Parser(StringStream(stream)) - parser.dictionary = OrderedDict - return iter(parser) - -def redump(stream): - docs = list(loadOrdered(stream)) - dumper = Dumper() - dumper.alphaSort = 0 - return dumper.dump(*docs) - diff --git a/server/yaml/stream.py b/server/yaml/stream.py deleted file mode 100755 index cc78c4b..0000000 --- a/server/yaml/stream.py +++ /dev/null @@ -1,193 +0,0 @@ -import re
-import string
-
-def indentLevel(line):
- n = 0
- while n < len(line) and line[n] == ' ':
- n = n + 1
- return n
-
-class LineNumberStream:
- def __init__(self, filename=None):
- self.curLine = 0
- self.filename = filename
-
- def get(self):
- line = self.getLine()
- self.curLine += 1 # used by subclass
- if line:
- line = noLineFeed(line)
- return line
-
- def lastLineRead(self):
- return self.curLine
-
-class FileStream(LineNumberStream):
- def __init__(self, filename):
- self.fp = open(filename)
- LineNumberStream.__init__(self, filename)
-
- def getLine(self):
- line = self.fp.readline()
- if line == '': line = None
- return line
-
-class StringStream(LineNumberStream):
- def __init__(self, text):
- self.lines = split(text)
- self.numLines = len(self.lines)
- LineNumberStream.__init__(self)
-
- def getLine(self):
- if self.curLine < self.numLines:
- return self.lines[self.curLine]
-
-def split(text):
- lines = string.split(text, '\n')
- if lines[-1] == '':
- lines.pop()
- return lines
-
-def eatNewLines(stream):
- while 1:
- line = stream.get()
- if line is None or len(string.strip(line)):
- return line
-
-COMMENT_LINE_REGEX = re.compile(R"\s*#")
-def isComment(line):
- return line is not None and COMMENT_LINE_REGEX.match(line)
-
-class CommentEater:
- def __init__(self, stream):
- self.stream = stream
- self.peeked = 1
- self.line = eatNewLines(stream)
- self.eatComments()
-
- def eatComments(self):
- while isComment(self.line):
- self.line = self.stream.get()
-
- def peek(self):
- if self.peeked:
- return self.line
- self.peeked = 1
- self.line = self.stream.get()
- self.eatComments()
- return self.line
-
- def lastLineRead(self):
- return self.stream.lastLineRead()
-
- def pop(self):
- data = self.peek()
- self.peeked = 0
- return data
-
-class NestedText:
- def __init__(self, stream):
- self.commentEater = CommentEater(stream)
- self.reset()
-
- def lastLineRead(self):
- return self.commentEater.lastLineRead()
-
- def reset(self):
- self.indentLevel = 0
- self.oldIndents = [0]
-
- def peek(self):
- nextLine = self.commentEater.peek()
- if nextLine is not None:
- if indentLevel(nextLine) >= self.indentLevel:
- return nextLine[self.indentLevel:]
- elif nextLine == '':
- return ''
-
- def pop(self):
- line = self.peek()
- if line is None:
- self.indentLevel = self.oldIndents.pop()
- return
- self.commentEater.pop()
- return line
-
- def popNestedLines(self):
- nextLine = self.peek()
- if nextLine is None or nextLine == '' or nextLine[0] != ' ':
- return []
- self.nestToNextLine()
- lines = []
- while 1:
- line = self.pop()
- if line is None:
- break
- lines.append(line)
- return lines
-
- def nestToNextLine(self):
- line = self.commentEater.peek()
- indentation = indentLevel(line)
- if len(self.oldIndents) > 1 and indentation <= self.indentLevel:
- self.error("Inadequate indentation", line)
- self.setNewIndent(indentation)
-
- def nestBySpecificAmount(self, adjust):
- self.setNewIndent(self.indentLevel + adjust)
-
- def setNewIndent(self, indentLevel):
- self.oldIndents.append(self.indentLevel)
- self.indentLevel = indentLevel
-
-class YamlLoaderException(Exception):
- def __init__(self, *args):
- (self.msg, self.lineNum, self.line, self.filename) = args
-
- def __str__(self):
- msg = """\
-%(msg)s:
-near line %(lineNum)d:
-%(line)s
-""" % self.__dict__
- if self.filename:
- msg += "file: " + self.filename
- return msg
-
-class NestedDocs(NestedText):
- def __init__(self, stream):
- self.filename = stream.filename
- NestedText.__init__(self,stream)
- line = NestedText.peek(self)
- self.sep = '---'
- if self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- else:
- self.eatenDocSep = self.sep
-
- def startsWithSep(self,line):
- if line and self.sep == line[:3]: return 1
- return 0
-
- def popDocSep(self):
- line = self.eatenDocSep
- self.eatenDocSep = None
- self.reset()
- return line
-
- def pop(self):
- if self.eatenDocSep is not None:
- raise "error"
- line = self.commentEater.peek()
- if line and self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- return None
- return NestedText.pop(self)
-
- def error(self, msg, line):
- raise YamlLoaderException(msg, self.lastLineRead(), line, self.filename)
-
-def noLineFeed(s):
- while s[-1:] in ('\n', '\r'):
- s = s[:-1]
- return s
diff --git a/server/yaml/timestamp.py b/server/yaml/timestamp.py deleted file mode 100755 index abcb2e6..0000000 --- a/server/yaml/timestamp.py +++ /dev/null @@ -1,145 +0,0 @@ -import time, re, string -from types import ListType, TupleType - -PRIVATE_NOTICE = """ - This module is considered to be private implementation - details and is subject to change. Please only use the - objects and methods exported to the top level yaml package. -""" - -# -# Time specific operations -# - -_splitTime = re.compile('\-|\s|T|t|:|\.|Z') -matchTime = re.compile(\ - '\d+-\d+-\d+([\s|T|t]\d+:\d+:\d+.\d+(Z|(\s?[\-|\+]\d+:\d+)))?') - -def _parseTime(val): - if not matchTime.match(val): raise ValueError(val) - tpl = _splitTime.split(val) - if not(tpl): raise ValueError(val) - siz = len(tpl) - sec = 0 - if 3 == siz: - tpl += [0,0,0,0,0,-1] - elif 7 == siz: - tpl.append(0) - tpl.append(-1) - elif 8 == siz: - if len(tpl.pop()) > 0: raise ValueError(val) - tpl.append(0) - tpl.append(-1) - elif 9 == siz or 10 == siz: - mn = int(tpl.pop()) - hr = int(tpl.pop()) - sec = (hr*60+mn)*60 - if val.find("+") > -1: sec = -sec - if 10 == siz: tpl.pop() - tpl.append(0) - tpl.append(-1) - else: - raise ValueError(val) - idx = 0 - while idx < 9: - tpl[idx] = int(tpl[idx]) - idx += 1 - if tpl[1] < 1 or tpl[1] > 12: raise ValueError(val) - if tpl[2] < 1 or tpl[2] > 31: raise ValueError(val) - if tpl[3] > 24: raise ValueError(val) - if tpl[4] > 61: raise ValueError(val) - if tpl[5] > 61: raise ValueError(val) - if tpl[0] > 2038: - #TODO: Truncation warning - tpl = (2038,1,18,0,0,0,0,0,-1) - tpl = tuple(tpl) - ret = time.mktime(tpl) - ret = time.localtime(ret+sec) - ret = ret[:8] + (0,) - return ret - - -class _timestamp: - def __init__(self,val=None): - if not val: - self.__tval = time.gmtime() - else: - typ = type(val) - if ListType == typ: - self.__tval = tuple(val) - elif TupleType == typ: - self.__tval = val - else: - self.__tval = _parseTime(val) - if 9 != len(self.__tval): raise ValueError - def __getitem__(self,idx): return self.__tval[idx] - def __len__(self): return 9 - def strftime(self,format): return time.strftime(format,self.__tval) - def mktime(self): return time.mktime(self.__tval) - def asctime(self): return time.asctime(self.__tval) - def isotime(self): - return "%04d-%02d-%02dT%02d:%02d:%02d.00Z" % self.__tval[:6] - def __repr__(self): return "yaml.timestamp('%s')" % self.isotime() - def __str__(self): return self.isotime() - def to_yaml_implicit(self): return self.isotime() - def __hash__(self): return hash(self.__tval[:6]) - def __cmp__(self,other): - try: - return cmp(self.__tval[:6],other.__tval[:6]) - except AttributeError: - return -1 - -try: # inherit from mx.DateTime functionality if available - from mx import DateTime - class timestamp(_timestamp): - def __init__(self,val=None): - _timestamp.__init__(self,val) - self.__mxdt = DateTime.mktime(self.__tval) - def __getattr__(self, name): - return getattr(self.__mxdt, name) -except: - class timestamp(_timestamp): pass - - - -def unquote(expr): - """ - summary: > - Simply returns the unquoted string, and the - length of the quoted string token at the - beginning of the expression. - """ - tok = expr[0] - if "'" == tok: - idx = 1 - odd = 0 - ret = "" - while idx < len(expr): - chr = expr[idx] - if "'" == chr: - if odd: ret += chr - odd = not odd - else: - if odd: - tok = expr[:idx] - break - ret += chr - idx += 1 - if "'" == tok: tok = expr - return (ret,len(tok)) - if '"' == tok: - idx = 1 - esc = 0 - while idx < len(expr): - chr = expr[idx] - if '"' == chr and not esc: - tok = expr[:idx] + '"' - break - if '\\' == chr and not esc: esc = 1 - else: esc = 0 - idx += 1 - if '"' == tok: - raise SyntaxError("unmatched quote: " + expr) - ret = eval(tok) #TODO: find better way to unquote - return (ret,len(tok)) - return (expr,len(expr)) diff --git a/server/yaml/ypath.py b/server/yaml/ypath.py deleted file mode 100755 index 51d9d2f..0000000 --- a/server/yaml/ypath.py +++ /dev/null @@ -1,462 +0,0 @@ -from types import ListType, StringType, IntType, DictType, InstanceType
-import re
-from urllib import quote
-from timestamp import unquote
-
-noTarget = object()
-
-def escape(node):
- """
- summary: >
- This function escapes a given key so that it
- may appear within a ypath. URI style escaping
- is used so that ypath expressions can be a
- valid URI expression.
- """
- typ = type(node)
- if typ is IntType: return str(node)
- if typ is StringType:
- return quote(node,'')
- raise ValueError("TODO: Support more than just string and integer keys.")
-
-class context:
- """
- summary: >
- A ypath visit context through a YAML rooted graph.
- This is implemented as a 3-tuple including the parent
- node, the current key/index and the value. This is
- an immutable object so it can be cached.
- properties:
- key: mapping key or index within the parent collection
- value: current value within the parent's range
- parent: the parent context
- root: the very top of the yaml graph
- path: a tuple of the domain keys
- notes: >
- The context class doesn't yet handle going down the
- domain side of the tree...
- """
- def __init__(self,parent,key,value):
- """
- args:
- parent: parent context (or None if this is the root)
- key: mapping key or index for this context
- value: value of current location...
- """
- self.parent = parent
- self.key = key
- self.value = value
- if parent:
- assert parent.__class__ is self.__class__
- self.path = parent.path + (escape(key),)
- self.root = parent.root
- else:
- assert not key
- self.path = tuple()
- self.root = self
- def __setattr__(self,attname,attval):
- if attname in ('parent','key','value'):
- if self.__dict__.get(attname):
- raise ValueError("context is read-only")
- self.__dict__[attname] = attval
- def __hash__(self): return hash(self.path)
- def __cmp__(self,other):
- try:
- return cmp(self.path,other.path)
- except AttributeError:
- return -1
- def __str__(self):
- if self.path:
- return "/".join(('',)+self.path)
- else:
- return '/'
-
-def to_context(target):
- if type(target) is InstanceType:
- if target.__class__ is context:
- return target
- return context(None,None,target)
-
-def context_test():
- lst = ['value']
- map = {'key':lst}
- x = context(None,None,map)
- y = context(x,'key',lst)
- z = context(y,0,'value')
- assert ('key',) == y.path
- assert 'key' == y.key
- assert lst == y.value
- assert x == y.parent
- assert x == y.root
- assert 0 == z.key
- assert 'value' == z.value
- assert y == z.parent
- assert x == z.root
- assert hash(x)
- assert hash(y)
- assert hash(z)
- assert '/' == str(x)
- assert '/key' == str(y)
- assert '/key/0' == str(z)
-
-class null_seg:
- """
- summary: >
- This is the simplest path segment, it
- doesn't return any results and doesn't
- depend upon its context. It also happens to
- be the base class which all segments derive.
- """
- def __iter__(self):
- return self
- def next_null(self):
- raise StopIteration
- def bind(self,cntx):
- """
- summary: >
- The bind function is called whenever
- the parent context has changed.
- """
- assert(cntx.__class__ is context)
- self.cntx = cntx
- def apply(self,target):
- self.bind(to_context(target))
- return iter(self)
- def exists(self,cntx):
- try:
- self.bind(cntx)
- self.next()
- return 1
- except StopIteration:
- return 0
- next = next_null
-
-class self_seg(null_seg):
- """
- summary: >
- This path segment returns the context
- node exactly once.
- """
- def __str__(self): return '.'
- def next_self(self):
- self.next = self.next_null
- return self.cntx
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.next = self.next_self
-
-class root_seg(self_seg):
- def __str__(self): return '/'
- def bind(self,cntx):
- self_seg.bind(self,cntx.root)
-
-class parent_seg(self_seg):
- def __str__(self): return '..'
- def bind(self,cntx):
- if cntx.parent: cntx = cntx.parent
- self_seg.bind(self,cntx)
-
-class wild_seg(null_seg):
- """
- summary: >
- The wild segment simply loops through
- all of the sub-contexts for a given object.
- If there aren't any children, this isn't an
- error it just doesn't return anything.
- """
- def __str__(self): return '*'
- def next_wild(self):
- key = self.keys.next()
- return context(self.cntx,key,self.values[key])
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- typ = type(cntx.value)
- if typ is ListType:
- self.keys = iter(xrange(0,len(cntx.value)))
- self.values = cntx.value
- self.next = self.next_wild
- return
- if typ is DictType:
- self.keys = iter(cntx.value)
- self.values = cntx.value
- self.next = self.next_wild
- return
- self.next = self.next_null
-
-class trav_seg(null_seg):
- """
- summary: >
- This is a recursive traversal of the range, preorder.
- It is a recursive combination of self and wild.
- """
- def __str__(self): return '/'
- def next(self):
- while 1:
- (cntx,seg) = self.stk[-1]
- if not seg:
- seg = wild_seg()
- seg.bind(cntx)
- self.stk[-1] = (cntx,seg)
- return cntx
- try:
- cntx = seg.next()
- self.stk.append((cntx,None))
- except StopIteration:
- self.stk.pop()
- if not(self.stk):
- self.next = self.next_null
- raise StopIteration
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.stk = [(cntx,None)]
-
-class match_seg(self_seg):
- """
- summary: >
- Matches a particular key within the
- current context. Kinda boring.
- """
- def __str__(self): return str(self.key)
- def __init__(self,key):
- #TODO: Do better implicit typing
- try:
- key = int(key)
- except: pass
- self.key = key
- def bind(self,cntx):
- try:
- mtch = cntx.value[self.key]
- cntx = context(cntx,self.key,mtch)
- self_seg.bind(self,cntx)
- except:
- null_seg.bind(self,cntx)
-
-class conn_seg(null_seg):
- """
- summary: >
- When two segments are connected via a slash,
- this is a composite. For each context of the
- parent, it binds the child, and returns each
- context of the child.
- """
- def __str__(self):
- if self.parent.__class__ == root_seg:
- return "/%s" % self.child
- return "%s/%s" % (self.parent, self.child)
- def __init__(self,parent,child):
- self.parent = parent
- self.child = child
- def next(self):
- while 1:
- try:
- return self.child.next()
- except StopIteration:
- cntx = self.parent.next()
- self.child.bind(cntx)
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
- try:
- cntx = self.parent.next()
- except StopIteration:
- return
- self.child.bind(cntx)
-
-
-class pred_seg(null_seg):
- def __str__(self): return "%s[%s]" % (self.parent, self.filter)
- def __init__(self,parent,filter):
- self.parent = parent
- self.filter = filter
- def next(self):
- while 1:
- ret = self.parent.next()
- if self.filter.exists(ret):
- return ret
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
-
-class or_seg(null_seg):
- def __str__(self): return "%s|%s" % (self.lhs,self.rhs)
- def __init__(self,lhs,rhs):
- self.rhs = rhs
- self.lhs = lhs
- self.unq = {}
- def next(self):
- seg = self.lhs
- try:
- nxt = seg.next()
- self.unq[nxt] = nxt
- return nxt
- except StopIteration: pass
- seg = self.rhs
- while 1:
- nxt = seg.next()
- if self.unq.get(nxt,None):
- continue
- return nxt
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.lhs.bind(cntx)
- self.rhs.bind(cntx)
-
-class scalar:
- def __init__(self,val):
- self.val = val
- def __str__(self):
- return str(self.val)
- def value(self):
- return self.val
-
-class equal_pred:
- def exists_true(self,cntx): return 1
- def exists_false(self,cntx): return 0
- def exists_scalar(self,cntx):
- self.rhs.bind(cntx)
- try:
- while 1:
- cntx = self.rhs.next()
- if str(cntx.value) == self.lhs: #TODO: Remove type hack
- return 1
- except StopIteration: pass
- return 0
- def exists_segment(self,cntx):
- raise NotImplementedError()
- def __init__(self,lhs,rhs):
- if lhs.__class__ == scalar:
- if rhs.__class__ == scalar:
- if rhs.value() == lhs.value():
- self.exists = self.exists_true
- else:
- self.exists = self.exists_false
- else:
- self.exists = self.exists_scalar
- else:
- if rhs.__class__ == scalar:
- (lhs,rhs) = (rhs,lhs)
- self.exists = self.exists_scalar
- else:
- self.exists = self.exists_segment
- self.lhs = str(lhs.value()) #TODO: Remove type hack
- self.rhs = rhs
-
-matchSegment = re.compile(r"""^(\w+|/|\.|\*|\"|\')""")
-
-def parse_segment(expr):
- """
- Segments occur between the slashes...
- """
- mtch = matchSegment.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok: return (trav_seg(),expr)
- elif '.' == tok:
- if len(expr) > 1 and '.' == expr[1]:
- seg = parent_seg()
- siz = 2
- else:
- seg = self_seg()
- elif '*' == tok: seg = wild_seg()
- elif '"' == tok or "'" == tok:
- (cur,siz) = unquote(expr)
- seg = match_seg(cur)
- else:
- seg = match_seg(tok)
- return (seg,expr[siz:])
-
-matchTerm = re.compile(r"""^(\w+|/|\.|\(|\"|\')""")
-
-def parse_term(expr):
- mtch = matchTerm.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok or '.' == tok:
- return parse(expr)
- if '(' == tok:
- (term,expr) = parse_predicate(expr)
- assert ')' == expr[0]
- return (term,expr[1:])
- elif '"' == tok or "'" == tok:
- (val,siz) = unquote(expr)
- else:
- val = tok; siz = len(tok)
- return (scalar(val),expr[siz:])
-
-def parse_predicate(expr):
- (term,expr) = parse_term(expr)
- if not term: raise SyntaxError("term expected: '%s'" % expr)
- tok = expr[0]
- if '=' == tok:
- (rhs,expr) = parse_term(expr[1:])
- return (equal_pred(term,rhs),expr)
- if '(' == tok:
- raise "No functions allowed... yet!"
- if ']' == tok or ')' == tok:
- if term.__class__ is scalar:
- term = match_seg(str(term))
- return (term,expr)
- raise SyntaxError("ypath: expecting operator '%s'" % expr)
-
-def parse_start(expr):
- """
- Initial checking on the expression, and
- determine if it is relative or absolute.
- """
- if type(expr) != StringType or len(expr) < 1:
- raise TypeError("string required: " + repr(expr))
- if '/' == expr[0]:
- ypth = root_seg()
- else:
- ypth = self_seg()
- expr = '/' + expr
- return (ypth,expr)
-
-def parse(expr):
- """
- This the parser entry point, the top level node
- is always a root or self segment. The self isn't
- strictly necessary, but it keeps things simple.
- """
- (ypth,expr) = parse_start(expr)
- while expr:
- tok = expr[0]
- if '/' == tok:
- (child, expr) = parse_segment(expr[1:])
- if child: ypth = conn_seg(ypth,child)
- continue
- if '[' == tok:
- (filter, expr) = parse_predicate(expr[1:])
- assert ']' == expr[0]
- expr = expr[1:]
- ypth = pred_seg(ypth,filter)
- continue
- if '|' == tok:
- (rhs, expr) = parse(expr[1:])
- ypth = or_seg(ypth,rhs)
- continue
- if '(' == tok:
- (child,expr) = parse(expr[1:])
- assert ')' == expr[0]
- expr = expr[1:]
- ypth = conn_seg(ypth,child)
- continue
- break
- return (ypth,expr)
-
-class convert_to_value(null_seg):
- def __init__(self,itr):
- self.itr = itr
- def next(self):
- return self.itr.next().value
- def bind(self,cntx):
- self.itr.bind(cntx)
-
-def ypath(expr,target=noTarget,cntx=0):
- (ret,expr) = parse(expr)
- if expr: raise SyntaxError("ypath parse error `%s`" % expr)
- if not cntx: ret = convert_to_value(ret)
- if target is noTarget: return ret
- return ret.apply(target)
diff --git a/settings b/settings new file mode 100644 index 0000000..8344dee --- /dev/null +++ b/settings @@ -0,0 +1,7 @@ +# configuration for master servers + +[general] +is_master = 0 +is_minion = 1 +master_server = funcmaster +log_level = DEBUG diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b91f1c6 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +#!/usr/bin/python + +import sys +from distutils.core import setup, Extension +#from setuptools import setup,find_packages +import string +import glob + +NAME = "func" +VERSION = open("version", "r+").read().split()[0] +SHORT_DESC = "%s remote configuration and management api" % NAME +LONG_DESC = """ +A small pluggabe xml-rpc daemon used by %s to implement various web services hooks +""" % NAME + + +if __name__ == "__main__": + + manpath = "share/man/man1/" + etcpath = "/etc/%s" % NAME + etcpathdb = "/etc/%s/db" % NAME + wwwpath = "/var/www/%s" % NAME + initpath = "/etc/init.d/" + logpath = "/var/log/%s/" % NAME + logpathdb = "/var/log/%s/db/" % NAME + settingspath = "/var/lib/%s/" % NAME + migraterepopath = "/var/lib/%s/db/" % NAME + schemapath = "/usr/share/%s/db_schema/" % NAME + upgradepath = schemapath + "upgrade/" + puppetpath = "/usr/share/%s/puppet-config/" % NAME + manifestpath = "/etc/puppet/manifests/" + profiletemplatepath = "/usr/share/%s/profile-template/" % NAME + profilespath = "/var/lib/%s/profiles/" % NAME + queuedprofilespath = "/var/lib/%s/profiles/queued/" % NAME + setup( + name="%s" % NAME, + version = VERSION, + author = "Lots", + author_email = "et-mgmt-tools@redhat.com", + url = "https://hosted.fedoraproject.org/projects/func/", + license = "GPL", + scripts = ["scripts/funcd", + ], + # package_data = { '' : ['*.*'] }, + package_dir = {"%s" % NAME: "", + "%s/server" % NAME: "server", + "%s/server/modules" % NAME: "modules/", + "%s/client" % NAME: "client" + }, + packages = ["%s" % NAME, + "%s/server" % NAME, + "%s/client" % NAME, + "%s/server/modules" % NAME + ], + data_files = [(initpath, ["init-scripts/funcd"]), + (etcpath, ["settings",]), + (etcpathdb, []), + (logpath, []), + (logpathdb, []), + (migraterepopath, []), + (profilespath, []), + (queuedprofilespath, [])], + description = SHORT_DESC, + long_description = LONG_DESC + ) + @@ -0,0 +1 @@ +0.11 2 |