Cloning into '/root/all-modules-git/DistributedRateLimiting'... fatal: '/git/DistributedRateLimiting.git' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Cloning into '/root/all-modules-git/PingOfDeath'... fatal: '/git/PingOfDeath.git' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Cloning into '/root/all-modules-git/PLCRT'... fatal: '/git/PLCRT.git' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Auto-merging plnet.py CONFLICT (content): Merge conflict in plnet.py Automatic merge failed; fix conflicts and then commit the result. FAILED ! -- out+err below (command was git clone root@git.onelab.eu:/git/DistributedRateLimiting.git /root/all-modules-git/DistributedRateLimiting) FAILED ! -- end of quoted output FAILED ! -- out+err below (command was git clone root@git.onelab.eu:/git/PingOfDeath.git /root/all-modules-git/PingOfDeath) FAILED ! -- end of quoted output FAILED ! -- out+err below (command was git clone root@git.onelab.eu:/git/PLCRT.git /root/all-modules-git/PLCRT) FAILED ! -- end of quoted output FAILED ! -- out+err below (command was git merge --ff origin/master) FAILED ! -- end of quoted output Changes to tag in mainline

Changes to tag in mainline - status on 2016-09-24 at 22:06 (CEST)


Diffs in module monitor (3.1-6) : 18377 chars

< (left) monitor-3.1-6

> (right) monitor

diff --git a/commands/myops.py b/commands/myops.py
new file mode 100755
index 0000000..75b969e
--- /dev/null
+++ b/commands/myops.py
@@ -0,0 +1,352 @@
+#!/usr/bin/python
+
+import os
+import sys
+import string
+import time
+
+import getopt
+import sys
+import os
+import xml, xmlrpclib
+from getpass import getpass
+from operator import attrgetter, itemgetter
+
+def get_plc_api(target, url, username, password, expires, debug_mode):
+    # Either read session from disk or create it and save it for later
+    metasession = "%s/%s_%s" % (os.environ['HOME'], ".metasession", target)
+    if os.path.exists(metasession):
+        (localurl,session) =  open(metasession, 'r').read().strip().split()
+        plc = xmlrpclib.Server(localurl, verbose=False, allow_none=True)
+    else:
+        plc = xmlrpclib.Server(url, verbose=False, allow_none=True)
+        if password == None: password = getpass()
+        auth = {'Username' : username, 
+                'AuthMethod' : 'password',
+                'AuthString' : password}
+        session = plc.GetSession(auth, expires*(60*60*24))
+        with open(metasession, 'w') as f: f.write("%s %s\n" % (url,session)) # 'with' auto-closes
+
+    auth = {'AuthMethod' : 'session', 'session' : session}
+
+    class PLC:
+        def __init__(self, plc, auth):
+            self.plc = plc
+            self.auth = auth
+
+        def __getattr__(self, name):
+            method = getattr(self.plc, name)
+            if method is None:
+                raise AssertionError("Method does not exist: %s" % method)
+            if not debug_mode or ('Get' in name or 'AuthCheck' in name):
+                return lambda *params : method(self.auth, *params)
+            else:
+                def call(name,*params):
+                    print "DEBUG not running: %s(%s)" % (name, params)
+                return lambda *params : call(name,*params)
+
+    plc_api = PLC(plc, auth)
+    try:
+        # make sure the session is working
+        plc_api.AuthCheck()
+    except:
+        # everything worked except the auth check. try again asking for passwd.
+        plc_api = get_plc_api(target, url, username, None, expires, debug_mode)
+
+    return plc_api
+
+def usage(parser):
+    print """
+myops.py <TARGET> <ACTION> [<object>] [args]
+    MYOPS CLI uses sessions to avoid storing passwords.
+    You choose the session expiration via --expires <days>.
+
+TARGET:
+    When your session is saved it is identified by your given 'target'
+    name.  This is a unique string you chose to identify the --apiurl. 
+    For example, one might use:
+        plc
+        vicci
+        test
+        vini
+
+ACTION:
+    Connect to TARGET and perform ACTION. The current actions are:
+        enabled   --  Manage site, node, and slice 'enabled' states.
+                      Object may be sitename, hostname, or slicename.
+        
+        exempt    --  Manage site, node, and slice exemptions from 
+                      myops policy actions.  Object may be sitename, 
+                      hostname, or slicename.
+
+        removeall --  Remove all exemptions at site & from nodes, slices 
+
+        exemptall --  Add exemptions at site & to nodes, slices 
+
+        disableall--  Disable everything at a site: 
+                        disable site, 
+                        disable slices,
+
+        enableall --  Release everything at a site: 
+                        re-enable site, 
+                        re-enable slices,
+
+EXAMPLES:
+    # setup session and save target name 'plc' for future calls
+    myops.py plc --apiurl https://boot.planet-lab.org/PLCAPI/ \\
+                --username soltesz@cs.princeton.edu
+
+    # list current exemptions at plc
+    myops.py plc exempt
+
+    # to list only one site (nothing will show if no exemption is present)
+    myops.py plc exempt princeton
+
+    # add an exemption until a specific date
+    myops.py plc exempt princeton --expires 20120131
+
+    # remove this exemption
+    myops.py plc exempt princeton -r
+
+    # exempt just a slice, not the whole site.
+    myops.py plc exempt princeton_comon --expires 20120131
+
+    # re-enable a site & slices then, exempt site & slices for 7 days
+    myops.py plc enableall princeton 
+    myops.py plc exemptall princeton --expires 7
+
+""" 
+    parser.print_help()
+
+def unparse_expire_str(value):
+    if value == None:
+        expires = 60*60*24*30   # 30 days default
+    else:
+        expires = time.mktime(time.strptime(value, "%Y%m%d")) - time.time()
+    return int(expires)
+
+def parse_expire_str(value):
+    import optparse
+    if value == None:
+        return None
+    elif len(value) <= 3:
+        # days from now
+        value = time.strftime("%Y%m%d", time.localtime(time.time()+int(value)*60*60*24))
+    elif len(value) != 8 and value[:3] != "201": # 201 == this decade.
+        # flip out
+        raise optparse.OptionValueError
+    return value
+
+class PlcObj(object):
+    def __init__(self, name):
+        if type(name) == type(""):
+            self.name = name
+        elif type(name) == type({}):
+            if 'login_base' in name:
+                self.name = name['login_base']
+            elif 'hostname' in name:
+                self.name = name['hostname']
+            elif 'name' in name:
+                self.name = name['name']
+
+        self.kind = None
+        if '_' in self.name: 
+            kind = 'Slice'
+        elif '.' in self.name: 
+            kind='Node'
+        else: 
+            kind='Site'
+        self.kind = kind
+
+    def list(self,target,action,*vals):
+        if action == "enabled":
+            print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s --disable" % self.name)
+        elif action == "exempt":
+            print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s --expires %s" % ((self.name,)+ vals))
+
+    def enable(self,api,state):
+        if self.kind == 'Slice':
+            # change value of existing slice tag, if it exists.
+            tl = api.GetSliceTags({'name' : self.name, 'tagname' : 'enabled', 'value' : '0' if state else '1'})
+            if len(tl) == 0:
+                api.AddSliceTag(self.name, 'enabled', '1' if state else '0')
+            else:
+                for t in tl: 
+                    api.UpdateSliceTag(t['slice_tag_id'], {'value' : '1' if state else '0'})
+        elif self.kind == 'Node':
+            if state == True: 
+                api.UpdateNode(self.name, {'boot_state' : 'boot'})
+            else: 
+                api.UpdateNode(self.name, {'boot_state' : 'disabled'})
+        elif self.kind == 'Site':
+            api.UpdateSite(self.name, {'enabled' : state})
+            
+    def exempt(self,api,expires):
+        if expires != None:
+            if self.kind == 'Slice': 
+                try: api.AddSliceTag(self.name, 'exempt_slice_until', expires)
+                except: api.UpdateSliceTag(self.name, expires)
+            elif self.kind == 'Node': 
+                try: api.AddNodeTag(self.name, 'exempt_node_until', expires)
+                except: api.UpdateNodeTag(self.name, expires)
+            elif self.kind == 'Site': 
+                try: api.AddSiteTag(api.GetSites(self.name, ['site_id'])[0]['site_id'], 'exempt_site_until', expires)
+                except: api.UpdateSiteTag(api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'})[0]['site_tag_id'], expires)
+        else:
+            # remove
+            if self.kind == 'Slice': 
+                tag_id_l = api.GetSliceTags({'name' : self.name, 'tagname' : 'exempt_slice_until'}, ['slice_tag_id'])
+                if len(tag_id_l) > 0:   
+                    tag_id = tag_id_l[0]['slice_tag_id']
+                    api.DeleteSliceTag(tag_id)
+            elif self.kind == 'Node': 
+                tag_id_l = api.GetNodeTags({'hostname' : self.name, 'tagname' : 'exempt_node_until'}, ['node_tag_id'])
+                if len(tag_id_l) > 0: 
+                    tag_id = tag_id_l[0]['node_tag_id']
+                    api.DeleteNodeTag(tag_id)
+            elif self.kind == 'Site': 
+                tag_id_l = api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'}, ['site_tag_id'])
+                if len(tag_id_l) > 0: 
+                    tag_id = tag_id_l[0]['site_tag_id']
+                    api.DeleteSiteTag(tag_id)
+
+
+def main():
+    from optparse import OptionParser
+    copy = False
+    parser = OptionParser()
+
+    parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False, help="")
+    parser.add_option("-v", "--verbose", dest="verbose", default=False, help="")
+    parser.add_option("-u", "--apiurl", dest="url", default="https://www.planet-lab.org/PLCAPI/", help="Set PLC URL for action")
+    parser.add_option("-U", "--username", dest="username", default=None, help="Login as username")
+    parser.add_option("-P", "--password", dest="password", default=None, help="Use provided password; otherwise prompt for password")
+    parser.add_option("-e", "--expires", dest="expires", default=None, help="Set expiration date YYYYMMDD (or <days>); default is None (i.e. removed)")
+    parser.add_option("", "--disable", dest="disable", default=False, action="store_true", help="Disable object.")
+    parser.add_option("-r", "--remove", dest="remove", action="store_true", default=False, help="Remove object from exemption" )
+    parser.add_option("-l", "--list", dest="list", action="store_true", default=False, help="List objects with command used to generate them")
+    parser.add_option("-S", "--site", dest="login_base", default=None, help="Act on this site")
+    parser.add_option("-H", "--host", dest="hostname", default=None, help="Act on this node")
+    parser.add_option("-s", "--slice", dest="slicename", default=None, help="Act on this site")
+
+    (opt, args) = parser.parse_args()
+    opt.expires = parse_expire_str(opt.expires)
+
+    if len(args) == 0:
+        usage(parser)
+        sys.exit(1)
+
+    target = args[0]; 
+    api = get_plc_api(target, opt.url, opt.username, opt.password, unparse_expire_str(opt.expires), opt.debug)
+
+    action_list = ['enabled', 'exempt', 'removeall', 'exemptall', 'enableall', 'disableall']
+
+    for i,action in enumerate(args[1:]):
+        if action in action_list:
+            if len(args) > i+2 and args[i+2] not in action_list:
+                objname = args[i+2]
+            else:
+                objname = None
+
+        if action == "enabled":
+
+            if not opt.list and not opt.hostname and not opt.slicename and not opt.login_base:
+                opt.list = True
+            if opt.list:
+                print "Listing only *disabled* objects"
+                sites = api.GetSites({'peer_id' : None, 'enabled': False})
+                nodes = api.GetNodes({'peer_id' : None, 'boot_state' : 'disabled'})
+                slices= api.GetSliceTags({'tagname' : 'enabled'})
+
+                for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
+                    if len(objlist) > 0: print header
+                    for t in objlist:
+                        o = PlcObj(t)
+                        o.list(target, action)
+
+        if action == "exempt":
+            if not opt.list and not opt.remove and opt.expires == None:
+                opt.list = True
+
+            if opt.list:
+                if objname == None:
+                    # NOTE: this works around a bug as of 2011/12/23 that
+                    #    deleted sites do not also delete all associated site tags.
+                    site_lb = [ l['login_base'] for l in api.GetSites({'peer_id' : None}, ['login_base']) ]
+                    sites = api.GetSiteTags({'tagname' : 'exempt_site_until'})
+                    sites = filter(lambda x: x['login_base'] in site_lb, sites)
+
+                    nodes = api.GetNodeTags({'tagname' : 'exempt_node_until'})
+                    slices = api.GetSliceTags({'tagname' : 'exempt_slice_until'})
+                else:
+                    try: sites = api.GetSiteTags({'login_base': objname, 'tagname' : 'exempt_site_until'})
+                    except: sites = []
+                    try: nodes = api.GetNodeTags({'hostname' : objname, 'tagname' : 'exempt_node_until'})
+                    except: nodes = []
+                    try: slices = api.GetSliceTags({'name' : objname, 'tagname' : 'exempt_slice_until'})
+                    except: slices = []
+
+                for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
+                    if len(objlist) > 0: print header
+                    for t in objlist:
+                        o = PlcObj(t)
+                        o.list(target, action, t['value'])
+
+            if opt.remove or opt.expires:
+                obj = PlcObj(objname)
+                # if opt.expires == None, the exemption will be removed.
+                obj.exempt(api,opt.expires)
+
+        if action == "disableall":
+            if objname == None: raise Exception("Provide a site name to disable")
+            #  disable site, disable slices,
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.enable(api,False)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.enable(api,False)
+                
+        if action == "enableall":
+            #  enable site, enable slices,
+            if objname == None: raise Exception("Provide a site name to enableall")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.enable(api,True)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.enable(api,True)
+
+        if action == "removeall":
+            #  remove enable site, enable slices,
+            if objname == None: raise Exception("Provide a site name to remove")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.exempt(api,None)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.exempt(api,None)
+
+        if action == "exemptall":
+            if objname == None: raise Exception("Provide a site name to exempt")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.exempt(api,opt.expires)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.exempt(api,opt.expires)
+
+if __name__ == '__main__':
+    main()
diff --git a/web/MonitorWeb/monitorweb/controllers.py b/web/MonitorWeb/monitorweb/controllers.py
index 0f919ab..b368df9 100644
--- a/web/MonitorWeb/monitorweb/controllers.py
+++ b/web/MonitorWeb/monitorweb/controllers.py
@@ -25,6 +25,13 @@ from monitor.wrapper.plccache import plcdb_hn2lb as site_hn2lb
 
 from monitorweb.templates.links import *
 
+def session_clear_all():
+    session.flush()
+    try: 
+        session.expunge_all() 
+    except AttributeError: # SQLAlchemy < 0.5.1 
+        session.clear() 
+
 class ObjectQueryFields(widgets.WidgetsList):
 	"""The WidgetsList defines the fields of the form."""
 	pass
@@ -389,7 +396,7 @@ class Root(controllers.RootController, MonitorXmlrpcServer, LocalExtensions):
 	def nodeslow(self, filter='boot'):
 		print "NODE------------------"
 		print "befor-len: ", len( [ i for i in session] )
-		session.flush(); session.clear()
+        session_clear_all()
 		print "after-len: ", len( [ i for i in session] )
 		fbquery = FindbadNodeRecord.get_all_latest()
 		query = []
@@ -513,7 +520,7 @@ class Root(controllers.RootController, MonitorXmlrpcServer, LocalExtensions):
 
 
 	def pre_view(self, **data):
-		session.flush(); session.clear()
+        session_clear_all()
 
 		loginbase=None
 		loginbase_list=[]
@@ -620,7 +627,7 @@ class Root(controllers.RootController, MonitorXmlrpcServer, LocalExtensions):
 	@expose(template="monitorweb.templates.pcuview")
 	@exception_handler(nodeaction_handler,"isinstance(tg_exceptions,RuntimeError)")
 	def pcuviewold(self, loginbase=None, pcuid=None, hostname=None, since=20, **data):
-		session.flush(); session.clear()
+        session_clear_all()
 		sitequery=[]
 		pcuquery=[]
 		nodequery=[]
@@ -737,7 +744,7 @@ class Root(controllers.RootController, MonitorXmlrpcServer, LocalExtensions):
 	def pcu(self, filter='all'):
 		print "PCUVIEW------------------"
 		print "befor-len: ", len( [ i for i in session] )
-		session.flush(); session.clear()
+        session_clear_all()
 		print "after-len: ", len( [ i for i in session] )
 		fbquery = FindbadPCURecord.get_all_latest()
 		query = []
@@ -775,7 +782,7 @@ class Root(controllers.RootController, MonitorXmlrpcServer, LocalExtensions):
 	def site(self, filter='all'):
 		print "SITE------------------"
 		print "befor-len: ", len( [ i for i in session] )
-		session.flush(); session.clear()
+        session_clear_all()
 		print "after-len: ", len( [ i for i in session] )
 		filtercount = {'good' : 0, 'down': 0, 'online':0, 'offline' : 0, 'new' : 0, 'pending' : 0, 'all' : 0}
 		fbquery = HistorySiteRecord.query.all()
diff --git a/web/MonitorWeb/monitorweb/monitor_xmlrpc.py b/web/MonitorWeb/monitorweb/monitor_xmlrpc.py
index 2f46879..7d04c48 100644
--- a/web/MonitorWeb/monitorweb/monitor_xmlrpc.py
+++ b/web/MonitorWeb/monitorweb/monitor_xmlrpc.py
@@ -98,7 +98,10 @@ class MonitorXmlrpcServer(object):
 			if method is None or not getattr(method, "exposed", False):
 				raise AssertionError("method does not exist")
 
-			session.clear()
+            try:
+			    session.expunge_all()
+            except AttributeError: # SQLAlchemy < 0.5.1
+                session.clear()
 			# Call the method, convert it into a 1-element tuple
 			# as expected by dumps					   
 			response = method(*params)

Back to top


Diffs in module myplc (5.3-3) : 4154 chars

< (left) myplc-5.3-3

> (right) myplc

diff --git a/support-scripts/cleanup-zombies.py b/support-scripts/cleanup-zombies.py
new file mode 100755
index 0000000..c3df0ac
--- /dev/null
+++ b/support-scripts/cleanup-zombies.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+
+# node manager has a few working assumptions, like
+# if a domain d does not exist, there is no /vservers/d 
+
+# this utility tries to detect and assess potentially
+# conflictual situations, that could prevent nodemanager
+# from recovering properly
+#
+# the logic is simply to find zombie-containers, i.e.
+# VMs that do have a workdir in /vservers/<zombie>
+# but that are not reported as running by virsh --list
+# which suggests they have been improperly trashed
+###
+#
+# then we trash them but for that some subdirs must be
+# btrfs-subvolume-delete'd and not rm-rf'ed
+# 
+
+import subprocess
+import glob
+import os, os.path
+from argparse import ArgumentParser
+
+def running_domains():
+    command = [
+        'virsh',
+    	'-c',
+        'lxc:///',
+        'list',
+        '--name',
+    ]
+    names_string = subprocess.check_output(
+        command,
+        universal_newlines = True,
+        stdin = subprocess.DEVNULL,
+        )
+    names = [ name for name in names_string.strip().split("\n") if name ]
+    return names
+
+def existing_vservers():
+    all_dirs = glob.glob("/vservers/*")
+    dirs = ( dir for dir in all_dirs if os.path.isdir(dir) )
+    dirnames = ( path.replace("/vservers/", "") for path in dirs)
+    return dirnames
+
+def display_or_run_commands(commands, run):
+    if commands:
+        if not run:
+            print("========== You should run")
+            for command in commands:
+                print(" ".join(command))
+        else:
+            for command in commands:
+                print("Running {}".format(" ".join(command)))
+                retcod = subprocess.call(command)
+                if retcod != 0:
+                    print("Warning: failed with retcod = {}".format(retcod))
+
+def main():
+    parser = ArgumentParser()
+    # the default is to cowardly show commands to run
+    # use --run to actually do it
+    parser.add_argument("-r", "--run", action='store_true', default=False)
+    args = parser.parse_args()
+
+    running_containers = set(running_domains())
+    existing_containers = set(existing_vservers())
+    zombies_containers = existing_containers - running_containers
+
+    # the prefix used to locate subvolumes
+    flavour_prefixes = [
+        'onelab-',
+        'lxc-',
+        'omf-',
+        ]
+
+    # we need to call 'btrfs subvolume delete' on these remainings
+    # instead of just 'rm'
+    if zombies_containers:
+        commands = []
+        zombie_dirs = ["/vservers/"+z for z in zombies_containers]
+        print("-------- Found {} existing, but not running, containers".format(len(zombies_containers)))
+        print("zombie_dirs='{}'".format(" ".join(zombie_dirs)))
+        subvolumes = [ path
+                       for z in zombies_containers
+                       for prefix in flavour_prefixes
+                       for path in glob.glob("/vservers/{z}/{prefix}*".format(z=z, prefix=prefix))]
+        if subvolumes:
+            print("zombie_subvolumes='{}'".format(" ".join(subvolumes)))
+            for subvolume in subvolumes:
+                commands.append([ 'btrfs', 'subvolume', 'delete', subvolume])
+        for zombie_dir in zombie_dirs:
+            commands.append([ 'btrfs', 'subvolume', 'delete', zombie_dir ])
+        display_or_run_commands(commands, args.run)
+        # find the containers dirs that might still exist
+        zombie_dirs = [ path for path in zombie_dirs if os.path.isdir(path) ]
+        commands = [ ['rm', '-rf', path] for path in zombie_dirs ]
+        display_or_run_commands(commands, args.run)
+        
+    #### should happen much less frequently
+    weirdos_containers = running_containers - existing_containers
+    if weirdos_containers:
+        print("-------- Found {} running but non existing".format(len(weirdos_containers)))
+        for w in weirdos_containers:
+            print("/vservers/{}".format(w))
+
+main()    

Back to top


Diffs in module nodeconfig (5.2-5) : 381 chars

< (left) nodeconfig-5.2-5

> (right) nodeconfig

diff --git a/PlanetLabConf/sysctl.conf b/PlanetLabConf/sysctl.conf
index ce6f512..52db593 100644
--- a/PlanetLabConf/sysctl.conf
+++ b/PlanetLabConf/sysctl.conf
@@ -28,3 +28,5 @@ net.ipv4.tcp_rmem = 4096 87380 4194304
 net.ipv4.tcp_wmem = 4096 16384 4194304
 net.netfilter.nf_conntrack_icmp_timeout = 60
 
+fs.inotify.max_user_watches = 81920
+fs.inotify.max_user_instances = 1024

Back to top


Diffs in module oml (2.6.1-1) : 463 chars

< (left) oml-2.6.1-1

> (right) oml

diff --git a/liboml.spec b/liboml.spec
index 76e8a03..3a716e7 100644
--- a/liboml.spec
+++ b/liboml.spec
@@ -73,6 +73,12 @@ popd
 rm -fr $RPM_BUILD_ROOT
 pushd %{oml_actual_name}
 make install DESTDIR=$RPM_BUILD_ROOT
+# for rpm on fedora23 : since we don't do debug packages anymore
+# see build/Makefile 
+%if ("%{distro}" == "Fedora" && %{distrorelease} >= 23)
+rm -rf $RPM_BUILD_ROOT/usr/lib/debug
+rm -rf $RPM_BUILD_ROOT/usr/src/debug
+%endif
 popd
 
 %clean

Back to top


Diffs in module plcapi (5.3-10) : 3476 chars

< (left) plcapi-5.3-10

> (right) plcapi

diff --git a/PLC/API.py b/PLC/API.py
index 829e759..f09034c 100644
--- a/PLC/API.py
+++ b/PLC/API.py
@@ -13,20 +13,6 @@ import traceback
 import string
 
 import xmlrpclib
-import simplejson
-# use this one
-json=simplejson
-#try:
-#    # Try to use jsonlib before using simpljson. This is a hack to get around
-#    # the fact that the version of simplejson available for f8 is slightly 
-#    # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib 
-#    # package available for f8, so this has to be installed manually and
-#    # is not expected to always be available. Remove this once we move away
-#    # from f8 based MyPLC's         
-#    import jsonlib
-#    json = jsonlib
-#except:
-#    json = simplejson 
 
 # See "2.2 Characters" in the XML specification:
 #
@@ -34,8 +20,13 @@ json=simplejson
 # avoiding
 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
 
-invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
-xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
+invalid_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
+# broke with f24, somehow we get a unicode as an incoming string to be translated
+str_xml_escape_table = string.maketrans("".join((chr(x) for x in invalid_codepoints)),
+                                        "?" * len(invalid_codepoints))
+# loosely inspired from
+# http://stackoverflow.com/questions/1324067/how-do-i-get-str-translate-to-work-with-unicode-strings
+unicode_xml_escape_table = { invalid : u"?" for invalid in invalid_codepoints}
 
 def xmlrpclib_escape(s, replace = string.replace):
     """
@@ -50,7 +41,23 @@ def xmlrpclib_escape(s, replace = string.replace):
     s = replace(s, ">", "&gt;",)
 
     # Replace invalid 7-bit control characters with '?'
-    return s.translate(xml_escape_table)
+    if isinstance(s, str):
+        return s.translate(str_xml_escape_table)
+    else:
+        return s.translate(unicode_xml_escape_table)
+
+def test_xmlrpclib_escape():
+    inputs = [
+        # full ASCII 
+        "".join( (chr(x) for x in range(128))),
+        # likewise but as a unicode string up to 256
+        u"".join( (unichr(x) for x in range(256))),
+        ]
+    for input in inputs:
+        print "==================== xmlrpclib_escape INPUT"
+        print type(input), '->', input
+        print "==================== xmlrpclib_escape OUTPUT"
+        print xmlrpclib_escape(input)
 
 def xmlrpclib_dump(self, value, write):
     """
@@ -258,4 +265,6 @@ class PLCAPI:
        
         return json.dumps(result) 
         
-        
+# one simple unit test        
+if __name__ == '__main__':
+    test_xmlrpclib_escape()
diff --git a/plcapi.spec b/plcapi.spec
index 47b84cb..0cae1a4 100644
--- a/plcapi.spec
+++ b/plcapi.spec
@@ -38,7 +38,7 @@ Requires: python-lxml
 %if ("%{distro}" == "Fedora" && %{distrorelease} <= 20) || ("%{distro}" != "Fedora")
 Requires: SOAPpy
 %endif
-Requires: python-simplejson
+#Requires: python-simplejson
 # for the RebootNodeWithPCU method
 Requires: pcucontrol >= 1.0-6
 # for OMF integration
@@ -61,7 +61,8 @@ Conflicts: MyPLC <= 4.3
 # Standard xmlrpc.so that ships with PHP does not marshal NULL
 # for building the wsdl interface we used to require PyXML
 # but this has gone with f20 so turning this off for now
-BuildRequires: php-devel python-simplejson
+BuildRequires: php-devel
+#BuildRequires: python-simplejson
 Obsoletes: php-xmlrpc
 Provides: php-xmlrpc
 

Back to top


Diffs in module sfa (3.1-20) : 297363 chars

< (left) sfa-3.1-20

> (right) sfa

diff --git a/Makefile b/Makefile
index 45add83..e6aa5a9 100644
--- a/Makefile
+++ b/Makefile
@@ -224,6 +224,8 @@ BINS =	./config/sfa-config-tty ./config/gen-sfa-cm-config.py \
 
 synclib: synccheck
 	+$(RSYNC) --relative ./sfa/ --exclude migrations $(SSHURL)/usr/lib\*/python2.\*/site-packages/
+synclib3: synccheck
+	+$(RSYNC) --relative ./sfa/ --exclude migrations $(SSHURL)/usr/lib\*/python3.\*/site-packages/
 synclibdeb: synccheck
 	+$(RSYNC) --relative ./sfa/ --exclude migrations $(SSHURL)/usr/share/pyshared/
 syncmigrations:
diff --git a/clientbin/getNodes.py b/clientbin/getNodes.py
index d77bbbc..ec2e4a2 100644
--- a/clientbin/getNodes.py
+++ b/clientbin/getNodes.py
@@ -4,18 +4,23 @@ import sys
 import os
 from optparse import OptionParser
 from pprint import pprint
-from types import StringTypes
+
+from sfa.util.py23 import StringType
 
 def create_parser():
     command = sys.argv[0]
     argv = sys.argv[1:]
     usage = "%(command)s [options]" % locals()
     description = """getNodes will open a rspec file and print all key/values, or filter results based on a given key or set of keys."""
-    parser = OptionParser(usage=usage,description=description)
-    parser.add_option("-i", "--infile", dest="infile", default=None,  help = "input rspec file")
-    parser.add_option("-t", "--tag", dest="tag", default=None,  help = "filter rspec for this tag")
-    parser.add_option("-a", "--attribute", dest="attribute", default=None,  help = "comma separated list of attributes to display")
-    parser.add_option("-r", "--recursive", dest="print_children", default=False,  action="store_true", help = "print the tag's child nodes")
+    parser = OptionParser(usage=usage, description=description)
+    parser.add_option("-i", "--infile", dest="infile", default=None,
+                      help = "input rspec file")
+    parser.add_option("-t", "--tag", dest="tag", default=None,
+                      help = "filter rspec for this tag")
+    parser.add_option("-a", "--attribute", dest="attribute", default=None,
+                      help = "comma separated list of attributes to display")
+    parser.add_option("-r", "--recursive", dest="print_children", default=False,  action="store_true",
+                      help = "print the tag's child nodes")
 
     return parser    
 
@@ -31,7 +36,7 @@ def print_dict(rdict, options, counter=1):
     if not isinstance(rdict, dict):
         raise "%s not a dict" % rdict 
     for (key, value) in rdict.iteritems():
-        if isinstance(value, StringTypes):
+        if isinstance(value, StringType):
             if (attributes and key in attributes) or not attributes:
                 print tab * counter + "%s: %s" % (key, value)
         elif isinstance(value, list):
@@ -82,6 +87,6 @@ def main():
 
 if __name__ == '__main__':
     try: main()
-    except Exception, e:
+    except Exception as e:
         raise
         print e
diff --git a/clientbin/getRecord.py b/clientbin/getRecord.py
index e2be593..c88f193 100755
--- a/clientbin/getRecord.py
+++ b/clientbin/getRecord.py
@@ -78,8 +78,8 @@ def main():
 
 if __name__ == '__main__':
     try: main()
-    except ExpatError, e:
+    except ExpatError as e:
         print "RecordError.  Is your record valid XML?"
         print e
-    except Exception, e:
+    except Exception as e:
         print e
diff --git a/clientbin/setRecord.py b/clientbin/setRecord.py
index 405c90d..aafd95c 100755
--- a/clientbin/setRecord.py
+++ b/clientbin/setRecord.py
@@ -47,9 +47,9 @@ def editDict(args, recordDict, options):
             if vect in recordDict:
                 del recordDict[vect]
             else:
-                raise TypeError, "Argument error: Records are updated with \n" \
+                raise TypeError("Argument error: Records are updated with \n" \
                             "key=val1,val2,valN or\n" \
-                            "key+=val1,val2,valN \n%s Unknown key/val" % vect
+                            "key+=val1,val2,valN \n%s Unknown key/val" % vect)
 
 
 def replaceDict(newval, recordDict, options):
@@ -103,5 +103,5 @@ def main():
 
 if __name__ == '__main__':
     try: main()
-    except Exception, e:
+    except Exception as e:
         print e
diff --git a/clientbin/sfadump.py b/clientbin/sfadump.py
index fb654e7..617635b 100755
--- a/clientbin/sfadump.py
+++ b/clientbin/sfadump.py
@@ -4,7 +4,6 @@ from __future__ import with_statement
 import sys
 import os, os.path
 import tempfile
-from types import StringTypes, ListType
 from argparse import ArgumentParser
 
 from sfa.util.sfalogging import logger
diff --git a/clientbin/sfiAddLinks.py b/clientbin/sfiAddLinks.py
index 2e667b1..b16cae6 100755
--- a/clientbin/sfiAddLinks.py
+++ b/clientbin/sfiAddLinks.py
@@ -21,15 +21,15 @@ if not command.opts.linkfile:
     sys.exit(1)
     
 if command.opts.infile:
-    infile=file(command.opts.infile)
+    infile = open(command.opts.infile)
 else:
-    infile=sys.stdin
+    infile = sys.stdin
 if command.opts.outfile:
-    outfile=file(command.opts.outfile,"w")
+    outfile = open(command.opts.outfile, "w")
 else:
-    outfile=sys.stdout
+    outfile = sys.stdout
 ad_rspec = RSpec(infile)
-links = file(command.opts.linkfile).read().split('\n')
+links = open(command.opts.linkfile).read().split('\n')
 link_tuples = map(lambda x: tuple(x.split()), links)
 
 version_manager = VersionManager()
diff --git a/clientbin/sfiAddSliver.py b/clientbin/sfiAddSliver.py
index 84ffa8b..6e6042c 100755
--- a/clientbin/sfiAddSliver.py
+++ b/clientbin/sfiAddSliver.py
@@ -21,15 +21,15 @@ if not command.opts.nodefile:
     sys.exit(1)
     
 if command.opts.infile:
-    infile=file(command.opts.infile)
+    infile = open(command.opts.infile)
 else:
-    infile=sys.stdin
+    infile = sys.stdin
 if command.opts.outfile:
-    outfile=file(command.opts.outfile,"w")
+    outfile = open(command.opts.outfile,"w")
 else:
-    outfile=sys.stdout
+    outfile = sys.stdout
 ad_rspec = RSpec(infile)
-nodes = file(command.opts.nodefile).read().split()
+nodes = open(command.opts.nodefile).read().split()
 version_manager = VersionManager()
 try:
     type = ad_rspec.version.type
diff --git a/flashpolicy/sfa_flashpolicy.py b/flashpolicy/sfa_flashpolicy.py
index de98be1..f4d3f16 100644
--- a/flashpolicy/sfa_flashpolicy.py
+++ b/flashpolicy/sfa_flashpolicy.py
@@ -50,7 +50,7 @@ class policy_server(object):
         self.sock.bind(('', port))
         self.sock.listen(5)
     def read_policy(self, path):
-        with file(path, 'rb') as f:
+        with open(path, 'rb') as f:
             policy = f.read(10001)
             if len(policy) > 10000:
                 raise exceptions.RuntimeError('File probably too large to be a policy file',
@@ -63,7 +63,7 @@ class policy_server(object):
         try:
             while True:
                 thread.start_new_thread(self.handle, self.sock.accept())
-        except socket.error, e:
+        except socket.error as e:
             self.log('Error accepting connection: %s' % (e[1],))
     def handle(self, conn, addr):
         addrstr = '%s:%s' % (addr[0],addr[1])
@@ -79,9 +79,9 @@ class policy_server(object):
                 self.log('Valid request received from %s' % (addrstr,))
                 conn.sendall(self.policy)
                 self.log('Sent policy file to %s' % (addrstr,))
-        except socket.error, e:
+        except socket.error as e:
             self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
-        except Exception, e:
+        except Exception as e:
             self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
     def log(self, str):
         print >>sys.stderr, str
@@ -105,7 +105,7 @@ def main():
         if opts.daemon:
             daemon()
         policy_server(opts.port, opts.path).run()
-    except Exception, e:
+    except Exception as e:
         print >> sys.stderr, e
         sys.exit(1)
     except KeyboardInterrupt:
diff --git a/sfa.spec b/sfa.spec
index 39aa494..047de43 100644
--- a/sfa.spec
+++ b/sfa.spec
@@ -33,8 +33,6 @@ Requires: python-psycopg2
 # f8=0.4 - f12=0.5 f14=0.6 f16=0.7
 Requires: python-sqlalchemy
 Requires: python-migrate
-# the eucalyptus aggregate uses this module
-#Requires: python-xmlbuilder
 # for uuidgen - used in db password generation
 # on f8 this actually comes with e2fsprogs, go figure
 Requires: util-linux-ng
diff --git a/sfa/client/candidates.py b/sfa/client/candidates.py
index f3a99ae..830e24d 100644
--- a/sfa/client/candidates.py
+++ b/sfa/client/candidates.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 ### utility to match command-line args to names
 class Candidates:
     def __init__ (self, names):
@@ -43,8 +45,8 @@ def test_candidates ():
         names=names.split()
         for (input,expected) in tuples:
             got=Candidates(names).only_match(input)
-            if got==expected: print '.',
-            else: print 'X FAIL','names[',names,'] input',input,'expected',expected,'got',got
+            if got==expected: print('.', end=' ')
+            else: print('X FAIL','names[',names,'] input',input,'expected',expected,'got',got)
 
 if __name__ == '__main__':
     test_candidates()
diff --git a/sfa/client/client_helper.py b/sfa/client/client_helper.py
index d9239ad..6e91706 100644
--- a/sfa/client/client_helper.py
+++ b/sfa/client/client_helper.py
@@ -41,7 +41,7 @@ def sfa_users_arg (records, slice_record):
         extra_fields = list ( set(pl_fields).union(set(nitos_fields)))
         # try to fill all these in
         for field in extra_fields:
-            if record.has_key(field): user[field]=record[field]
+            if field in record: user[field]=record[field]
         users.append(user)
 
     return users
diff --git a/sfa/client/common.py b/sfa/client/common.py
index 52ae3ee..16a0694 100644
--- a/sfa/client/common.py
+++ b/sfa/client/common.py
@@ -1,5 +1,7 @@
 # a few utilities common to sfi and sfaadmin
 
+from __future__ import print_function
+
 def optparse_listvalue_callback(option, opt, value, parser):
     former=getattr(parser.values,option.dest)
     if not former: former=[]
@@ -41,37 +43,37 @@ def terminal_render_plural (how_many, name,names=None):
     else: return "%d %s"%(how_many,names)
 
 def terminal_render_default (record,options):
-    print "%s (%s)" % (record['hrn'], record['type'])
+    print("%s (%s)" % (record['hrn'], record['type']))
 def terminal_render_user (record, options):
-    print "%s (User)"%record['hrn'],
+    print("%s (User)"%record['hrn'], end=' ')
     if options.verbose and record.get('email', None):
-        print "email='{}'".format(record['email']),
+        print("email='{}'".format(record['email']), end=' ')
     if record.get('reg-pi-authorities', None):
-        print " [PI at %s]"%(" and ".join(record['reg-pi-authorities'])),
+        print(" [PI at %s]"%(" and ".join(record['reg-pi-authorities'])), end=' ')
     if record.get('reg-slices', None):
-        print " [IN slices %s]"%(" and ".join(record['reg-slices'])),
+        print(" [IN slices %s]"%(" and ".join(record['reg-slices'])), end=' ')
     user_keys=record.get('reg-keys',[])
     if not options.verbose:
-        print " [has %s]"%(terminal_render_plural(len(user_keys),"key"))
+        print(" [has %s]"%(terminal_render_plural(len(user_keys),"key")))
     else:
-        print ""
-        for key in user_keys: print 8*' ',key.strip("\n")
+        print("")
+        for key in user_keys: print(8*' ',key.strip("\n"))
         
 def terminal_render_slice (record, options):
-    print "%s (Slice)"%record['hrn'],
+    print("%s (Slice)"%record['hrn'], end=' ')
     if record.get('reg-researchers', None):
-        print " [USERS %s]"%(" and ".join(record['reg-researchers'])),
+        print(" [USERS %s]"%(" and ".join(record['reg-researchers'])), end=' ')
 #    print record.keys()
-    print ""
+    print("")
 def terminal_render_authority (record, options):
-    print "%s (Authority)"%record['hrn'],
+    print("%s (Authority)"%record['hrn'], end=' ')
     if options.verbose and record.get('name'):
-        print "name='{}'".format(record['name'])
+        print("name='{}'".format(record['name']))
     if record.get('reg-pis', None):
-        print " [PIS %s]"%(" and ".join(record['reg-pis'])),
-    print ""
+        print(" [PIS %s]"%(" and ".join(record['reg-pis'])), end=' ')
+    print("")
 def terminal_render_node (record, options):
-    print "%s (Node)"%record['hrn']
+    print("%s (Node)"%record['hrn'])
 
 
 ### used in sfi list
diff --git a/sfa/client/manifolduploader.py b/sfa/client/manifolduploader.py
index c291a42..3b9de16 100755
--- a/sfa/client/manifolduploader.py
+++ b/sfa/client/manifolduploader.py
@@ -30,9 +30,10 @@ import ssl
 try:    turn_off_server_verify = { 'context' : ssl._create_unverified_context() } 
 except: turn_off_server_verify = {}
 
-import xmlrpclib
 import getpass
 
+from sfa.util.py23 import xmlrpc_client
+
 class ManifoldUploader:
     """A utility class for uploading delegated credentials to a manifold/MySlice infrastructure"""
 
@@ -79,12 +80,12 @@ class ManifoldUploader:
 #        if not self._proxy:
 #            url=self.url()
 #            self.logger.info("Connecting manifold url %s"%url)
-#            self._proxy = xmlrpclib.ServerProxy(url, allow_none = True)
+#            self._proxy = xmlrpc_client.ServerProxy(url, allow_none = True)
 #        return self._proxy
         url=self.url()
         self.logger.debug("Connecting manifold url %s"%url)
-        proxy = xmlrpclib.ServerProxy(url, allow_none = True,
-                                      **turn_off_server_verify)
+        proxy = xmlrpc_client.ServerProxy(url, allow_none = True,
+                                          **turn_off_server_verify)
 
         return proxy
 
@@ -113,7 +114,7 @@ class ManifoldUploader:
             try:
                 self.logger.debug("Using new v2 method forward+annotation@%s %s"%(platform,message))
                 retcod2=manifold.forward (query, annotation)
-            except Exception,e:
+            except Exception as e:
                 # xxx we need a constant constant for UNKNOWN, how about using 1
                 MANIFOLD_UNKNOWN=1
                 retcod2={'code':MANIFOLD_UNKNOWN,'description':"%s"%e}
@@ -129,7 +130,7 @@ class ManifoldUploader:
             self.logger.debug("****** full retcod2")
             for (k,v) in retcod2.items(): self.logger.debug("**** %s: %s"%(k,v))
             return False
-        except Exception, e:
+        except Exception as e:
             if message: self.logger.error("Could not upload %s %s"%(message,e))
             else:        self.logger.error("Could not upload credential %s"%e)
             if self.logger.debugEnabled():
@@ -163,7 +164,7 @@ def main ():
                                  logger=sfi_logger)
 
     for filename in args.credential_files:
-        with file(filename) as f:
+        with open(filename) as f:
             result=uploader.upload (f.read(),filename)
             sfi_logger.info('... result=%s'%result)
 
diff --git a/sfa/client/multiclient.py b/sfa/client/multiclient.py
index 75573ed..6bdf2b2 100644
--- a/sfa/client/multiclient.py
+++ b/sfa/client/multiclient.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import threading
 import traceback
 import time
@@ -15,7 +17,7 @@ def ThreadedMethod(callable, results, errors):
             def run(self):
                 try:
                     results.put(callable(*args, **kwds))
-                except Exception, e:
+                except Exception as e:
                     logger.log_exc('MultiClient: Error in thread: ')
                     errors.put(traceback.format_exc())
                     
@@ -95,14 +97,14 @@ if __name__ == '__main__':
     def f(name, n, sleep=1):
         nums = []
         for i in range(n, n+5):
-            print "%s: %s" % (name, i)
+            print("%s: %s" % (name, i))
             nums.append(i)
             time.sleep(sleep)
         return nums
     def e(name, n, sleep=1):
         nums = []
         for i in range(n, n+3) + ['n', 'b']:
-            print "%s: 1 + %s:" % (name, i)
+            print("%s: 1 + %s:" % (name, i))
             nums.append(i + 1)
             time.sleep(sleep)
         return nums      
diff --git a/sfa/client/return_value.py b/sfa/client/return_value.py
index 034dcd8..c2c4f47 100644
--- a/sfa/client/return_value.py
+++ b/sfa/client/return_value.py
@@ -16,7 +16,7 @@ class ReturnValue(dict):
 
     @staticmethod
     def get_key_value(key, return_value):
-        if isinstance(return_value, dict) and return_value.has_key(key):
+        if isinstance(return_value, dict) and key in return_value:
             return return_value.get(key)
         else:
             return return_value              
diff --git a/sfa/client/sfaadmin.py b/sfa/client/sfaadmin.py
index a21cb5f..49757b3 100755
--- a/sfa/client/sfaadmin.py
+++ b/sfa/client/sfaadmin.py
@@ -1,4 +1,6 @@
 #!/usr/bin/python
+from __future__ import print_function
+
 import os
 import sys
 import copy
@@ -130,15 +132,15 @@ class RegistryCommands(Commands):
             hrn = Xrn(xrn).get_hrn()
             db_query = db_query.filter_by(hrn=hrn)
         elif all and xrn:
-            print "Use either -a or -x <xrn>, not both !!!"
+            print("Use either -a or -x <xrn>, not both !!!")
             sys.exit(1)
         elif not all and not xrn:
-            print "Use either -a or -x <xrn>, one of them is mandatory !!!"
+            print("Use either -a or -x <xrn>, one of them is mandatory !!!")
             sys.exit(1)
 
         records = db_query.all()
         if not records:
-            print "No Record found"
+            print("No Record found")
             sys.exit(1)
 
         OK = []
@@ -171,15 +173,15 @@ class RegistryCommands(Commands):
                  NOK.append(record.hrn)
 
         if not verbose:
-            print "Users NOT having a PubKey: %s\n\
+            print("Users NOT having a PubKey: %s\n\
 Users having a non RSA PubKey: %s\n\
 Users having a GID/PubKey correpondence OK: %s\n\
-Users having a GID/PubKey correpondence Not OK: %s\n"%(len(NOKEY), len(ERROR), len(OK), len(NOK))
+Users having a GID/PubKey correpondence Not OK: %s\n"%(len(NOKEY), len(ERROR), len(OK), len(NOK)))
         else:
-            print "Users NOT having a PubKey: %s and are: \n%s\n\n\
+            print("Users NOT having a PubKey: %s and are: \n%s\n\n\
 Users having a non RSA PubKey: %s and are: \n%s\n\n\
 Users having a GID/PubKey correpondence OK: %s and are: \n%s\n\n\
-Users having a GID/PubKey correpondence NOT OK: %s and are: \n%s\n\n"%(len(NOKEY),NOKEY, len(ERROR), ERROR, len(OK), OK, len(NOK), NOK)
+Users having a GID/PubKey correpondence NOT OK: %s and are: \n%s\n\n"%(len(NOKEY),NOKEY, len(ERROR), ERROR, len(OK), OK, len(NOK), NOK))
 
 
 
@@ -252,7 +254,7 @@ Users having a GID/PubKey correpondence NOT OK: %s and are: \n%s\n\n"%(len(NOKEY
     def credential(self, xrn, type=None):
         """Invoke GetCredential"""
         cred = self.api.manager.GetCredential(self.api, xrn, type, self.api.hrn)
-        print cred
+        print(cred)
    
 
     def import_registry(self):
@@ -337,7 +339,7 @@ class CertCommands(Commands):
                 auth_info = hierarchy.get_auth_info(hrn)
                 gid = auth_info.gid_object
             except:
-                print "Record: %s not found" % hrn
+                print("Record: %s not found" % hrn)
                 sys.exit(1)
         # save to file
         if not outfile:
@@ -349,7 +351,7 @@ class CertCommands(Commands):
         """Print contents of a GID file"""
         gid_path = os.path.abspath(gidfile)
         if not gid_path or not os.path.isfile(gid_path):
-            print "No such gid file: %s" % gidfile
+            print("No such gid file: %s" % gidfile)
             sys.exit(1)
         gid = GID(filename=gid_path)
         gid.dump(dump_parents=True)
@@ -377,9 +379,9 @@ class AggregateCommands(Commands):
     def resources(self, rspec_version='GENI'):
         """Display the available resources at an aggregate"""  
         options = {'geni_rspec_version': rspec_version}
-        print options
+        print(options)
         resources = self.api.manager.ListResources(self.api, [], options)
-        print resources
+        print(resources)
         
 
     @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
@@ -391,7 +393,7 @@ class AggregateCommands(Commands):
         rspec_string = open(rspec).read()
         options={}
         manifest = self.api.manager.Allocate(self.api, slice_urn, [], rspec_string, options)
-        print manifest
+        print(manifest)
 
 
     @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
@@ -401,7 +403,7 @@ class AggregateCommands(Commands):
         slice_urn=xrn.get_urn()
         options={}
         manifest = self.api.manager.provision(self.api, [slice_urn], [], options)
-        print manifest
+        print(manifest)
 
 
 
@@ -433,14 +435,14 @@ class SfaAdmin:
         return (full_name,SfaAdmin.CATEGORIES[full_name])
 
     def summary_usage (self, category=None):
-        print "Usage:", self.script_name + " category command [<options>]"
+        print("Usage:", self.script_name + " category command [<options>]")
         if category and category in SfaAdmin.CATEGORIES: 
             categories=[category]
         else:
             categories=SfaAdmin.CATEGORIES
         for c in categories:
             cls=SfaAdmin.CATEGORIES[c]
-            print "==================== category=%s"%c
+            print("==================== category=%s"%c)
             names=cls.__dict__.keys()
             names.sort()
             for name in names:
@@ -448,15 +450,15 @@ class SfaAdmin:
                 if name.startswith('_'): continue
                 margin=15
                 format="%%-%ds"%margin
-                print "%-15s"%name,
+                print("%-15s"%name, end=' ')
                 doc=getattr(method,'__doc__',None)
                 if not doc: 
-                    print "<missing __doc__>"
+                    print("<missing __doc__>")
                     continue
                 lines=[line.strip() for line in doc.split("\n")]
                 line1=lines.pop(0)
-                print line1
-                for extra_line in lines: print margin*" ",extra_line
+                print(line1)
+                for extra_line in lines: print(margin*" ",extra_line)
         sys.exit(2)
 
     def main(self):
@@ -509,14 +511,14 @@ class SfaAdmin:
             command(*cmd_args, **cmd_kwds)
             sys.exit(0)
         except TypeError:
-            print "Possible wrong number of arguments supplied"
+            print("Possible wrong number of arguments supplied")
             #import traceback
             #traceback.print_exc()
-            print command.__doc__
+            print(command.__doc__)
             parser.print_help()
             sys.exit(1)
             #raise
         except Exception:
-            print "Command failed, please check log for more info"
+            print("Command failed, please check log for more info")
             raise
             sys.exit(1)
diff --git a/sfa/client/sfaclientlib.py b/sfa/client/sfaclientlib.py
index bc4a1d1..b7114be 100644
--- a/sfa/client/sfaclientlib.py
+++ b/sfa/client/sfaclientlib.py
@@ -1,14 +1,16 @@
 # Thierry Parmentelat -- INRIA
-#
-# a minimal library for writing "lightweight" SFA clients
-#
+"""
+a minimal library for writing "lightweight" SFA clients
+"""
+
+from __future__ import print_function
 
 # xxx todo
 # this library should probably check for the expiration date of the various
 # certificates and automatically retrieve fresh ones when expired
 
 import sys
-import os,os.path
+import os, os.path
 import subprocess
 from datetime import datetime
 from sfa.util.xrn import Xrn
@@ -100,90 +102,91 @@ from sfa.trust.gid import GID
 #
 ####################
 
-class SfaClientException (Exception): pass
+class SfaClientException(Exception): pass
 
 class SfaClientBootstrap:
 
     # dir is mandatory but defaults to '.'
-    def __init__ (self, user_hrn, registry_url, dir=None, 
-                  verbose=False, timeout=None, logger=None):
-        self.hrn=user_hrn
-        self.registry_url=registry_url
-        if dir is None: dir="."
-        self.dir=dir
-        self.verbose=verbose
-        self.timeout=timeout
+    def __init__(self, user_hrn, registry_url, dir=None, 
+                 verbose=False, timeout=None, logger=None):
+        self.hrn = user_hrn
+        self.registry_url = registry_url
+        if dir is None:
+            dir="."
+        self.dir = dir
+        self.verbose = verbose
+        self.timeout = timeout
         # default for the logger is to use the global sfa logger
         if logger is None: 
             logger = sfa.util.sfalogging.logger
-        self.logger=logger
+        self.logger = logger
 
     ######################################## *_produce methods
     ### step1
     # unconditionnally create a self-signed certificate
-    def self_signed_cert_produce (self, output):
+    def self_signed_cert_produce(self, output):
         self.assert_private_key()
         private_key_filename = self.private_key_filename()
-        keypair=Keypair(filename=private_key_filename)
-        self_signed = Certificate (subject = self.hrn)
-        self_signed.set_pubkey (keypair)
-        self_signed.set_issuer (keypair, self.hrn)
-        self_signed.sign ()
-        self_signed.save_to_file (output)
-        self.logger.debug("SfaClientBootstrap: Created self-signed certificate for %s in %s"%\
-                              (self.hrn, output))
+        keypair = Keypair(filename=private_key_filename)
+        self_signed = Certificate(subject = self.hrn)
+        self_signed.set_pubkey(keypair)
+        self_signed.set_issuer(keypair, self.hrn)
+        self_signed.sign()
+        self_signed.save_to_file(output)
+        self.logger.debug("SfaClientBootstrap: Created self-signed certificate for {} in {}"
+                          .format(self.hrn, output))
         return output
 
     ### step2 
     # unconditionnally retrieve my credential (GetSelfCredential)
     # we always use the self-signed-cert as the SSL cert
-    def my_credential_produce (self, output):
+    def my_credential_produce(self, output):
         self.assert_self_signed_cert()
         certificate_filename = self.self_signed_cert_filename()
-        certificate_string = self.plain_read (certificate_filename)
+        certificate_string = self.plain_read(certificate_filename)
         self.assert_private_key()
-        registry_proxy = SfaServerProxy (self.registry_url,
-                                         self.private_key_filename(),
-                                         certificate_filename)
+        registry_proxy = SfaServerProxy(self.registry_url,
+                                        self.private_key_filename(),
+                                        certificate_filename)
         try:
-            credential_string=registry_proxy.GetSelfCredential (certificate_string, self.hrn, "user")
+            credential_string = registry_proxy.GetSelfCredential(certificate_string, self.hrn, "user")
         except:
             # some urns hrns may replace non hierarchy delimiters '.' with an '_' instead of escaping the '.'
             hrn = Xrn(self.hrn).get_hrn().replace('\.', '_') 
-            credential_string=registry_proxy.GetSelfCredential (certificate_string, hrn, "user")
-        self.plain_write (output, credential_string)
-        self.logger.debug("SfaClientBootstrap: Wrote result of GetSelfCredential in %s"%output)
+            credential_string = registry_proxy.GetSelfCredential(certificate_string, hrn, "user")
+        self.plain_write(output, credential_string)
+        self.logger.debug("SfaClientBootstrap: Wrote result of GetSelfCredential in {}".format(output))
         return output
 
     ### step3
     # unconditionnally retrieve my GID - use the general form 
-    def my_gid_produce (self,output):
-        return self.gid_produce (output, self.hrn, "user")
+    def my_gid_produce(self, output):
+        return self.gid_produce(output, self.hrn, "user")
 
     ### retrieve any credential (GetCredential) unconditionnal form
     # we always use the GID as the SSL cert
-    def credential_produce (self, output, hrn, type):
+    def credential_produce(self, output, hrn, type):
         self.assert_my_gid()
         certificate_filename = self.my_gid_filename()
         self.assert_private_key()
-        registry_proxy = SfaServerProxy (self.registry_url, self.private_key_filename(),
-                                         certificate_filename)
+        registry_proxy = SfaServerProxy(self.registry_url, self.private_key_filename(),
+                                        certificate_filename)
         self.assert_my_credential()
         my_credential_string = self.my_credential_string()
-        credential_string=registry_proxy.GetCredential (my_credential_string, hrn, type)
-        self.plain_write (output, credential_string)
-        self.logger.debug("SfaClientBootstrap: Wrote result of GetCredential in %s"%output)
+        credential_string = registry_proxy.GetCredential(my_credential_string, hrn, type)
+        self.plain_write(output, credential_string)
+        self.logger.debug("SfaClientBootstrap: Wrote result of GetCredential in {}".format(output))
         return output
 
-    def slice_credential_produce (self, output, hrn):
-        return self.credential_produce (output, hrn, "slice")
+    def slice_credential_produce(self, output, hrn):
+        return self.credential_produce(output, hrn, "slice")
 
-    def authority_credential_produce (self, output, hrn):
-        return self.credential_produce (output, hrn, "authority")
+    def authority_credential_produce(self, output, hrn):
+        return self.credential_produce(output, hrn, "authority")
 
-    ### retrieve any gid (Resolve) - unconditionnal form
+    ### retrieve any gid(Resolve) - unconditionnal form
     # use my GID when available as the SSL cert, otherwise the self-signed
-    def gid_produce (self, output, hrn, type ):
+    def gid_produce(self, output, hrn, type ):
         try:
             self.assert_my_gid()
             certificate_filename = self.my_gid_filename()
@@ -192,16 +195,16 @@ class SfaClientBootstrap:
             certificate_filename = self.self_signed_cert_filename()
             
         self.assert_private_key()
-        registry_proxy = SfaServerProxy (self.registry_url, self.private_key_filename(),
-                                         certificate_filename)
-        credential_string=self.plain_read (self.my_credential())
-        records = registry_proxy.Resolve (hrn, credential_string)
-        records=[record for record in records if record['type']==type]
+        registry_proxy = SfaServerProxy(self.registry_url, self.private_key_filename(),
+                                        certificate_filename)
+        credential_string = self.plain_read(self.my_credential())
+        records = registry_proxy.Resolve(hrn, credential_string)
+        records = [record for record in records if record['type'] == type]
         if not records:
-            raise RecordNotFound, "hrn %s (%s) unknown to registry %s"%(hrn,type,self.registry_url)
-        record=records[0]
-        self.plain_write (output, record['gid'])
-        self.logger.debug("SfaClientBootstrap: Wrote GID for %s (%s) in %s"% (hrn,type,output))
+            raise RecordNotFound("hrn {} ({}) unknown to registry {}".format(hrn, type, self.registry_url))
+        record = records[0]
+        self.plain_write(output, record['gid'])
+        self.logger.debug("SfaClientBootstrap: Wrote GID for {} ({}) in {}".format(hrn, type, output))
         return output
 
 
@@ -209,17 +212,17 @@ class SfaClientBootstrap:
 ### produce a pkcs12 bundled certificate from GID and private key
 # xxx for now we put a hard-wired password that's just, well, 'password'
 # when leaving this empty on the mac, result can't seem to be loaded in keychain..
-    def my_pkcs12_produce (self, filename):
-        password=raw_input("Enter password for p12 certificate: ")
-        openssl_command=['openssl', 'pkcs12', "-export"]
-        openssl_command += [ "-password", "pass:%s"%password ]
+    def my_pkcs12_produce(self, filename):
+        password = raw_input("Enter password for p12 certificate: ")
+        openssl_command =  ['openssl', 'pkcs12', "-export"]
+        openssl_command += [ "-password", "pass:{}".format(password) ]
         openssl_command += [ "-inkey", self.private_key_filename()]
         openssl_command += [ "-in",    self.my_gid_filename()]
         openssl_command += [ "-out",   filename ]
-        if subprocess.call(openssl_command) ==0:
-            print "Successfully created %s"%filename
+        if subprocess.call(openssl_command) == 0:
+            print("Successfully created {}".format(filename))
         else:
-            print "Failed to create %s"%filename
+            print("Failed to create {}".format(filename))
 
     # Returns True if credential file is valid. Otherwise return false.
     def validate_credential(self, filename):
@@ -234,170 +237,168 @@ class SfaClientBootstrap:
     #################### public interface
     
     # return my_gid, run all missing steps in the bootstrap sequence
-    def bootstrap_my_gid (self):
+    def bootstrap_my_gid(self):
         self.self_signed_cert()
         self.my_credential()
         return self.my_gid()
 
     # once we've bootstrapped we can use this object to issue any other SFA call
     # always use my gid
-    def server_proxy (self, url):
+    def server_proxy(self, url):
         self.assert_my_gid()
-        return SfaServerProxy (url, self.private_key_filename(), self.my_gid_filename(),
-                               verbose=self.verbose, timeout=self.timeout)
+        return SfaServerProxy(url, self.private_key_filename(), self.my_gid_filename(),
+                              verbose=self.verbose, timeout=self.timeout)
 
     # now in some cases the self-signed is enough
-    def server_proxy_simple (self, url):
+    def server_proxy_simple(self, url):
         self.assert_self_signed_cert()
-        return SfaServerProxy (url, self.private_key_filename(), self.self_signed_cert_filename(),
-                               verbose=self.verbose, timeout=self.timeout)
+        return SfaServerProxy(url, self.private_key_filename(), self.self_signed_cert_filename(),
+                              verbose=self.verbose, timeout=self.timeout)
 
     # this method can optionnally be invoked to ensure proper
     # installation of the private key that belongs to this user
     # installs private_key in working dir with expected name -- preserve mode
     # typically user_private_key would be ~/.ssh/id_rsa
     # xxx should probably check the 2 files are identical
-    def init_private_key_if_missing (self, user_private_key):
-        private_key_filename=self.private_key_filename()
-        if not os.path.isfile (private_key_filename):
-            key=self.plain_read(user_private_key)
+    def init_private_key_if_missing(self, user_private_key):
+        private_key_filename = self.private_key_filename()
+        if not os.path.isfile(private_key_filename):
+            key = self.plain_read(user_private_key)
             self.plain_write(private_key_filename, key)
-            os.chmod(private_key_filename,os.stat(user_private_key).st_mode)
-            self.logger.debug("SfaClientBootstrap: Copied private key from %s into %s"%\
-                                  (user_private_key,private_key_filename))
+            os.chmod(private_key_filename, os.stat(user_private_key).st_mode)
+            self.logger.debug("SfaClientBootstrap: Copied private key from {} into {}"
+                              .format(user_private_key, private_key_filename))
         
     #################### private details
     # stupid stuff
-    def fullpath (self, file): return os.path.join (self.dir,file)
+    def fullpath(self, file):
+        return os.path.join(self.dir, file)
 
     # the expected filenames for the various pieces
-    def private_key_filename (self): 
-        return self.fullpath ("%s.pkey" % Xrn.unescape(self.hrn))
-    def self_signed_cert_filename (self): 
-        return self.fullpath ("%s.sscert"%self.hrn)
-    def my_credential_filename (self):
-        return self.credential_filename (self.hrn, "user")
+    def private_key_filename(self): 
+        return self.fullpath("{}.pkey".format(Xrn.unescape(self.hrn)))
+    def self_signed_cert_filename(self): 
+        return self.fullpath("{}.sscert".format(self.hrn))
+    def my_credential_filename(self):
+        return self.credential_filename(self.hrn, "user")
     # the tests use sfi -u <pi-user>; meaning that the slice credential filename
     # needs to keep track of the user too
-    def credential_filename (self, hrn, type): 
+    def credential_filename(self, hrn, type): 
         if type in ['user']:
-            basename="%s.%s.cred"%(hrn,type)
+            basename = "{}.{}.cred".format(hrn, type)
         else:
-            basename="%s-%s.%s.cred"%(self.hrn,hrn,type)
-        return self.fullpath (basename)
-    def slice_credential_filename (self, hrn): 
-        return self.credential_filename(hrn,'slice')
-    def authority_credential_filename (self, hrn): 
-        return self.credential_filename(hrn,'authority')
-    def my_gid_filename (self):
-        return self.gid_filename (self.hrn, "user")
-    def gid_filename (self, hrn, type): 
-        return self.fullpath ("%s.%s.gid"%(hrn,type))
-    def my_pkcs12_filename (self):
-        return self.fullpath ("%s.p12"%self.hrn)
+            basename = "{}-{}.{}.cred".format(self.hrn, hrn, type)
+        return self.fullpath(basename)
+    def slice_credential_filename(self, hrn): 
+        return self.credential_filename(hrn, 'slice')
+    def authority_credential_filename(self, hrn): 
+        return self.credential_filename(hrn, 'authority')
+    def my_gid_filename(self):
+        return self.gid_filename(self.hrn, "user")
+    def gid_filename(self, hrn, type): 
+        return self.fullpath("{}.{}.gid".format(hrn, type))
+    def my_pkcs12_filename(self):
+        return self.fullpath("{}.p12".format(self.hrn))
 
 # optimizing dependencies
 # originally we used classes GID or Credential or Certificate 
 # like e.g. 
 #        return Credential(filename=self.my_credential()).save_to_string()
 # but in order to make it simpler to other implementations/languages..
-    def plain_read (self, filename):
-        infile=file(filename,"r")
-        result=infile.read()
-        infile.close()
-        return result
-
-    def plain_write (self, filename, contents):
-        outfile=file(filename,"w")
-        result=outfile.write(contents)
-        outfile.close()
-
-    def assert_filename (self, filename, kind):
-        if not os.path.isfile (filename):
-            raise IOError,"Missing %s file %s"%(kind,filename)
+    def plain_read(self, filename):
+        with open(filename) as infile:
+            return infile.read()
+
+    def plain_write(self, filename, contents):
+        with open(filename, "w") as outfile:
+            outfile.write(contents)
+
+    def assert_filename(self, filename, kind):
+        if not os.path.isfile(filename):
+            raise IOError("Missing {} file {}".format(kind, filename))
         return True
         
-    def assert_private_key (self):
-        return self.assert_filename (self.private_key_filename(),"private key")
-    def assert_self_signed_cert (self):
-        return self.assert_filename (self.self_signed_cert_filename(),"self-signed certificate")
-    def assert_my_credential (self):
-        return self.assert_filename (self.my_credential_filename(),"user's credential")
-    def assert_my_gid (self):
-        return self.assert_filename (self.my_gid_filename(),"user's GID")
+    def assert_private_key(self):
+        return self.assert_filename(self.private_key_filename(), "private key")
+    def assert_self_signed_cert(self):
+        return self.assert_filename(self.self_signed_cert_filename(), "self-signed certificate")
+    def assert_my_credential(self):
+        return self.assert_filename(self.my_credential_filename(), "user's credential")
+    def assert_my_gid(self):
+        return self.assert_filename(self.my_gid_filename(), "user's GID")
 
 
     # decorator to make up the other methods
-    def get_or_produce (filename_method, produce_method, validate_method=None):
+    def get_or_produce(filename_method, produce_method, validate_method=None):
         # default validator returns true
-        def wrap (f):
-            def wrapped (self, *args, **kw):
-                filename=filename_method (self, *args, **kw)
-                if os.path.isfile ( filename ):
+        def wrap(f):
+            def wrapped(self, *args, **kw):
+                filename = filename_method(self, *args, **kw)
+                if os.path.isfile(filename):
                     if not validate_method:
                         return filename
                     elif validate_method(self, filename): 
                         return filename
                     else:
                         # remove invalid file
-                        self.logger.warning ("Removing %s - has expired"%filename)
+                        self.logger.warning("Removing {} - has expired".format(filename))
                         os.unlink(filename) 
                 try:
-                    produce_method (self, filename, *args, **kw)
+                    produce_method(self, filename, *args, **kw)
                     return filename
                 except IOError:
                     raise 
                 except :
                     error = sys.exc_info()[:2]
-                    message="Could not produce/retrieve %s (%s -- %s)"%\
-                        (filename,error[0],error[1])
+                    message = "Could not produce/retrieve {} ({} -- {})"\
+                              .format(filename, error[0], error[1])
                     self.logger.log_exc(message)
-                    raise Exception, message
+                    raise Exception(message)
             return wrapped
         return wrap
 
-    @get_or_produce (self_signed_cert_filename, self_signed_cert_produce)
-    def self_signed_cert (self): pass
+    @get_or_produce(self_signed_cert_filename, self_signed_cert_produce)
+    def self_signed_cert(self): pass
 
-    @get_or_produce (my_credential_filename, my_credential_produce, validate_credential)
-    def my_credential (self): pass
+    @get_or_produce(my_credential_filename, my_credential_produce, validate_credential)
+    def my_credential(self): pass
 
-    @get_or_produce (my_gid_filename, my_gid_produce)
-    def my_gid (self): pass
+    @get_or_produce(my_gid_filename, my_gid_produce)
+    def my_gid(self): pass
 
-    @get_or_produce (my_pkcs12_filename, my_pkcs12_produce)
-    def my_pkcs12 (self): pass
+    @get_or_produce(my_pkcs12_filename, my_pkcs12_produce)
+    def my_pkcs12(self): pass
 
-    @get_or_produce (credential_filename, credential_produce, validate_credential)
-    def credential (self, hrn, type): pass
+    @get_or_produce(credential_filename, credential_produce, validate_credential)
+    def credential(self, hrn, type): pass
 
-    @get_or_produce (slice_credential_filename, slice_credential_produce, validate_credential)
-    def slice_credential (self, hrn): pass
+    @get_or_produce(slice_credential_filename, slice_credential_produce, validate_credential)
+    def slice_credential(self, hrn): pass
 
-    @get_or_produce (authority_credential_filename, authority_credential_produce, validate_credential)
-    def authority_credential (self, hrn): pass
+    @get_or_produce(authority_credential_filename, authority_credential_produce, validate_credential)
+    def authority_credential(self, hrn): pass
 
-    @get_or_produce (gid_filename, gid_produce)
-    def gid (self, hrn, type ): pass
+    @get_or_produce(gid_filename, gid_produce)
+    def gid(self, hrn, type ): pass
 
 
     # get the credentials as strings, for inserting as API arguments
-    def my_credential_string (self): 
+    def my_credential_string(self): 
         self.my_credential()
         return self.plain_read(self.my_credential_filename())
-    def slice_credential_string (self, hrn): 
+    def slice_credential_string(self, hrn): 
         self.slice_credential(hrn)
         return self.plain_read(self.slice_credential_filename(hrn))
-    def authority_credential_string (self, hrn): 
+    def authority_credential_string(self, hrn): 
         self.authority_credential(hrn)
         return self.plain_read(self.authority_credential_filename(hrn))
 
     # for consistency
-    def private_key (self):
+    def private_key(self):
         self.assert_private_key()
         return self.private_key_filename()
 
-    def delegate_credential_string (self, original_credential, to_hrn, to_type='authority'):
+    def delegate_credential_string(self, original_credential, to_hrn, to_type='authority'):
         """
         sign a delegation credential to someone else
 
@@ -412,22 +413,23 @@ class SfaClientBootstrap:
         and uses Credential.delegate()"""
 
         # the gid and hrn of the object we are delegating
-        if isinstance (original_credential, str):
-            original_credential = Credential (string=original_credential)
+        if isinstance(original_credential, str):
+            original_credential = Credential(string=original_credential)
         original_gid = original_credential.get_gid_object()
         original_hrn = original_gid.get_hrn()
 
         if not original_credential.get_privileges().get_all_delegate():
-            self.logger.error("delegate_credential_string: original credential %s does not have delegate bit set"%original_hrn)
+            self.logger.error("delegate_credential_string: original credential {} does not have delegate bit set"
+                              .format(original_hrn))
             return
 
         # the delegating user's gid
         my_gid = self.my_gid()
 
         # retrieve the GID for the entity that we're delegating to
-        to_gidfile = self.gid (to_hrn,to_type)
-#        to_gid = GID ( to_gidfile )
+        to_gidfile = self.gid(to_hrn, to_type)
+#        to_gid = GID(to_gidfile )
 #        to_hrn = delegee_gid.get_hrn()
-#        print 'to_hrn',to_hrn
+#        print 'to_hrn', to_hrn
         delegated_credential = original_credential.delegate(to_gidfile, self.private_key(), my_gid)
         return delegated_credential.save_to_string(save_parents=True)
diff --git a/sfa/client/sfascan.py b/sfa/client/sfascan.py
index f7f5dda..136835d 100644
--- a/sfa/client/sfascan.py
+++ b/sfa/client/sfascan.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import sys, os.path
 import pickle
 import time
@@ -8,7 +10,7 @@ from urlparse import urlparse
 try:
     import pygraphviz
 except:
-    print 'Warning, could not import pygraphviz, test mode only'
+    print('Warning, could not import pygraphviz, test mode only')
 
 from optparse import OptionParser
 
@@ -49,7 +51,7 @@ class VersionCache:
 
     def load (self):
         try:
-            infile=file(self.filename,'r')
+            infile=open(self.filename,'r')
             self.url2version=pickle.load(infile)
             infile.close()
         except:
@@ -59,7 +61,7 @@ class VersionCache:
 
     def save (self):
         try:
-            outfile=file(self.filename,'w')
+            outfile=open(self.filename,'w')
             pickle.dump(self.url2version,outfile)
             outfile.close()
         except:
@@ -73,7 +75,7 @@ class VersionCache:
 
     def show (self):
         entries=len(self.url2version)
-        print "version cache from file %s has %d entries"%(self.filename,entries)
+        print("version cache from file %s has %d entries"%(self.filename,entries))
         key_values=self.url2version.items()
         def old_first (kv1,kv2): return int(kv1[1][0]-kv2[1][0])
         key_values.sort(old_first)
@@ -82,9 +84,9 @@ class VersionCache:
             (timestamp,version) = tuple
             how_old = time.time()-timestamp
             if how_old<=self.expires:
-                print url,"-- %d seconds ago"%how_old
+                print(url,"-- %d seconds ago"%how_old)
             else:
-                print "OUTDATED",url,"(%d seconds ago, expires=%d)"%(how_old,self.expires)
+                print("OUTDATED",url,"(%d seconds ago, expires=%d)"%(how_old,self.expires))
     
     # turns out we might have trailing slashes or not
     def normalize (self, url):
@@ -366,5 +368,5 @@ class SfaScan:
         # test mode when pygraphviz is not available
         except:
             entry=entries[0]
-            print "GetVersion at %s returned %s"%(entry.url(),entry.get_version())
+            print("GetVersion at %s returned %s"%(entry.url(),entry.get_version()))
 
diff --git a/sfa/client/sfaserverproxy.py b/sfa/client/sfaserverproxy.py
index d326b3a..e281f66 100644
--- a/sfa/client/sfaserverproxy.py
+++ b/sfa/client/sfaserverproxy.py
@@ -5,8 +5,8 @@ import ssl
 try:    turn_off_server_verify = { 'context' : ssl._create_unverified_context() } 
 except: turn_off_server_verify = {}
 
-import xmlrpclib
-from httplib import HTTPS, HTTPSConnection
+from sfa.util.py23 import xmlrpc_client
+from sfa.util.py23 import http_client
 
 try:
     from sfa.util.sfalogging import logger
@@ -23,11 +23,11 @@ except:
 class ServerException(Exception):
     pass
 
-class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+class ExceptionUnmarshaller(xmlrpc_client.Unmarshaller):
     def close(self):
         try:
-            return xmlrpclib.Unmarshaller.close(self)
-        except xmlrpclib.Fault, e:
+            return xmlrpc_client.Unmarshaller.close(self)
+        except xmlrpc_client.Fault as e:
             raise ServerException(e.faultString)
 
 ##
@@ -37,10 +37,10 @@ class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
 
 # targetting only python-2.7 we can get rid of some older code
 
-class XMLRPCTransport(xmlrpclib.Transport):
+class XMLRPCTransport(xmlrpc_client.Transport):
     
     def __init__(self, key_file = None, cert_file = None, timeout = None):
-        xmlrpclib.Transport.__init__(self)
+        xmlrpc_client.Transport.__init__(self)
         self.timeout=timeout
         self.key_file = key_file
         self.cert_file = cert_file
@@ -49,9 +49,9 @@ class XMLRPCTransport(xmlrpclib.Transport):
         # create a HTTPS connection object from a host descriptor
         # host may be a string, or a (host, x509-dict) tuple
         host, extra_headers, x509 = self.get_host_info(host)
-        conn = HTTPSConnection(host, None, key_file = self.key_file,
-                               cert_file = self.cert_file,
-                               **turn_off_server_verify)
+        conn = http_client.HTTPSConnection(host, None, key_file = self.key_file,
+                                           cert_file = self.cert_file,
+                                           **turn_off_server_verify)
 
         # Some logic to deal with timeouts. It appears that some (or all) versions
         # of python don't set the timeout after the socket is created. We'll do it
@@ -74,21 +74,21 @@ class XMLRPCTransport(xmlrpclib.Transport):
 
     def getparser(self):
         unmarshaller = ExceptionUnmarshaller()
-        parser = xmlrpclib.ExpatParser(unmarshaller)
+        parser = xmlrpc_client.ExpatParser(unmarshaller)
         return parser, unmarshaller
 
-class XMLRPCServerProxy(xmlrpclib.ServerProxy):
+class XMLRPCServerProxy(xmlrpc_client.ServerProxy):
     def __init__(self, url, transport, allow_none=True, verbose=False):
         # remember url for GetVersion
         # xxx not sure this is still needed as SfaServerProxy has this too
-        self.url=url
-        xmlrpclib.ServerProxy.__init__(self, url, transport, allow_none=allow_none,
-                                       verbose=verbose,
-                                       **turn_off_server_verify)
+        self.url = url
+        xmlrpc_client.ServerProxy.__init__(self, url, transport, allow_none=allow_none,
+                                           verbose=verbose,
+                                           **turn_off_server_verify)
 
     def __getattr__(self, attr):
-        logger.debug ("xml-rpc %s method:%s" % (self.url, attr))
-        return xmlrpclib.ServerProxy.__getattr__(self, attr)
+        logger.debug("xml-rpc %s method:%s" % (self.url, attr))
+        return xmlrpc_client.ServerProxy.__getattr__(self, attr)
 
 ########## the object on which we can send methods that get sent over xmlrpc
 class SfaServerProxy:
@@ -99,7 +99,7 @@ class SfaServerProxy:
         self.certfile = certfile
         self.verbose = verbose
         self.timeout = timeout
-        # an instance of xmlrpclib.ServerProxy
+        # an instance of xmlrpc_client.ServerProxy
         transport = XMLRPCTransport(keyfile, certfile, timeout)
         self.serverproxy = XMLRPCServerProxy(url, transport, allow_none=True, verbose=verbose)
 
diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py
index 6887250..95acf18 100644
--- a/sfa/client/sfi.py
+++ b/sfa/client/sfi.py
@@ -17,7 +17,6 @@ import pickle
 import json
 import shutil
 from lxml import etree
-from StringIO import StringIO
 from optparse import OptionParser
 from pprint import PrettyPrinter
 from tempfile import mkstemp
@@ -34,6 +33,7 @@ from sfa.util.config import Config
 from sfa.util.version import version_core
 from sfa.util.cache import Cache
 from sfa.util.printable import printable
+from sfa.util.py23 import StringIO
 
 from sfa.storage.record import Record
 
@@ -98,7 +98,7 @@ def filter_records(type, records):
     return filtered_records
 
 
-def credential_printable (cred):
+def credential_printable(cred):
     credential = Credential(cred=cred)
     result = ""
     result += credential.pretty_cred()
@@ -109,8 +109,8 @@ def credential_printable (cred):
     result += "rights={}\n".format(rights)
     return result
 
-def show_credentials (cred_s):
-    if not isinstance (cred_s, list): cred_s = [cred_s]
+def show_credentials(cred_s):
+    if not isinstance(cred_s, list): cred_s = [cred_s]
     for cred in cred_s:
         print("Using Credential {}".format(credential_printable(cred)))
 
@@ -178,12 +178,12 @@ def save_records_to_file(filename, record_dicts, format="xml"):
         print("unknown output format", format)
 
 # minimally check a key argument
-def check_ssh_key (key):
+def check_ssh_key(key):
     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
     return re.match(good_ssh_key, key, re.IGNORECASE)
 
 # load methods
-def normalize_type (type):
+def normalize_type(type):
     if type.startswith('au'):
         return 'authority'
     elif type.startswith('us'):
@@ -215,7 +215,7 @@ def load_record_from_opts(options):
             pubkey = open(options.key, 'r').read()
         except IOError:
             pubkey = options.key
-        if not check_ssh_key (pubkey):
+        if not check_ssh_key(pubkey):
             raise SfaInvalidArgument(name='key', msg="Could not find file, or wrong key format")
         record_dict['reg-keys'] = [pubkey]
     if hasattr(options, 'slices') and options.slices:
@@ -259,7 +259,7 @@ from functools import wraps
 commands_list=[]
 commands_dict={}
 
-def declare_command (args_string, example, aliases=None):
+def declare_command(args_string, example, aliases=None):
     def wrap(m): 
         name=getattr(m, '__name__')
         doc=getattr(m, '__doc__', "-- missing doc --")
@@ -273,12 +273,12 @@ def declare_command (args_string, example, aliases=None):
                 commands_list.append(alias)
                 commands_dict[alias]=command_tuple
         @wraps(m)
-        def new_method (*args, **kwds): return m(*args, **kwds)
+        def new_method(*args, **kwds): return m(*args, **kwds)
         return new_method
     return wrap
 
 
-def remove_none_fields (record):
+def remove_none_fields(record):
     none_fields=[ k for (k, v) in record.items() if v is None ]
     for k in none_fields: del record[k]
 
@@ -290,7 +290,7 @@ class Sfi:
     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
 
     @staticmethod
-    def default_sfi_dir ():
+    def default_sfi_dir():
         if os.path.isfile("./sfi_config"): 
             return os.getcwd()
         else:
@@ -301,7 +301,7 @@ class Sfi:
     class DummyOptions:
         pass
 
-    def __init__ (self, options=None):
+    def __init__(self, options=None):
         if options is None: options=Sfi.DummyOptions()
         for opt in Sfi.required_options:
             if not hasattr(options, opt):
@@ -321,7 +321,7 @@ class Sfi:
         self.client_bootstrap = None
 
     ### suitable if no reasonable command has been provided
-    def print_commands_help (self, options):
+    def print_commands_help(self, options):
         verbose = getattr(options, 'verbose')
         format3 = "%10s %-35s %s"
         format3offset = 47
@@ -350,7 +350,7 @@ class Sfi:
                 print(format3 % (command, "<<alias for %s>>"%canonical, ""))
             
     ### now if a known command was found we can be more verbose on that one
-    def print_help (self):
+    def print_help(self):
         print("==================== Generic sfi usage")
         self.sfi_parser.print_help()
         (doc, _, example, canonical) = commands_dict[self.command]
@@ -422,7 +422,7 @@ class Sfi:
         parser = OptionParser(add_help_option=False,
                               usage="sfi [sfi_options] {} [cmd_options] {}"\
                               .format(command, args_string))
-        parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
+        parser.add_option("-h","--help",dest='help',action='store_true',default=False,
                            help="Summary of one command usage")
 
         if canonical in ("config"):
@@ -434,7 +434,7 @@ class Sfi:
                               action="store_true", dest="version_local", default=False,
                               help="display version of the local client")
 
-        if canonical in ("version", "trusted"):
+        if canonical in ("version", "trusted", "introspect"):
             parser.add_option("-R","--registry_interface",
                               action="store_true", dest="registry_interface", default=False,
                               help="target the registry interface instead of slice interface")
@@ -453,7 +453,7 @@ class Sfi:
                               callback=optparse_listvalue_callback)
             parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
-            parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
+            parser.add_option('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
 
@@ -579,7 +579,7 @@ use this if you mean an authority instead""")
             return -1
     
         # complete / find unique match with command set
-        command_candidates = Candidates (commands_list)
+        command_candidates = Candidates(commands_list)
         input = args[0]
         command = command_candidates.only_match(input)
         if not command:
@@ -600,8 +600,8 @@ use this if you mean an authority instead""")
             if not command_options.type:
                 sys.exit(1)
         
-        self.read_config () 
-        self.bootstrap ()
+        self.read_config() 
+        self.bootstrap()
         self.logger.debug("Command={}".format(self.command))
 
         try:
@@ -609,7 +609,7 @@ use this if you mean an authority instead""")
         except SystemExit:
             return 1
         except:
-            self.logger.log_exc ("sfi command {} failed".format(command))
+            self.logger.log_exc("sfi command {} failed".format(command))
             return 1
         return retcod
     
@@ -619,7 +619,7 @@ use this if you mean an authority instead""")
         shell_config_file  = os.path.join(self.options.sfi_dir, "sfi_config.sh")
         try:
             if Config.is_ini(config_file):
-                config = Config (config_file)
+                config = Config(config_file)
             else:
                 # try upgrading from shell config format
                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
@@ -703,25 +703,25 @@ use this if you mean an authority instead""")
     #
     
     # init self-signed cert, user credentials and gid
-    def bootstrap (self):
+    def bootstrap(self):
         if self.options.verbose:
             self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
-        client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
+        client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir,
                                                logger=self.logger)
         # if -k is provided, use this to initialize private key
         if self.options.user_private_key:
-            client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
+            client_bootstrap.init_private_key_if_missing(self.options.user_private_key)
         else:
             # trigger legacy compat code if needed 
             # the name has changed from just <leaf>.pkey to <hrn>.pkey
             if not os.path.isfile(client_bootstrap.private_key_filename()):
-                self.logger.info ("private key not found, trying legacy name")
+                self.logger.info("private key not found, trying legacy name")
                 try:
-                    legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
+                    legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey"
                                                        .format(Xrn.unescape(get_leaf(self.user))))
                     self.logger.debug("legacy_private_key={}"
                                       .format(legacy_private_key))
-                    client_bootstrap.init_private_key_if_missing (legacy_private_key)
+                    client_bootstrap.init_private_key_if_missing(legacy_private_key)
                     self.logger.info("Copied private key from legacy location {}"
                                      .format(legacy_private_key))
                 except:
@@ -732,11 +732,11 @@ use this if you mean an authority instead""")
         client_bootstrap.bootstrap_my_gid()
         # extract what's needed
         self.private_key = client_bootstrap.private_key()
-        self.my_credential_string = client_bootstrap.my_credential_string ()
+        self.my_credential_string = client_bootstrap.my_credential_string()
         self.my_credential = {'geni_type': 'geni_sfa',
                               'geni_version': '3', 
                               'geni_value': self.my_credential_string}
-        self.my_gid = client_bootstrap.my_gid ()
+        self.my_gid = client_bootstrap.my_gid()
         self.client_bootstrap = client_bootstrap
 
 
@@ -744,13 +744,13 @@ use this if you mean an authority instead""")
         if not self.authority:
             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
             sys.exit(-1)
-        return self.client_bootstrap.authority_credential_string (self.authority)
+        return self.client_bootstrap.authority_credential_string(self.authority)
 
     def authority_credential_string(self, auth_hrn):
-        return self.client_bootstrap.authority_credential_string (auth_hrn)
+        return self.client_bootstrap.authority_credential_string(auth_hrn)
 
     def slice_credential_string(self, name):
-        return self.client_bootstrap.slice_credential_string (name)
+        return self.client_bootstrap.slice_credential_string(name)
 
     def slice_credential(self, name):
         return {'geni_type': 'geni_sfa',
@@ -783,18 +783,18 @@ use this if you mean an authority instead""")
     # Management of the servers
     # 
 
-    def registry (self):
+    def registry(self):
         # cache the result
-        if not hasattr (self, 'registry_proxy'):
+        if not hasattr(self, 'registry_proxy'):
             self.logger.info("Contacting Registry at: {}".format(self.reg_url))
             self.registry_proxy \
                 =  SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
                                   timeout=self.options.timeout, verbose=self.options.debug)  
         return self.registry_proxy
 
-    def sliceapi (self):
+    def sliceapi(self):
         # cache the result
-        if not hasattr (self, 'sliceapi_proxy'):
+        if not hasattr(self, 'sliceapi_proxy'):
             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
             if hasattr(self.command_options, 'component') and self.command_options.component:
                 # resolve the hrn at the registry
@@ -867,18 +867,18 @@ use this if you mean an authority instead""")
         return result                 
 
     ### ois = options if supported
-    # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
-    def ois (self, server, option_dict):
-        if self.server_supports_options_arg (server): 
+    # to be used in something like serverproxy.Method(arg1, arg2, *self.ois(api_options))
+    def ois(self, server, option_dict):
+        if self.server_supports_options_arg(server): 
             return [option_dict]
-        elif self.server_supports_call_id_arg (server):
-            return [ unique_call_id () ]
+        elif self.server_supports_call_id_arg(server):
+            return [ unique_call_id() ]
         else: 
             return []
 
     ### cis = call_id if supported - like ois
-    def cis (self, server):
-        if self.server_supports_call_id_arg (server):
+    def cis(self, server):
+        if self.server_supports_call_id_arg(server):
             return [ unique_call_id ]
         else:
             return []
@@ -909,7 +909,7 @@ use this if you mean an authority instead""")
 
     # helper function to analyze raw output
     # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
-    def success (self, raw):
+    def success(self, raw):
         return_value = ReturnValue(raw)
         output = ReturnValue.get_output(return_value)
         # means everything is fine
@@ -926,8 +926,14 @@ use this if you mean an authority instead""")
     #==========================================================================
 
     @declare_command("", "")
-    def config (self, options, args):
-        "Display contents of current config"
+    def config(self, options, args):
+        """
+        Display contents of current config
+        """
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
+
         print("# From configuration file {}".format(self.config_file))
         flags = [ ('sfi', [ ('registry', 'reg_url'),
                             ('auth', 'authority'),
@@ -936,7 +942,7 @@ use this if you mean an authority instead""")
                         ]),
                 ]
         if options.myslice:
-            flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
+            flags.append( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
 
         for (section, tuples) in flags:
             print("[{}]".format(section))
@@ -955,8 +961,12 @@ use this if you mean an authority instead""")
     def version(self, options, args):
         """
         display an SFA server version (GetVersion)
-    or version information about sfi itself
+        or version information about sfi itself
         """
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
+        
         if options.version_local:
             version = version_core()
         else:
@@ -982,6 +992,7 @@ use this if you mean an authority instead""")
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         opts = {}
         if options.recursive:
@@ -997,7 +1008,7 @@ use this if you mean an authority instead""")
         # filter on person, slice, site, node, etc.
         # This really should be in the self.filter_records funct def comment...
         list = filter_records(options.type, list)
-        terminal_render (list, options)
+        terminal_render(list, options)
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         # xxx should analyze result
@@ -1011,6 +1022,7 @@ use this if you mean an authority instead""")
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         # explicitly require Resolve to run in details mode
         resolve_options = {}
@@ -1023,13 +1035,13 @@ use this if you mean an authority instead""")
             return
         # user has required to focus on some keys
         if options.keys:
-            def project (record):
+            def project(record):
                 projected = {}
                 for key in options.keys:
                     try: projected[key] = record[key]
                     except: pass
                 return projected
-            record_dicts = [ project (record) for record in record_dicts ]
+            record_dicts = [ project(record) for record in record_dicts ]
         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
         for record in records:
             if (options.format == "text"):      record.dump(sort=True)  
@@ -1042,17 +1054,19 @@ use this if you mean an authority instead""")
     # this historically was named 'add', it is now 'register' with an alias for legacy
     @declare_command("[xml-filename]", "", ['add'])
     def register(self, options, args):
-        """create new record in registry (Register) 
-    from command line options (recommended) 
-    old-school method involving an xml file still supported"""
+        """
+        create new record in registry (Register) 
+        from command line options (recommended) 
+        old-school method involving an xml file still supported
+        """
+        if len(args) > 1:
+            self.print_help()
+            sys.exit(1)
 
         auth_cred = self.my_authority_credential_string()
         if options.show_credential:
             show_credentials(auth_cred)
         record_dict = {}
-        if len(args) > 1:
-            self.print_help()
-            sys.exit(1)
         if len(args) == 1:
             try:
                 record_filepath = args[0]
@@ -1082,11 +1096,17 @@ use this if you mean an authority instead""")
     
     @declare_command("[xml-filename]", "")
     def update(self, options, args):
-        """update record into registry (Update) 
-    from command line options (recommended) 
-    old-school method involving an xml file still supported"""
+        """
+        update record into registry (Update) 
+        from command line options (recommended) 
+        old-school method involving an xml file still supported
+        """
+        if len(args) > 1:
+            self.print_help()
+            sys.exit(1)
+
         record_dict = {}
-        if len(args) > 0:
+        if len(args) == 1:
             record_filepath = args[0]
             rec_file = self.get_record_file(record_filepath)
             record_dict.update(load_record_from_file(rec_file).record_to_dict())
@@ -1130,11 +1150,14 @@ use this if you mean an authority instead""")
   
     @declare_command("hrn", "")
     def remove(self, options, args):
-        "remove registry record by name (Remove)"
+        """
+        remove registry record by name (Remove)
+        """
         auth_cred = self.my_authority_credential_string()
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         type = options.type 
         if type in ['all']:
@@ -1157,8 +1180,11 @@ use this if you mean an authority instead""")
         """
         discover available resources (ListResources)
         """
-        server = self.sliceapi()
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
 
+        server = self.sliceapi()
         # set creds
         creds = [self.my_credential]
         if options.delegate:
@@ -1182,18 +1208,10 @@ use this if you mean an authority instead""")
                 api_options['cached'] = False
             else:
                 api_options['cached'] = True
-        if options.rspec_version:
-            version_manager = VersionManager()
-            server_version = self.get_cached_server_version(server)
-            if 'sfa' in server_version:
-                # just request the version the client wants
-                api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
-            else:
-                api_options['geni_rspec_version'] = {'type': options.rspec_version}
-        else:
-            api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
+        version_manager = VersionManager()
+        api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
 
-        list_resources = server.ListResources (creds, api_options)
+        list_resources = server.ListResources(creds, api_options)
         value = ReturnValue.get_value(list_resources)
         if self.options.raw:
             save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
@@ -1207,10 +1225,13 @@ use this if you mean an authority instead""")
     def describe(self, options, args):
         """
         shows currently allocated/provisioned resources 
-    of the named slice or set of slivers (Describe) 
+        of the named slice or set of slivers (Describe) 
         """
-        server = self.sliceapi()
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
 
+        server = self.sliceapi()
         # set creds
         creds = [self.slice_credential(args[0])]
         if options.delegate:
@@ -1245,15 +1266,18 @@ use this if you mean an authority instead""")
             save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
             display_rspec(value['geni_rspec'], options.format)
-        return self.success (describe)
+        return self.success(describe)
 
     @declare_command("slice_hrn [<sliver_urn>...]", "")
     def delete(self, options, args):
         """
         de-allocate and de-provision all or named slivers of the named slice (Delete)
         """
-        server = self.sliceapi()
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
 
+        server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
@@ -1280,18 +1304,19 @@ use this if you mean an authority instead""")
             save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
             print(value)
-        return self.success (delete)
+        return self.success(delete)
 
     @declare_command("slice_hrn rspec", "")
     def allocate(self, options, args):
         """
          allocate resources to the named slice (Allocate)
         """
-        server = self.sliceapi()
-        server_version = self.get_cached_server_version(server)
         if len(args) != 2:
             self.print_help()
             sys.exit(1)
+
+        server = self.sliceapi()
+        server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
         rspec_file = self.get_rspec_file(args[1])
 
@@ -1339,7 +1364,7 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
-            save_rspec_to_file (value['geni_rspec'], options.file)
+            save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
             print(value)
         return self.success(allocate)
@@ -1349,6 +1374,10 @@ use this if you mean an authority instead""")
         """
         provision all or named already allocated slivers of the named slice (Provision)
         """
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
@@ -1404,7 +1433,7 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
-            save_rspec_to_file (value['geni_rspec'], options.file)
+            save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
             print(value)
         return self.success(provision)
@@ -1414,8 +1443,11 @@ use this if you mean an authority instead""")
         """
         retrieve the status of the slivers belonging to the named slice (Status)
         """
-        server = self.sliceapi()
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
 
+        server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
@@ -1435,13 +1467,17 @@ use this if you mean an authority instead""")
             save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
             print(value)
-        return self.success (status)
+        return self.success(status)
 
     @declare_command("slice_hrn [<sliver_urn>...] action", "")
     def action(self, options, args):
         """
         Perform the named operational action on all or named slivers of the named slice
         """
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         api_options = {}
         # slice urn
@@ -1467,7 +1503,7 @@ use this if you mean an authority instead""")
             save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
             print(value)
-        return self.success (perform_action)
+        return self.success(perform_action)
 
     @declare_command("slice_hrn [<sliver_urn>...] time",
                      "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
@@ -1477,12 +1513,13 @@ use this if you mean an authority instead""")
                                 "sfi renew onelab.ple.heartbeat +2m",]))
     def renew(self, options, args):
         """
-        renew slice (Renew)
+        renew slice(Renew)
         """
-        server = self.sliceapi()
         if len(args) < 2:
             self.print_help()
             sys.exit(1)
+
+        server = self.sliceapi()
         slice_hrn = args[0]
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
@@ -1518,6 +1555,10 @@ use this if you mean an authority instead""")
         """
         shutdown named slice (Shutdown)
         """
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
@@ -1531,7 +1572,7 @@ use this if you mean an authority instead""")
             save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
             print(value)
-        return self.success (shutdown)
+        return self.success(shutdown)
 
     @declare_command("[name]", "")
     def gid(self, options, args):
@@ -1541,6 +1582,7 @@ use this if you mean an authority instead""")
         if len(args) < 1:
             self.print_help()
             sys.exit(1)
+
         target_hrn = args[0]
         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
@@ -1567,7 +1609,7 @@ use this if you mean an authority instead""")
       because of the two -s options
 
 """)
-    def delegate (self, options, args):
+    def delegate(self, options, args):
         """
         (locally) create delegate credential for use by given hrn
     make sure to check for 'sfi myslice' instead if you plan
@@ -1576,6 +1618,7 @@ use this if you mean an authority instead""")
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         to_hrn = args[0]
         # support for several delegations in the same call
         # so first we gather the things to do
@@ -1583,16 +1626,16 @@ use this if you mean an authority instead""")
         for slice_hrn in options.delegate_slices:
             message = "{}.slice".format(slice_hrn)
             original = self.slice_credential_string(slice_hrn)
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         if options.delegate_pi:
             my_authority = self.authority
             message = "{}.pi".format(my_authority)
             original = self.my_authority_credential_string()
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         for auth_hrn in options.delegate_auths:
             message = "{}.auth".format(auth_hrn)
             original = self.authority_credential_string(auth_hrn)
-            tuples.append ( (message, original, ) )
+            tuples.append( (message, original, ) )
         # if nothing was specified at all at this point, let's assume -u
         if not tuples:
             options.delegate_user = True
@@ -1600,7 +1643,7 @@ use this if you mean an authority instead""")
         if options.delegate_user:
             message = "{}.user".format(self.user)
             original = self.my_credential_string
-            tuples.append ( (message, original, ) )
+            tuples.append( (message, original, ) )
 
         # default type for beneficial is user unless -A
         to_type = 'authority' if options.delegate_to_authority else 'user'
@@ -1609,7 +1652,7 @@ use this if you mean an authority instead""")
         # it's all in the filenaming scheme
         for (message, original) in tuples:
             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
-            delegated_credential = Credential (string=delegated_string)
+            delegated_credential = Credential(string=delegated_string)
             filename = os.path.join(self.options.sfi_dir,
                                     "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
             delegated_credential.save_to_file(filename, save_parents=True)
@@ -1642,17 +1685,18 @@ $ sfi m -b http://mymanifold.foo.com:7080/
   and uses a custom backend for this one call
 """
 ) # declare_command
-    def myslice (self, options, args):
+    def myslice(self, options, args):
 
         """ This helper is for refreshing your credentials at myslice; it will
     * compute all the slices that you currently have credentials on
     * refresh all your credentials (you as a user and pi, your slices)
     * upload them to the manifold backend server
     for last phase, sfi_config is read to look for the [myslice] section, 
-    and namely the 'backend', 'delegate' and 'user' settings"""
+    and namely the 'backend', 'delegate' and 'user' settings
+        """
 
         ##########
-        if len(args)>0:
+        if len(args) > 0:
             self.print_help()
             sys.exit(1)
         # enable info by default
@@ -1710,11 +1754,15 @@ $ sfi m -b http://mymanifold.foo.com:7080/
 
         # (d) make sure we have *valid* credentials for all these
         hrn_credentials = []
-        hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
+        hrn_credentials.append( (self.user, 'user', self.my_credential_string,) )
         for auth_hrn in my_auths:
-            hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
+            hrn_credentials.append( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
         for slice_hrn in my_slices:
-            hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
+            try:
+                hrn_credentials.append( (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),) )
+            except:
+                print("WARNING: could not get slice credential for slice {}"
+                      .format(slice_hrn))
 
         # (e) check for the delegated version of these
         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
@@ -1723,21 +1771,21 @@ $ sfi m -b http://mymanifold.foo.com:7080/
         delegatee_hrn = myslice_dict['delegate']
         hrn_delegated_credentials = []
         for (hrn, htype, credential) in hrn_credentials:
-            delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
+            delegated_credential = self.client_bootstrap.delegate_credential_string(credential, delegatee_hrn, delegatee_type)
             # save these so user can monitor what she's uploaded
-            filename = os.path.join ( self.options.sfi_dir,
+            filename = os.path.join( self.options.sfi_dir,
                                       "{}.{}_for_{}.{}.cred"\
                                       .format(hrn, htype, delegatee_hrn, delegatee_type))
-            with file(filename, 'w') as f:
+            with open(filename, 'w') as f:
                 f.write(delegated_credential)
             self.logger.debug("(Over)wrote {}".format(filename))
-            hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
+            hrn_delegated_credentials.append((hrn, htype, delegated_credential, filename, ))
 
         # (f) and finally upload them to manifold server
         # xxx todo add an option so the password can be set on the command line
         # (but *NOT* in the config file) so other apps can leverage this
         self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
-        uploader = ManifoldUploader (logger=self.logger,
+        uploader = ManifoldUploader(logger=self.logger,
                                      url=myslice_dict['backend'],
                                      platform=myslice_dict['platform'],
                                      username=myslice_dict['username'],
@@ -1787,3 +1835,31 @@ $ sfi m -b http://mymanifold.foo.com:7080/
             print("Certificate:\n{}\n\n".format(trusted_cert))
         # xxx should analyze result
         return 0
+
+    @declare_command("", "")
+    def introspect(self, options, args):
+        """
+        If remote server supports XML-RPC instrospection API, allows
+        to list supported methods
+        """
+        if options.registry_interface:
+            server = self.registry()
+        else:
+            server = self.sliceapi()
+        results = server.serverproxy.system.listMethods()
+        # at first sight a list here means it's fine,
+        # and a dict suggests an error (no support for introspection?)
+        if isinstance(results, list):
+            results = [ name for name in results if 'system.' not in name ]
+            results.sort()
+            print("== methods supported at {}".format(server.url))
+            if 'Discover' in results:
+                print("== has support for 'Discover' - most likely a v3")
+            else:
+                print("== has no support for 'Discover' - most likely a v2")
+            for name in results:
+                print(name)
+        else:
+            print("Got return of type {}, expected a list".format(type(results)))
+            print("This suggests the remote end does not support introspection")
+            print(results)
diff --git a/sfa/dummy/dummy_testbed_api_client.py b/sfa/dummy/dummy_testbed_api_client.py
index 38ba0d3..57a0482 100644
--- a/sfa/dummy/dummy_testbed_api_client.py
+++ b/sfa/dummy/dummy_testbed_api_client.py
@@ -1,9 +1,11 @@
-import xmlrpclib
+from __future__ import print_function
+
 from datetime import datetime
 import time
+from sfa.util.py23 import xmlrpc_client
 
 dummy_url = "http://localhost:8080"
-dummy_api = xmlrpclib.ServerProxy(dummy_url)
+dummy_api = xmlrpc_client.ServerProxy(dummy_url)
 
 # Add a user:
 my_user_id = dummy_api.AddUser({'email': 'john.doe@test.net', 'user_name': 'john.doe', 'keys': ['copy here your ssh-rsa public key']})
@@ -11,6 +13,6 @@ my_user_id = dummy_api.AddUser({'email': 'john.doe@test.net', 'user_name': 'john
 dummy_api.AddUserToSlice({'slice_id': 2, 'user_id': my_user_id})
 
 # Display the list of users
-print dummy_api.GetUsers()
+print(dummy_api.GetUsers())
 # Display the list of slices
-print dummy_api.GetSlices()
+print(dummy_api.GetSlices())
diff --git a/sfa/dummy/dummyshell.py b/sfa/dummy/dummyshell.py
index c6cc833..67007d7 100644
--- a/sfa/dummy/dummyshell.py
+++ b/sfa/dummy/dummyshell.py
@@ -1,9 +1,9 @@
 import sys
-import xmlrpclib
 import socket
 from urlparse import urlparse
 
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import xmlrpc_client
 
 class DummyShell:
     """
@@ -21,12 +21,12 @@ class DummyShell:
 
     def __init__ ( self, config ) :
         url = config.SFA_DUMMY_URL
-        self.proxy = xmlrpclib.Server(url, verbose = False, allow_none = True)
+        self.proxy = xmlrpc_client.ServerProxy(url, verbose = False, allow_none = True)
 
     def __getattr__(self, name):
         def func(*args, **kwds):
             if not name in DummyShell.direct_calls:
-                raise Exception, "Illegal method call %s for DUMMY driver"%(name)
+                raise Exception("Illegal method call %s for DUMMY driver"%(name))
             result=getattr(self.proxy, name)(*args, **kwds)
             logger.debug('DummyShell %s returned ... '%(name))
             return result
diff --git a/sfa/dummy/dummyslices.py b/sfa/dummy/dummyslices.py
index 7ab94ba..394e816 100644
--- a/sfa/dummy/dummyslices.py
+++ b/sfa/dummy/dummyslices.py
@@ -1,5 +1,4 @@
 import time
-from types import StringTypes
 from collections import defaultdict
 
 from sfa.util.sfatime import utcparse, datetime_to_epoch
diff --git a/sfa/examples/miniclient.py b/sfa/examples/miniclient.py
index 1c625e0..535b446 100755
--- a/sfa/examples/miniclient.py
+++ b/sfa/examples/miniclient.py
@@ -3,6 +3,8 @@
 # this is designed to use a totally empty new directory
 # so we demonstrate how to bootstrap the whole thing
 
+from __future__ import print_function
+
 # init logging on console
 import logging
 console = logging.StreamHandler()
@@ -17,7 +19,7 @@ def unique_call_id(): return uuid.uuid4().urn
 import sys
 args=sys.argv[1:]
 if len(args)!=1:
-    print "Usage: %s directory"%sys.argv[0]
+    print("Usage: %s directory"%sys.argv[0])
     sys.exit(1)
 dir=args[0]
 logger.debug('sfaclientsample: Using directory %s'%dir)
@@ -62,8 +64,8 @@ def get_version (url):
     bootstrap.self_signed_cert()
     server_proxy = bootstrap.server_proxy_simple(url)
     server_version = server_proxy.GetVersion()
-    print "miniclient: GetVersion at %s returned:"%(url)
-    for (k,v) in server_version.iteritems(): print "miniclient: \tversion[%s]=%s"%(k,truncate(v))
+    print("miniclient: GetVersion at %s returned:"%(url))
+    for (k,v) in server_version.iteritems(): print("miniclient: \tversion[%s]=%s"%(k,truncate(v)))
 
 # version_dict = {'type': 'SFA', 'version': '1', }
 
@@ -79,7 +81,7 @@ def list_resources ():
     options [ 'geni_rspec_version' ] = version_dict
     options [ 'call_id' ] = unique_call_id()
     list_resources = bootstrap.server_proxy (aggregate_url).ListResources(credentials,options)
-    print "miniclient: ListResources at %s returned : %s"%(aggregate_url,truncate(list_resources))
+    print("miniclient: ListResources at %s returned : %s"%(aggregate_url,truncate(list_resources)))
 
 def list_slice_resources ():
     bootstrap.bootstrap_my_gid()
@@ -90,7 +92,7 @@ def list_slice_resources ():
     options [ 'geni_slice_urn' ] = slice_urn
     options [ 'call_id' ] = unique_call_id()
     list_resources = bootstrap.server_proxy (aggregate_url).ListResources(credentials,options)
-    print "miniclient: ListResources at %s for slice %s returned : %s"%(aggregate_url,slice_urn,truncate(list_resources))
+    print("miniclient: ListResources at %s for slice %s returned : %s"%(aggregate_url,slice_urn,truncate(list_resources)))
     
     
 
diff --git a/sfa/federica/fdshell.py b/sfa/federica/fdshell.py
index 42ec030..1e7349c 100644
--- a/sfa/federica/fdshell.py
+++ b/sfa/federica/fdshell.py
@@ -1,6 +1,5 @@
-import xmlrpclib
-
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import xmlrpc_client
 
 class FdShell:
     """
@@ -20,9 +19,9 @@ class FdShell:
     def __init__ ( self, config ) :
         url=config.SFA_FEDERICA_URL
         # xxx not sure if java xmlrpc has support for None
-        # self.proxy = xmlrpclib.Server(url, verbose = False, allow_none = True)
+        # self.proxy = xmlrpc_client.ServerProxy(url, verbose = False, allow_none = True)
         # xxx turn on verbosity
-        self.proxy = xmlrpclib.Server(url, verbose = True)
+        self.proxy = xmlrpc_client.ServerProxy(url, verbose = True)
 
     # xxx get credentials from the config ?
     # right now basic auth data goes into the URL
@@ -30,7 +29,7 @@ class FdShell:
     def __getattr__(self, name):
         def func(*args, **kwds):
             if name not in FdShell.direct_calls:
-                raise Exception, "Illegal method call %s for FEDERICA driver"%(name)
+                raise Exception("Illegal method call %s for FEDERICA driver"%(name))
             logger.info("Issuing %s args=%s kwds=%s to federica"%\
                             (name,args,kwds))
 #            result=getattr(self.proxy, "AggregateManager.%s"%name)(credential, *args, **kwds)
diff --git a/sfa/iotlab/iotlabaggregate.py b/sfa/iotlab/iotlabaggregate.py
index ee95299..a0edf3a 100644
--- a/sfa/iotlab/iotlabaggregate.py
+++ b/sfa/iotlab/iotlabaggregate.py
@@ -59,6 +59,7 @@ class IotLABAggregate(object):
         # rspec_node['boot_state'] = 'true'
         if node['state'] == 'Absent' or \
            node['state'] == 'Suspected' or \
+           node['state'] == 'Dead' or \
            node['state'] == 'Busy':
             rspec_node['available'] = 'false'
         else:
diff --git a/sfa/iotlab/iotlabshell.py b/sfa/iotlab/iotlabshell.py
index c51a7d6..363e4b5 100644
--- a/sfa/iotlab/iotlabshell.py
+++ b/sfa/iotlab/iotlabshell.py
@@ -138,11 +138,6 @@ class IotLABShell(object):
                            err.reason)
             return {'error': err.reason}
         for exp in experiments['items']:
-            # BUG IN OAR REST API : job with reservation didn't return
-            # resources attribute list. We use another request for
-            # finding job resources
-            exp_nodes = self.api.method('admin/experiments/%d' % exp['id'])
-            exp['resources'] = exp_nodes['nodes']
             # BUG ASAP jobs without date information
             if exp['date'] == "as soon as possible":
                 exp['date'] = 0
diff --git a/sfa/managers/aggregate_manager_eucalyptus.py b/sfa/managers/aggregate_manager_eucalyptus.py
deleted file mode 100644
index b8c83d8..0000000
--- a/sfa/managers/aggregate_manager_eucalyptus.py
+++ /dev/null
@@ -1,701 +0,0 @@
-from __future__ import with_statement 
-
-import sys
-import os, errno
-import logging
-import datetime
-from multiprocessing import Process
-from time import sleep
-
-import boto
-from boto.ec2.regioninfo import RegionInfo
-from boto.exception import EC2ResponseError
-from ConfigParser import ConfigParser
-from xmlbuilder import XMLBuilder
-from lxml import etree as ET
-from sqlobject import *
-
-from sfa.util.faults import InvalidRSpec 
-from sfa.util.xrn import urn_to_hrn, Xrn
-from sfa.util.callids import Callids
-#comes with its own logging
-#from sfa.util.sfalogging import logger
-from sfa.util.version import version_core
-
-from sfa.trust.credential import Credential
-
-from sfa.rspecs.version_manager import VersionManager
-from sfa.rspecs.rspec import RSpec
-
-from sfa.planetlab.plaggregate import PlAggregate
-from sfa.planetlab.plslices import PlSlices
-from sfa.planetlab.plxrn import slicename_to_hrn
-
-##
-# Meta data of an instance.
-#
-class Meta(SQLObject):
-    instance   = SingleJoin('EucaInstance')
-    state      = StringCol(default = 'new')
-    pub_addr   = StringCol(default = None)
-    pri_addr   = StringCol(default = None)
-    start_time = DateTimeCol(default = None)
-
-##
-# A representation of an Eucalyptus instance. This is a support class
-# for instance <-> slice mapping.
-#
-class EucaInstance(SQLObject):
-    instance_id = StringCol(unique=True, default=None)
-    kernel_id   = StringCol()
-    image_id    = StringCol()
-    ramdisk_id  = StringCol()
-    inst_type   = StringCol()
-    key_pair    = StringCol()
-    slice       = ForeignKey('Slice')
-    meta        = ForeignKey('Meta')
-
-    ##
-    # Contacts Eucalyptus and tries to reserve this instance.
-    # 
-    # @param botoConn A connection to Eucalyptus.
-    # @param pubKeys A list of public keys for the instance.
-    #
-    def reserveInstance(self, botoConn, pubKeys):
-        logger = logging.getLogger('EucaAggregate')
-        logger.info('Reserving an instance: image: %s, kernel: ' \
-                    '%s, ramdisk: %s, type: %s, key: %s' % \
-                    (self.image_id, self.kernel_id, self.ramdisk_id,
-                    self.inst_type, self.key_pair))
-
-        try:
-            reservation = botoConn.run_instances(self.image_id,
-                                                 kernel_id = self.kernel_id,
-                                                 ramdisk_id = self.ramdisk_id,
-                                                 instance_type = self.inst_type,
-                                                 key_name  = self.key_pair,
-                                                 user_data = pubKeys)
-            for instance in reservation.instances:
-                self.instance_id = instance.id
-
-        # If there is an error, destroy itself.
-        except EC2ResponseError, ec2RespErr:
-            errTree = ET.fromstring(ec2RespErr.body)
-            msg = errTree.find('.//Message')
-            logger.error(msg.text)
-            self.destroySelf()
-
-##
-# A representation of a PlanetLab slice. This is a support class
-# for instance <-> slice mapping.
-#
-class Slice(SQLObject):
-    slice_hrn = StringCol()
-    #slice_index = DatabaseIndex('slice_hrn')
-    instances = MultipleJoin('EucaInstance')
-
-##
-# A class that builds the RSpec for Eucalyptus.
-#
-class EucaRSpecBuilder(object):
-    ##
-    # Initizes a RSpec builder
-    #
-    # @param cloud A dictionary containing data about a 
-    #              cloud (ex. clusters, ip)
-    def __init__(self, cloud):
-        self.eucaRSpec = XMLBuilder(format = True, tab_step = "  ")
-        self.cloudInfo = cloud
-
-    ##
-    # Creates a request stanza.
-    # 
-    # @param num The number of instances to create.
-    # @param image The disk image id.
-    # @param kernel The kernel image id.
-    # @param keypair Key pair to embed.
-    # @param ramdisk Ramdisk id (optional).
-    #
-    def __requestXML(self, num, image, kernel, keypair, ramdisk = ''):
-        xml = self.eucaRSpec
-        with xml.request:
-            with xml.instances:
-                xml << str(num)
-            with xml.kernel_image(id=kernel):
-                xml << ''
-            if ramdisk == '':
-                with xml.ramdisk:
-                    xml << ''
-            else:
-                with xml.ramdisk(id=ramdisk):
-                    xml << ''
-            with xml.disk_image(id=image):
-                xml << ''
-            with xml.keypair:
-                xml << keypair
-
-    ##
-    # Creates the cluster stanza.
-    #
-    # @param clusters Clusters information.
-    #
-    def __clustersXML(self, clusters):
-        cloud = self.cloudInfo
-        xml = self.eucaRSpec
-
-        for cluster in clusters:
-            instances = cluster['instances']
-            with xml.cluster(id=cluster['name']):
-                with xml.ipv4:
-                    xml << cluster['ip']
-                with xml.vm_types:
-                    for inst in instances:
-                        with xml.vm_type(name=inst[0]):
-                            with xml.free_slots:
-                                xml << str(inst[1])
-                            with xml.max_instances:
-                                xml << str(inst[2])
-                            with xml.cores:
-                                xml << str(inst[3])
-                            with xml.memory(unit='MB'):
-                                xml << str(inst[4])
-                            with xml.disk_space(unit='GB'):
-                                xml << str(inst[5])
-                            if 'instances' in cloud and inst[0] in cloud['instances']:
-                                existingEucaInstances = cloud['instances'][inst[0]]
-                                with xml.euca_instances:
-                                    for eucaInst in existingEucaInstances:
-                                        with xml.euca_instance(id=eucaInst['id']):
-                                            with xml.state:
-                                                xml << eucaInst['state']
-                                            with xml.public_dns:
-                                                xml << eucaInst['public_dns']
-
-    def __imageBundleXML(self, bundles):
-        xml = self.eucaRSpec
-        with xml.bundles:
-            for bundle in bundles.keys():
-                with xml.bundle(id=bundle):
-                    xml << ''
-
-    ##
-    # Creates the Images stanza.
-    #
-    # @param images A list of images in Eucalyptus.
-    #
-    def __imagesXML(self, images):
-        xml = self.eucaRSpec
-        with xml.images:
-            for image in images:
-                with xml.image(id=image.id):
-                    with xml.type:
-                        xml << image.type
-                    with xml.arch:
-                        xml << image.architecture
-                    with xml.state:
-                        xml << image.state
-                    with xml.location:
-                        xml << image.location
-
-    ##
-    # Creates the KeyPairs stanza.
-    #
-    # @param keypairs A list of key pairs in Eucalyptus.
-    #
-    def __keyPairsXML(self, keypairs):
-        xml = self.eucaRSpec
-        with xml.keypairs:
-            for key in keypairs:
-                with xml.keypair:
-                    xml << key.name
-
-    ##
-    # Generates the RSpec.
-    #
-    def toXML(self):
-        logger = logging.getLogger('EucaAggregate')
-        if not self.cloudInfo:
-            logger.error('No cloud information')
-            return ''
-
-        xml = self.eucaRSpec
-        cloud = self.cloudInfo
-        with xml.RSpec(type='eucalyptus'):
-            with xml.network(name=cloud['name']):
-                with xml.ipv4:
-                    xml << cloud['ip']
-                #self.__keyPairsXML(cloud['keypairs'])
-                #self.__imagesXML(cloud['images'])
-                self.__imageBundleXML(cloud['imageBundles'])
-                self.__clustersXML(cloud['clusters'])
-        return str(xml)
-
-##
-# A parser to parse the output of availability-zones.
-#
-# Note: Only one cluster is supported. If more than one, this will
-#       not work.
-#
-class ZoneResultParser(object):
-    def __init__(self, zones):
-        self.zones = zones
-
-    def parse(self):
-        if len(self.zones) < 3:
-            return
-        clusterList = []
-        cluster = {} 
-        instList = []
-
-        cluster['name'] = self.zones[0].name
-        cluster['ip']   = self.zones[0].state
-
-        for i in range(2, len(self.zones)):
-            currZone = self.zones[i]
-            instType = currZone.name.split()[1]
-
-            stateString = currZone.state.split('/')
-            rscString   = stateString[1].split()
-
-            instFree      = int(stateString[0])
-            instMax       = int(rscString[0])
-            instNumCpu    = int(rscString[1])
-            instRam       = int(rscString[2])
-            instDiskSpace = int(rscString[3])
-
-            instTuple = (instType, instFree, instMax, instNumCpu, instRam, instDiskSpace)
-            instList.append(instTuple)
-        cluster['instances'] = instList
-        clusterList.append(cluster)
-
-        return clusterList
-
-class AggregateManagerEucalyptus:
-
-    # The data structure used to represent a cloud.
-    # It contains the cloud name, its ip address, image information,
-    # key pairs, and clusters information.
-    cloud = {}
-    
-    # The location of the RelaxNG schema.
-    EUCALYPTUS_RSPEC_SCHEMA='/etc/sfa/eucalyptus.rng'
-    
-    _inited=False
-
-    # the init_server mechanism has vanished
-    def __init__ (self, config):
-        if AggregateManagerEucalyptus._inited: return
-        AggregateManagerEucalyptus.init_server()
-
-    # Initialize the aggregate manager by reading a configuration file.
-    @staticmethod
-    def init_server():
-        logger = logging.getLogger('EucaAggregate')
-        fileHandler = logging.FileHandler('/var/log/euca.log')
-        fileHandler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
-        logger.addHandler(fileHandler)
-        fileHandler.setLevel(logging.DEBUG)
-        logger.setLevel(logging.DEBUG)
-
-        configParser = ConfigParser()
-        configParser.read(['/etc/sfa/eucalyptus_aggregate.conf', 'eucalyptus_aggregate.conf'])
-        if len(configParser.sections()) < 1:
-            logger.error('No cloud defined in the config file')
-            raise Exception('Cannot find cloud definition in configuration file.')
-    
-        # Only read the first section.
-        cloudSec = configParser.sections()[0]
-        AggregateManagerEucalyptus.cloud['name'] = cloudSec
-        AggregateManagerEucalyptus.cloud['access_key'] = configParser.get(cloudSec, 'access_key')
-        AggregateManagerEucalyptus.cloud['secret_key'] = configParser.get(cloudSec, 'secret_key')
-        AggregateManagerEucalyptus.cloud['cloud_url']  = configParser.get(cloudSec, 'cloud_url')
-        cloudURL = AggregateManagerEucalyptus.cloud['cloud_url']
-        if cloudURL.find('https://') >= 0:
-            cloudURL = cloudURL.replace('https://', '')
-        elif cloudURL.find('http://') >= 0:
-            cloudURL = cloudURL.replace('http://', '')
-        (AggregateManagerEucalyptus.cloud['ip'], parts) = cloudURL.split(':')
-    
-        # Create image bundles
-        images = self.getEucaConnection().get_all_images()
-        AggregateManagerEucalyptus.cloud['images'] = images
-        AggregateManagerEucalyptus.cloud['imageBundles'] = {}
-        for i in images:
-            if i.type != 'machine' or i.kernel_id is None: continue
-            name = os.path.dirname(i.location)
-            detail = {'imageID' : i.id, 'kernelID' : i.kernel_id, 'ramdiskID' : i.ramdisk_id}
-            AggregateManagerEucalyptus.cloud['imageBundles'][name] = detail
-    
-        # Initialize sqlite3 database and tables.
-        dbPath = '/etc/sfa/db'
-        dbName = 'euca_aggregate.db'
-    
-        if not os.path.isdir(dbPath):
-            logger.info('%s not found. Creating directory ...' % dbPath)
-            os.mkdir(dbPath)
-    
-        conn = connectionForURI('sqlite://%s/%s' % (dbPath, dbName))
-        sqlhub.processConnection = conn
-        Slice.createTable(ifNotExists=True)
-        EucaInstance.createTable(ifNotExists=True)
-        Meta.createTable(ifNotExists=True)
-    
-        # Start the update process to keep track of the meta data
-        # about Eucalyptus instance.
-        Process(target=AggregateManagerEucalyptus.updateMeta).start()
-    
-        # Make sure the schema exists.
-        if not os.path.exists(AggregateManagerEucalyptus.EUCALYPTUS_RSPEC_SCHEMA):
-            err = 'Cannot location schema at %s' % AggregateManagerEucalyptus.EUCALYPTUS_RSPEC_SCHEMA
-            logger.error(err)
-            raise Exception(err)
-    
-    #
-    # A separate process that will update the meta data.
-    #
-    @staticmethod    
-    def updateMeta():
-        logger = logging.getLogger('EucaMeta')
-        fileHandler = logging.FileHandler('/var/log/euca_meta.log')
-        fileHandler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
-        logger.addHandler(fileHandler)
-        fileHandler.setLevel(logging.DEBUG)
-        logger.setLevel(logging.DEBUG)
-    
-        while True:
-            sleep(30)
-    
-            # Get IDs of the instances that don't have IPs yet.
-            dbResults = Meta.select(
-                          AND(Meta.q.pri_addr == None,
-                              Meta.q.state    != 'deleted')
-                        )
-            dbResults = list(dbResults)
-            logger.debug('[update process] dbResults: %s' % dbResults)
-            instids = []
-            for r in dbResults:
-                if not r.instance:
-                    continue
-                instids.append(r.instance.instance_id)
-            logger.debug('[update process] Instance Id: %s' % ', '.join(instids))
-    
-            # Get instance information from Eucalyptus
-            conn = self.getEucaConnection()
-            vmInstances = []
-            reservations = conn.get_all_instances(instids)
-            for reservation in reservations:
-                vmInstances += reservation.instances
-    
-            # Check the IPs
-            instIPs = [ {'id':i.id, 'pri_addr':i.private_dns_name, 'pub_addr':i.public_dns_name}
-                        for i in vmInstances if i.private_dns_name != '0.0.0.0' ]
-            logger.debug('[update process] IP dict: %s' % str(instIPs))
-    
-            # Update the local DB
-            for ipData in instIPs:
-                dbInst = EucaInstance.select(EucaInstance.q.instance_id == ipData['id']).getOne(None)
-                if not dbInst:
-                    logger.info('[update process] Could not find %s in DB' % ipData['id'])
-                    continue
-                dbInst.meta.pri_addr = ipData['pri_addr']
-                dbInst.meta.pub_addr = ipData['pub_addr']
-                dbInst.meta.state    = 'running'
-    
-            self.dumpinstanceInfo()
-    
-    ##
-    # Creates a connection to Eucalytpus. This function is inspired by 
-    # the make_connection() in Euca2ools.
-    #
-    # @return A connection object or None
-    #
-    def getEucaConnection():
-        accessKey = AggregateManagerEucalyptus.cloud['access_key']
-        secretKey = AggregateManagerEucalyptus.cloud['secret_key']
-        eucaURL   = AggregateManagerEucalyptus.cloud['cloud_url']
-        useSSL    = False
-        srvPath   = '/'
-        eucaPort  = 8773
-        logger    = logging.getLogger('EucaAggregate')
-    
-        if not accessKey or not secretKey or not eucaURL:
-            logger.error('Please set ALL of the required environment ' \
-                         'variables by sourcing the eucarc file.')
-            return None
-        
-        # Split the url into parts
-        if eucaURL.find('https://') >= 0:
-            useSSL  = True
-            eucaURL = eucaURL.replace('https://', '')
-        elif eucaURL.find('http://') >= 0:
-            useSSL  = False
-            eucaURL = eucaURL.replace('http://', '')
-        (eucaHost, parts) = eucaURL.split(':')
-        if len(parts) > 1:
-            parts = parts.split('/')
-            eucaPort = int(parts[0])
-            parts = parts[1:]
-            srvPath = '/'.join(parts)
-    
-        return boto.connect_ec2(aws_access_key_id=accessKey,
-                                aws_secret_access_key=secretKey,
-                                is_secure=useSSL,
-                                region=RegionInfo(None, 'eucalyptus', eucaHost), 
-                                port=eucaPort,
-                                path=srvPath)
-    
-    def ListResources(api, creds, options):
-        call_id = options.get('call_id') 
-        if Callids().already_handled(call_id): return ""
-        # get slice's hrn from options
-        xrn = options.get('geni_slice_urn', '')
-        hrn, type = urn_to_hrn(xrn)
-        logger = logging.getLogger('EucaAggregate')
-    
-        # get hrn of the original caller
-        origin_hrn = options.get('origin_hrn', None)
-        if not origin_hrn:
-            origin_hrn = Credential(string=creds[0]).get_gid_caller().get_hrn()
-    
-        conn = self.getEucaConnection()
-    
-        if not conn:
-            logger.error('Cannot create a connection to Eucalyptus')
-            return 'Cannot create a connection to Eucalyptus'
-    
-        try:
-            # Zones
-            zones = conn.get_all_zones(['verbose'])
-            p = ZoneResultParser(zones)
-            clusters = p.parse()
-            AggregateManagerEucalyptus.cloud['clusters'] = clusters
-            
-            # Images
-            images = conn.get_all_images()
-            AggregateManagerEucalyptus.cloud['images'] = images
-            AggregateManagerEucalyptus.cloud['imageBundles'] = {}
-            for i in images:
-                if i.type != 'machine' or i.kernel_id is None: continue
-                name = os.path.dirname(i.location)
-                detail = {'imageID' : i.id, 'kernelID' : i.kernel_id, 'ramdiskID' : i.ramdisk_id}
-                AggregateManagerEucalyptus.cloud['imageBundles'][name] = detail
-    
-            # Key Pairs
-            keyPairs = conn.get_all_key_pairs()
-            AggregateManagerEucalyptus.cloud['keypairs'] = keyPairs
-    
-            if hrn:
-                instanceId = []
-                instances  = []
-    
-                # Get the instances that belong to the given slice from sqlite3
-                # XXX use getOne() in production because the slice's hrn is supposed
-                # to be unique. For testing, uniqueness is turned off in the db.
-                # If the slice isn't found in the database, create a record for the 
-                # slice.
-                matchedSlices = list(Slice.select(Slice.q.slice_hrn == hrn))
-                if matchedSlices:
-                    theSlice = matchedSlices[-1]
-                else:
-                    theSlice = Slice(slice_hrn = hrn)
-                for instance in theSlice.instances:
-                    instanceId.append(instance.instance_id)
-    
-                # Get the information about those instances using their ids.
-                if len(instanceId) > 0:
-                    reservations = conn.get_all_instances(instanceId)
-                else:
-                    reservations = []
-                for reservation in reservations:
-                    for instance in reservation.instances:
-                        instances.append(instance)
-    
-                # Construct a dictionary for the EucaRSpecBuilder
-                instancesDict = {}
-                for instance in instances:
-                    instList = instancesDict.setdefault(instance.instance_type, [])
-                    instInfoDict = {} 
-    
-                    instInfoDict['id'] = instance.id
-                    instInfoDict['public_dns'] = instance.public_dns_name
-                    instInfoDict['state'] = instance.state
-                    instInfoDict['key'] = instance.key_name
-    
-                    instList.append(instInfoDict)
-                AggregateManagerEucalyptus.cloud['instances'] = instancesDict
-    
-        except EC2ResponseError, ec2RespErr:
-            errTree = ET.fromstring(ec2RespErr.body)
-            errMsgE = errTree.find('.//Message')
-            logger.error(errMsgE.text)
-    
-        rspec = EucaRSpecBuilder(AggregateManagerEucalyptus.cloud).toXML()
-    
-        # Remove the instances records so next time they won't 
-        # show up.
-        if 'instances' in AggregateManagerEucalyptus.cloud:
-            del AggregateManagerEucalyptus.cloud['instances']
-    
-        return rspec
-    
-    """
-    Hook called via 'sfi.py create'
-    """
-    def CreateSliver(api, slice_xrn, creds, xml, users, options):
-        call_id = options.get('call_id') 
-        if Callids().already_handled(call_id): return ""
-    
-        logger = logging.getLogger('EucaAggregate')
-        logger.debug("In CreateSliver")
-    
-        aggregate = PlAggregate(self.driver)
-        slices = PlSlices(self.driver)
-        (hrn, type) = urn_to_hrn(slice_xrn)
-        peer = slices.get_peer(hrn)
-        sfa_peer = slices.get_sfa_peer(hrn)
-        slice_record=None
-        if users:
-            slice_record = users[0].get('slice_record', {})
-    
-        conn = self.getEucaConnection()
-        if not conn:
-            logger.error('Cannot create a connection to Eucalyptus')
-            return ""
-    
-        # Validate RSpec
-        schemaXML = ET.parse(AggregateManagerEucalyptus.EUCALYPTUS_RSPEC_SCHEMA)
-        rspecValidator = ET.RelaxNG(schemaXML)
-        rspecXML = ET.XML(xml)
-        for network in rspecXML.iterfind("./network"):
-            if network.get('name') != AggregateManagerEucalyptus.cloud['name']:
-                # Throw away everything except my own RSpec
-                # sfa_logger().error("CreateSliver: deleting %s from rspec"%network.get('id'))
-                network.getparent().remove(network)
-        if not rspecValidator(rspecXML):
-            error = rspecValidator.error_log.last_error
-            message = '%s (line %s)' % (error.message, error.line) 
-            raise InvalidRSpec(message)
-    
-        """
-        Create the sliver[s] (slice) at this aggregate.
-        Verify HRN and initialize the slice record in PLC if necessary.
-        """
-    
-        # ensure site record exists
-        site = slices.verify_site(hrn, slice_record, peer, sfa_peer)
-        # ensure slice record exists
-        slice = slices.verify_slice(hrn, slice_record, peer, sfa_peer)
-        # ensure person records exists
-        persons = slices.verify_persons(hrn, slice, users, peer, sfa_peer)
-    
-        # Get the slice from db or create one.
-        s = Slice.select(Slice.q.slice_hrn == hrn).getOne(None)
-        if s is None:
-            s = Slice(slice_hrn = hrn)
-    
-        # Process any changes in existing instance allocation
-        pendingRmInst = []
-        for sliceInst in s.instances:
-            pendingRmInst.append(sliceInst.instance_id)
-        existingInstGroup = rspecXML.findall(".//euca_instances")
-        for instGroup in existingInstGroup:
-            for existingInst in instGroup:
-                if existingInst.get('id') in pendingRmInst:
-                    pendingRmInst.remove(existingInst.get('id'))
-        for inst in pendingRmInst:
-            dbInst = EucaInstance.select(EucaInstance.q.instance_id == inst).getOne(None)
-            if dbInst.meta.state != 'deleted':
-                logger.debug('Instance %s will be terminated' % inst)
-                # Terminate instances one at a time for robustness
-                conn.terminate_instances([inst])
-                # Only change the state but do not remove the entry from the DB.
-                dbInst.meta.state = 'deleted'
-                #dbInst.destroySelf()
-    
-        # Process new instance requests
-        requests = rspecXML.findall(".//request")
-        if requests:
-            # Get all the public keys associate with slice.
-            keys = []
-            for user in users:
-                keys += user['keys']
-                logger.debug("Keys: %s" % user['keys'])
-            pubKeys = '\n'.join(keys)
-            logger.debug('Passing the following keys to the instance:\n%s' % pubKeys)
-        for req in requests:
-            vmTypeElement = req.getparent()
-            instType = vmTypeElement.get('name')
-            numInst  = int(req.find('instances').text)
-            
-            bundleName = req.find('bundle').text
-            if not AggregateManagerEucalyptus.cloud['imageBundles'][bundleName]:
-                logger.error('Cannot find bundle %s' % bundleName)
-            bundleInfo = AggregateManagerEucalyptus.cloud['imageBundles'][bundleName]
-            instKernel  = bundleInfo['kernelID']
-            instDiskImg = bundleInfo['imageID']
-            instRamDisk = bundleInfo['ramdiskID']
-            instKey     = None
-    
-            # Create the instances
-            for i in range(0, numInst):
-                eucaInst = EucaInstance(slice      = s,
-                                        kernel_id  = instKernel,
-                                        image_id   = instDiskImg,
-                                        ramdisk_id = instRamDisk,
-                                        key_pair   = instKey,
-                                        inst_type  = instType,
-                                        meta       = Meta(start_time=datetime.datetime.utcnow()))
-                eucaInst.reserveInstance(conn, pubKeys)
-    
-        # xxx - should return altered rspec 
-        # with enough data for the client to understand what's happened
-        return xml
-    
-    ##
-    # Return information on the IP addresses bound to each slice's instances
-    #
-    def dumpInstanceInfo():
-        logger = logging.getLogger('EucaMeta')
-        outdir = "/var/www/html/euca/"
-        outfile = outdir + "instances.txt"
-    
-        try:
-            os.makedirs(outdir)
-        except OSError, e:
-            if e.errno != errno.EEXIST:
-                raise
-    
-        dbResults = Meta.select(
-            AND(Meta.q.pri_addr != None,
-                Meta.q.state    == 'running')
-            )
-        dbResults = list(dbResults)
-        f = open(outfile, "w")
-        for r in dbResults:
-            instId = r.instance.instance_id
-            ipaddr = r.pri_addr
-            hrn = r.instance.slice.slice_hrn
-            logger.debug('[dumpInstanceInfo] %s %s %s' % (instId, ipaddr, hrn))
-            f.write("%s %s %s\n" % (instId, ipaddr, hrn))
-        f.close()
-    
-    def GetVersion(api, options):
-
-        version_manager = VersionManager()
-        ad_rspec_versions = []
-        request_rspec_versions = []
-        for rspec_version in version_manager.versions:
-            if rspec_version.content_type in ['*', 'ad']:
-                ad_rspec_versions.append(rspec_version.to_dict())
-            if rspec_version.content_type in ['*', 'request']:
-                request_rspec_versions.append(rspec_version.to_dict())
-        xrn=Xrn(api.hrn)
-        version_more = {'interface':'aggregate',
-                        'sfa': 1,
-                        'geni_api': '2',
-                        'testbed':'myplc',
-                        'hrn':xrn.get_hrn(),
-                        'geni_request_rspec_versions': request_rspec_versions,
-                        'geni_ad_rspec_versions': ad_rspec_versions,
-                        }
-        return version_core(version_more)
diff --git a/sfa/managers/component_manager_pl.py b/sfa/managers/component_manager_pl.py
index 8518839..ba9b7eb 100644
--- a/sfa/managers/component_manager_pl.py
+++ b/sfa/managers/component_manager_pl.py
@@ -1,7 +1,6 @@
-import xmlrpclib
-
 from sfa.util.faults import SliverDoesNotExist
 from sfa.util.version import version_core
+from sfa.util.py23 import xmlrpc_client
 
 from sfa.trust.sfaticket import SfaTicket
 
@@ -66,7 +65,7 @@ def redeem_ticket(api, ticket_string):
         raise SliverDoesNotExist(slicename)
 
     # convert ticket to format nm is used to
-    nm_ticket = xmlrpclib.dumps((ticket.attributes,), methodresponse=True)
+    nm_ticket = xmlrpc_client.dumps((ticket.attributes,), methodresponse=True)
     api.driver.nodemanager.AdminTicket(nm_ticket)
     
 
diff --git a/sfa/managers/eucalyptus/euca_rspec_validator.py b/sfa/managers/eucalyptus/euca_rspec_validator.py
index 7e25063..7ebaae5 100755
--- a/sfa/managers/eucalyptus/euca_rspec_validator.py
+++ b/sfa/managers/eucalyptus/euca_rspec_validator.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
-from __future__ import with_statement 
+from __future__ import print_function
+
 import sys
 import os
 from lxml import etree as ET
@@ -19,9 +20,9 @@ def main():
         if not rspecValidator(rspecXML):
             error = rspecValidator.error_log.last_error
             message = '%s (line %s)' % (error.message, error.line) 
-            print message
+            print(message)
         else:
-            print 'It is valid'
+            print('It is valid')
 
 if __name__ == "__main__":
     main()
diff --git a/sfa/managers/managerwrapper.py b/sfa/managers/managerwrapper.py
index 946f7d2..da8c98f 100644
--- a/sfa/managers/managerwrapper.py
+++ b/sfa/managers/managerwrapper.py
@@ -27,7 +27,7 @@ class ManagerWrapper:
             # that's what happens when there's something wrong with the db
             # or any bad stuff of that kind at startup time
             logger.log_exc("Failed to create a manager, startup sequence is broken")
-            raise SfaAPIError,"Argument to ManagerWrapper must be a module or class"
+            raise SfaAPIError("Argument to ManagerWrapper must be a module or class")
         self.interface = interface
         
     def __getattr__(self, method):
diff --git a/sfa/managers/registry_manager.py b/sfa/managers/registry_manager.py
index 1eafc10..78d933d 100644
--- a/sfa/managers/registry_manager.py
+++ b/sfa/managers/registry_manager.py
@@ -1,4 +1,5 @@
-import types
+from __future__ import print_function
+
 # for get_key_from_incoming_ip
 import tempfile
 import os
@@ -161,7 +162,7 @@ class RegistryManager:
     def Resolve(self, api, xrns, type=None, details=False):
     
         dbsession = api.dbsession()
-        if not isinstance(xrns, types.ListType):
+        if not isinstance(xrns, list):
             # try to infer type if not set and we get a single input
             if not type:
                 type = Xrn(xrns).get_type()
@@ -385,7 +386,7 @@ class RegistryManager:
             pub_key=getattr(record,'reg-keys',None)
             if pub_key is not None:
                 # use only first key in record
-                if pub_key and isinstance(pub_key, types.ListType): pub_key = pub_key[0]
+                if pub_key and isinstance(pub_key, list): pub_key = pub_key[0]
                 pkey = convert_public_key(pub_key)
     
             email = getattr(record,'email',None)
@@ -414,10 +415,12 @@ class RegistryManager:
         elif isinstance (record, RegUser):
             # create RegKey objects for incoming keys
             if hasattr(record,'reg-keys'):
-                keys=getattr(record,'reg-keys')
+                keys = getattr(record, 'reg-keys')
                 # some people send the key as a string instead of a list of strings
-                if isinstance(keys,types.StringTypes): keys=[keys]
-                logger.debug ("creating {} keys for user {}".format(len(keys), record.hrn))
+                # note for python2/3 : no need to consider unicode in a key
+                if isinstance(keys, str):
+                    keys = [keys]
+                logger.debug("creating {} keys for user {}".format(len(keys), record.hrn))
                 record.reg_keys = [ RegKey (key) for key in keys ]
             
         # update testbed-specific data if needed
@@ -458,7 +461,7 @@ class RegistryManager:
         if type == 'user':
             if getattr(new_record, 'keys', None):
                 new_key = new_record.keys
-                if isinstance (new_key, types.ListType):
+                if isinstance (new_key, list):
                     new_key = new_key[0]
 
         # take new_key into account
@@ -503,7 +506,7 @@ class RegistryManager:
         # record.__dict__ as received by the driver seems to be off
         # anyway the driver should receive an object
         # (and then extract __dict__ itself if needed)
-        print "DO NOT REMOVE ME before driver.update, record={}".format(record)
+        print("DO NOT REMOVE ME before driver.update, record={}".format(record))
         # as of June 2015: I suspect we could remove that print line above and replace it with
         # augment_with_sfa_builtins(record)
         # instead, that checks for these fields, like it is done above in List()
@@ -623,7 +626,7 @@ class RegistryManager:
         for command in all_commands:
             (status, output) = commands.getstatusoutput(command)
             if status:
-                raise Exception, output
+                raise Exception(output)
 
         for filename in [key_filename, gid_filename]:
             os.unlink(filename)
diff --git a/sfa/managers/slice_manager.py b/sfa/managers/slice_manager.py
index 2a99b6f..8252ec3 100644
--- a/sfa/managers/slice_manager.py
+++ b/sfa/managers/slice_manager.py
@@ -1,7 +1,6 @@
 import sys
 import time
 import traceback
-from StringIO import StringIO
 from copy import copy
 from lxml import etree
 
@@ -73,7 +72,7 @@ class SliceManager:
             stats_elements = rspec.xml.xpath('//statistics')
             for node in stats_elements:
                 node.getparent().remove(node)
-        except Exception, e:
+        except Exception as e:
             logger.warn("drop_slicemgr_stats failed: %s " % (str(e)))
     
     def add_slicemgr_stat(self, rspec, callname, aggname, elapsed, status, exc_info=None):
@@ -99,7 +98,7 @@ class SliceManager:
                     exc_frame = exc_tag.add_element("tb_frame", filename=str(item[0]), 
                                                     line=str(item[1]), func=str(item[2]), code=str(item[3]))
 
-        except Exception, e:
+        except Exception as e:
             logger.warn("add_slicemgr_stat failed on  %s: %s" %(aggname, str(e)))
     
     def ListResources(self, api, creds, options):
@@ -117,7 +116,7 @@ class SliceManager:
                 forward_options['geni_rspec_version'] = options.get('geni_rspec_version')
                 result = server.ListResources(credential, forward_options)
                 return {"aggregate": aggregate, "result": result, "elapsed": time.time()-tStart, "status": "success"}
-            except Exception, e:
+            except Exception as e:
                 api.logger.log_exc("ListResources failed at %s" %(server.url))
                 return {"aggregate": aggregate, "elapsed": time.time()-tStart, "status": "exception", "exc_info": sys.exc_info()}
     
diff --git a/sfa/methods/Describe.py b/sfa/methods/Describe.py
index ae198c0..69d4fb7 100644
--- a/sfa/methods/Describe.py
+++ b/sfa/methods/Describe.py
@@ -55,7 +55,7 @@ class Describe(Method):
         self.api.logger.debug("ListResources: sfatables on chain %s"%chain_name)
         desc['geni_rspec'] = run_sfatables(chain_name, '', origin_hrn, desc['geni_rspec']) 
  
-        if options.has_key('geni_compressed') and options['geni_compressed'] == True:
+        if 'geni_compressed' in options and options['geni_compressed'] == True:
             desc['geni_rspec'] = zlib.compress(desc['geni_rspec']).encode('base64')
 
         return desc  
diff --git a/sfa/methods/ListResources.py b/sfa/methods/ListResources.py
index 33777fd..795594b 100644
--- a/sfa/methods/ListResources.py
+++ b/sfa/methods/ListResources.py
@@ -50,7 +50,7 @@ class ListResources(Method):
         self.api.logger.debug("ListResources: sfatables on chain %s"%chain_name)
         filtered_rspec = run_sfatables(chain_name, '', origin_hrn, rspec) 
  
-        if options.has_key('geni_compressed') and options['geni_compressed'] == True:
+        if 'geni_compressed' in options and options['geni_compressed'] == True:
             filtered_rspec = zlib.compress(filtered_rspec).encode('base64')
 
         return filtered_rspec  
diff --git a/sfa/methods/Resolve.py b/sfa/methods/Resolve.py
index dc34f75..fc12df1 100644
--- a/sfa/methods/Resolve.py
+++ b/sfa/methods/Resolve.py
@@ -1,5 +1,3 @@
-import types
-
 from sfa.util.xrn import Xrn, urn_to_hrn
 from sfa.util.method import Method
 
@@ -37,7 +35,7 @@ class Resolve(Method):
         if 'details' in options: details=options['details']
         else:                    details=False
         type = None
-        if not isinstance(xrns, types.ListType):
+        if not isinstance(xrns, list):
             type = Xrn(xrns).get_type()
             xrns=[xrns]
         hrns = [urn_to_hrn(xrn)[0] for xrn in xrns]
diff --git a/sfa/nitos/nitosdriver.py b/sfa/nitos/nitosdriver.py
index 40db2a7..8e3da34 100644
--- a/sfa/nitos/nitosdriver.py
+++ b/sfa/nitos/nitosdriver.py
@@ -668,7 +668,7 @@ class NitosDriver (Driver):
     # xxx this code is quite old and has not run for ages
     # it is obviously totally broken and needs a rewrite
     def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
-        raise SfaNotImplemented,"NitosDriver.get_ticket needs a rewrite"
+        raise SfaNotImplemented("NitosDriver.get_ticket needs a rewrite")
 # please keep this code for future reference
 #        slices = PlSlices(self)
 #        peer = slices.get_peer(slice_hrn)
diff --git a/sfa/nitos/nitosshell.py b/sfa/nitos/nitosshell.py
index 269e367..cf543f2 100644
--- a/sfa/nitos/nitosshell.py
+++ b/sfa/nitos/nitosshell.py
@@ -1,9 +1,9 @@
 import sys
-import xmlrpclib
 import socket
 from urlparse import urlparse
 
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import xmlrpc_client
 
 class NitosShell:
     """
@@ -26,14 +26,14 @@ class NitosShell:
     # use the 'capability' auth mechanism for higher performance when the PLC db is local    
     def __init__ ( self, config ) :
         url = config.SFA_NITOS_URL
-        self.proxy = xmlrpclib.Server(url, verbose = False, allow_none = True)
+        self.proxy = xmlrpc_client.ServerProxy(url, verbose = False, allow_none = True)
 
     def __getattr__(self, name):
         def func(*args, **kwds):
             actual_name=None
             if name in NitosShell.direct_calls: actual_name=name
             if not actual_name:
-                raise Exception, "Illegal method call %s for NITOS driver"%(name)
+                raise Exception("Illegal method call %s for NITOS driver"%(name))
             actual_name = "scheduler.server." + actual_name
             result=getattr(self.proxy, actual_name)(*args, **kwds)
             logger.debug('NitosShell %s (%s) returned ... '%(name,actual_name))
diff --git a/sfa/nitos/nitosslices.py b/sfa/nitos/nitosslices.py
index 875a5a9..ffdb6e9 100644
--- a/sfa/nitos/nitosslices.py
+++ b/sfa/nitos/nitosslices.py
@@ -1,4 +1,3 @@
-from types import StringTypes
 from collections import defaultdict
 
 from sfa.util.sfatime import utcparse, datetime_to_epoch
diff --git a/sfa/nitos/nitosxrn.py b/sfa/nitos/nitosxrn.py
index 47d8aad..9722ffe 100644
--- a/sfa/nitos/nitosxrn.py
+++ b/sfa/nitos/nitosxrn.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 # specialized Xrn class for NITOS
 import re
 from sfa.util.xrn import Xrn
@@ -110,4 +112,4 @@ if __name__ == '__main__':
         #slice_name = NitosXrn(xrn="omf.nitos.aminesl",type='slice').nitos_slicename()
         slicename = "giorgos_n"
         hrn = slicename_to_hrn("pla", "nitos", slicename)
-        print hrn  
+        print(hrn)  
diff --git a/sfa/openstack/osaggregate.py b/sfa/openstack/osaggregate.py
index 2b65399..29681a0 100644
--- a/sfa/openstack/osaggregate.py
+++ b/sfa/openstack/osaggregate.py
@@ -395,7 +395,7 @@ class OSAggregate:
                                                             meta=metadata, 
                                                             name=instance_name)
                     slivers.append(server)
-                except Exception, err:    
+                except Exception as err:    
                     logger.log_exc(err)                                
                            
         return slivers        
diff --git a/sfa/openstack/security_group.py b/sfa/openstack/security_group.py
index 70e191e..6aced8c 100644
--- a/sfa/openstack/security_group.py
+++ b/sfa/openstack/security_group.py
@@ -9,7 +9,7 @@ class SecurityGroup:
     def create_security_group(self, name):
         try:
             self.client.security_groups.create(name=name, description=name)
-        except Exception, ex:
+        except Exception as ex:
             logger.log_exc("Failed to add security group")
             raise
 
@@ -17,7 +17,7 @@ class SecurityGroup:
         try:
             security_group = self.client.security_groups.find(name=name)
             self.client.security_groups.delete(security_group.id)
-        except Exception, ex:
+        except Exception as ex:
             logger.log_exc("Failed to delete security group")
 
 
@@ -58,7 +58,7 @@ class SecurityGroup:
             group = self.client.security_groups.find(name=group_name)
             self.client.security_group_rules.create(group.id, \
                                 protocol, from_port, to_port,cidr_ip)
-        except Exception, ex:
+        except Exception as ex:
             logger.log_exc("Failed to add rule to group %s" % group_name)
 
 
@@ -81,6 +81,6 @@ class SecurityGroup:
             rule = self.client.security_group_rules.find(**filter)
             if rule:
                 self.client.security_group_rules.delete(rule)
-        except Exception, ex:
+        except Exception as ex:
             logger.log_exc("Failed to remove rule from group %s" % group_name) 
              
diff --git a/sfa/openstack/shell.py b/sfa/openstack/shell.py
index acb9cff..e31be9d 100644
--- a/sfa/openstack/shell.py
+++ b/sfa/openstack/shell.py
@@ -1,5 +1,4 @@
 import sys
-import xmlrpclib
 import socket
 import gettext
 from urlparse import urlparse
diff --git a/sfa/planetlab/nodemanager.py b/sfa/planetlab/nodemanager.py
index 1671b9d..12e7f22 100644
--- a/sfa/planetlab/nodemanager.py
+++ b/sfa/planetlab/nodemanager.py
@@ -24,6 +24,8 @@ class NodeManager:
         (fd, filename) = tempfile.mkstemp(dir=path)        
         scriptname = script_path + os.sep + filename.split(os.sep)[-1:][0]
         # define the script to execute
+        # when providing support for python3 wrt xmlrpclib
+        # looks safer to keep things as-is
         script = """
 #!%(python)s
 import xmlrpclib
diff --git a/sfa/planetlab/peers.py b/sfa/planetlab/peers.py
index 63c9f7f..7c6e1b7 100644
--- a/sfa/planetlab/peers.py
+++ b/sfa/planetlab/peers.py
@@ -1,5 +1,6 @@
 from sfa.util.xrn import get_authority
-from types import StringTypes
+
+from sfa.util.py23 import StringType
 
 def get_peer(pldriver, hrn):
     # Because of myplc native federation,  we first need to determine if this
@@ -15,7 +16,7 @@ def get_peer(pldriver, hrn):
     # check if we are already peered with this site_authority, if so
     peers = pldriver.shell.GetPeers( {}, ['peer_id', 'peername', 'shortname', 'hrn_root'])
     for peer_record in peers:
-        names = [name.lower() for name in peer_record.values() if isinstance(name, StringTypes)]
+        names = [name.lower() for name in peer_record.values() if isinstance(name, StringType)]
         if site_authority in names:
             peer = peer_record['shortname']
 
diff --git a/sfa/planetlab/plshell.py b/sfa/planetlab/plshell.py
index 16af128..15c58b6 100644
--- a/sfa/planetlab/plshell.py
+++ b/sfa/planetlab/plshell.py
@@ -89,7 +89,7 @@ class PlShell:
             if name in PlShell.direct_calls: actual_name=name
             if name in PlShell.alias_calls: actual_name=PlShell.alias_calls[name]
             if not actual_name:
-                raise Exception, "Illegal method call %s for PL driver"%(name)
+                raise Exception("Illegal method call %s for PL driver"%(name))
             result=getattr(self.proxy, actual_name)(self.plauth, *args, **kwds)
             logger.debug('PlShell %s (%s) returned ... '%(name,actual_name))
             return result
diff --git a/sfa/planetlab/plslices.py b/sfa/planetlab/plslices.py
index ffa6f16..2b59a01 100644
--- a/sfa/planetlab/plslices.py
+++ b/sfa/planetlab/plslices.py
@@ -1,5 +1,4 @@
 import time
-from types import StringTypes
 from collections import defaultdict
 
 from sfa.util.sfatime import utcparse, datetime_to_epoch
diff --git a/sfa/planetlab/plxrn.py b/sfa/planetlab/plxrn.py
index 1b0e474..70ff5e0 100644
--- a/sfa/planetlab/plxrn.py
+++ b/sfa/planetlab/plxrn.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 # specialized Xrn class for PlanetLab
 import re
 from sfa.util.xrn import Xrn, get_authority
@@ -27,14 +29,15 @@ def top_auth (hrn):
 
 def hash_loginbase(site_hrn):
     if len(site_hrn) <= 12:
-        return site_hrn.replace('.','8')
+        return site_hrn.replace('.','8').replace('_', '8')
     ratio = float(12) / len(site_hrn)
     auths_tab = site_hrn.split('.')
     auths_tab2 = []
     for auth in auths_tab:
-         auth2 = auth[:int(len(auth)*ratio)]
-         auths_tab2.append(auth2)
 
+        auth = auth.replace('_', '8')
+        auth2 = auth[:int(len(auth)*ratio)]
+        auths_tab2.append(auth2)
     return '8'.join(auths_tab2)
 
 class PlXrn (Xrn):
@@ -102,3 +105,15 @@ class PlXrn (Xrn):
         base = re.sub('[\\\\]*[^a-zA-Z0-9]', '', base)
 
         return base
+
+tests = [
+    'inria.foo.x',
+    'in.foo.x_y',
+    'inria.foo.longer',
+    'onelab.upmc.fit_demo',
+    'onelab.upmc.fit_demo.some_other',
+]
+
+if __name__ == '__main__':
+    for test in tests:
+        print(("{} - hash_loginbase -> {}".format(test, hash_loginbase(test))))
diff --git a/sfa/planetlab/topology.py b/sfa/planetlab/topology.py
index cd6036a..c05b198 100644
--- a/sfa/planetlab/topology.py
+++ b/sfa/planetlab/topology.py
@@ -25,6 +25,6 @@ class Topology(set):
                 tup = line.split()
                 if len(tup) > 1:
                     self.add((tup[0], tup[1]))    
-        except Exception, e:
+        except Exception as e:
             logger.log_exc("Could not find or load the configuration file: %s" % config_file)
             raise
diff --git a/sfa/planetlab/vlink.py b/sfa/planetlab/vlink.py
index 625963d..b0a8300 100644
--- a/sfa/planetlab/vlink.py
+++ b/sfa/planetlab/vlink.py
@@ -41,7 +41,7 @@ def get_tc_rate(s):
     if m is None:
         return -1
     suffix = m.group(2).lower()
-    if suffixes.has_key(suffix):
+    if suffix in suffixes:
         return int(float(m.group(1)) * suffixes[suffix])
     else:
         return -1
diff --git a/sfa/rspecs/elements/versions/iotlabv1Sliver.py b/sfa/rspecs/elements/versions/iotlabv1Sliver.py
index 0f9fb01..269de56 100644
--- a/sfa/rspecs/elements/versions/iotlabv1Sliver.py
+++ b/sfa/rspecs/elements/versions/iotlabv1Sliver.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from sfa.rspecs.elements.element import Element
 from sfa.rspecs.elements.sliver import Sliver
 
@@ -50,7 +52,7 @@ class Iotlabv1Sliver:
                 sliver['type'] = sliver_elem.attrib['name']
             #sliver['images'] = Iotlabv1DiskImage.get_images(sliver_elem)
 
-            print>>sys.stderr, "\r\n \r\n SLABV1SLIVER.PY  \t\t\t  get_slivers sliver %s " %( sliver)
+            print("\r\n \r\n SLABV1SLIVER.PY  \t\t\t  get_slivers sliver %s " %( sliver), file=sys.stderr)
             slivers.append(sliver)
         return slivers
 
diff --git a/sfa/rspecs/elements/versions/ofeliav1Port.py b/sfa/rspecs/elements/versions/ofeliav1Port.py
index f4cf74d..009d551 100644
--- a/sfa/rspecs/elements/versions/ofeliav1Port.py
+++ b/sfa/rspecs/elements/versions/ofeliav1Port.py
@@ -8,7 +8,7 @@ class Ofeliav1Port:
 
     @staticmethod
     def add_portrs(xml, ports):
-        raise Exception, "not implemented yet"
+        raise Exception("not implemented yet")
         if not ports:
             return
         if not isinstance(ports, list):
@@ -22,7 +22,7 @@ class Ofeliav1Port:
 
     @staticmethod
     def add_port_attribute(xml, name, value):
-        raise Exception, "not implemented yet"
+        raise Exception("not implemented yet")
         elem = xml.add_element(name)
         elem.set_text(value)
     
diff --git a/sfa/rspecs/elements/versions/pgv2Node.py b/sfa/rspecs/elements/versions/pgv2Node.py
index 60447b0..a61749c 100644
--- a/sfa/rspecs/elements/versions/pgv2Node.py
+++ b/sfa/rspecs/elements/versions/pgv2Node.py
@@ -130,7 +130,7 @@ class PGv2Node:
             node['slivers'] = PGv2SliverType.get_slivers(node_elem)    
             
             # get boot state
-	    available_elems = node_elem.xpath('./default:available | ./available')
+            available_elems = node_elem.xpath('./default:available | ./available')
             if len(available_elems) > 0 and 'now' in available_elems[0].attrib:
                 if available_elems[0].attrib.get('now', '').lower() == 'true': 
                     node['boot_state'] = 'boot'
diff --git a/sfa/rspecs/pg_rspec_converter.py b/sfa/rspecs/pg_rspec_converter.py
index b39184a..ef021c0 100755
--- a/sfa/rspecs/pg_rspec_converter.py
+++ b/sfa/rspecs/pg_rspec_converter.py
@@ -1,10 +1,13 @@
 #!/usr/bin/python 
+from __future__ import print_function
+
 from lxml import etree
-from StringIO import StringIO
 from sfa.util.xrn import Xrn, urn_to_hrn
 from sfa.rspecs.rspec import RSpec
 from sfa.rspecs.version_manager import VersionManager
 
+from sfa.util.py23 import StringIO
+
 xslt='''<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="no"/>
 
@@ -91,4 +94,4 @@ class PGRSpecConverter:
 if __name__ == '__main__':
     import sys
     if len(sys.argv) > 1:        
-        print PGRSpecConverter.to_sfa_rspec(sys.argv[1])  
+        print(PGRSpecConverter.to_sfa_rspec(sys.argv[1]))  
diff --git a/sfa/rspecs/rspec_converter.py b/sfa/rspecs/rspec_converter.py
index 7dff2f0..5d7bdd6 100755
--- a/sfa/rspecs/rspec_converter.py
+++ b/sfa/rspecs/rspec_converter.py
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from sfa.rspecs.pg_rspec_converter import PGRSpecConverter
 from sfa.rspecs.sfa_rspec_converter import SfaRSpecConverter
 from sfa.rspecs.rspec import RSpec
@@ -39,8 +41,8 @@ if __name__ == '__main__':
     pg_rspec = 'test/protogeni.rspec'
     sfa_rspec = 'test/nodes.rspec'  
 
-    print "converting pg rspec to sfa rspec"
-    print RSpecConverter.to_sfa_rspec(pg_rspec)
+    print("converting pg rspec to sfa rspec")
+    print(RSpecConverter.to_sfa_rspec(pg_rspec))
     
-    print "converting sfa rspec to pg rspec"
-    print RSpecConverter.to_pg_rspec(sfa_rspec)                   
+    print("converting sfa rspec to pg rspec")
+    print(RSpecConverter.to_pg_rspec(sfa_rspec))                   
diff --git a/sfa/rspecs/sfa_rspec_converter.py b/sfa/rspecs/sfa_rspec_converter.py
index 7bcc787..577b788 100755
--- a/sfa/rspecs/sfa_rspec_converter.py
+++ b/sfa/rspecs/sfa_rspec_converter.py
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from sfa.util.xrn import hrn_to_urn
 from sfa.rspecs.rspec import RSpec
 from sfa.rspecs.version_manager import VersionManager
@@ -83,4 +85,4 @@ class SfaRSpecConverter:
 if __name__ == '__main__':
     import sys
     if len(sys.argv) > 1:    
-        print SfaRSpecConverter.to_pg_rspec(sys.argv[1])
+        print(SfaRSpecConverter.to_pg_rspec(sys.argv[1]))
diff --git a/sfa/rspecs/version.py b/sfa/rspecs/version.py
index 0c74283..91c96e0 100644
--- a/sfa/rspecs/version.py
+++ b/sfa/rspecs/version.py
@@ -8,7 +8,7 @@ class RSpecVersion:
     schema = None
     namespace = None
     extensions = {}
-    namespaces = dict(extensions.items() + [('default', namespace)])
+    namespaces = dict(list(extensions.items()) + [('default', namespace)])
     elements = []
     enabled = False
 
@@ -21,7 +21,7 @@ class RSpecVersion:
             'version': self.version,
             'schema': self.schema,
             'namespace': self.namespace,
-            'extensions': self.extensions.values()
+            'extensions': list(self.extensions.values()),
         }
 
     def __str__(self):
diff --git a/sfa/rspecs/version_manager.py b/sfa/rspecs/version_manager.py
index 9efabea..e0a604e 100644
--- a/sfa/rspecs/version_manager.py
+++ b/sfa/rspecs/version_manager.py
@@ -1,7 +1,10 @@
+from __future__ import print_function
+
 import os
 from sfa.util.faults import InvalidRSpec, UnsupportedRSpecVersion
 from sfa.rspecs.version import RSpecVersion 
 from sfa.util.sfalogging import logger    
+from sfa.util.py23 import StringType
 
 class VersionManager:
     def __init__(self):
@@ -47,7 +50,7 @@ class VersionManager:
         retval = None
         if isinstance(version, dict):
             retval =  self._get_version(version.get('type'), version.get('version'), version.get('content_type'))
-        elif isinstance(version, basestring):
+        elif isinstance(version, StringType):
             version_parts = version.split(' ')     
             num_parts = len(version_parts)
             type = version_parts[0]
@@ -78,19 +81,19 @@ class VersionManager:
 
     def show_by_string(self, string):
         try:
-            print self.get_version(string)
+            print(self.get_version(string))
         except Exception as e:
-            print e
+            print(e)
 
     def show_by_schema(self, string):
         try:
-            print self.get_version_by_schema(string)
+            print(self.get_version_by_schema(string))
         except Exception as e:
-            print e
+            print(e)
 
 if __name__ == '__main__':
     manager = VersionManager()
-    print manager
+    print(manager)
     manager.show_by_string('sfa 1') 
     manager.show_by_string('protogeni 2') 
     manager.show_by_string('protogeni 2 advertisement') 
diff --git a/sfa/rspecs/versions/iotlabv1.py b/sfa/rspecs/versions/iotlabv1.py
index ad49157..0d39323 100644
--- a/sfa/rspecs/versions/iotlabv1.py
+++ b/sfa/rspecs/versions/iotlabv1.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from copy import deepcopy
 
 
@@ -30,7 +32,7 @@ class Iotlabv1(RSpecVersion):
         'flack': "http://www.protogeni.net/resources/rspec/ext/flack/1",
         'planetlab': "http://www.planet-lab.org/resources/sfa/ext/planetlab/1",
     }
-    namespaces = dict(extensions.items() + [('default', namespace)])
+    namespaces = dict(list(extensions.items()) + [('default', namespace)])
     elements = []
 
     # Network
@@ -78,10 +80,10 @@ class Iotlabv1(RSpecVersion):
     # Slivers
 
     def get_sliver_attributes(self, hostname, node, network=None):
-        print>>sys.stderr, "\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes hostname %s " %(hostname)
+        print("\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes hostname %s " %(hostname), file=sys.stderr)
         nodes = self.get_nodes({'component_id': '*%s*' %hostname})
         attribs = []
-        print>>sys.stderr, "\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes-----------------nodes %s  " %(nodes)
+        print("\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes-----------------nodes %s  " %(nodes), file=sys.stderr)
         if nodes is not None and isinstance(nodes, list) and len(nodes) > 0:
             node = nodes[0]
         #if node :
@@ -93,7 +95,7 @@ class Iotlabv1(RSpecVersion):
                 sliver = sliver[0]
                 attribs = sliver
                 #attribs = self.attributes_list(sliver)
-                print>>sys.stderr, "\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes----------NN------- sliver %s self.namespaces %s attribs %s " %(sliver, self.namespaces,attribs)
+                print("\r\n \r\n \r\n \t\t SLABV1.PY  get_sliver_attributes----------NN------- sliver %s self.namespaces %s attribs %s " %(sliver, self.namespaces,attribs), file=sys.stderr)
         return attribs
 
     def get_slice_attributes(self, network=None):
@@ -143,7 +145,7 @@ class Iotlabv1(RSpecVersion):
         if attributes is None: attributes=[]
         # all nodes hould already be present in the rspec. Remove all
         # nodes that done have slivers
-        print>>sys.stderr, "\r\n \r\n \r\n \t\t\t Iotlabv1.PY add_slivers  ----->get_node "
+        print("\r\n \r\n \r\n \t\t\t Iotlabv1.PY add_slivers  ----->get_node ", file=sys.stderr)
         for hostname in hostnames:
             node_elems = self.get_nodes({'component_id': '*%s*' % hostname})
             if not node_elems:
@@ -163,7 +165,7 @@ class Iotlabv1(RSpecVersion):
                 continue
             sliver = {'type': requested_sliver_type,
                      'pl_tags': attributes}
-            print>>sys.stderr, "\r\n \r\n \r\n \t\t\t Iotlabv1.PY add_slivers  node_elem %s sliver_type %s \r\n \r\n " %(node_elem, sliver_type)
+            print("\r\n \r\n \r\n \t\t\t Iotlabv1.PY add_slivers  node_elem %s sliver_type %s \r\n \r\n " %(node_elem, sliver_type), file=sys.stderr)
             # remove available element
             for available_elem in node_elem.xpath('./default:available | ./available'):
                 node_elem.remove(available_elem)
@@ -303,4 +305,4 @@ if __name__ == '__main__':
     r = RSpec('/tmp/iotlab.rspec')
     r.load_rspec_elements(Iotlabv1.elements)
     r.namespaces = Iotlabv1.namespaces
-    print r.get(RSpecElements.NODE)
+    print(r.get(RSpecElements.NODE))
diff --git a/sfa/rspecs/versions/nitosv1.py b/sfa/rspecs/versions/nitosv1.py
index 3288b48..60caf1d 100644
--- a/sfa/rspecs/versions/nitosv1.py
+++ b/sfa/rspecs/versions/nitosv1.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from copy import deepcopy
 from lxml import etree
 
@@ -237,4 +239,4 @@ if __name__ == '__main__':
     from sfa.rspecs.rspec_elements import *
     r = RSpec('/tmp/resources.rspec')
     r.load_rspec_elements(SFAv1.elements)
-    print r.get(RSpecElements.NODE)
+    print(r.get(RSpecElements.NODE))
diff --git a/sfa/rspecs/versions/ofeliav1.py b/sfa/rspecs/versions/ofeliav1.py
index d074694..0a001cb 100755
--- a/sfa/rspecs/versions/ofeliav1.py
+++ b/sfa/rspecs/versions/ofeliav1.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
+from __future__ import print_function
+
 from copy import deepcopy
 from lxml import etree
 
@@ -22,13 +24,13 @@ class Ofelia(RSpecVersion):
     schema = 'https://github.com/fp7-ofelia/ocf/blob/ocf.rspecs/openflow/schemas/ad.xsd'
     namespace = 'openflow'
     extensions = {}
-    namespaces = dict(extensions.items() + [('default', namespace)])
+    namespaces = dict(list(extensions.items()) + [('default', namespace)])
     #template = '<RSpec type="%s"></RSpec>' % type
     template = '<rspec></rspec>'
 
     # Network 
     def get_networks(self):
-        raise Exception, "Not implemented"
+        raise Exception("Not implemented")
         network_elems = self.xml.xpath('//network')
         networks = [network_elem.get_instance(fields=['name', 'slice']) for \
                     network_elem in network_elems]
@@ -36,7 +38,7 @@ class Ofelia(RSpecVersion):
 
 
     def add_network(self, network):
-        raise Exception, "Not implemented"
+        raise Exception("Not implemented")
         network_tags = self.xml.xpath('//network[@name="%s"]' % network)
         if not network_tags:
             network_tag = self.xml.add_element('network', name=network)
@@ -231,7 +233,7 @@ if __name__ == '__main__':
     import pprint
     from sfa.rspecs.rspec import RSpec
     from sfa.rspecs.rspec_elements import *
-    print "main ofeliav1"
+    print("main ofeliav1")
     if len(sys.argv)!=2:
         r = RSpec('/tmp/resources.rspec')
     else:
diff --git a/sfa/rspecs/versions/pgv2.py b/sfa/rspecs/versions/pgv2.py
index 01a3155..3e25a37 100644
--- a/sfa/rspecs/versions/pgv2.py
+++ b/sfa/rspecs/versions/pgv2.py
@@ -1,5 +1,6 @@
+from __future__ import print_function
+
 from copy import deepcopy
-from StringIO import StringIO
 from sfa.util.xrn import Xrn
 from sfa.rspecs.version import RSpecVersion
 from sfa.rspecs.elements.versions.pgv2Link import PGv2Link
@@ -7,6 +8,7 @@ from sfa.rspecs.elements.versions.pgv2Node import PGv2Node
 from sfa.rspecs.elements.versions.pgv2SliverType import PGv2SliverType
 from sfa.rspecs.elements.versions.pgv2Lease import PGv2Lease
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringType
 
 class PGv2(RSpecVersion):
     type = 'ProtoGENI'
@@ -19,7 +21,7 @@ class PGv2(RSpecVersion):
         'planetlab': "http://www.planet-lab.org/resources/sfa/ext/planetlab/1",
         'plos': "http://www.planet-lab.org/resources/sfa/ext/plos/1",
     }
-    namespaces = dict(extensions.items() + [('default', namespace)])
+    namespaces = dict(list(extensions.items()) + [('default', namespace)])
 
     # Networks
     def get_networks(self):
@@ -203,7 +205,7 @@ class PGv2(RSpecVersion):
         """
         from sfa.rspecs.rspec import RSpec
         # just copy over all the child elements under the root element
-        if isinstance(in_rspec, basestring):
+        if isinstance(in_rspec, StringType):
             in_rspec = RSpec(in_rspec)
 
         nodes = in_rspec.version.get_nodes()
@@ -213,7 +215,7 @@ class PGv2(RSpecVersion):
             if not node['component_name']:
                 # this node element is part of a lease
                 continue
-            if not node.has_key('sliver') or not node['sliver']:
+            if 'sliver' not in node or not node['sliver']:
                 node['sliver'] = {'name': 'plab-vserver'}
                 main_nodes.append(node)
         self.add_nodes(main_nodes)
@@ -262,4 +264,4 @@ if __name__ == '__main__':
     r = RSpec('/tmp/pg.rspec')
     r.load_rspec_elements(PGv2.elements)
     r.namespaces = PGv2.namespaces
-    print r.get(RSpecElements.NODE)
+    print(r.get(RSpecElements.NODE))
diff --git a/sfa/rspecs/versions/pgv3.py b/sfa/rspecs/versions/pgv3.py
index 6d6a449..eb15b82 100644
--- a/sfa/rspecs/versions/pgv3.py
+++ b/sfa/rspecs/versions/pgv3.py
@@ -11,7 +11,7 @@ class GENIv3(PGv2):
         'planetlab': "http://www.planet-lab.org/resources/sfa/ext/planetlab/1",
         'plos': "http://www.planet-lab.org/resources/sfa/ext/plos/1",
     }
-    namespaces = dict(extensions.items() + [('default', namespace)])
+    namespaces = dict(list(extensions.items()) + [('default', namespace)])
     elements = []
 
 
diff --git a/sfa/rspecs/versions/sfav1.py b/sfa/rspecs/versions/sfav1.py
index 6e973e7..645a660 100644
--- a/sfa/rspecs/versions/sfav1.py
+++ b/sfa/rspecs/versions/sfav1.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from copy import deepcopy
 from lxml import etree
 
@@ -239,4 +241,4 @@ if __name__ == '__main__':
     from sfa.rspecs.rspec_elements import *
     r = RSpec('/tmp/resources.rspec')
     r.load_rspec_elements(SFAv1.elements)
-    print r.get(RSpecElements.NODE)
+    print(r.get(RSpecElements.NODE))
diff --git a/sfa/server/modpython/SfaAggregateModPython.py b/sfa/server/modpython/SfaAggregateModPython.py
index 50f9f5f..5d38a55 100755
--- a/sfa/server/modpython/SfaAggregateModPython.py
+++ b/sfa/server/modpython/SfaAggregateModPython.py
@@ -50,7 +50,7 @@ def handler(req):
 
         return apache.OK
 
-    except Exception, err:
+    except Exception as err:
         # Log error in /var/log/httpd/(ssl_)?error_log
         logger.log_exc('%r'%err)
         return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpython/SfaRegistryModPython.py b/sfa/server/modpython/SfaRegistryModPython.py
index 31d0812..6d17cd2 100755
--- a/sfa/server/modpython/SfaRegistryModPython.py
+++ b/sfa/server/modpython/SfaRegistryModPython.py
@@ -50,7 +50,7 @@ def handler(req):
 
         return apache.OK
 
-    except Exception, err:
+    except Exception as err:
         # Log error in /var/log/httpd/(ssl_)?error_log
         logger.log_exc('%r'%err)
         return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpython/SfaSliceMgrModPython.py b/sfa/server/modpython/SfaSliceMgrModPython.py
index 61cb161..dcb8562 100755
--- a/sfa/server/modpython/SfaSliceMgrModPython.py
+++ b/sfa/server/modpython/SfaSliceMgrModPython.py
@@ -50,7 +50,7 @@ def handler(req):
 
         return apache.OK
 
-    except Exception, err:
+    except Exception as err:
         # Log error in /var/log/httpd/(ssl_)?error_log
         logger.log_exc('%r'%err)
         return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/sfa_component_setup.py b/sfa/server/sfa_component_setup.py
index f75f1ca..e35a40a 100755
--- a/sfa/server/sfa_component_setup.py
+++ b/sfa/server/sfa_component_setup.py
@@ -1,4 +1,6 @@
 #!/usr/bin/python
+from __future__ import print_function
+
 import sys
 import os
 import tempfile
@@ -24,7 +26,7 @@ def handle_gid_mismatch_exception(f):
         try: return f(*args, **kwds)
         except ConnectionKeyGIDMismatch:
             # clean regen server keypair and try again
-            print "cleaning keys and trying again"
+            print("cleaning keys and trying again")
             clean_key_cred()
             return f(args, kwds)
 
@@ -48,7 +50,7 @@ def server_proxy(url=None, port=None, keyfile=None, certfile=None,verbose=False)
         url = "http://%(addr)s:%(port)s" % locals()
 
     if verbose:
-        print "Contacting registry at: %(url)s" % locals()
+        print("Contacting registry at: %(url)s" % locals())
 
     server = SfaServerProxy(url, keyfile, certfile)
     return server    
@@ -125,11 +127,11 @@ def GetCredential(registry=None, force=False, verbose=False):
     # check for existing credential
     if not force and os.path.exists(credfile):
         if verbose:
-            print "Loading Credential from %(credfile)s " % locals()  
+            print("Loading Credential from %(credfile)s " % locals())  
         cred = Credential(filename=credfile).save_to_string(save_parents=True)
     else:
         if verbose:
-            print "Getting credential from registry" 
+            print("Getting credential from registry") 
         # make sure node private key exists
         node_pkey_file = config_dir + os.sep + "node.key"
         node_gid_file = config_dir + os.sep + "node.gid"
@@ -177,7 +179,7 @@ def get_trusted_certs(registry=None, verbose=False):
     registry = server_proxy(url=registry, keyfile=keyfile, certfile=certfile)
     # get the trusted certs and save them in the right place
     if verbose:
-        print "Getting trusted certs from registry"
+        print("Getting trusted certs from registry")
     trusted_certs = registry.get_trusted_certs(cred)
     trusted_gid_names = [] 
     for gid_str in trusted_certs:
@@ -187,7 +189,7 @@ def get_trusted_certs(registry=None, verbose=False):
         trusted_gid_names.append(relative_filename)
         gid_filename = trusted_certs_dir + os.sep + relative_filename
         if verbose:
-            print "Writing GID for %s as %s" % (gid.get_hrn(), gid_filename) 
+            print("Writing GID for %s as %s" % (gid.get_hrn(), gid_filename)) 
         gid.save_to_file(gid_filename, save_parents=True)
 
     # remove old certs
@@ -195,7 +197,7 @@ def get_trusted_certs(registry=None, verbose=False):
     for gid_name in all_gids_names:
         if gid_name not in trusted_gid_names:
             if verbose:
-                print "Removing old gid ", gid_name
+                print("Removing old gid ", gid_name)
             os.unlink(trusted_certs_dir + os.sep + gid_name)                     
 
 @handle_gid_mismatch_exception
@@ -222,7 +224,7 @@ def get_gids(registry=None, verbose=False):
     registry = server_proxy(url=registry, keyfile=keyfile, certfile=certfile)
             
     if verbose:
-        print "Getting current slices on this node"
+        print("Getting current slices on this node")
     # get a list of slices on this node
     from sfa.generic import Generic
     generic=Generic.the_flavour()
@@ -247,7 +249,7 @@ def get_gids(registry=None, verbose=False):
         return
         
     if verbose:
-        print "Getting gids for slices on this node from registry"  
+        print("Getting gids for slices on this node from registry")  
     # get the gids
     # and save them in the right palce
     records = registry.GetGids(hrns, cred)
@@ -265,12 +267,12 @@ def get_gids(registry=None, verbose=False):
         gid = record['gid']
         slice_gid_filename = os.sep.join([vserver_path, "etc", "slice.gid"])
         if verbose:
-            print "Saving GID for %(slicename)s as %(slice_gid_filename)s" % locals()
+            print("Saving GID for %(slicename)s as %(slice_gid_filename)s" % locals())
         GID(string=gid).save_to_file(slice_gid_filename, save_parents=True)
         # save the node gid in /etc/sfa
         node_gid_filename = os.sep.join([vserver_path, "etc", "node.gid"])
         if verbose:
-            print "Saving node GID for %(slicename)s as %(node_gid_filename)s" % locals()
+            print("Saving node GID for %(slicename)s as %(node_gid_filename)s" % locals())
         node_gid.save_to_file(node_gid_filename, save_parents=True) 
                 
 
@@ -279,15 +281,15 @@ def dispatch(options, args):
     create_default_dirs()
     if options.key:
         if options.verbose:
-            print "Getting the component's pkey"
+            print("Getting the component's pkey")
         get_node_key(registry=options.registry, verbose=options.verbose)
     if options.certs:
         if options.verbose:
-            print "Getting the component's trusted certs"
+            print("Getting the component's trusted certs")
         get_trusted_certs(verbose=options.verbose)
     if options.gids:        
         if options.verbose:
-            print "Geting the component's GIDs"
+            print("Geting the component's GIDs")
         get_gids(verbose=options.verbose)
 
 def main():
diff --git a/sfa/server/sfaapi.py b/sfa/server/sfaapi.py
index 9911e46..499e3bd 100644
--- a/sfa/server/sfaapi.py
+++ b/sfa/server/sfaapi.py
@@ -19,7 +19,7 @@ from sfa.storage.alchemy import alchemy
 ####################
 class SfaApi (XmlrpcApi): 
     """
-    An SfaApi instance is a basic xmlrcp service
+    An SfaApi instance is a basic xmlrpc service
     augmented with the local cryptographic material and hrn
 
     It also has the notion of its own interface (a string describing
diff --git a/sfa/server/threadedserver.py b/sfa/server/threadedserver.py
index 7dfac7d..7a26ad2 100644
--- a/sfa/server/threadedserver.py
+++ b/sfa/server/threadedserver.py
@@ -11,7 +11,6 @@ import socket
 import traceback
 import threading
 from Queue import Queue
-import xmlrpclib
 import SocketServer
 import BaseHTTPServer
 import SimpleXMLRPCServer
@@ -22,6 +21,7 @@ from sfa.util.config import Config
 from sfa.util.cache import Cache 
 from sfa.trust.certificate import Certificate
 from sfa.trust.trustedroots import TrustedRoots
+from sfa.util.py23 import xmlrpc_client
 
 # don't hard code an api class anymore here
 from sfa.generic import Generic
@@ -119,7 +119,7 @@ class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
             remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
             self.api.remote_addr = remote_addr            
             response = self.api.handle(remote_addr, request, self.server.method_map)
-        except Exception, fault:
+        except Exception as fault:
             # This should only happen if the module is buggy
             # internal error, report as HTTP server error
             logger.log_exc("server.do_POST")
@@ -195,7 +195,7 @@ class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLR
             # can't use format_exc() as it is not available in jython yet
             # (even in trunk).
             type, value, tb = sys.exc_info()
-            raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
+            raise xmlrpc_client.Fault(1,''.join(traceback.format_exception(type, value, tb)))
 
     # override this one from the python 2.7 code
     # originally defined in class TCPServer
diff --git a/sfa/server/xmlrpcapi.py b/sfa/server/xmlrpcapi.py
index b6f8e95..74e0026 100644
--- a/sfa/server/xmlrpcapi.py
+++ b/sfa/server/xmlrpcapi.py
@@ -3,7 +3,6 @@
 #
 
 import string
-import xmlrpclib
 
 # SOAP support is optional
 try:
@@ -19,6 +18,7 @@ except ImportError:
 #from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault
 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import xmlrpc_client
 
 ####################
 # See "2.2 Characters" in the XML specification:
@@ -71,12 +71,14 @@ def xmlrpclib_dump(self, value, write):
             if isinstance(value, Type):
                 f(*args)
                 return
-        raise TypeError, "cannot marshal %s objects" % type(value)
+        raise TypeError("cannot marshal %s objects" % type(value))
     else:
         f(*args)
 
 # You can't hide from me!
-xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
+# Note: not quite  sure if this will still cause
+# the expected behaviour under python3
+xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
 
 class XmlrpcApi:
     """
@@ -102,7 +104,7 @@ class XmlrpcApi:
         """
         # Look up method
         if method not in self.methods:
-            raise SfaInvalidAPIMethod, method
+            raise SfaInvalidAPIMethod(method)
         
         # Get new instance of method
         try:
@@ -112,7 +114,7 @@ class XmlrpcApi:
             return getattr(module, classname)(self)
         except (ImportError, AttributeError):
             self.logger.log_exc("Error importing method: %s" % method)
-            raise SfaInvalidAPIMethod, method
+            raise SfaInvalidAPIMethod(method)
 
     def call(self, source, method, *args):
         """
@@ -131,14 +133,14 @@ class XmlrpcApi:
         """
         # Parse request into method name and arguments
         try:
-            interface = xmlrpclib
-            self.protocol = 'xmlrpclib'
-            (args, method) = xmlrpclib.loads(data)
-            if method_map.has_key(method):
+            interface = xmlrpc_client
+            self.protocol = 'xmlrpc'
+            (args, method) = xmlrpc_client.loads(data)
+            if method in method_map:
                 method = method_map[method]
             methodresponse = True
             
-        except Exception, e:
+        except Exception as e:
             if SOAPpy is not None:
                 self.protocol = 'soap'
                 interface = SOAPpy
@@ -151,10 +153,10 @@ class XmlrpcApi:
 
         try:
             result = self.call(source, method, *args)
-        except SfaFault, fault:
+        except SfaFault as fault:
             result = fault
             self.logger.log_exc("XmlrpcApi.handle has caught Exception") 
-        except Exception, fault:
+        except Exception as fault:
             self.logger.log_exc("XmlrpcApi.handle has caught Exception")
             result = SfaAPIError(fault)
 
@@ -168,10 +170,10 @@ class XmlrpcApi:
         convert result to a valid xmlrpc or soap response
         """   
  
-        if self.protocol == 'xmlrpclib':
+        if self.protocol == 'xmlrpc':
             if not isinstance(result, SfaFault):
                 result = (result,)
-            response = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
+            response = xmlrpc_client.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
         elif self.protocol == 'soap':
             if isinstance(result, Exception):
                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
diff --git a/sfa/storage/alchemy.py b/sfa/storage/alchemy.py
index f052fba..64c39cf 100644
--- a/sfa/storage/alchemy.py
+++ b/sfa/storage/alchemy.py
@@ -1,5 +1,3 @@
-from types import StringTypes
-
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker
 
@@ -39,7 +37,7 @@ class Alchemy:
             except:
                 pass
         self.engine=None
-        raise Exception,"Could not connect to database %s as %s with psycopg2"%(dbname,config.SFA_DB_USER)
+        raise Exception("Could not connect to database %s as %s with psycopg2"%(dbname,config.SFA_DB_USER))
 
 
     # expects boolean True: debug is ON or False: debug is OFF
diff --git a/sfa/storage/dbschema.py b/sfa/storage/dbschema.py
index 4c99fb2..5166b93 100644
--- a/sfa/storage/dbschema.py
+++ b/sfa/storage/dbschema.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import sys
 import traceback
 
@@ -62,10 +64,10 @@ class DBSchema:
 
     def drop_table (self, tablename):
         if self.table_exists (tablename):
-            print >>sys.stderr, "%s: Dropping table %s"%(DBSchema.header,tablename)
+            print("%s: Dropping table %s"%(DBSchema.header,tablename), file=sys.stderr)
             self.engine.execute ("drop table %s cascade"%tablename)
         else:
-            print >>sys.stderr, "%s: no need to drop table %s"%(DBSchema.header,tablename)
+            print("%s: no need to drop table %s"%(DBSchema.header,tablename), file=sys.stderr)
         
     def handle_old_releases (self):
         try:
@@ -75,7 +77,7 @@ class DBSchema:
                 # (.) we've just created the db, so it's either a fresh install, or
                 # (.) we come from a 'very old' depl.
                 # in either case, an import is required but there's nothing to clean up
-                print >> sys.stderr,"%s: make sure to run import"%(DBSchema.header,)
+                print("%s: make sure to run import"%(DBSchema.header,), file=sys.stderr)
             elif self.table_exists ('sfa_db_version'):
                 # we come from an 'old' version
                 self.drop_table ('records')
@@ -85,7 +87,7 @@ class DBSchema:
                 # we should be good here
                 pass
         except:
-            print >> sys.stderr, "%s: unknown exception"%(DBSchema.header,)
+            print("%s: unknown exception"%(DBSchema.header,), file=sys.stderr)
             traceback.print_exc ()
 
     # after this call the db schema and the version as known by migrate should 
diff --git a/sfa/storage/model.py b/sfa/storage/model.py
index 051ba87..923576e 100644
--- a/sfa/storage/model.py
+++ b/sfa/storage/model.py
@@ -1,4 +1,3 @@
-from types import StringTypes
 from datetime import datetime
 
 from sqlalchemy import or_, and_ 
@@ -14,6 +13,7 @@ from sfa.storage.record import Record
 from sfa.util.sfalogging import logger
 from sfa.util.sfatime import utcparse, datetime_to_string
 from sfa.util.xml import XML 
+from sfa.util.py23 import StringType
 
 from sfa.trust.gid import GID
 
@@ -99,7 +99,7 @@ class RegRecord(Base, AlchemyObj):
         if type:                                self.type=type
         if hrn:                                 self.hrn=hrn
         if gid: 
-            if isinstance(gid, StringTypes):    self.gid=gid
+            if isinstance(gid, StringType):     self.gid=gid
             else:                               self.gid=gid.save_to_string(save_parents=True)
         if authority:                           self.authority=authority
         if peer_authority:                      self.peer_authority=peer_authority
@@ -130,7 +130,7 @@ class RegRecord(Base, AlchemyObj):
     @validates ('gid')
     def validate_gid (self, key, gid):
         if gid is None:                     return
-        elif isinstance(gid, StringTypes):  return gid
+        elif isinstance(gid, StringType):   return gid
         else:                               return gid.save_to_string(save_parents=True)
 
     def validate_datetime (self, key, incoming):
diff --git a/sfa/storage/parameter.py b/sfa/storage/parameter.py
index e520bfc..dc9d5b5 100644
--- a/sfa/storage/parameter.py
+++ b/sfa/storage/parameter.py
@@ -5,9 +5,11 @@
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 
-from types import NoneType, IntType, LongType, FloatType, StringTypes, DictType, TupleType, ListType
+from types import NoneType, IntType, LongType, FloatType, DictType, TupleType, ListType
 from sfa.util.faults import SfaAPIError
 
+from sfa.util.py23 import StringType
+
 class Parameter:
     """
     Typed value wrapper. Use in accepts and returns to document method
@@ -89,7 +91,7 @@ def xmlrpc_type(arg):
         return "boolean"
     elif arg_type == FloatType:
         return "double"
-    elif arg_type in StringTypes:
+    elif issubclass(arg_type, StringType):
         return "string"
     elif arg_type == ListType or arg_type == TupleType:
         return "array"
@@ -100,4 +102,4 @@ def xmlrpc_type(arg):
         # documentation purposes.
         return "mixed"
     else:
-        raise SfaAPIError, "XML-RPC cannot marshal %s objects" % arg_type
+        raise SfaAPIError("XML-RPC cannot marshal %s objects" % arg_type)
diff --git a/sfa/storage/record.py b/sfa/storage/record.py
index 8600723..a03ce30 100644
--- a/sfa/storage/record.py
+++ b/sfa/storage/record.py
@@ -1,10 +1,12 @@
+from __future__ import print_function
+
 from sfa.util.sfatime import utcparse, datetime_to_string
-from types import StringTypes
 from datetime import datetime
 from sfa.util.xml import XML
 from sfa.trust.gid import GID
 
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringType
 
 class Record:
 
@@ -54,9 +56,9 @@ class Record:
     def load_from_dict (self, d):
         for (k,v) in d.iteritems():
             # experimental
-            if isinstance(v, StringTypes) and v.lower() in ['true']:
+            if isinstance(v, StringType) and v.lower() in ['true']:
                 v = True
-            if isinstance(v, StringTypes) and v.lower() in ['false']:
+            if isinstance(v, StringType) and v.lower() in ['false']:
                 v = False
             setattr(self, k, v)
 
@@ -82,15 +84,15 @@ class Record:
         if format == 'text':
             self.dump_text(dump_parents,sort=sort)
         elif format == 'xml':
-            print self.save_as_xml()
+            print(self.save_as_xml())
         elif format == 'simple':
-            print self.dump_simple()
+            print(self.dump_simple())
         else:
-            raise Exception, "Invalid format %s" % format
+            raise Exception("Invalid format %s" % format)
 
     def dump_text(self, dump_parents=False, sort=False):
-        print 40*'='
-        print "RECORD"
+        print(40*'=')
+        print("RECORD")
         # print remaining fields
         fields = self.fields()
         if sort: fields.sort()
@@ -102,12 +104,12 @@ class Record:
             if callable (attrib):               continue
             # handle gid 
             if attrib_name == 'gid':
-                print "    gid:"      
-                print GID(string=attrib).dump_string(8, dump_parents)
+                print("    gid:")      
+                print(GID(string=attrib).dump_string(8, dump_parents))
             elif attrib_name in ['date created', 'last updated']:
-                print "    %s: %s" % (attrib_name, self.date_repr(attrib_name))
+                print("    %s: %s" % (attrib_name, self.date_repr(attrib_name)))
             else:
-                print "    %s: %s" % (attrib_name, attrib)
+                print("    %s: %s" % (attrib_name, attrib))
 
     def dump_simple(self):
         return "%s"%self    
diff --git a/sfa/trust/abac_credential.py b/sfa/trust/abac_credential.py
index f454d18..cb6e686 100644
--- a/sfa/trust/abac_credential.py
+++ b/sfa/trust/abac_credential.py
@@ -21,13 +21,16 @@
 # IN THE WORK.
 #----------------------------------------------------------------------
 
+from __future__ import print_function
+
 from sfa.trust.credential import Credential, append_sub, DEFAULT_CREDENTIAL_LIFETIME
 from sfa.util.sfalogging import logger
 from sfa.util.sfatime import SFATIME_FORMAT
 
-from StringIO import StringIO
 from xml.dom.minidom import Document, parseString
 
+from sfa.util.py23 import StringIO
+
 HAVELXML = False
 try:
     from lxml import etree
@@ -179,7 +182,7 @@ class ABACCredential(Credential):
                 result += "\nEnd XML\n"
             except:
                 import traceback
-                print "exc. Credential.dump_string / XML"
+                print("exc. Credential.dump_string / XML")
                 traceback.print_exc()
         return result
 
diff --git a/sfa/trust/auth.py b/sfa/trust/auth.py
index 86d5d4f..512c58b 100644
--- a/sfa/trust/auth.py
+++ b/sfa/trust/auth.py
@@ -2,13 +2,13 @@
 # SfaAPI authentication 
 #
 import sys
-from types import StringTypes
 
 from sfa.util.faults import InsufficientRights, MissingCallerGID, \
     MissingTrustedRoots, PermissionError, BadRequestHash, \
     ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, \
     Forbidden, BadArgs
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringType
 from sfa.util.config import Config
 from sfa.util.xrn import Xrn, get_authority
 
@@ -62,7 +62,7 @@ class Auth:
         if xrns is None: xrns = []
         error = (None, None)
         def log_invalid_cred(cred):
-            if not isinstance (cred, StringTypes):
+            if not isinstance (cred, StringType):
                 logger.info("cannot validate credential %s - expecting a string"%cred)
                 error = ('TypeMismatch',
                          "checkCredentials: expected a string, received {} -- {}"
diff --git a/sfa/trust/certificate.py b/sfa/trust/certificate.py
index ff6534e..a30d73a 100644
--- a/sfa/trust/certificate.py
+++ b/sfa/trust/certificate.py
@@ -35,15 +35,19 @@
 ##
 #
 
+from __future__ import print_function
+
 import functools
 import os
 import tempfile
 import base64
 from tempfile import mkstemp
 
-from OpenSSL import crypto
-import M2Crypto
-from M2Crypto import X509
+import OpenSSL
+# M2Crypto is imported on the fly to minimize crashes
+#import M2Crypto
+
+from sfa.util.py23 import PY3
 
 from sfa.util.faults import CertExpired, CertMissingParent, CertNotSignedByParent
 from sfa.util.sfalogging import logger
@@ -80,7 +84,7 @@ def set_passphrase(passphrase):
 
 def test_passphrase(string, passphrase):
     try:
-        crypto.load_privatekey(crypto.FILETYPE_PEM, string, (lambda x: passphrase))
+        OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, string, (lambda x: passphrase))
         return True
     except:
         return False
@@ -88,11 +92,11 @@ def test_passphrase(string, passphrase):
 def convert_public_key(key):
     keyconvert_path = "/usr/bin/keyconvert.py"
     if not os.path.isfile(keyconvert_path):
-        raise IOError, "Could not find keyconvert in %s" % keyconvert_path
+        raise IOError("Could not find keyconvert in {}".format(keyconvert_path))
 
     # we can only convert rsa keys
     if "ssh-dss" in key:
-        raise Exception, "keyconvert: dss keys are not supported"
+        raise Exception("keyconvert: dss keys are not supported")
 
     (ssh_f, ssh_fn) = tempfile.mkstemp()
     ssl_fn = tempfile.mktemp()
@@ -106,7 +110,7 @@ def convert_public_key(key):
     # that it can be expected to see why it failed.
     # TODO: for production, cleanup the temporary files
     if not os.path.exists(ssl_fn):
-        raise Exception, "keyconvert: generated certificate not found. keyconvert may have failed."
+        raise Exception("keyconvert: generated certificate not found. keyconvert may have failed.")
 
     k = Keypair()
     try:
@@ -135,8 +139,8 @@ class Keypair:
     # Creates a Keypair object
     # @param create If create==True, creates a new public/private key and
     #     stores it in the object
-    # @param string If string!=None, load the keypair from the string (PEM)
-    # @param filename If filename!=None, load the keypair from the file
+    # @param string If string != None, load the keypair from the string (PEM)
+    # @param filename If filename != None, load the keypair from the file
 
     def __init__(self, create=False, string=None, filename=None):
         if create:
@@ -150,8 +154,8 @@ class Keypair:
     # Create a RSA public/private key pair and store it inside the keypair object
 
     def create(self):
-        self.key = crypto.PKey()
-        self.key.generate_key(crypto.TYPE_RSA, 2048)
+        self.key = OpenSSL.crypto.PKey()
+        self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
 
     ##
     # Save the private key to a file
@@ -159,13 +163,13 @@ class Keypair:
 
     def save_to_file(self, filename):
         open(filename, 'w').write(self.as_pem())
-        self.filename=filename
+        self.filename = filename
 
     ##
     # Load the private key from a file. Implicity the private key includes the public key.
 
     def load_from_file(self, filename):
-        self.filename=filename
+        self.filename = filename
         buffer = open(filename, 'r').read()
         self.load_from_string(buffer)
 
@@ -173,19 +177,21 @@ class Keypair:
     # Load the private key from a string. Implicitly the private key includes the public key.
 
     def load_from_string(self, string):
+        import M2Crypto
         if glo_passphrase_callback:
-            self.key = crypto.load_privatekey(
-                crypto.FILETYPE_PEM, string, functools.partial(glo_passphrase_callback, self, string))
+            self.key = OpenSSL.crypto.load_privatekey(
+                OpenSSL.crypto.FILETYPE_PEM, string, functools.partial(glo_passphrase_callback, self, string))
             self.m2key = M2Crypto.EVP.load_key_string(
                 string, functools.partial(glo_passphrase_callback, self, string))
         else:
-            self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
+            self.key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, string)
             self.m2key = M2Crypto.EVP.load_key_string(string)
 
     ##
     #  Load the public key from a string. No private key is loaded.
 
     def load_pubkey_from_file(self, filename):
+        import M2Crypto
         # load the m2 public key
         m2rsakey = M2Crypto.RSA.load_pub_key(filename)
         self.m2key = M2Crypto.EVP.PKey()
@@ -211,11 +217,11 @@ class Keypair:
 
         # convert the m2 x509 cert to a pyopenssl x509
         m2pem = m2x509.as_pem()
-        pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem)
+        pyx509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, m2pem)
 
         # get the pyopenssl pkey from the pyopenssl x509
         self.key = pyx509.get_pubkey()
-        self.filename=filename
+        self.filename = filename
 
     ##
     # Load the public key from a string. No private key is loaded.
@@ -231,12 +237,13 @@ class Keypair:
     # Return the private key in PEM format.
 
     def as_pem(self):
-        return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key)
+        return OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, self.key)
 
     ##
     # Return an M2Crypto key object
 
     def get_m2_pubkey(self):
+        import M2Crypto
         if not self.m2key:
             self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
         return self.m2key
@@ -267,6 +274,7 @@ class Keypair:
         return base64.b64encode(k.sign_final())
 
     def verify_string(self, data, sig):
+        import M2Crypto
         k = self.get_m2_pubkey()
         k.verify_init()
         k.verify_update(data)
@@ -279,14 +287,14 @@ class Keypair:
     def get_filename(self):
         return getattr(self,'filename',None)
 
-    def dump (self, *args, **kwargs):
-        print self.dump_string(*args, **kwargs)
+    def dump(self, *args, **kwargs):
+        print(self.dump_string(*args, **kwargs))
 
-    def dump_string (self):
-        result=""
-        result += "KEYPAIR: pubkey=%40s..."%self.get_pubkey_string()
-        filename=self.get_filename()
-        if filename: result += "Filename %s\n"%filename
+    def dump_string(self):
+        result =  ""
+        result += "KEYPAIR: pubkey={:>40}...".format(self.get_pubkey_string())
+        filename = self.get_filename()
+        if filename: result += "Filename {}\n".format(filename)
         return result
 
 ##
@@ -310,7 +318,7 @@ class Certificate:
 #    parent = None
     isCA = None # will be a boolean once set
 
-    separator="-----parent-----"
+    separator = "-----parent-----"
 
     ##
     # Create a certificate object.
@@ -347,7 +355,7 @@ class Certificate:
     # Create a blank X509 certificate and store it in this object.
 
     def create(self, lifeDays=1825):
-        self.x509 = crypto.X509()
+        self.x509 = OpenSSL.crypto.X509()
         # FIXME: Use different serial #s
         self.x509.set_serial_number(3)
         self.x509.gmtime_adj_notBefore(0) # 0 means now
@@ -377,7 +385,8 @@ class Certificate:
         
         # If it's not in proper PEM format, wrap it
         if string.count('-----BEGIN CERTIFICATE') == 0:
-            string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string
+            string = '-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----'\
+                     .format(string)
 
         # If there is a PEM cert in there, but there is some other text first
         # such as the text of the certificate, skip the text
@@ -395,10 +404,10 @@ class Certificate:
         else:
             parts = string.split(Certificate.separator, 1)
 
-        self.x509 = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0])
+        self.x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, parts[0])
 
         if self.x509 is None:
-            logger.warn("Loaded from string but cert is None: %s" % string)
+            logger.warn("Loaded from string but cert is None: {}".format(string))
 
         # if there are more certs, then create a parent and let the parent load
         # itself from the remainder of the string
@@ -413,7 +422,7 @@ class Certificate:
         file = open(filename)
         string = file.read()
         self.load_from_string(string)
-        self.filename=filename
+        self.filename = filename
 
     ##
     # Save the certificate to a string.
@@ -424,7 +433,9 @@ class Certificate:
         if self.x509 is None:
             logger.warn("None cert in certificate.save_to_string")
             return ""
-        string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.x509)
+        string = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, self.x509)
+        if PY3 and isinstance(string, bytes):
+            string = string.decode()
         if save_parents and self.parent:
             string = string + self.parent.save_to_string(save_parents)
         return string
@@ -439,9 +450,11 @@ class Certificate:
             f = filep
         else:
             f = open(filename, 'w')
+        if PY3 and isinstance(string, bytes):
+            string = string.decode()
         f.write(string)
         f.close()
-        self.filename=filename
+        self.filename = filename
 
     ##
     # Save the certificate to a random file in /tmp/
@@ -464,9 +477,9 @@ class Certificate:
             # it's a mistake to use subject and cert params at the same time
             assert(not cert)
             if isinstance(subject, dict) or isinstance(subject, str):
-                req = crypto.X509Req()
+                req = OpenSSL.crypto.X509Req()
                 reqSubject = req.get_subject()
-                if (isinstance(subject, dict)):
+                if isinstance(subject, dict):
                     for key in reqSubject.keys():
                         setattr(reqSubject, key, subject[key])
                 else:
@@ -491,9 +504,9 @@ class Certificate:
     # Set the subject name of the certificate
 
     def set_subject(self, name):
-        req = crypto.X509Req()
+        req = OpenSSL.crypto.X509Req()
         subj = req.get_subject()
-        if (isinstance(name, dict)):
+        if isinstance(name, dict):
             for key in name.keys():
                 setattr(subj, key, name[key])
         else:
@@ -530,7 +543,7 @@ class Certificate:
             counter = 0
             filtered = [self.filter_chunk(chunk) for chunk in data.split()]
             message += " ".join( [f for f in filtered if f])
-            omitted = len ([f for f in filtered if not f])
+            omitted = len([f for f in filtered if not f])
             if omitted:
                 message += "..+{} omitted".format(omitted)
         message += "]"
@@ -550,7 +563,8 @@ class Certificate:
     # It is returned in the form of a Keypair object.
 
     def get_pubkey(self):
-        m2x509 = X509.load_cert_string(self.save_to_string())
+        import M2Crypto
+        m2x509 = M2Crypto.X509.load_cert_string(self.save_to_string())
         pkey = Keypair()
         pkey.key = self.x509.get_pubkey()
         pkey.m2key = m2x509.get_pubkey()
@@ -568,7 +582,8 @@ class Certificate:
 
         if self.isCA != None:
             # Can't double set properties
-            raise Exception, "Cannot set basicConstraints CA:?? more than once. Was %s, trying to set as %s" % (self.isCA, val)
+            raise Exception("Cannot set basicConstraints CA:?? more than once. "
+                            "Was {}, trying to set as {}%s".format(self.isCA, val))
 
         self.isCA = val
         if val:
@@ -587,6 +602,7 @@ class Certificate:
     # @param value string containing value of the extension
 
     def add_extension(self, name, critical, value):
+        import M2Crypto
         oldExtVal = None
         try:
             oldExtVal = self.get_extension(name)
@@ -604,9 +620,9 @@ class Certificate:
         # FIXME: What if they are trying to set with a different value?
         # Is this ever OK? Or should we raise an exception?
 #        elif oldExtVal:
-#            raise "Cannot add extension %s which had val %s with new val %s" % (name, oldExtVal, value)
+#            raise "Cannot add extension {} which had val {} with new val {}".format(name, oldExtVal, value)
 
-        ext = crypto.X509Extension (name, critical, value)
+        ext = OpenSSL.crypto.X509Extension(name, critical, value)
         self.x509.add_extensions([ext])
 
     ##
@@ -614,6 +630,7 @@ class Certificate:
 
     def get_extension(self, name):
 
+        import M2Crypto
         if name is None:
             return None
 
@@ -621,7 +638,7 @@ class Certificate:
         if certstr is None or certstr == "":
             return None
         # pyOpenSSL does not have a way to get extensions
-        m2x509 = X509.load_cert_string(certstr)
+        m2x509 = M2Crypto.X509.load_cert_string(certstr)
         if m2x509 is None:
             logger.warn("No cert loaded in get_extension")
             return None
@@ -639,8 +656,8 @@ class Certificate:
     def set_data(self, str, field='subjectAltName'):
         # pyOpenSSL only allows us to add extensions, so if we try to set the
         # same extension more than once, it will not work
-        if self.data.has_key(field):
-            raise "Cannot set ", field, " more than once"
+        if field in self.data:
+            raise Exception("Cannot set {} more than once".format(field))
         self.data[field] = str
         self.add_extension(field, 0, str)
 
@@ -648,7 +665,7 @@ class Certificate:
     # Return the data string that was previously set with set_data
 
     def get_data(self, field='subjectAltName'):
-        if self.data.has_key(field):
+        if field in self.data:
             return self.data[field]
 
         try:
@@ -676,8 +693,9 @@ class Certificate:
     #     did not sign the certificate, then an exception will be thrown.
 
     def verify(self, pubkey):
+        import M2Crypto
         # pyOpenSSL does not have a way to verify signatures
-        m2x509 = X509.load_cert_string(self.save_to_string())
+        m2x509 = M2Crypto.X509.load_cert_string(self.save_to_string())
         m2pubkey = pubkey.get_m2_pubkey()
         # verify it
         # verify returns -1 or 0 on failure depending on how serious the
@@ -752,7 +770,8 @@ class Certificate:
         # verify expiration time
         if self.x509.has_expired():
             if debug_verify_chain:
-                logger.debug("verify_chain: NO, Certificate %s has expired" % self.pretty_cert())
+                logger.debug("verify_chain: NO, Certificate {} has expired"
+                             .format(self.pretty_cert()))
             raise CertExpired(self.pretty_cert(), "client cert")
 
         # if this cert is signed by a trusted_cert, then we are set
@@ -761,35 +780,38 @@ class Certificate:
                 # verify expiration of trusted_cert ?
                 if not trusted_cert.x509.has_expired():
                     if debug_verify_chain:
-                        logger.debug("verify_chain: YES. Cert %s signed by trusted cert %s"%(
-                            self.pretty_cert(), trusted_cert.pretty_cert()))
+                        logger.debug("verify_chain: YES. Cert {} signed by trusted cert {}"
+                                     .format(self.pretty_cert(), trusted_cert.pretty_cert()))
                     return trusted_cert
                 else:
                     if debug_verify_chain:
-                        logger.debug("verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..."%(
-                            self.pretty_cert(),trusted_cert.pretty_cert()))
-                    raise CertExpired(self.pretty_cert()," signer trusted_cert %s"%trusted_cert.pretty_cert())
+                        logger.debug("verify_chain: NO. Cert {} is signed by trusted_cert {}, "
+                                     "but that signer is expired..."
+                                     .format(self.pretty_cert(),trusted_cert.pretty_cert()))
+                    raise CertExpired("{} signer trusted_cert {}"
+                                      .format(self.pretty_cert(), trusted_cert.pretty_cert()))
 
         # if there is no parent, then no way to verify the chain
         if not self.parent:
             if debug_verify_chain:
-                logger.debug("verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots"%\
-                             (self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
-            raise CertMissingParent(self.pretty_cert() + \
-                                    ": Issuer %s is not one of the %d trusted roots, and cert has no parent." %\
-                                    (self.get_issuer(), len(trusted_certs)))
+                logger.debug("verify_chain: NO. {} has no parent "
+                             "and issuer {} is not in {} trusted roots"
+                             .format(self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
+            raise CertMissingParent("{}: Issuer {} is not one of the {} trusted roots, "
+                                    "and cert has no parent."
+                                    .format(self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
 
         # if it wasn't signed by the parent...
         if not self.is_signed_by_cert(self.parent):
             if debug_verify_chain:
-                logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%\
-                             (self.pretty_cert(),
-                              self.parent.pretty_cert(),
-                              self.get_issuer()))
-            raise CertNotSignedByParent("%s: Parent %s, issuer %s"\
-                                            % (self.pretty_cert(),
-                                               self.parent.pretty_cert(),
-                                               self.get_issuer()))
+                logger.debug("verify_chain: NO. {} is not signed by parent {}, but by {}"
+                             .format(self.pretty_cert(),
+                                     self.parent.pretty_cert(),
+                                     self.get_issuer()))
+            raise CertNotSignedByParent("{}: Parent {}, issuer {}"
+                                        .format(self.pretty_cert(),
+                                                self.parent.pretty_cert(),
+                                                self.get_issuer()))
 
         # Confirm that the parent is a CA. Only CAs can be trusted as
         # signers.
@@ -798,35 +820,36 @@ class Certificate:
         # Ugly - cert objects aren't parsed so we need to read the
         # extension and hope there are no other basicConstraints
         if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'):
-            logger.warn("verify_chain: cert %s's parent %s is not a CA" % \
-                            (self.pretty_cert(), self.parent.pretty_cert()))
-            raise CertNotSignedByParent("%s: Parent %s not a CA" % (self.pretty_cert(),
-                                                                    self.parent.pretty_cert()))
+            logger.warn("verify_chain: cert {}'s parent {} is not a CA"
+                        .format(self.pretty_cert(), self.parent.pretty_cert()))
+            raise CertNotSignedByParent("{}: Parent {} not a CA"
+                                        .format(self.pretty_cert(), self.parent.pretty_cert()))
 
         # if the parent isn't verified...
         if debug_verify_chain:
-            logger.debug("verify_chain: .. %s, -> verifying parent %s"%\
-                         (self.pretty_cert(),self.parent.pretty_cert()))
+            logger.debug("verify_chain: .. {}, -> verifying parent {}"
+                         .format(self.pretty_cert(),self.parent.pretty_cert()))
         self.parent.verify_chain(trusted_certs)
 
         return
 
     ### more introspection
     def get_extensions(self):
+        import M2Crypto
         # pyOpenSSL does not have a way to get extensions
         triples = []
-        m2x509 = X509.load_cert_string(self.save_to_string())
+        m2x509 = M2Crypto.X509.load_cert_string(self.save_to_string())
         nb_extensions = m2x509.get_ext_count()
-        logger.debug("X509 had %d extensions"%nb_extensions)
+        logger.debug("X509 had {} extensions".format(nb_extensions))
         for i in range(nb_extensions):
-            ext=m2x509.get_ext_at(i)
+            ext = m2x509.get_ext_at(i)
             triples.append( (ext.get_name(), ext.get_value(), ext.get_critical(),) )
         return triples
 
     def get_data_names(self):
         return self.data.keys()
 
-    def get_all_datas (self):
+    def get_all_datas(self):
         triples = self.get_extensions()
         for name in self.get_data_names():
             triples.append( (name,self.get_data(name),'data',) )
@@ -836,21 +859,22 @@ class Certificate:
     def get_filename(self):
         return getattr(self,'filename',None)
 
-    def dump (self, *args, **kwargs):
-        print self.dump_string(*args, **kwargs)
+    def dump(self, *args, **kwargs):
+        print(self.dump_string(*args, **kwargs))
 
-    def dump_string (self,show_extensions=False):
+    def dump_string(self, show_extensions=False):
         result = ""
-        result += "CERTIFICATE for %s\n"%self.pretty_cert()
-        result += "Issued by %s\n"%self.get_issuer()
-        filename=self.get_filename()
-        if filename: result += "Filename %s\n"%filename
+        result += "CERTIFICATE for {}\n".format(self.pretty_cert())
+        result += "Issued by {}\n".format(self.get_issuer())
+        filename = self.get_filename()
+        if filename:
+            result += "Filename {}\n".format(filename)
         if show_extensions:
             all_datas = self.get_all_datas()
-            result += " has %d extensions/data attached"%len(all_datas)
-            for (n, v, c) in all_datas:
-                if c=='data':
-                    result += "   data: %s=%s\n"%(n,v)
+            result += " has {} extensions/data attached".format(len(all_datas))
+            for n, v, c in all_datas:
+                if c == 'data':
+                    result += "   data: {}={}\n".format(n, v)
                 else:
-                    result += "    ext: %s (crit=%s)=<<<%s>>>\n"%(n,c,v)
+                    result += "    ext: {} (crit={})=<<<{}>>>\n".format(n, c, v)
         return result
diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py
index 3f658fb..daa8a89 100644
--- a/sfa/trust/credential.py
+++ b/sfa/trust/credential.py
@@ -26,14 +26,16 @@
 # Credentials are signed XML files that assign a subject gid privileges to an object gid
 ##
 
+from __future__ import print_function
+
 import os, os.path
 import subprocess
-from types import StringTypes
 import datetime
-from StringIO import StringIO
 from tempfile import mkstemp
 from xml.dom.minidom import Document, parseString
 
+from sfa.util.py23 import PY3, StringType, StringIO
+
 HAVELXML = False
 try:
     from lxml import etree
@@ -59,13 +61,13 @@ DEFAULT_CREDENTIAL_LIFETIME = 86400 * 31
 # . Need to add support for other types of credentials, e.g. tickets
 # . add namespaces to signed-credential element?
 
-signature_template = \
+signature_format = \
 '''
-<Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
+<Signature xml:id="Sig_{refid}" xmlns="http://www.w3.org/2000/09/xmldsig#">
   <SignedInfo>
     <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
     <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
-    <Reference URI="#%s">
+    <Reference URI="#{refid}">
       <Transforms>
         <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
       </Transforms>
@@ -85,35 +87,6 @@ signature_template = \
 </Signature>
 '''
 
-# PG formats the template (whitespace) slightly differently.
-# Note that they don't include the xmlns in the template, but add it later.
-# Otherwise the two are equivalent.
-#signature_template_as_in_pg = \
-#'''
-#<Signature xml:id="Sig_%s" >
-# <SignedInfo>
-#  <CanonicalizationMethod      Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
-#  <SignatureMethod      Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
-#  <Reference URI="#%s">
-#    <Transforms>
-#      <Transform         Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
-#    </Transforms>
-#    <DigestMethod        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
-#    <DigestValue></DigestValue>
-#    </Reference>
-# </SignedInfo>
-# <SignatureValue />
-# <KeyInfo>
-#  <X509Data >
-#   <X509SubjectName/>
-#   <X509IssuerSerial/>
-#   <X509Certificate/>
-#  </X509Data>
-#  <KeyValue />
-# </KeyInfo>
-#</Signature>
-#'''
-
 ##
 # Convert a string into a bool
 # used to convert an xsd:boolean to a Python boolean
@@ -181,21 +154,27 @@ class Signature(object):
         self.gid = gid
 
     def decode(self):
+        # Helper function to pull characters off the front of a string if present
+        def remove_prefix(text, prefix):
+            if text and prefix and text.startswith(prefix):
+                return text[len(prefix):]
+            return text
+
         try:
             doc = parseString(self.xml)
-        except ExpatError,e:
-            logger.log_exc ("Failed to parse credential, %s"%self.xml)
+        except ExpatError as e:
+            logger.log_exc("Failed to parse credential, {}".format(self.xml))
             raise
         sig = doc.getElementsByTagName("Signature")[0]
         ## This code until the end of function rewritten by Aaron Helsinger
-        ref_id = sig.getAttribute("xml:id").strip().strip("Sig_")
+        ref_id = remove_prefix(sig.getAttribute("xml:id").strip(), "Sig_")
         # The xml:id tag is optional, and could be in a 
         # Reference xml:id or Reference UID sub element instead
         if not ref_id or ref_id == '':
             reference = sig.getElementsByTagName('Reference')[0]
-            ref_id = reference.getAttribute('xml:id').strip().strip('Sig_')
+            ref_id = remove_prefix(reference.getAttribute('xml:id').strip(), "Sig_")
             if not ref_id or ref_id == '':
-                ref_id = reference.getAttribute('URI').strip().strip('#')
+                ref_id = remove_prefix(reference.getAttribute('URI').strip(), "#")
         self.set_refid(ref_id)
         keyinfos = sig.getElementsByTagName("X509Data")
         gids = None
@@ -205,7 +184,7 @@ class Signature(object):
                 if len(cert.childNodes) > 0:
                     szgid = cert.childNodes[0].nodeValue
                     szgid = szgid.strip()
-                    szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
+                    szgid = "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----".format(szgid)
                     if gids is None:
                         gids = szgid
                     else:
@@ -215,7 +194,7 @@ class Signature(object):
         self.set_issuer_gid(GID(string=gids))
         
     def encode(self):
-        self.xml = signature_template % (self.get_refid(), self.get_refid())
+        self.xml = signature_format.format(refid=self.get_refid())
 
 ##
 # A credential provides a caller gid with privileges to an object gid.
@@ -277,7 +256,7 @@ class Credential(object):
         self.version = None
 
         if cred:
-            if isinstance(cred, StringTypes):
+            if isinstance(cred, StringType):
                 string = cred
                 self.type = Credential.SFA_CREDENTIAL_TYPE
                 self.version = '3'
@@ -290,11 +269,12 @@ class Credential(object):
             if string:                
                 str = string
             elif filename:
-                str = file(filename).read()
+                with open(filename) as infile:
+                    str = infile.read()
                 
             # if this is a legacy credential, write error and bail out
-            if isinstance (str, StringTypes) and str.strip().startswith("-----"):
-                logger.error("Legacy credentials not supported any more - giving up with %s..."%str[:10])
+            if isinstance(str, StringType) and str.strip().startswith("-----"):
+                logger.error("Legacy credentials not supported any more - giving up with {}...".format(str[:10]))
                 return
             else:
                 self.xml = str
@@ -405,11 +385,11 @@ class Credential(object):
     # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)
     # 
     def set_expiration(self, expiration):
-        expiration_datetime = utcparse (expiration)
+        expiration_datetime = utcparse(expiration)
         if expiration_datetime is not None:
             self.expiration = expiration_datetime
         else:
-            logger.error ("unexpected input %s in Credential.set_expiration"%expiration)
+            logger.error("unexpected input {} in Credential.set_expiration".format(expiration))
 
     ##
     # get the lifetime of the credential (always in datetime format)
@@ -496,7 +476,7 @@ class Credential(object):
         append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
         append_sub(doc, cred, "uuid", "")
         if not self.expiration:
-            logger.debug("Creating credential valid for %s s"%DEFAULT_CREDENTIAL_LIFETIME)
+            logger.debug("Creating credential valid for {} s".format(DEFAULT_CREDENTIAL_LIFETIME))
             self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
         self.expiration = self.expiration.replace(microsecond=0)
         if self.expiration.tzinfo is not None and self.expiration.tzinfo.utcoffset(self.expiration) is not None:
@@ -557,10 +537,12 @@ class Credential(object):
                     # Below throws InUse exception if we forgot to clone the attribute first
                     oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
                     if oldAttr and oldAttr.value != attr.value:
-                        msg = "Delegating cred from owner %s to %s over %s:\n - Replaced attribute %s value '%s' with '%s'" % \
-                              (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value)
+                        msg = "Delegating cred from owner {} to {} over {}:\n"
+                        "- Replaced attribute {} value '{}' with '{}'"\
+                            .format(self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(),
+                                    self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value)
                         logger.warn(msg)
-                        #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
+                        #raise CredentialNotVerifiable("Can't encode new valid delegated credential: {}".format(msg))
 
             p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
             p = doc.createElement("parent")
@@ -596,12 +578,16 @@ class Credential(object):
             f = filep 
         else:
             f = open(filename, "w")
+        if PY3 and isinstance(self.xml, bytes):
+            self.xml = self.xml.decode()
         f.write(self.xml)
         f.close()
 
     def save_to_string(self, save_parents=True):
         if not self.xml:
             self.encode()
+        if PY3 and isinstance(self.xml, bytes):
+            self.xml = self.xml.decode()
         return self.xml
 
     def get_refid(self):
@@ -637,7 +623,7 @@ class Credential(object):
         rid = self.get_refid()
         while rid in refs:
             val = int(rid[3:])
-            rid = "ref%d" % (val + 1)
+            rid = "ref{}".format(val + 1)
 
         # Set the new refid
         self.set_refid(rid)
@@ -690,14 +676,13 @@ class Credential(object):
 
 
         # Call out to xmlsec1 to sign it
-        ref = 'Sig_%s' % self.get_refid()
+        ref = 'Sig_{}'.format(self.get_refid())
         filename = self.save_to_random_tmp_file()
         xmlsec1 = self.get_xmlsec1_path()
         if not xmlsec1:
             raise Exception("Could not locate required 'xmlsec1' program")
-        command = '%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
-            % (xmlsec1, ref, self.issuer_privkey, ",".join(gid_files), filename)
-#        print 'command',command
+        command = '{} --sign --node-id "{}" --privkey-pem {},{} {}' \
+                  .format(xmlsec1, ref, self.issuer_privkey, ",".join(gid_files), filename)
         signed = os.popen(command).read()
         os.remove(filename)
 
@@ -722,7 +707,7 @@ class Credential(object):
         doc = None
         try:
             doc = parseString(self.xml)
-        except ExpatError,e:
+        except ExpatError as e:
             raise CredentialNotVerifiable("Malformed credential")
         doc = parseString(self.xml)
         sigs = []
@@ -834,7 +819,8 @@ class Credential(object):
                 xmlschema = etree.XMLSchema(schema_doc)
                 if not xmlschema.validate(tree):
                     error = xmlschema.error_log.last_error
-                    message = "%s: %s (line %s)" % (self.pretty_cred(), error.message, error.line)
+                    message = "{}: {} (line {})".format(self.pretty_cred(),
+                                                        error.message, error.line)
                     raise CredentialNotVerifiable(message)
 
         if trusted_certs_required and trusted_certs is None:
@@ -852,15 +838,15 @@ class Credential(object):
                     # or non PEM files
                     trusted_cert_objects.append(GID(filename=f))
                     ok_trusted_certs.append(f)
-                except Exception, exc:
-                    logger.error("Failed to load trusted cert from %s: %r"%( f, exc))
+                except Exception as exc:
+                    logger.error("Failed to load trusted cert from {}: {}".format(f, exc))
             trusted_certs = ok_trusted_certs
 
         # make sure it is not expired
         if self.get_expiration() < datetime.datetime.utcnow():
-            raise CredentialNotVerifiable("Credential %s expired at %s" % \
-                                          (self.pretty_cred(),
-                                           self.expiration.strftime(SFATIME_FORMAT)))
+            raise CredentialNotVerifiable("Credential {} expired at {}" \
+                                          .format(self.pretty_cred(),
+                                                  self.expiration.strftime(SFATIME_FORMAT)))
 
         # Verify the signatures
         filename = self.save_to_random_tmp_file()
@@ -874,11 +860,11 @@ class Credential(object):
                 cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)
 
         refs = []
-        refs.append("Sig_%s" % self.get_refid())
+        refs.append("Sig_{}".format(self.get_refid()))
 
         parentRefs = self.updateRefID()
         for ref in parentRefs:
-            refs.append("Sig_%s" % ref)
+            refs.append("Sig_{}".format(ref))
 
         for ref in refs:
             # If caller explicitly passed in None that means skip xmlsec1 validation.
@@ -890,7 +876,7 @@ class Credential(object):
             # up to fedora20 we used os.popen and checked that the output begins with OK
             # turns out, with fedora21, there is extra input before this 'OK' thing
             # looks like we're better off just using the exit code - that's what it is made for
-            #cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
+            #cert_args = " ".join(['--trusted-pem {}'.format(x) for x in trusted_certs])
             #command = '{} --verify --node-id "{}" {} {} 2>&1'.\
             #          format(self.xmlsec_path, ref, cert_args, filename)
             xmlsec1 = self.get_xmlsec1_path()
@@ -916,8 +902,8 @@ class Credential(object):
                     mend = verified.find('\\', mstart)
                     msg = verified[mstart:mend]
                 logger.warning("Credential.verify - failed - xmlsec1 returned {}".format(verified.strip()))
-                raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s" % \
-                                              (self.pretty_cred(), ref, msg))
+                raise CredentialNotVerifiable("xmlsec1 error verifying cred {} using Signature ID {}: {}"\
+                                              .format(self.pretty_cred(), ref, msg))
         os.remove(filename)
 
         # Verify the parents (delegation)
@@ -954,7 +940,9 @@ class Credential(object):
         root_target_gid = root_cred.get_gid_object()
         if root_cred.get_signature() is None:
             # malformed
-            raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred has no signature" % (self.gidCaller.get_urn(), self.gidObject.get_urn()))
+            raise CredentialNotVerifiable("Could not verify credential owned by {} for object {}. "
+                                          "Cred has no signature" \
+                                          .format(self.gidCaller.get_urn(), self.gidObject.get_urn()))
 
         root_cred_signer = root_cred.get_signature().get_issuer_gid()
 
@@ -1098,7 +1086,7 @@ class Credential(object):
   
         #user_key = Keypair(filename=keyfile)
         #user_hrn = self.get_gid_caller().get_hrn()
-        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
+        subject_string = "{} delegated to {}".format(object_hrn, delegee_hrn)
         dcred = Credential(subject=subject_string)
         dcred.set_gid_caller(delegee_gid)
         dcred.set_gid_object(object_gid)
@@ -1117,7 +1105,7 @@ class Credential(object):
     def get_filename(self):
         return getattr(self,'filename',None)
 
-    def actual_caller_hrn (self):
+    def actual_caller_hrn(self):
         """a helper method used by some API calls like e.g. Allocate
         to try and find out who really is the original caller
 
@@ -1139,26 +1127,26 @@ class Credential(object):
         # else this looks like a delegated credential, and the real caller is the issuer
         else:
             actual_caller_hrn=issuer_hrn
-        logger.info("actual_caller_hrn: caller_hrn=%s, issuer_hrn=%s, returning %s"
-                    %(caller_hrn,issuer_hrn,actual_caller_hrn))
+        logger.info("actual_caller_hrn: caller_hrn={}, issuer_hrn={}, returning {}"
+                    .format(caller_hrn,issuer_hrn,actual_caller_hrn))
         return actual_caller_hrn
 
     ##
     # Dump the contents of a credential to stdout in human-readable format
     #
     # @param dump_parents If true, also dump the parent certificates
-    def dump (self, *args, **kwargs):
-        print self.dump_string(*args, **kwargs)
+    def dump(self, *args, **kwargs):
+        print(self.dump_string(*args, **kwargs))
 
     # SFA code ignores show_xml and disables printing the cred xml
     def dump_string(self, dump_parents=False, show_xml=False):
         result=""
-        result += "CREDENTIAL %s\n" % self.pretty_subject()
+        result += "CREDENTIAL {}\n".format(self.pretty_subject())
         filename=self.get_filename()
-        if filename: result += "Filename %s\n"%filename
+        if filename: result += "Filename {}\n".format(filename)
         privileges = self.get_privileges()
         if privileges:
-            result += "      privs: %s\n" % privileges.save_to_string()
+            result += "      privs: {}\n".format(privileges.save_to_string())
         else:
             result += "      privs: \n"
         gidCaller = self.get_gid_caller()
@@ -1191,7 +1179,7 @@ class Credential(object):
                 result += "\nEnd XML\n"
             except:
                 import traceback
-                print "exc. Credential.dump_string / XML"
+                print("exc. Credential.dump_string / XML")
                 traceback.print_exc()
 
         return result
diff --git a/sfa/trust/credential_factory.py b/sfa/trust/credential_factory.py
index 2fe37a7..cf5a8fb 100644
--- a/sfa/trust/credential_factory.py
+++ b/sfa/trust/credential_factory.py
@@ -1,110 +1,112 @@
-#----------------------------------------------------------------------
-# Copyright (c) 2014 Raytheon BBN Technologies
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and/or hardware specification (the "Work") to
-# deal in the Work without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense,
-# and/or sell copies of the Work, and to permit persons to whom the Work
-# is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Work.
-#
-# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
-# IN THE WORK.
-#----------------------------------------------------------------------
-
-from sfa.util.sfalogging import logger
-from sfa.trust.credential import Credential
-from sfa.trust.abac_credential import ABACCredential
-
-import json
-import re
-
-# Factory for creating credentials of different sorts by type.
-# Specifically, this factory can create standard SFA credentials
-# and ABAC credentials from XML strings based on their identifying content
-
-class CredentialFactory:
-
-    UNKNOWN_CREDENTIAL_TYPE = 'geni_unknown'
-
-    # Static Credential class method to determine the type of a credential
-    # string depending on its contents
-    @staticmethod
-    def getType(credString):
-        credString_nowhitespace = re.sub('\s', '', credString)
-        if credString_nowhitespace.find('<type>abac</type>') > -1:
-            return ABACCredential.ABAC_CREDENTIAL_TYPE
-        elif credString_nowhitespace.find('<type>privilege</type>') > -1:
-            return Credential.SFA_CREDENTIAL_TYPE
-        else:
-            st = credString_nowhitespace.find('<type>')
-            end = credString_nowhitespace.find('</type>', st)
-            return credString_nowhitespace[st + len('<type>'):end]
-#            return CredentialFactory.UNKNOWN_CREDENTIAL_TYPE
-
-    # Static Credential class method to create the appropriate credential
-    # (SFA or ABAC) depending on its type
-    @staticmethod
-    def createCred(credString=None, credFile=None):
-        if not credString and not credFile:
-            raise Exception("CredentialFactory.createCred called with no argument")
-        if credFile:
-            try:
-                credString = open(credFile).read()
-            except Exception, e:
-                logger.info("Error opening credential file %s: %s" % credFile, e)
-                return None
-
-        # Try to treat the file as JSON, getting the cred_type from the struct
-        try:
-            credO = json.loads(credString, encoding='ascii')
-            if credO.has_key('geni_value') and credO.has_key('geni_type'):
-                cred_type = credO['geni_type']
-                credString = credO['geni_value']
-        except Exception, e:
-            # It wasn't a struct. So the credString is XML. Pull the type directly from the string
-            logger.debug("Credential string not JSON: %s" % e)
-            cred_type = CredentialFactory.getType(credString)
-
-        if cred_type == Credential.SFA_CREDENTIAL_TYPE:
-            try:
-                cred = Credential(string=credString)
-                return cred
-            except Exception, e:
-                if credFile:
-                    msg = "credString started: %s" % credString[:50]
-                    raise Exception("%s not a parsable SFA credential: %s. " % (credFile, e) + msg)
-                else:
-                    raise Exception("SFA Credential not parsable: %s. Cred start: %s..." % (e, credString[:50]))
-
-        elif cred_type == ABACCredential.ABAC_CREDENTIAL_TYPE:
-            try:
-                cred = ABACCredential(string=credString)
-                return cred
-            except Exception, e:
-                if credFile:
-                    raise Exception("%s not a parsable ABAC credential: %s" % (credFile, e))
-                else:
-                    raise Exception("ABAC Credential not parsable: %s. Cred start: %s..." % (e, credString[:50]))
-        else:
-            raise Exception("Unknown credential type '%s'" % cred_type)
-
-if __name__ == "__main__":
-    c2 = open('/tmp/sfa.xml').read()
-    cred1 = CredentialFactory.createCred(credFile='/tmp/cred.xml')
-    cred2 = CredentialFactory.createCred(credString=c2)
-
-    print "C1 = %s" % cred1
-    print "C2 = %s" % cred2
-    c1s = cred1.dump_string()
-    print "C1 = %s" % c1s
-#    print "C2 = %s" % cred2.dump_string()
+#----------------------------------------------------------------------
+# Copyright (c) 2014 Raytheon BBN Technologies
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
+# IN THE WORK.
+#----------------------------------------------------------------------
+
+from __future__ import print_function
+
+from sfa.util.sfalogging import logger
+from sfa.trust.credential import Credential
+from sfa.trust.abac_credential import ABACCredential
+
+import json
+import re
+
+# Factory for creating credentials of different sorts by type.
+# Specifically, this factory can create standard SFA credentials
+# and ABAC credentials from XML strings based on their identifying content
+
+class CredentialFactory:
+
+    UNKNOWN_CREDENTIAL_TYPE = 'geni_unknown'
+
+    # Static Credential class method to determine the type of a credential
+    # string depending on its contents
+    @staticmethod
+    def getType(credString):
+        credString_nowhitespace = re.sub('\s', '', credString)
+        if credString_nowhitespace.find('<type>abac</type>') > -1:
+            return ABACCredential.ABAC_CREDENTIAL_TYPE
+        elif credString_nowhitespace.find('<type>privilege</type>') > -1:
+            return Credential.SFA_CREDENTIAL_TYPE
+        else:
+            st = credString_nowhitespace.find('<type>')
+            end = credString_nowhitespace.find('</type>', st)
+            return credString_nowhitespace[st + len('<type>'):end]
+#            return CredentialFactory.UNKNOWN_CREDENTIAL_TYPE
+
+    # Static Credential class method to create the appropriate credential
+    # (SFA or ABAC) depending on its type
+    @staticmethod
+    def createCred(credString=None, credFile=None):
+        if not credString and not credFile:
+            raise Exception("CredentialFactory.createCred called with no argument")
+        if credFile:
+            try:
+                credString = open(credFile).read()
+            except Exception as e:
+                logger.info("Error opening credential file %s: %s" % credFile, e)
+                return None
+
+        # Try to treat the file as JSON, getting the cred_type from the struct
+        try:
+            credO = json.loads(credString, encoding='ascii')
+            if 'geni_value' in credO and 'geni_type' in credO:
+                cred_type = credO['geni_type']
+                credString = credO['geni_value']
+        except Exception as e:
+            # It wasn't a struct. So the credString is XML. Pull the type directly from the string
+            logger.debug("Credential string not JSON: %s" % e)
+            cred_type = CredentialFactory.getType(credString)
+
+        if cred_type == Credential.SFA_CREDENTIAL_TYPE:
+            try:
+                cred = Credential(string=credString)
+                return cred
+            except Exception as e:
+                if credFile:
+                    msg = "credString started: %s" % credString[:50]
+                    raise Exception("%s not a parsable SFA credential: %s. " % (credFile, e) + msg)
+                else:
+                    raise Exception("SFA Credential not parsable: %s. Cred start: %s..." % (e, credString[:50]))
+
+        elif cred_type == ABACCredential.ABAC_CREDENTIAL_TYPE:
+            try:
+                cred = ABACCredential(string=credString)
+                return cred
+            except Exception as e:
+                if credFile:
+                    raise Exception("%s not a parsable ABAC credential: %s" % (credFile, e))
+                else:
+                    raise Exception("ABAC Credential not parsable: %s. Cred start: %s..." % (e, credString[:50]))
+        else:
+            raise Exception("Unknown credential type '%s'" % cred_type)
+
+if __name__ == "__main__":
+    c2 = open('/tmp/sfa.xml').read()
+    cred1 = CredentialFactory.createCred(credFile='/tmp/cred.xml')
+    cred2 = CredentialFactory.createCred(credString=c2)
+
+    print("C1 = %s" % cred1)
+    print("C2 = %s" % cred2)
+    c1s = cred1.dump_string()
+    print("C1 = %s" % c1s)
+#    print "C2 = %s" % cred2.dump_string()
diff --git a/sfa/trust/gid.py b/sfa/trust/gid.py
index 6e668fa..3f903d9 100644
--- a/sfa/trust/gid.py
+++ b/sfa/trust/gid.py
@@ -25,14 +25,16 @@
 # descendant of the certificate class.
 ##
 
-import xmlrpclib
+from __future__ import print_function
+
 import uuid
 
 from sfa.trust.certificate import Certificate
 
 from sfa.util.faults import GidInvalidParentHrn, GidParentHrn
-from sfa.util.sfalogging import logger
 from sfa.util.xrn import hrn_to_urn, urn_to_hrn, hrn_authfor_hrn
+from sfa.util.sfalogging import logger
+from sfa.util.py23 import xmlrpc_client
 
 ##
 # Create a new uuid. Returns the UUID as a string.
@@ -174,7 +176,7 @@ class GID(Certificate):
         dict = {}
         if data:
             if data.lower().startswith('uri:http://<params>'):
-                dict = xmlrpclib.loads(data[11:])[0][0]
+                dict = xmlrpc_client.loads(data[11:])[0][0]
             else:
                 spl = data.split(', ')
                 for val in spl:
@@ -201,7 +203,7 @@ class GID(Certificate):
     # @param dump_parents If true, also dump the parents of the GID
 
     def dump(self, *args, **kwargs):
-        print self.dump_string(*args,**kwargs)
+        print(self.dump_string(*args,**kwargs))
 
     def dump_string(self, indent=0, dump_parents=False):
         result=" "*(indent-2) + "GID\n"
diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py
index 43b318e..5e76dbf 100644
--- a/sfa/trust/hierarchy.py
+++ b/sfa/trust/hierarchy.py
@@ -160,7 +160,8 @@ class Hierarchy:
         try:
             os.makedirs(directory)
         # if the path already exists then pass
-        except OSError, (errno, strerr):
+        except OSError as xxx_todo_changeme:
+            (errno, strerr) = xxx_todo_changeme.args
             if errno == 17:
                 pass
 
diff --git a/sfa/trust/sfaticket.py b/sfa/trust/sfaticket.py
index bc8584b..6d4a009 100644
--- a/sfa/trust/sfaticket.py
+++ b/sfa/trust/sfaticket.py
@@ -24,11 +24,13 @@
 # implements SFA tickets
 #
 
-import xmlrpclib
+from __future__ import print_function
 
 from sfa.trust.certificate import Certificate
 from sfa.trust.gid import GID
 
+from sfa.util.py23 import xmlrpc_client
+
 # Ticket is tuple:
 #   (gidCaller, gidObject, attributes, rspec, delegate)
 #
@@ -97,13 +99,13 @@ class SfaTicket(Certificate):
             dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True)
         if self.gidObject:
             dict["gidObject"] = self.gidObject.save_to_string(save_parents=True)
-        str = "URI:" + xmlrpclib.dumps((dict,), allow_none=True)
+        str = "URI:" + xmlrpc_client.dumps((dict,), allow_none=True)
         self.set_data(str)
 
     def decode(self):
         data = self.get_data()
         if data:
-            dict = xmlrpclib.loads(self.get_data()[4:])[0][0]
+            dict = xmlrpc_client.loads(self.get_data()[4:])[0][0]
         else:
             dict = {}
 
@@ -124,25 +126,25 @@ class SfaTicket(Certificate):
             self.gidObject = None
 
     def dump(self, dump_parents=False):
-        print "TICKET", self.get_subject()
+        print("TICKET", self.get_subject())
 
-        print "  gidCaller:"
+        print("  gidCaller:")
         gidCaller = self.get_gid_caller()
         if gidCaller:
             gidCaller.dump(8, dump_parents)
 
-        print "  gidObject:"
+        print("  gidObject:")
         gidObject = self.get_gid_object()
         if gidObject:
             gidObject.dump(8, dump_parents)
 
-        print "  attributes:"
+        print("  attributes:")
         for attrname in self.get_attributes().keys():
-            print "        ", attrname, self.get_attributes()[attrname]
+            print("        ", attrname, self.get_attributes()[attrname])
 
-        print "       rspec:"
-        print "        ", self.get_rspec()
+        print("       rspec:")
+        print("        ", self.get_rspec())
 
         if self.parent and dump_parents:
-           print "PARENT",
+           print("PARENT", end=' ')
            self.parent.dump(dump_parents)
diff --git a/sfa/trust/speaksfor_util.py b/sfa/trust/speaksfor_util.py
index 9bad61d..640d512 100644
--- a/sfa/trust/speaksfor_util.py
+++ b/sfa/trust/speaksfor_util.py
@@ -31,16 +31,16 @@ import subprocess
 import sys
 import tempfile
 from xml.dom.minidom import *
-from StringIO import StringIO
 
 from sfa.util.sfatime import SFATIME_FORMAT
 
 from sfa.trust.certificate import Certificate
-from sfa.trust.credential import Credential, signature_template, HAVELXML
+from sfa.trust.credential import Credential, signature_format, HAVELXML
 from sfa.trust.abac_credential import ABACCredential, ABACElement
 from sfa.trust.credential_factory import CredentialFactory
 from sfa.trust.gid import GID
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringIO
 
 # Routine to validate that a speaks-for credential 
 # says what it claims to say:
@@ -56,7 +56,8 @@ from sfa.util.sfalogging import logger
 # Find the text associated with first child text node
 def findTextChildValue(root):
     child = findChildNamed(root, '#text')
-    if child: return str(child.nodeValue)
+    if child:
+        return str(child.nodeValue)
     return None
 
 # Find first child with given name
@@ -85,7 +86,7 @@ def run_subprocess(cmd, stdout, stderr):
             output = proc.returncode
         return output
     except Exception as e:
-        raise Exception("Failed call to subprocess '%s': %s" % (" ".join(cmd), e))
+        raise Exception("Failed call to subprocess '{}': {}".format(" ".join(cmd), e))
 
 def get_cert_keyid(gid):
     """Extract the subject key identifier from the given certificate.
@@ -134,26 +135,28 @@ def verify_speaks_for(cred, tool_gid, speaking_for_urn,
 
     # Credential has not expired
     if cred.expiration and cred.expiration < datetime.datetime.utcnow():
-        return False, None, "ABAC Credential expired at %s (%s)" % (cred.expiration.strftime(SFATIME_FORMAT), cred.pretty_cred())
+        return False, None, "ABAC Credential expired at {} ({})"\
+            .format(cred.expiration.strftime(SFATIME_FORMAT), cred.pretty_cred())
 
     # Must be ABAC
     if cred.get_cred_type() != ABACCredential.ABAC_CREDENTIAL_TYPE:
-        return False, None, "Credential not of type ABAC but %s" % cred.get_cred_type
+        return False, None, "Credential not of type ABAC but {}".format(cred.get_cred_type)
 
     if cred.signature is None or cred.signature.gid is None:
-        return False, None, "Credential malformed: missing signature or signer cert. Cred: %s" % cred.pretty_cred()
+        return False, None, "Credential malformed: missing signature or signer cert. Cred: {}"\
+            .format(cred.pretty_cred())
     user_gid = cred.signature.gid
     user_urn = user_gid.get_urn()
 
     # URN of signer from cert must match URN of 'speaking-for' argument
     if user_urn != speaking_for_urn:
-        return False, None, "User URN from cred doesn't match speaking_for URN: %s != %s (cred %s)" % \
-            (user_urn, speaking_for_urn, cred.pretty_cred())
+        return False, None, "User URN from cred doesn't match speaking_for URN: {} != {} (cred {})"\
+            .format(user_urn, speaking_for_urn, cred.pretty_cred())
 
     tails = cred.get_tails()
     if len(tails) != 1: 
-        return False, None, "Invalid ABAC-SF credential: Need exactly 1 tail element, got %d (%s)" % \
-            (len(tails), cred.pretty_cred())
+        return False, None, "Invalid ABAC-SF credential: Need exactly 1 tail element, got {} ({})"\
+            .format(len(tails), cred.pretty_cred())
 
     user_keyid = get_cert_keyid(user_gid)
     tool_keyid = get_cert_keyid(tool_gid)
@@ -188,13 +191,14 @@ def verify_speaks_for(cred, tool_gid, speaking_for_urn,
             msg = verified[mstart:mend]
         if msg == "":
             msg = output
-        return False, None, "ABAC credential failed to xmlsec1 verify: %s" % msg
+        return False, None, "ABAC credential failed to xmlsec1 verify: {}".format(msg)
 
     # Must say U.speaks_for(U)<-T
     if user_keyid != principal_keyid or \
             tool_keyid != subject_keyid or \
-            role != ('speaks_for_%s' % user_keyid):
-        return False, None, "ABAC statement doesn't assert U.speaks_for(U)<-T (%s)" % cred.pretty_cred()
+            role != ('speaks_for_{}'.format(user_keyid)):
+        return False, None, "ABAC statement doesn't assert U.speaks_for(U)<-T ({})"\
+            .format(cred.pretty_cred())
 
     # If schema provided, validate against schema
     if HAVELXML and schema and os.path.exists(schema):
@@ -204,23 +208,22 @@ def verify_speaks_for(cred, tool_gid, speaking_for_urn,
         xmlschema = etree.XMLSchema(schema_doc)
         if not xmlschema.validate(tree):
             error = xmlschema.error_log.last_error
-            message = "%s: %s (line %s)" % (cred.pretty_cred(), error.message, error.line)
-            return False, None, ("XML Credential schema invalid: %s" % message)
+            message = "{}: {} (line {})".format(cred.pretty_cred(), error.message, error.line)
+            return False, None, ("XML Credential schema invalid: {}".format(message))
 
     if trusted_roots:
         # User certificate must validate against trusted roots
         try:
             user_gid.verify_chain(trusted_roots)
-        except Exception, e:
+        except Exception as e:
             return False, None, \
-                "Cred signer (user) cert not trusted: %s" % e
+                "Cred signer (user) cert not trusted: {}".format(e)
 
         # Tool certificate must validate against trusted roots
         try:
             tool_gid.verify_chain(trusted_roots)
-        except Exception, e:
-            return False, None, \
-                "Tool cert not trusted: %s" % e
+        except Exception as e:
+            return False, None, "Tool cert not trusted: {}".format(e)
 
     return True, user_gid, ""
 
@@ -258,9 +261,9 @@ def determine_speaks_for(logger, credentials, caller_gid, speaking_for_xrn, trus
             if not isinstance(cred_value, ABACCredential):
                 cred = CredentialFactory.createCred(cred_value)
 
-#            print("Got a cred to check speaksfor for: %s" % cred.pretty_cred())
+#            print("Got a cred to check speaksfor for: {}".format(cred.pretty_cred()))
 #            #cred.dump(True, True)
-#            print("Caller: %s" % caller_gid.dump_string(2, True))
+#            print("Caller: {}".format(caller_gid.dump_string(2, True)))
             # See if this is a valid speaks_for
             is_valid_speaks_for, user_gid, msg = \
                 verify_speaks_for(cred,
@@ -270,7 +273,8 @@ def determine_speaks_for(logger, credentials, caller_gid, speaking_for_xrn, trus
             if is_valid_speaks_for:
                 return user_gid # speaks-for
             else:
-                logger.info("Got speaks-for option but not a valid speaks_for with this credential: %s" % msg)
+                logger.info("Got speaks-for option but not a valid speaks_for with this credential: {}"
+                            .format(msg))
     return caller_gid # Not speaks-for
 
 # Create an ABAC Speaks For credential using the ABACCredential object and it's encode&sign methods
@@ -294,7 +298,7 @@ def create_sign_abaccred(tool_gid, user_gid, ma_gid, user_key_file, cred_filenam
     user_urn = user_gid.get_urn()
     user_keyid = get_cert_keyid(user_gid)
     tool_keyid = get_cert_keyid(tool_gid)
-    cred.head = ABACElement(user_keyid, user_urn, "speaks_for_%s" % user_keyid)
+    cred.head = ABACElement(user_keyid, user_urn, "speaks_for_{}".format(user_keyid))
     cred.tails.append(ABACElement(tool_keyid, tool_urn))
     cred.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(days=dur_days))
     cred.expiration = cred.expiration.replace(microsecond=0)
@@ -306,8 +310,8 @@ def create_sign_abaccred(tool_gid, user_gid, ma_gid, user_key_file, cred_filenam
     cred.sign()
     # Save it
     cred.save_to_file(cred_filename)
-    logger.info("Created ABAC credential: '%s' in file %s" % 
-                (cred.pretty_cred(), cred_filename))
+    logger.info("Created ABAC credential: '{}' in file {}"
+                .format(cred.pretty_cred(), cred_filename))
 
 # FIXME: Assumes signer is itself signed by an 'ma_gid' that can be trusted
 def create_speaks_for(tool_gid, user_gid, ma_gid, 
@@ -315,41 +319,37 @@ def create_speaks_for(tool_gid, user_gid, ma_gid,
     tool_urn = tool_gid.get_urn()
     user_urn = user_gid.get_urn()
 
-    header = '<?xml version="1.0" encoding="UTF-8"?>'
-    reference = "ref0"
-    signature_block = \
-        '<signatures>\n' + \
-        signature_template + \
-        '</signatures>'
-    template = header + '\n' + \
-        '<signed-credential '
-    template += 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.geni.net/resources/credential/2/credential.xsd" xsi:schemaLocation="http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd"'
-    template += '>\n' + \
-        '<credential xml:id="%s">\n' + \
-        '<type>abac</type>\n' + \
-        '<serial/>\n' +\
-        '<owner_gid/>\n' + \
-        '<owner_urn/>\n' + \
-        '<target_gid/>\n' + \
-        '<target_urn/>\n' + \
-        '<uuid/>\n' + \
-        '<expires>%s</expires>' +\
-        '<abac>\n' + \
-        '<rt0>\n' + \
-        '<version>%s</version>\n' + \
-        '<head>\n' + \
-        '<ABACprincipal><keyid>%s</keyid><mnemonic>%s</mnemonic></ABACprincipal>\n' +\
-        '<role>speaks_for_%s</role>\n' + \
-        '</head>\n' + \
-        '<tail>\n' +\
-        '<ABACprincipal><keyid>%s</keyid><mnemonic>%s</mnemonic></ABACprincipal>\n' +\
-        '</tail>\n' +\
-        '</rt0>\n' + \
-        '</abac>\n' + \
-        '</credential>\n' + \
-        signature_block + \
-        '</signed-credential>\n'
-
+    refid = "ref0"
+
+    credential_format = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<signed-credential xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.geni.net/resources/credential/2/credential.xsd" xsi:schemaLocation="http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd">
+ <credential xml:id="{refid}">
+  <type>abac</type>
+  <serial/>
+  <owner_gid/>
+  <owner_urn/>
+  <target_gid/>
+  <target_urn/>
+  <uuid/>
+  <expires>{expiration_str}</expires>
+  <abac>
+   <rt0>
+    <version>{version}</version>
+    <head>
+     <ABACprincipal><keyid>{user_keyid}</keyid><mnemonic>{user_urn}</mnemonic></ABACprincipal>
+     <role>speaks_for_{user_keyid}</role>
+    </head>
+    <tail>
+     <ABACprincipal><keyid>{tool_keyid}/keyid><mnemonic>{tool_urn}</mnemonic></ABACprincipal>
+    </tail>
+   </rt0>
+  </abac>
+ </credential>
+ <signatures>""" + signature_format + """\
+ </signatures>
+</signed-credential>\
+"""
 
     credential_duration = datetime.timedelta(days=dur_days)
     expiration = datetime.datetime.utcnow() + credential_duration
@@ -358,16 +358,15 @@ def create_speaks_for(tool_gid, user_gid, ma_gid,
 
     user_keyid = get_cert_keyid(user_gid)
     tool_keyid = get_cert_keyid(tool_gid)
-    unsigned_cred = template % (reference, expiration_str, version, 
-                                user_keyid, user_urn, user_keyid, tool_keyid, tool_urn,
-                                reference, reference)
+    # apply the format - itself uses signature_format which uses 'refid'
+    unsigned_cred = credential_format.format(**locals())
     unsigned_cred_filename = write_to_tempfile(unsigned_cred)
 
     # Now sign the file with xmlsec1
     # xmlsec1 --sign --privkey-pem privkey.pem,cert.pem 
     # --output signed.xml tosign.xml
-    pems = "%s,%s,%s" % (user_key_file, user_gid.get_filename(),
-                         ma_gid.get_filename())
+    pems = "{},{},{}".format(user_key_file, user_gid.get_filename(),
+                             ma_gid.get_filename())
     xmlsec1 = Credential.get_xmlsec1_path()
     if not xmlsec1:
         raise Exception("Could not locate required 'xmlsec1' program")
@@ -379,8 +378,8 @@ def create_speaks_for(tool_gid, user_gid, ma_gid,
     if sign_proc_output == None:
         logger.info("xmlsec1 returns empty output")
     else:
-        logger.info("Created ABAC credential: '%s speaks_for %s' in file %s" % 
-                    (tool_urn, user_urn, cred_filename))
+        logger.info("Created ABAC credential: '{} speaks_for {}' in file {}"
+                    .format(tool_urn, user_urn, cred_filename))
     os.unlink(unsigned_cred_filename)
 
 
@@ -451,5 +450,5 @@ if __name__ == "__main__":
                                trusted_roots)
 
 
-    print('SPEAKS_FOR = %s' % (gid != tool_gid))
-    print("CERT URN = %s" % gid.get_urn())
+    print('SPEAKS_FOR = {}'.format(gid != tool_gid))
+    print("CERT URN = {}".format(gid.get_urn()))
diff --git a/sfa/util/cache.py b/sfa/util/cache.py
index ee4716c..75b22e4 100644
--- a/sfa/util/cache.py
+++ b/sfa/util/cache.py
@@ -75,7 +75,7 @@ class Cache:
    
     def add(self, key, value, ttl = DEFAULT_CACHE_TTL):
         with self.lock:
-            if self.cache.has_key(key):
+            if key in self.cache:
                 self.cache[key].set_data(value, ttl=ttl)
             else:
                 self.cache[key] = CacheData(value, ttl)
@@ -108,9 +108,9 @@ class Cache:
         return self.__str()    
 
     def save_to_file(self, filename):
-        f = open(filename, 'w')
+        f = open(filename, 'wb')
         pickle.dump(self.cache, f)
 
     def load_from_file(self, filename):
-        f = open(filename, 'r')
+        f = open(filename, 'rb')
         self.cache = pickle.load(f)
diff --git a/sfa/util/callids.py b/sfa/util/callids.py
index ead60bb..6748546 100644
--- a/sfa/util/callids.py
+++ b/sfa/util/callids.py
@@ -44,7 +44,7 @@ class _call_ids_impl (dict):
             logger.warning("_call_ids_impl.should_handle_call_id: could not acquire lock")
             return False
         # we're good to go
-        if self.has_key(call_id):
+        if call_id in self:
             self._purge()
             self._lock.release()
             return True
diff --git a/sfa/util/config.py b/sfa/util/config.py
index 797bed7..e5435a7 100644
--- a/sfa/util/config.py
+++ b/sfa/util/config.py
@@ -2,11 +2,11 @@
 import sys
 import os
 import time
-import ConfigParser
 import tempfile
 import codecs
-from StringIO import StringIO
 from sfa.util.xml import XML
+from sfa.util.py23 import StringIO
+from sfa.util.py23 import ConfigParser
 
 default_config = \
 """
diff --git a/sfa/util/faults.py b/sfa/util/faults.py
index 4a614b1..702b685 100644
--- a/sfa/util/faults.py
+++ b/sfa/util/faults.py
@@ -24,14 +24,14 @@
 # SFA API faults
 #
 
-import xmlrpclib
 from sfa.util.genicode import GENICODE
+from sfa.util.py23 import xmlrpc_client
 
-class SfaFault(xmlrpclib.Fault):
+class SfaFault(xmlrpc_client.Fault):
     def __init__(self, faultCode, faultString, extra = None):
         if extra:
             faultString += ": " + str(extra)
-        xmlrpclib.Fault.__init__(self, faultCode, faultString)
+        xmlrpc_client.Fault.__init__(self, faultCode, faultString)
 
 class Forbidden(SfaFault):
     def __init__(self,  extra = None):
@@ -272,10 +272,10 @@ class SliverDoesNotExist(SfaFault):
     def __str__(self):
         return repr(self.value)
 
-class BadRequestHash(xmlrpclib.Fault):
+class BadRequestHash(xmlrpc_client.Fault):
     def __init__(self, hash = None, extra = None):
         faultString = "bad request hash: " + str(hash)
-        xmlrpclib.Fault.__init__(self, GENICODE.ERROR, faultString)
+        xmlrpc_client.Fault.__init__(self, GENICODE.ERROR, faultString)
 
 class MissingTrustedRoots(SfaFault):
     def __init__(self, value, extra = None):
diff --git a/sfa/util/method.py b/sfa/util/method.py
index e7bdee7..009220c 100644
--- a/sfa/util/method.py
+++ b/sfa/util/method.py
@@ -4,10 +4,11 @@
 #
 
 import time
-from types import IntType, LongType, StringTypes
+from types import IntType, LongType
 import textwrap
 
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringType
 from sfa.util.faults import SfaFault, SfaInvalidAPIMethod, SfaInvalidArgumentCount, SfaInvalidArgument
 
 from sfa.storage.parameter import Parameter, Mixed, python_type, xmlrpc_type
@@ -203,7 +204,7 @@ class Method:
                 try:
                     self.type_check(name, value, item, args)
                     return
-                except SfaInvalidArgument, fault:
+                except SfaInvalidArgument as fault:
                     pass
             raise fault
 
@@ -233,7 +234,7 @@ class Method:
 
         # Strings are a special case. Accept either unicode or str
         # types if a string is expected.
-        if expected_type in StringTypes and isinstance(value, StringTypes):
+        if issubclass(expected_type, StringType) and isinstance(value, StringType):
             pass
 
         # Integers and long integers are also special types. Accept
@@ -247,23 +248,23 @@ class Method:
                                      name)
 
         # If a minimum or maximum (length, value) has been specified
-        if expected_type in StringTypes:
+        if issubclass(expected_type, StringType):
             if min is not None and \
                len(value.encode(self.api.encoding)) < min:
-                raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+                raise SfaInvalidArgument("%s must be at least %d bytes long" % (name, min))
             if max is not None and \
                len(value.encode(self.api.encoding)) > max:
-                raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
+                raise SfaInvalidArgument("%s must be at most %d bytes long" % (name, max))
         elif expected_type in (list, tuple, set):
             if min is not None and len(value) < min:
-                raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
+                raise SfaInvalidArgument("%s must contain at least %d items" % (name, min))
             if max is not None and len(value) > max:
-                raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
+                raise SfaInvalidArgument("%s must contain at most %d items" % (name, max))
         else:
             if min is not None and value < min:
-                raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
+                raise SfaInvalidArgument("%s must be > %s" % (name, str(min)))
             if max is not None and value > max:
-                raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
+                raise SfaInvalidArgument("%s must be < %s" % (name, str(max)))
 
         # If a list with particular types of items is expected
         if isinstance(expected, (list, tuple, set)):
diff --git a/sfa/util/prefixTree.py b/sfa/util/prefixTree.py
index 93b0c5c..0d7a557 100755
--- a/sfa/util/prefixTree.py
+++ b/sfa/util/prefixTree.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 class prefixNode:
 
     def __init__(self, prefix):
@@ -88,10 +90,10 @@ class prefixTree:
         """
         if not node:
             node = self.root
-            print node.prefix
+            print(node.prefix)
 
         for child in node.children:
-            print child.prefix, 
+            print(child.prefix, end=' ') 
          
         for child in node.children:
             self.dump(child)
diff --git a/sfa/util/py23.py b/sfa/util/py23.py
new file mode 100644
index 0000000..d26ebd1
--- /dev/null
+++ b/sfa/util/py23.py
@@ -0,0 +1,28 @@
+# our own simplistic replacement for six
+import sys
+PY3 = sys.version_info[0] == 3
+
+try:
+    StringType = basestring
+except:
+    StringType = str
+
+try:
+    from StringIO import StringIO
+except:
+    from io import StringIO
+
+try:
+    import xmlrpclib as xmlrpc_client
+except:
+    from xmlrpc import client as xmlrpc_client
+
+try:
+    import httplib as http_client
+except:
+    from http import client as http_client
+    
+try:
+    import ConfigParser
+except:
+    import configparser as ConfigParser
diff --git a/sfa/util/sfalogging.py b/sfa/util/sfalogging.py
index 361a243..2b73b48 100644
--- a/sfa/util/sfalogging.py
+++ b/sfa/util/sfalogging.py
@@ -23,6 +23,8 @@
 # IN THE WORK.
 #----------------------------------------------------------------------
 
+from __future__ import print_function
+
 import os, sys
 import traceback
 import logging, logging.handlers
@@ -183,14 +185,14 @@ def profile(logger):
 
 
 if __name__ == '__main__': 
-    print 'testing sfalogging into logger.log'
+    print('testing sfalogging into logger.log')
     logger1=_SfaLogger('logger.log', loggername='std(info)')
     logger2=_SfaLogger('logger.log', loggername='error', level=logging.ERROR)
     logger3=_SfaLogger('logger.log', loggername='debug', level=logging.DEBUG)
     
     for (logger,msg) in [ (logger1,"std(info)"),(logger2,"error"),(logger3,"debug")]:
         
-        print "====================",msg, logger.logger.handlers
+        print("====================",msg, logger.logger.handlers)
    
         logger.enable_console()
         logger.critical("logger.critical")
diff --git a/sfa/util/sfatime.py b/sfa/util/sfatime.py
index 75a2e4a..7435667 100644
--- a/sfa/util/sfatime.py
+++ b/sfa/util/sfatime.py
@@ -20,7 +20,8 @@
 # OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
 # IN THE WORK.
 #----------------------------------------------------------------------
-from types import StringTypes
+from __future__ import print_function
+
 import time
 import datetime
 import dateutil.parser
@@ -28,6 +29,7 @@ import calendar
 import re
 
 from sfa.util.sfalogging import logger
+from sfa.util.py23 import StringType
 
 SFATIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
 
@@ -53,7 +55,7 @@ For safety this can also handle inputs that are either timestamps, or datetimes
 
     # prepare the input for the checks below by
     # casting strings ('1327098335') to ints
-    if isinstance(input, StringTypes):
+    if isinstance(input, StringType):
         try:
             input = int(input)
         except ValueError:
@@ -68,7 +70,7 @@ For safety this can also handle inputs that are either timestamps, or datetimes
     if isinstance (input, datetime.datetime):
         #logger.info ("argument to utcparse already a datetime - doing nothing")
         return input
-    elif isinstance (input, StringTypes):
+    elif isinstance (input, StringType):
         t = dateutil.parser.parse(input)
         if t.utcoffset() is not None:
             t = t.utcoffset() + t.replace(tzinfo=None)
@@ -98,9 +100,9 @@ def add_datetime(input, days=0, hours=0, minutes=0, seconds=0):
 
 if __name__ == '__main__':
         # checking consistency
-    print 20*'X'
-    print ("Should be close to zero: %s"%(datetime_to_epoch(datetime.datetime.utcnow())-time.time()))
-    print 20*'X'
+    print(20*'X')
+    print(("Should be close to zero: %s"%(datetime_to_epoch(datetime.datetime.utcnow())-time.time())))
+    print(20*'X')
     for input in [
             '+2d',
             '+3w',
@@ -112,4 +114,4 @@ if __name__ == '__main__':
             '2014-05-28T15:18',
             '2014-05-28T15:18:30',
     ]:
-        print "input=%20s -> parsed %s"%(input,datetime_to_string(utcparse(input)))
+        print("input=%20s -> parsed %s"%(input,datetime_to_string(utcparse(input))))
diff --git a/sfa/util/storage.py b/sfa/util/storage.py
index 89a2509..9033434 100644
--- a/sfa/util/storage.py
+++ b/sfa/util/storage.py
@@ -19,8 +19,8 @@ class SimpleStorage(dict):
             db_file = open(self.db_filename, 'r')
             dict.__init__(self, eval(db_file.read()))
         elif os.path.exists(self.db_filename) and not os.path.isfile(self.db_filename):
-            raise IOError, '%s exists but is not a file. please remove it and try again' \
-                           % self.db_filename
+            raise IOError('%s exists but is not a file. please remove it and try again' \
+                           % self.db_filename)
         else:
             self.write()
             self.load()
@@ -49,8 +49,8 @@ class XmlStorage(SimpleStorage):
             xml = XML(self.db_filename)
             dict.__init__(self, xml.todict())
         elif os.path.exists(self.db_filename) and not os.path.isfile(self.db_filename):
-            raise IOError, '%s exists but is not a file. please remove it and try again' \
-                           % self.db_filename
+            raise IOError('%s exists but is not a file. please remove it and try again' \
+                           % self.db_filename)
         else:
             self.write()
             self.load()
diff --git a/sfa/util/xml.py b/sfa/util/xml.py
index f46443a..3a38ecc 100755
--- a/sfa/util/xml.py
+++ b/sfa/util/xml.py
@@ -1,10 +1,11 @@
 #!/usr/bin/python 
-from types import StringTypes
 from lxml import etree
-from StringIO import StringIO
 from sfa.util.faults import InvalidXML
 from sfa.rspecs.elements.element import Element
 
+from sfa.util.py23 import StringType
+from sfa.util.py23 import StringIO
+
 # helper functions to help build xpaths
 class XpathFilter:
     @staticmethod
@@ -163,7 +164,7 @@ class XML:
         self.namespaces = namespaces
         self.default_namespace = None
         self.schema = None
-        if isinstance(xml, basestring):
+        if isinstance(xml, StringType):
             self.parse_xml(xml)
         if isinstance(xml, XmlElement):
             self.root = xml
@@ -226,7 +227,7 @@ class XML:
                     if isinstance(val, dict):
                         child_element = etree.SubElement(element, key)
                         self.parse_dict(val, key, child_element)
-                    elif isinstance(val, basestring):
+                    elif isinstance(val, StringType):
                         child_element = etree.SubElement(element, key).text = val
 
             elif isinstance(value, int):
@@ -239,7 +240,7 @@ class XML:
         d=d.copy()
         # looks like iteritems won't stand side-effects
         for k in d.keys():
-            if not isinstance(d[k],StringTypes):
+            if not isinstance(d[k], StringType):
                 del d[k]
 
         element.attrib.update(d)
diff --git a/sfa/util/xrn.py b/sfa/util/xrn.py
index b16ea51..6198f83 100644
--- a/sfa/util/xrn.py
+++ b/sfa/util/xrn.py
@@ -162,7 +162,7 @@ class Xrn:
     def get_hrn_type(self): return (self.hrn, self.type)
 
     def _normalize(self):
-        if self.hrn is None: raise SfaAPIError, "Xrn._normalize"
+        if self.hrn is None: raise SfaAPIError("Xrn._normalize")
         if not hasattr(self,'leaf'): 
             self.leaf=Xrn.hrn_split(self.hrn)[-1]
         # self.authority keeps a list
@@ -211,7 +211,7 @@ class Xrn:
         
 #        if not self.urn or not self.urn.startswith(Xrn.URN_PREFIX):
         if not Xrn.is_urn(self.urn):
-            raise SfaAPIError, "Xrn.urn_to_hrn"
+            raise SfaAPIError("Xrn.urn_to_hrn")
 
         parts = Xrn.urn_split(self.urn)
         type=parts.pop(2)
@@ -249,7 +249,7 @@ class Xrn:
 
 #        if not self.hrn or self.hrn.startswith(Xrn.URN_PREFIX):
         if Xrn.is_urn(self.hrn):
-            raise SfaAPIError, "Xrn.hrn_to_urn, hrn=%s"%self.hrn
+            raise SfaAPIError("Xrn.hrn_to_urn, hrn=%s"%self.hrn)
 
         if self.type and self.type.startswith('authority'):
             self.authority = Xrn.hrn_auth_list(self.hrn)
diff --git a/sfatables/commands/moo.py b/sfatables/commands/moo.py
index bd8c88e..65b7598 100644
--- a/sfatables/commands/moo.py
+++ b/sfatables/commands/moo.py
@@ -1,5 +1,7 @@
 import os, time
 
+from sfa.util.py23 import StringType
+
 class Command:
     commandline_options = []
     help = "Add a new rule"
@@ -122,7 +124,7 @@ class Command:
                 try:
                     self.type_check(name, value, item, args)
                     return
-                except SfaInvalidArgument, fault:
+                except SfaInvalidArgument as fault:
                     pass
             raise fault
 
@@ -152,7 +154,7 @@ class Command:
 
         # Strings are a special case. Accept either unicode or str
         # types if a string is expected.
-        if expected_type in StringTypes and isinstance(value, StringTypes):
+        if issubclass(expected_type, StringType) and isinstance(value, StringType):
             pass
 
         # Integers and long integers are also special types. Accept
@@ -167,23 +169,23 @@ class Command:
                                      name)
 
         # If a minimum or maximum (length, value) has been specified
-        if expected_type in StringTypes:
+        if issubclass(expected_type, StringType):
             if min is not None and \
                len(value.encode(self.api.encoding)) < min:
-                raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+                raise SfaInvalidArgument("%s must be at least %d bytes long" % (name, min))
             if max is not None and \
                len(value.encode(self.api.encoding)) > max:
-                raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
+                raise SfaInvalidArgument("%s must be at most %d bytes long" % (name, max))
         elif expected_type in (list, tuple, set):
             if min is not None and len(value) < min:
-                raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
+                raise SfaInvalidArgument("%s must contain at least %d items" % (name, min))
             if max is not None and len(value) > max:
-                raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
+                raise SfaInvalidArgument("%s must contain at most %d items" % (name, max))
         else:
             if min is not None and value < min:
-                raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
+                raise SfaInvalidArgument("%s must be > %s" % (name, str(min)))
             if max is not None and value > max:
-                raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
+                raise SfaInvalidArgument("%s must be < %s" % (name, str(max)))
 
         # If a list with particular types of items is expected
         if isinstance(expected, (list, tuple, set)):
diff --git a/tests/testCert.py b/tests/testCert.py
index 0a6a90c..b2e8133 100755
--- a/tests/testCert.py
+++ b/tests/testCert.py
@@ -1,6 +1,6 @@
 import unittest
-import xmlrpclib
 from sfa.trust.certificate import Certificate, Keypair
+from sfa.util.py23 import xmlrpc_client
 
 class TestCert(unittest.TestCase):
    def setUp(self):
@@ -42,7 +42,7 @@ class TestCert(unittest.TestCase):
       # try something a bit more complicated, like an xmlrpc encoding of
       # some parameters
       cert = Certificate(subject="test")
-      data = xmlrpclib.dumps((1, "foo", ["a", "b"], {"c": "d", "e": "f"}, True))
+      data = xmlrpc_client.dumps((1, "foo", ["a", "b"], {"c": "d", "e": "f"}, True))
       cert.set_data(data)
       self.assertEqual(cert.get_data(), data)
 
diff --git a/tests/testKeypair.py b/tests/testKeypair.py
index 424740a..3d8d40a 100755
--- a/tests/testKeypair.py
+++ b/tests/testKeypair.py
@@ -25,11 +25,11 @@ class TestKeypair(unittest.TestCase):
 
       self.assertEqual(k.as_pem(), k2.as_pem())
 
-   def test_get_m2_pkey(self):
+   def test_get_m2_pubkey(self):
       k = Keypair()
       k.create()
 
-      m2 = k.get_m2_pkey()
+      m2 = k.get_m2_pubkey()
       self.assert_(m2 != None)
 
    def test_get_openssl_pkey(self):
diff --git a/tools/depgraph2dot.py b/tools/depgraph2dot.py
index b8ecbce..ab07a31 100755
--- a/tools/depgraph2dot.py
+++ b/tools/depgraph2dot.py
@@ -43,7 +43,7 @@ class pydepgraphdot:
         # normalise our input data
         for k,d in p.items():
             for v in d.keys():
-                if not p.has_key(v):
+                if v not in p:
                     p[v] = {}
                     
         f = self.get_output_file()                    
diff --git a/tools/py2depgraph.py b/tools/py2depgraph.py
index 022add3..ef3b6f8 100755
--- a/tools/py2depgraph.py
+++ b/tools/py2depgraph.py
@@ -23,7 +23,7 @@
 import sys, pprint
 import modulefinder
 
-focus=[ 'sfa' , 'OpenSSL', 'M2Crypto', 'xmlrpclib', 'threading' ]
+focus = [ 'sfa' , 'OpenSSL', 'M2Crypto', 'xmlrpclib', 'threading' ]
 
 class mymf(modulefinder.ModuleFinder):
     def __init__(self,*args,**kwargs):
diff --git a/tools/reset_gids.py b/tools/reset_gids.py
index 21e25ce..e30ed32 100755
--- a/tools/reset_gids.py
+++ b/tools/reset_gids.py
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 # -*- coding:utf-8 -*-
 
-import types
-
 from sfa.storage.model import *
 from sfa.storage.alchemy import *
 from sfa.trust.gid import create_uuid
@@ -22,7 +20,7 @@ def fix_users():
             pub_key=getattr(record,'reg_keys',None)
             if len(pub_key) > 0:
                 # use only first key in record
-                if pub_key and isinstance(pub_key, types.ListType): pub_key = pub_key[0]
+                if pub_key and isinstance(pub_key, list): pub_key = pub_key[0]
                 pub_key = pub_key.key
                 pkey = convert_public_key(pub_key)
             urn = Xrn (xrn=record.hrn, type='user').get_urn()
diff --git a/wsdl/sfa2wsdl.py b/wsdl/sfa2wsdl.py
index a9e3c32..2eb9463 100755
--- a/wsdl/sfa2wsdl.py
+++ b/wsdl/sfa2wsdl.py
@@ -17,6 +17,8 @@ from optparse import OptionParser
 
 from sfa.storage.parameter import Parameter, Mixed
 
+from sfa.util.py23 import StringType
+
 plc_ns="http://www.planet-lab.org/sfa"
 
 class SoapError(Exception):
@@ -162,11 +164,11 @@ class WSDLGen:
             return "xsd:boolean"
         elif arg_type == FloatType:
             return "xsd:double"
-        elif arg_type in StringTypes:
+        elif issubclass(arg_type, StringType):
             return "xsd:string"
         else:
            pdb.set_trace()
-           raise SoapError, "Cannot handle %s objects" % arg_type
+           raise SoapError("Cannot handle %s objects" % arg_type)
 
     def param_type(self, arg):
         return (self.name_complex_type(arg))
@@ -188,7 +190,7 @@ class WSDLGen:
             in_el.setAttribute("name", method + "_in")
 
             for service_name in function.interfaces:
-                if (self.services.has_key(service_name)):
+                if (service_name in self.services):
                     if (not method in self.services[service_name]):
                         self.services[service_name].append(method)
                 else:
diff --git a/xmlbuilder-0.9/LICENSE b/xmlbuilder-0.9/LICENSE
deleted file mode 100644
index 0d0f57c..0000000
--- a/xmlbuilder-0.9/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License
-
-Copyright (c) 2008 Konstantin Danilov aka koder
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/xmlbuilder-0.9/MANIFEST.in b/xmlbuilder-0.9/MANIFEST.in
deleted file mode 100644
index 207725e..0000000
--- a/xmlbuilder-0.9/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include xmlbuild/docs *.rst
-include . LICENSE
\ No newline at end of file
diff --git a/xmlbuilder-0.9/PKG-INFO b/xmlbuilder-0.9/PKG-INFO
deleted file mode 100644
index d931c15..0000000
--- a/xmlbuilder-0.9/PKG-INFO
+++ /dev/null
@@ -1,80 +0,0 @@
-Metadata-Version: 1.0
-Name: xmlbuilder
-Version: 0.9
-Summary: Pythonic way to create xml files
-Home-page: http://pypi.python.org/pypi/xmlbuilder
-Author: koder
-Author-email: koder_dot_mail@gmail_dot_com
-License: MIT
-Download-URL: http://pypi.python.org/pypi/xmlbuilder
-Description: Example of usage:
-        -----------------
-        
-        
-        from __future__ import with_statement
-        from xmlbuilder import XMLBuilder
-        x = XMLBuilder(format=True)
-        with x.root(a = 1):
-        with x.data:
-        [x &lt;&lt; ('node',{'val':i}) for i in range(10)]
-        
-        print str(x)
-        
-        will print
-        
-        &lt;root a="1"&gt;
-        &lt;data&gt;
-        &lt;node val="0" /&gt;
-        &lt;node val="1" /&gt;
-        &lt;node val="2" /&gt;
-        &lt;node val="3" /&gt;
-        &lt;node val="4" /&gt;
-        &lt;node val="5" /&gt;
-        &lt;node val="6" /&gt;
-        &lt;node val="7" /&gt;
-        &lt;node val="8" /&gt;
-        &lt;node val="9" /&gt;
-        &lt;/data&gt;
-        &lt;/root&gt;
-        
-        Mercurial repo:http://hg.assembla.com/MyPackages/
-        
-        Documentations
-        --------------
-        `XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to
-        simplify xml files creation as much as possible. Althow it can produce
-        structured result with identated child tags. `XMLBuilder` use python `with`
-        statement to define xml tag levels and `&lt;&lt;` operator for simple cases -
-        text and tag without childs.
-        
-        First we need to create xmlbuilder
-        
-        from xmlbuilder import XMLBuilder
-        # params - encoding = 'utf8',
-        # builder = None, - ElementTree.TreeBuilder
-        # tab_level = None, - current tab l;evel - for formatted output only
-        # format = False, - create formatted output
-        # tab_step = " " * 4 - indentation step
-        xml = XMLBuilder()
-        
-        
-        Use `with` statement to make document structure
-        #create and open tag 'root_tag' with text 'text' and attributes
-        with xml.root_tag(text,attr1=val1,attr2=val2):
-        #create and open tag 'sub_tag'
-        with xml.sub_tag(text,attr3=val3):
-        #create tag which are not valid python identificator
-        with xml('one-more-sub-tag',attr7=val37):
-        xml &lt;&lt; "Some textual data"
-        #here tag 'one-more-sub-tag' are closed
-        #Tags without children can be created using `&lt;&lt;` operator
-        for val in range(15):
-        xml &lt;&lt; ('message',"python rocks!"[:i])
-        #create 15 child tag like &lt;message&gt; python r&lt;/message&gt;
-        #all tags closed
-        node = ~x # get etree.ElementTree object
-        xml_data = str(x)
-        unicode_xml_data = unicode(x)
-        
-Keywords: xml
-Platform: UNKNOWN
diff --git a/xmlbuilder-0.9/README.txt b/xmlbuilder-0.9/README.txt
deleted file mode 100644
index 7a7131f..0000000
--- a/xmlbuilder-0.9/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Pythonic way to build xml files
\ No newline at end of file
diff --git a/xmlbuilder-0.9/setup.cfg b/xmlbuilder-0.9/setup.cfg
deleted file mode 100644
index b14b0bc..0000000
--- a/xmlbuilder-0.9/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[egg_info]
-tag_build = 
-tag_date = 0
-tag_svn_revision = 0
-
diff --git a/xmlbuilder-0.9/setup.py b/xmlbuilder-0.9/setup.py
deleted file mode 100644
index bac4b12..0000000
--- a/xmlbuilder-0.9/setup.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-#-------------------------------------------------------------------------------
-import os
-import sys
-import glob
-import os.path
-from setuptools import setup
-#from distutils.core import setup
-#-------------------------------------------------------------------------------
-if 'upload' in sys.argv:
-    # for .pypirc file
-    try:
-        os.environ['HOME']
-    except KeyError:
-        os.environ['HOME'] = '..\\'
-#-------------------------------------------------------------------------------
-fpath = lambda x : os.path.join(*x.split('/'))
-#-------------------------------------------------------------------------------
-PYPI_URL = 'http://pypi.python.org/pypi/xmlbuilder'
-ld = open(fpath('xmlbuilder/docs/long_descr.rst')).read()
-ld = ld.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
-setup(
-    name = "xmlbuilder",
-    fullname = "xmlbuilder",
-    version = "0.9",
-    packages = ["xmlbuilder"],
-    package_dir = {'xmlbuilder':'xmlbuilder'},
-    author = "koder",
-    author_email = "koder_dot_mail@gmail_dot_com",
-    maintainer = 'koder',
-    maintainer_email = "koder_dot_mail@gmail_dot_com",
-    description = "Pythonic way to create xml files",
-    license = "MIT",
-    keywords = "xml",
-    test_suite = "xml_buider.tests",
-    url = PYPI_URL,
-    download_url = PYPI_URL,
-    long_description = ld,
-    #include_package_data = True,
-    #package_data = {'xmlbuilder':["docs/*.rst"]},
-    #data_files = [('', ['xmlbuilder/docs/long_descr.rst'])]
-)
-#-------------------------------------------------------------------------------
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO b/xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO
deleted file mode 100644
index 569b8ab..0000000
--- a/xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO
+++ /dev/null
@@ -1,80 +0,0 @@
-Metadata-Version: 1.1
-Name: xmlbuilder
-Version: 0.9
-Summary: Pythonic way to create xml files
-Home-page: http://pypi.python.org/pypi/xmlbuilder
-Author: koder
-Author-email: koder_dot_mail@gmail_dot_com
-License: MIT
-Download-URL: http://pypi.python.org/pypi/xmlbuilder
-Description: Example of usage:
-        -----------------
-        
-        
-        from __future__ import with_statement
-        from xmlbuilder import XMLBuilder
-        x = XMLBuilder(format=True)
-        with x.root(a = 1):
-            with x.data:
-                [x &lt;&lt; ('node',{'val':i}) for i in range(10)]
-        
-        print str(x)
-        
-        will print
-        
-        &lt;root a="1"&gt;
-            &lt;data&gt;
-                &lt;node val="0" /&gt;
-                &lt;node val="1" /&gt;
-                &lt;node val="2" /&gt;
-                &lt;node val="3" /&gt;
-                &lt;node val="4" /&gt;
-                &lt;node val="5" /&gt;
-                &lt;node val="6" /&gt;
-                &lt;node val="7" /&gt;
-                &lt;node val="8" /&gt;
-                &lt;node val="9" /&gt;
-            &lt;/data&gt;
-        &lt;/root&gt;
-        
-        Mercurial repo:http://hg.assembla.com/MyPackages/
-        
-        Documentations
-        --------------
-        `XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to
-        simplify xml files creation as much as possible. Althow it can produce
-        structured result with identated child tags. `XMLBuilder` use python `with`
-        statement to define xml tag levels and `&lt;&lt;` operator for simple cases -
-        text and tag without childs.
-        
-        First we need to create xmlbuilder
-        
-            from xmlbuilder import XMLBuilder
-            # params - encoding = 'utf8',
-            # builder = None, - ElementTree.TreeBuilder 
-            # tab_level = None, - current tab l;evel - for formatted output only
-            # format = False, - create formatted output
-            # tab_step = " " * 4 - indentation step
-            xml = XMLBuilder()
-        
-        
-        Use `with` statement to make document structure
-            #create and open tag 'root_tag' with text 'text' and attributes
-            with xml.root_tag(text,attr1=val1,attr2=val2):
-                #create and open tag 'sub_tag'
-                with xml.sub_tag(text,attr3=val3):
-                    #create tag which are not valid python identificator
-                    with xml('one-more-sub-tag',attr7=val37):
-                        xml &lt;&lt; "Some textual data"
-                    #here tag 'one-more-sub-tag' are closed
-        			#Tags without children can be created using `&lt;&lt;` operator
-                    for val in range(15):
-                        xml &lt;&lt; ('message',"python rocks!"[:i])
-                    #create 15 child tag like &lt;message&gt; python r&lt;/message&gt;
-            #all tags closed
-            node = ~x # get etree.ElementTree object
-            xml_data = str(x)
-            unicode_xml_data = unicode(x)
-        
-Keywords: xml
-Platform: UNKNOWN
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt
deleted file mode 100644
index 4cc27de..0000000
--- a/xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-LICENSE
-MANIFEST.in
-PKG-INFO
-README.txt
-setup.cfg
-setup.py
-xmlbuilder/__init__.py
-xmlbuilder.egg-info/PKG-INFO
-xmlbuilder.egg-info/SOURCES.txt
-xmlbuilder.egg-info/dependency_links.txt
-xmlbuilder.egg-info/top_level.txt
-xmlbuilder/docs/long_descr.rst
-xmlbuilder/tests/__init__.py
\ No newline at end of file
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt
deleted file mode 100644
index 9f1d486..0000000
--- a/xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-xmlbuilder
diff --git a/xmlbuilder-0.9/xmlbuilder/__init__.py b/xmlbuilder-0.9/xmlbuilder/__init__.py
deleted file mode 100644
index 24ce7a5..0000000
--- a/xmlbuilder-0.9/xmlbuilder/__init__.py
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env python
-#-------------------------------------------------------------------------------
-from __future__ import with_statement
-#-------------------------------------------------------------------------------
-from xml.etree.ElementTree import TreeBuilder,tostring
-#-------------------------------------------------------------------------------
-__all__ = ["XMLBuilder"]
-__doc__ = """
-XMLBuilder is simple library build on top of ElementTree.TreeBuilder to
-simplify xml files creation as much as possible. Althow it can produce
-structured result with identated child tags. `XMLBuilder` use python `with`
-statement to define xml tag levels and `<<` operator for simple cases -
-text and tag without childs.
-
-from __future__ import with_statement
-from xmlbuilder import XMLBuilder
-x = XMLBuilder(format=True)
-with x.root(a = 1):
-    with x.data:
-        [x << ('node',{'val':i}) for i in range(10)]
-
-etree_node = ~x
-print str(x)
-"""
-#-------------------------------------------------------------------------------
-class _XMLNode(object):
-    """Class for internal usage"""
-    def __init__(self,parent,name,builder):
-        self.builder = builder
-        self.name = name
-        self.text = []
-        self.attrs = {}
-        self.entered = False
-        self.parent = parent
-    def __call__(self,*dt,**mp):
-        text = "".join(dt)
-        if self.entered:
-            self.builder.data(text)
-        else:
-            self.text.append(text)
-        if self.entered:
-            raise ValueError("Can't add attributes to already opened element")
-        smp = dict((k,str(v)) for k,v in mp.items())
-        self.attrs.update(smp)
-        return self
-    def __enter__(self):
-        self.parent += 1
-        self.builder.start(self.name,self.attrs)
-        self.builder.data("".join(self.text))
-        self.entered = True
-        return self
-    def __exit__(self,x,y,z):
-        self.parent -= 1
-        self.builder.end(self.name)
-        return False
-#-------------------------------------------------------------------------------
-class XMLBuilder(object):
-    """XmlBuilder(encoding = 'utf-8', # result xml file encoding
-            builder = None, #etree.TreeBuilder or compatible class
-            tab_level = None, #current tabulation level - string
-            format = False,   # make formatted output
-            tab_step = " " * 4) # tabulation step
-    use str(builder) or unicode(builder) to get xml text or
-    ~builder to obtaine etree.ElementTree
-    """
-    def __init__(self,encoding = 'utf-8',
-                      builder = None,
-                      tab_level = None,
-                      format = False,
-                      tab_step = " " * 4):
-        self.__builder = builder or TreeBuilder()
-        self.__encoding = encoding 
-        if format :
-            if tab_level is None:
-                tab_level = ""
-        if tab_level is not None:
-            if not format:
-                raise ValueError("format is False, but tab_level not None")
-        self.__tab_level = tab_level # current format level
-        self.__tab_step = tab_step   # format step
-        self.__has_sub_tag = False   # True, if current tag had childrens
-        self.__node = None
-    # called from _XMLNode when tag opened
-    def __iadd__(self,val):
-        self.__has_sub_tag = False
-        if self.__tab_level is not None:
-            self.__builder.data("\n" + self.__tab_level)
-            self.__tab_level += self.__tab_step
-        return self
-    # called from XMLNode when tag closed
-    def __isub__(self,val):
-        if self.__tab_level is not None:
-            self.__tab_level = self.__tab_level[:-len(self.__tab_step)]
-            if self.__has_sub_tag:
-                self.__builder.data("\n" + self.__tab_level)
-        self.__has_sub_tag = True
-        return self
-    def __getattr__(self,name):
-        return _XMLNode(self,name,self.__builder)
-    def __call__(self,name,*dt,**mp):
-        x = _XMLNode(self,name,self.__builder)
-        x(*dt,**mp)
-        return x
-    #create new tag or add text
-    #possible shift values
-    #string - text
-    #tuple(string1,string2,dict) - new tag with name string1,attrs = dict,and text string2
-    #dict and string2 are optional
-    def __lshift__(self,val):
-        if isinstance(val,basestring):
-            self.__builder.data(val)
-        else:
-            self.__has_sub_tag = True
-            assert hasattr(val,'__len__'),\
-                'Shifted value should be tuple or list like object not %r' % val
-            assert hasattr(val,'__getitem__'),\
-                'Shifted value should be tuple or list like object not %r' % val
-            name = val[0]
-            if len(val) == 3:
-                text = val[1]
-                attrs = val[2]
-            elif len(val) == 1:
-                text = ""
-                attrs = {}
-            elif len(val) == 2:
-                if isinstance(val[1],basestring):
-                    text = val[1]
-                    attrs = {}
-                else:
-                    text = ""
-                    attrs = val[1]
-            if self.__tab_level is not None:
-                self.__builder.data("\n" + self.__tab_level)
-            self.__builder.start(name,
-                                 dict((k,str(v)) for k,v in attrs.items()))
-            if text:
-                self.__builder.data(text)
-            self.__builder.end(name)
-        return self # to allow xml << some1 << some2 << some3
-    #close builder
-    def __invert__(self):
-        if self.__node is not None:
-            return self.__node
-        self.__node = self.__builder.close()
-        return self.__node
-    def __str__(self):
-        """return generated xml"""
-        return tostring(~self,self.__encoding)
-    def __unicode__(self):
-        """return generated xml"""
-        res = tostring(~self,self.__encoding)
-        return res.decode(self.__encoding)
-#-------------------------------------------------------------------------------
diff --git a/xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst b/xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst
deleted file mode 100644
index 4e82bc8..0000000
--- a/xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-Example of usage:
------------------
-
-
-from __future__ import with_statement
-from xmlbuilder import XMLBuilder
-x = XMLBuilder(format=True)
-with x.root(a = 1):
-    with x.data:
-        [x << ('node',{'val':i}) for i in range(10)]
-
-print str(x)
-
-will print
-
-<root a="1">
-    <data>
-        <node val="0" />
-        <node val="1" />
-        <node val="2" />
-        <node val="3" />
-        <node val="4" />
-        <node val="5" />
-        <node val="6" />
-        <node val="7" />
-        <node val="8" />
-        <node val="9" />
-    </data>
-</root>
-
-Mercurial repo:http://hg.assembla.com/MyPackages/
-
-Documentations
---------------
-`XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to
-simplify xml files creation as much as possible. Althow it can produce
-structured result with identated child tags. `XMLBuilder` use python `with`
-statement to define xml tag levels and `<<` operator for simple cases -
-text and tag without childs.
-
-First we need to create xmlbuilder
-
-    from xmlbuilder import XMLBuilder
-    # params - encoding = 'utf8',
-    # builder = None, - ElementTree.TreeBuilder 
-    # tab_level = None, - current tab l;evel - for formatted output only
-    # format = False, - create formatted output
-    # tab_step = " " * 4 - indentation step
-    xml = XMLBuilder()
-
-
-Use `with` statement to make document structure
-    #create and open tag 'root_tag' with text 'text' and attributes
-    with xml.root_tag(text,attr1=val1,attr2=val2):
-        #create and open tag 'sub_tag'
-        with xml.sub_tag(text,attr3=val3):
-            #create tag which are not valid python identificator
-            with xml('one-more-sub-tag',attr7=val37):
-                xml << "Some textual data"
-            #here tag 'one-more-sub-tag' are closed
-			#Tags without children can be created using `<<` operator
-            for val in range(15):
-                xml << ('message',"python rocks!"[:i])
-            #create 15 child tag like <message> python r</message>
-    #all tags closed
-    node = ~x # get etree.ElementTree object
-    xml_data = str(x)
-    unicode_xml_data = unicode(x)
diff --git a/xmlbuilder-0.9/xmlbuilder/tests/__init__.py b/xmlbuilder-0.9/xmlbuilder/tests/__init__.py
deleted file mode 100644
index 43a67b1..0000000
--- a/xmlbuilder-0.9/xmlbuilder/tests/__init__.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env python
-from __future__ import with_statement
-#-------------------------------------------------------------------------------
-import unittest
-from xml.etree.ElementTree import fromstring
-#-------------------------------------------------------------------------------
-from xmlbuilder import XMLBuilder
-#-------------------------------------------------------------------------------
-def xmlStructureEqual(xml1,xml2):
-    tree1 = fromstring(xml1)
-    tree2 = fromstring(xml2)
-    return _xmlStructureEqual(tree1,tree2)
-#-------------------------------------------------------------------------------
-def _xmlStructureEqual(tree1,tree2):
-    if tree1.tag != tree2.tag:
-        return False
-    attr1 = list(tree1.attrib.items())
-    attr1.sort()
-    attr2 = list(tree2.attrib.items())
-    attr2.sort()
-    if attr1 != attr2:
-        return False
-    return tree1.getchildren() == tree2.getchildren()
-#-------------------------------------------------------------------------------
-result1 = \
-"""
-<root>
-    <array />
-    <array len="10">
-        <el val="0" />
-        <el val="1">xyz</el>
-        <el val="2">abc</el>
-        <el val="3" />
-        <el val="4" />
-        <el val="5" />
-        <sup-el val="23">test  </sup-el>
-    </array>
-</root>
-""".strip()
-#-------------------------------------------------------------------------------
-class TestXMLBuilder(unittest.TestCase):
-    def testShift(self):
-        xml = (XMLBuilder() << ('root',))
-        self.assertEqual(str(xml),"<root />")
-        
-        xml = XMLBuilder()
-        xml << ('root',"some text")
-        self.assertEqual(str(xml),"<root>some text</root>")
-        
-        xml = XMLBuilder()
-        xml << ('root',{'x':1,'y':'2'})
-        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>some text</root>"))
-        
-        xml = XMLBuilder()
-        xml << ('root',{'x':1,'y':'2'})
-        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'></root>"))
-
-        xml = XMLBuilder()
-        xml << ('root',{'x':1,'y':'2'})
-        self.assert_(not xmlStructureEqual(str(xml),"<root x='2' y='2'></root>"))
-
-        
-        xml = XMLBuilder()
-        xml << ('root',"gonduras.ua",{'x':1,'y':'2'})
-        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>gonduras.ua</root>"))
-        
-        xml = XMLBuilder()
-        xml << ('root',"gonduras.ua",{'x':1,'y':'2'})
-        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>gonduras.com</root>"))
-    #---------------------------------------------------------------------------
-    def testWith(self):
-        xml = XMLBuilder()
-        with xml.root(lenght = 12):
-            pass
-        self.assertEqual(str(xml),'<root lenght="12" />')
-        
-        xml = XMLBuilder()
-        with xml.root():
-            xml << "text1" << "text2" << ('some_node',)
-        self.assertEqual(str(xml),"<root>text1text2<some_node /></root>")
-    #---------------------------------------------------------------------------
-    def testFormat(self):
-        x = XMLBuilder('utf-8',format = True)
-        with x.root():
-            x << ('array',)
-            with x.array(len = 10):
-                with x.el(val = 0):
-                    pass
-                with x.el('xyz',val = 1):
-                    pass
-                x << ("el","abc",{'val':2}) << ('el',dict(val=3))
-                x << ('el',dict(val=4)) << ('el',dict(val='5'))
-                with x('sup-el',val = 23):
-                    x << "test  "
-        self.assertEqual(str(x),result1)
-#-------------------------------------------------------------------------------
-if __name__ == '__main__':
-    unittest.main()
-#-------------------------------------------------------------------------------

Back to top


Skipping module comgt - failure: Something is wrong with module comgt, cannot determine 'taglevel' - exiting


Skipping module DistributedRateLimiting - failure: Command git clone root@git.onelab.eu:/git/DistributedRateLimiting.git /root/all-modules-git/DistributedRateLimiting failed


Skipping module PingOfDeath - failure: Command git clone root@git.onelab.eu:/git/PingOfDeath.git /root/all-modules-git/PingOfDeath failed


Skipping module PLCRT - failure: Command git clone root@git.onelab.eu:/git/PLCRT.git /root/all-modules-git/PLCRT failed


Skipping module pyplnet - failure: Command git merge --ff origin/master failed