From 567b2eb0fcdf0d9c5e8024afdaaf30ea95bba0cf Mon Sep 17 00:00:00 2001
From: Tony Mack <tmack@paris.CS.Princeton.EDU>
Date: Thu, 11 Oct 2012 14:09:06 -0400
Subject: [PATCH] bug fixes

---
 PLC/API.py                           |  3 +-
 PLC/GPG.py                           | 18 +++------
 PLC/InterfaceTags.py                 |  2 +-
 PLC/Keys.py                          | 57 +++++++++++-----------------
 PLC/Methods/AddNode.py               | 20 +++-------
 PLC/Methods/AddNodeGroup.py          |  2 +-
 PLC/Methods/AddPCUType.py            |  1 -
 PLC/Methods/AddRoleToPerson.py       | 10 +----
 PLC/Methods/BootNotifyOwners.py      |  2 +-
 PLC/Methods/DeleteRoleFromTagType.py |  2 +-
 PLC/Methods/GetBootMedium.py         |  2 +-
 PLC/Methods/GetNodeFlavour.py        |  2 -
 PLC/Methods/GetPersons.py            |  2 +-
 PLC/Methods/GetSliceFamily.py        | 34 +++++++++--------
 PLC/Methods/GetSlivers.py            |  2 -
 PLC/Methods/UpdateKey.py             | 22 -----------
 PLC/Methods/UpdateNode.py            | 35 ++---------------
 PLC/Methods/UpdatePerson.py          |  5 +--
 PLC/Methods/UpdateSite.py            |  4 +-
 PLC/Methods/UpdateSlice.py           | 38 -------------------
 PLC/NodeGroups.py                    |  6 +--
 PLC/Nodes.py                         | 14 +++----
 PLC/PCUTypes.py                      |  2 +-
 PLC/PersonTags.py                    |  2 +-
 PLC/Shell.py                         |  4 --
 PLC/Slices.py                        | 55 +++++++++++++++++++++++++--
 PLCAPI.spec                          |  1 +
 27 files changed, 132 insertions(+), 215 deletions(-)

diff --git a/PLC/API.py b/PLC/API.py
index 16d3a61a..cd0520b4 100644
--- a/PLC/API.py
+++ b/PLC/API.py
@@ -171,7 +171,8 @@ class PLCAPI:
             module = __import__(fullpath, globals(), locals(), [classname])
             return getattr(module, classname)(self)
         except ImportError, AttributeError:
-            raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
+            raise
+            #raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath)
 
     def call(self, source, method, *args):
         """
diff --git a/PLC/GPG.py b/PLC/GPG.py
index ffecff09..f2397411 100644
--- a/PLC/GPG.py
+++ b/PLC/GPG.py
@@ -14,7 +14,8 @@ import shutil
 from types import StringTypes
 from StringIO import StringIO
 from xml.dom import minidom
-from xml.dom.ext import Canonicalize
+import lxml
+from lxml import etree
 from subprocess import Popen, PIPE, call
 from tempfile import NamedTemporaryFile, mkdtemp
 
@@ -28,17 +29,10 @@ def canonicalize(args, methodname = None, methodresponse = False):
     """
 
     xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
-    dom = minidom.parseString(xml)
-
-    # Canonicalize(), though it claims to, does not encode unicode
-    # nodes to UTF-8 properly and throws an exception unless you write
-    # the stream to a file object, so just encode it ourselves.
-    buf = StringIO()
-    Canonicalize(dom, output = buf)
-    xml = buf.getvalue().encode('utf-8')
-
-    return xml
-
+    parser = etree.XMLParser(remove_blank_text=True)        
+    tree = etree.parse(StringIO(xml), parser)
+    return etree.tostring(tree.getroot(), method='c14n')
+ 
 def gpg_export(keyring, armor = True):
     """
     Exports the specified public keyring file.
diff --git a/PLC/InterfaceTags.py b/PLC/InterfaceTags.py
index 4ed6e96b..3c0644e2 100644
--- a/PLC/InterfaceTags.py
+++ b/PLC/InterfaceTags.py
@@ -56,7 +56,7 @@ class InterfaceTags(list):
         if isinstance(interface_tag_filter, (list, tuple, set, int, long)):
             interface_tags = InterfaceTag().select(filter={'interface_tag_id': interface_tag_filter})
         elif isinstance(interface_tag_filter, dict):
-            interface_tags = InterfaceTag().select(filter=interface_tag_filter})
+            interface_tags = InterfaceTag().select(filter=interface_tag_filter)
         else:
             raise PLCInvalidArgument, "Wrong interface setting filter %r"%interface_tag_filter
 
diff --git a/PLC/Keys.py b/PLC/Keys.py
index 6f828384..c1744703 100644
--- a/PLC/Keys.py
+++ b/PLC/Keys.py
@@ -6,9 +6,9 @@ from PLC.Filter import Filter
 from PLC.Debug import profile
 from PLC.Table import Row, Table
 from PLC.KeyTypes import KeyType, KeyTypes
-from PLC.NovaTable import NovaObject, NovaTable
+from PLC.Storage.AlchemyObject import AlchemyObj
 
-class Key(NovaObject):
+class Key(AlchemyObj):
     """
     Representation of a row in the keys table. To use, instantiate with a
     dict of values. Update as you would a dict. Commit to the database
@@ -16,24 +16,30 @@ class Key(NovaObject):
     """
 
     tablename = 'keys'
-    join_tables = ['person_key', 'peer_key']
     fields = {
-        'id': Parameter(str, "Key identifier", primary_key=True),
-        #'key_type': Parameter(str, "Key type"),
-        'public_key': Parameter(str, "Key string", max = 4096),
-        'name': Parameter(str, "Key name",)
+        'key_id': Parameter(str, "Key identifier", primary_key=True),
+        'key_type': Parameter(str, "Key type"),
+        'key': Parameter(str, "Key string", max = 4096), # backwards compatibility
+        'name': Parameter(str, "Key name"),
+        'uuid': Parameter(str, "Key name", ro=True)
         }
 
-    def sync(self, insert = False, validate = True):
-        NovaObject.sync(self, insert, validate)
-        if insert == True or 'id' not in self:
-            self.object = self.api.client_shell.nova.keypairs.create(self.id,
-                                                                     self.key)
-        else:
-            self.object = self.api.client_shell.nova.keypairs.update(self.id, dict(self))
+    def sync(self, commit = True, validate = True):
+        # sync the nova record and the plc record
+        assert 'key' in self
+        assert 'name' in self
+        AlchemyObj.sync(self, commit=commit, validate=validate)
+        nova_key = {
+            'public_key': self['key'],
+            'name': self['name'] 
+        }
+        self.object = self.api.client_shell.nova.keypairs.create(**nova_key)
+        self['uuid'] = self.object.uuid
+        AlchemyObj.insert(self, dict(self))
 
     def delete(self):
-        assert 'id' in self
+        assert 'key_id' in self
+        assert 'name' in self
         self.api.client_shell.nova.keypairs.delete(self.id)
 
     def validate_public_key(self, key):
@@ -79,26 +85,9 @@ class Key(NovaObject):
         assert 'key_id' in self
         assert 'key' in self
 
-        # Get all matching keys
-        rows = self.api.db.selectall("SELECT key_id FROM keys WHERE key = %(key)s",
-                                     self)
-        key_ids = [row['key_id'] for row in rows]
-        assert key_ids
-        assert self['key_id'] in key_ids
-
-        # Keep the keys in the table
-        self.api.db.do("UPDATE keys SET is_blacklisted = True" \
-                       " WHERE key_id IN (%s)" % ", ".join(map(str, key_ids)))
-
-        # But disassociate them from all join tables
-        for table in self.join_tables:
-            self.api.db.do("DELETE FROM %s WHERE key_id IN (%s)" % \
-                           (table, ", ".join(map(str, key_ids))))
-
-        if commit:
-            self.api.db.commit()
+        pass
 
-class Keys(NovaTable):
+class Keys(list):
     """
     Representation of row(s) from the keys table in the
     database.
diff --git a/PLC/Methods/AddNode.py b/PLC/Methods/AddNode.py
index 3a5ce903..830444ee 100644
--- a/PLC/Methods/AddNode.py
+++ b/PLC/Methods/AddNode.py
@@ -10,7 +10,8 @@ from PLC.NodeTags import NodeTags, NodeTag
 from PLC.Methods.AddNodeTag import AddNodeTag
 from PLC.Methods.UpdateNodeTag import UpdateNodeTag
 
-can_update = ['hostname', 'node_type', 'boot_state', 'model', 'version']
+can_update = lambda (field, value): field in \
+            ['hostname', 'node_type', 'boot_state', 'model', 'version']
 
 class AddNode(Method):
     """
@@ -25,8 +26,7 @@ class AddNode(Method):
 
     roles = ['admin', 'pi', 'tech']
 
-    accepted_fields = Row.accepted_fields(can_update,Node.fields)
-    accepted_fields.update(Node.tags)
+    accepted_fields = dict(filter(can_update, Node.fields.items()))
 
     accepts = [
         Auth(),
@@ -39,13 +39,6 @@ class AddNode(Method):
 
     def call(self, auth, site_id_or_login_base, node_fields):
 
-        [native,tags,rejected]=Row.split_fields(node_fields,[Node.fields,Node.tags])
-
-        # type checking
-        native = Row.check_fields(native, self.accepted_fields)
-        if rejected:
-            raise PLCInvalidArgument, "Cannot add Node with column(s) %r"%rejected
-
         # Get site information
         sites = Sites(self.api, [site_id_or_login_base])
         if not sites:
@@ -65,13 +58,15 @@ class AddNode(Method):
             else:
                 assert self.caller['person_id'] in site['person_ids']
 
-        node = Node(self.api, native)
+        node_fields = dict(filter(can_update, node_fields.items()))
+        node = Node(self.api, node_fields)
         node['site_id'] = site['site_id']
         node.sync()
 
         # since hostname was specified lets add the 'hrn' node tag
         root_auth = self.api.config.PLC_HRN_ROOT
         login_base = site['login_base']
+        tags={}
         tags['hrn'] = hostname_to_hrn(root_auth, login_base, node['hostname'])
 
         for (tagname,value) in tags.iteritems():
@@ -93,8 +88,5 @@ class AddNode(Method):
                 node_tag['value'] = value
                 node_tag.sync() 
 
-        self.event_objects = {'Site': [site['site_id']],
-                              'Node': [node['node_id']]}
-        self.message = "Node %d=%s created" % (node['node_id'],node['hostname'])
 
         return node['node_id']
diff --git a/PLC/Methods/AddNodeGroup.py b/PLC/Methods/AddNodeGroup.py
index a24fa8e7..505327fa 100644
--- a/PLC/Methods/AddNodeGroup.py
+++ b/PLC/Methods/AddNodeGroup.py
@@ -7,7 +7,7 @@ from PLC.NodeGroups import NodeGroup, NodeGroups
 from PLC.TagTypes import TagType, TagTypes
 from PLC.NodeTags import NodeTag, NodeTags
 
-can_update = lambda (field, value): field in NodeGroup.fields.keys() and field != NodeGroup.primary_field
+can_update = lambda (field, value): field in NodeGroup.fields.keys() and field not in [NodeGroup.fields['nodegroup_id']] 
 
 class AddNodeGroup(Method):
     """
diff --git a/PLC/Methods/AddPCUType.py b/PLC/Methods/AddPCUType.py
index 2c8fbe54..1037883f 100644
--- a/PLC/Methods/AddPCUType.py
+++ b/PLC/Methods/AddPCUType.py
@@ -30,6 +30,5 @@ class AddPCUType(Method):
         pcu_type_fields = dict(filter(can_update, pcu_type_fields.items()))
         pcu_type = PCUType(self.api, pcu_type_fields)
         pcu_type.sync()
-        self.event_object = {'PCUType': [pcu_type['pcu_type_id']]}
 
         return pcu_type['pcu_type_id']
diff --git a/PLC/Methods/AddRoleToPerson.py b/PLC/Methods/AddRoleToPerson.py
index 1e47033d..3fc1df3d 100644
--- a/PLC/Methods/AddRoleToPerson.py
+++ b/PLC/Methods/AddRoleToPerson.py
@@ -19,7 +19,7 @@ class AddRoleToPerson(Method):
 
     accepts = [
         Auth(),
-        Mixed(Role.fields['role_id'],
+        Mixed(Role.fields['id'],
               Role.fields['name']),
         Mixed(Person.fields['person_id'],
               Person.fields['email']),
@@ -40,9 +40,6 @@ class AddRoleToPerson(Method):
             raise PLCInvalidArgument, "No such account"
         person = persons[0]
 
-        if person['peer_id'] is not None:
-            raise PLCInvalidArgument, "Not a local account"
-
         # Authenticated function
         assert self.caller is not None
 
@@ -58,9 +55,4 @@ class AddRoleToPerson(Method):
         if role['role_id'] not in person['role_ids']:
             person.add_role(role)
 
-        self.event_objects = {'Person': [person['person_id']],
-                              'Role': [role['role_id']]}
-        self.message = "Role %d granted to person %d" % \
-                       (role['role_id'], person['person_id'])
-
         return 1
diff --git a/PLC/Methods/BootNotifyOwners.py b/PLC/Methods/BootNotifyOwners.py
index 7458b9de..13799f70 100644
--- a/PLC/Methods/BootNotifyOwners.py
+++ b/PLC/Methods/BootNotifyOwners.py
@@ -1,6 +1,6 @@
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.Auth import Auth, BootAuth, SessionAuth
+from PLC.Auth import Auth
 from PLC.Nodes import Node, Nodes
 from PLC.Messages import Message, Messages
 
diff --git a/PLC/Methods/DeleteRoleFromTagType.py b/PLC/Methods/DeleteRoleFromTagType.py
index cdce6fa6..db009d28 100644
--- a/PLC/Methods/DeleteRoleFromTagType.py
+++ b/PLC/Methods/DeleteRoleFromTagType.py
@@ -19,7 +19,7 @@ class DeleteRoleFromTagType(Method):
 
     accepts = [
         Auth(),
-        Mixed(Role.fields['role_id'],
+        Mixed(Role.fields['id'],
               Role.fields['name']),
         Mixed(TagType.fields['tag_type_id'],
               TagType.fields['tagname']),
diff --git a/PLC/Methods/GetBootMedium.py b/PLC/Methods/GetBootMedium.py
index 7225b126..b1380dba 100644
--- a/PLC/Methods/GetBootMedium.py
+++ b/PLC/Methods/GetBootMedium.py
@@ -14,7 +14,7 @@ from PLC.Interfaces import Interface, Interfaces
 from PLC.InterfaceTags import InterfaceTag, InterfaceTags
 from PLC.NodeTags import NodeTag, NodeTags
 
-from PLC.Accessors.Accessors_standard import *                  # import node accessors
+#from PLC.Accessors.Accessors_standard import *                  # import node accessors
 
 # could not define this in the class..
 # create a dict with the allowed actions for each type of node
diff --git a/PLC/Methods/GetNodeFlavour.py b/PLC/Methods/GetNodeFlavour.py
index 92c0fc4b..9800f41f 100644
--- a/PLC/Methods/GetNodeFlavour.py
+++ b/PLC/Methods/GetNodeFlavour.py
@@ -4,8 +4,6 @@ from PLC.Faults import *
 from PLC.Parameter import *
 from PLC.Nodes import Node, Nodes
 
-from PLC.Accessors.Accessors_standard import *                  # import node accessors
-
 class GetNodeFlavour(Method):
     """
     Returns detailed information on a given node's flavour, i.e. its
diff --git a/PLC/Methods/GetPersons.py b/PLC/Methods/GetPersons.py
index 07a0cd06..6725f33a 100644
--- a/PLC/Methods/GetPersons.py
+++ b/PLC/Methods/GetPersons.py
@@ -24,7 +24,7 @@ class GetPersons(Method):
 
     accepts = [
         Auth(),
-        Mixed(Person.fields['id'],
+        Mixed(Person.fields['person_id'],
               Parameter(str,"email"),
               Filter(Person.fields)),
         Parameter([str], "List of fields to return", nullok = True)
diff --git a/PLC/Methods/GetSliceFamily.py b/PLC/Methods/GetSliceFamily.py
index 2a6e8307..8167fbd4 100644
--- a/PLC/Methods/GetSliceFamily.py
+++ b/PLC/Methods/GetSliceFamily.py
@@ -3,9 +3,7 @@ from PLC.Auth import Auth
 from PLC.Faults import *
 from PLC.Parameter import *
 from PLC.Slices import Slice, Slices
-
-from PLC.Accessors.Accessors_standard import *                  # import slice accessors
-from PLC.Accessors.Accessors_sliverauth import *                # import slice accessors
+from PLC.SliceTags import SliceTag, SliceTags
 
 class GetSliceFamily(Method):
     """
@@ -30,24 +28,28 @@ class GetSliceFamily(Method):
     ### system slices - at least planetflow - still rely on 'vref'
     #
     def call(self, auth, slice_id_or_name):
+        # default values
+        arch = self.api.config.PLC_FLAVOUR_SLICE_ARCH
+        pldistro = self.api.config.PLC_FLAVOUR_SLICE_PLDISTRO
+        fcdistro = self.api.config.PLC_FLAVOUR_SLICE_FCDISTRO
+        vref = None
+ 
         # Get slice information
         slices = Slices(self.api, [slice_id_or_name])
         if not slices:
             raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
         slice = slices[0]
-        slice_id = slice['slice_id']
-
-        arch = GetSliceArch (self.api,self.caller).call(auth,slice_id)
-        if not arch: arch = self.api.config.PLC_FLAVOUR_SLICE_ARCH
-
-        pldistro = GetSlicePldistro (self.api,self.caller).call(auth, slice_id)
-        if not pldistro: pldistro = self.api.config.PLC_FLAVOUR_SLICE_PLDISTRO
-
-        fcdistro = GetSliceFcdistro (self.api,self.caller).call(auth, slice_id)
-        if not fcdistro: fcdistro = self.api.config.PLC_FLAVOUR_SLICE_FCDISTRO
-
-        # the vref tag, if set, wins over pldistro
-        vref = GetSliceVref (self.api,self.caller).call(auth,slice_id)
+        slice_tags = SliceTags(self.api, slice['slice_tag_ids'])
+       
+        for slice_tag in slice_tags:
+            if slice_tag['tagname'] == 'arch':
+                arch = slice_tag['value']
+            elif slice_tag['tagname'] == 'pldistro':
+                pldistro = slice_tag['value']
+            elif slice_tag['tagname'] == 'fcdistro':
+                fcdistro = slice_tag['value']
+            elif slice_tag['tagname'] == 'vref':
+                vref = slice_tag['value']
 
         # omf-control'ed slivers need the omf vserver reference image
         # we used to issue SetSliceVref (self.api) (auth,slice_id,'omf')
diff --git a/PLC/Methods/GetSlivers.py b/PLC/Methods/GetSlivers.py
index 1f65e1eb..84768e5c 100644
--- a/PLC/Methods/GetSlivers.py
+++ b/PLC/Methods/GetSlivers.py
@@ -21,8 +21,6 @@ from PLC.Timestamp import Duration
 from PLC.Methods.GetSliceFamily import GetSliceFamily
 from PLC.PersonTags import PersonTag,PersonTags
 
-from PLC.Accessors.Accessors_standard import *
-
 # XXX used to check if slice expiration time is sane
 MAXINT =  2L**31-1
 
diff --git a/PLC/Methods/UpdateKey.py b/PLC/Methods/UpdateKey.py
index 870fca75..b858ddd5 100644
--- a/PLC/Methods/UpdateKey.py
+++ b/PLC/Methods/UpdateKey.py
@@ -30,26 +30,4 @@ class UpdateKey(Method):
     returns = Parameter(int, '1 if successful')
 
     def call(self, auth, key_id, key_fields):
-        key_fields = dict(filter(can_update, key_fields.items()))
-
-        # Get key information
-        keys = Keys(self.api, [key_id])
-        if not keys:
-            raise PLCInvalidArgument, "No such key"
-        key = keys[0]
-
-        if key['peer_id'] is not None:
-            raise PLCInvalidArgument, "Not a local key"
-
-        if 'admin' not in self.caller['roles']:
-            if key['key_id'] not in self.caller['key_ids']:
-                raise PLCPermissionDenied, "Key must be associated with one of your accounts"
-
-        key.update(key_fields)
-        key.sync()
-
-        # Logging variables
-        self.event_objects = {'Key': [key['key_id']]}
-        self.message = 'key %d updated: %s' % \
-                (key['key_id'], ", ".join(key_fields.keys()))
         return 1
diff --git a/PLC/Methods/UpdateNode.py b/PLC/Methods/UpdateNode.py
index c09e10c9..18207f8c 100644
--- a/PLC/Methods/UpdateNode.py
+++ b/PLC/Methods/UpdateNode.py
@@ -29,9 +29,6 @@ class UpdateNode(Method):
     roles = ['admin', 'pi', 'tech']
 
     accepted_fields = Row.accepted_fields(can_update,Node.fields)
-    # xxx check the related_fields feature
-    accepted_fields.update(Node.related_fields)
-    accepted_fields.update(Node.tags)
 
     accepts = [
         Auth(),
@@ -44,22 +41,14 @@ class UpdateNode(Method):
 
     def call(self, auth, node_id_or_hostname, node_fields):
 
-        # split provided fields
-        [native,related,tags,rejected] = Row.split_fields(node_fields,[Node.fields,Node.related_fields,Node.tags])
-
-        # type checking
-        native = Row.check_fields (native, self.accepted_fields)
-        if rejected:
-            raise PLCInvalidArgument, "Cannot update Node column(s) %r"%rejected
-
         # Authenticated function
         assert self.caller is not None
 
         # Remove admin only fields
         if 'admin' not in self.caller['roles']:
             for key in admin_only:
-                if native.has_key(key):
-                    del native[key]
+                if node_fields.has_key(key):
+                    del node_fields[key]
 
         # Get account information
         nodes = Nodes(self.api, [node_id_or_hostname])
@@ -67,21 +56,13 @@ class UpdateNode(Method):
             raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
         node = nodes[0]
 
-        if node['peer_id'] is not None:
-            raise PLCInvalidArgument, "Not a local node %r"%node_id_or_hostname
-
         # If we are not an admin, make sure that the caller is a
         # member of the site at which the node is located.
         if 'admin' not in self.caller['roles']:
             if node['site_id'] not in self.caller['site_ids']:
                 raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
 
-        # Make requested associations
-        for (k,v) in related.iteritems():
-            node.associate(auth, k,v)
-
-        node.update(native)
-        node.update_last_updated(commit=False)
+        node.update(node_fields)
         node.sync(commit=True)
 
         # if hostname was modifed make sure to update the hrn
@@ -112,14 +93,4 @@ class UpdateNode(Method):
                 node_tag = node_tags[0]
                 node_tag['value'] = value
                 node_tag.sync()
-        # Logging variables
-        self.event_objects = {'Node': [node['node_id']]}
-        if 'hostname' in node:
-            self.message = 'Node %s updated'%node['hostname']
-        else:
-            self.message = 'Node %d updated'%node['node_id']
-        self.message += " [%s]." % (", ".join(node_fields.keys()),)
-        if 'boot_state' in node_fields.keys():
-            self.message += ' boot_state updated to %s' % node_fields['boot_state']
-
         return 1
diff --git a/PLC/Methods/UpdatePerson.py b/PLC/Methods/UpdatePerson.py
index 79c19001..52462b39 100644
--- a/PLC/Methods/UpdatePerson.py
+++ b/PLC/Methods/UpdatePerson.py
@@ -5,11 +5,10 @@ from PLC.Persons import Person, Persons
 from PLC.Auth import Auth
 from PLC.sendmail import sendmail
 
-related_fields = Person.related_fields.keys()
 can_update = lambda (field, value): field in \
              ['first_name', 'last_name', 'title', 'email',
               'password', 'phone', 'url', 'bio', 'accepted_aup',
-              'enabled'] + related_fields
+              'enabled'] 
 
 class UpdatePerson(Method):
     """
@@ -24,7 +23,7 @@ class UpdatePerson(Method):
 
     roles = ['admin', 'pi', 'user', 'tech']
 
-    person_fields = dict(filter(can_update, Person.fields.items() + Person.related_fields.items()))
+    person_fields = dict(filter(can_update, Person.fields.items() ))
 
     accepts = [
         Auth(),
diff --git a/PLC/Methods/UpdateSite.py b/PLC/Methods/UpdateSite.py
index 936fdae7..b50890a1 100644
--- a/PLC/Methods/UpdateSite.py
+++ b/PLC/Methods/UpdateSite.py
@@ -24,7 +24,7 @@ class UpdateSite(Method):
 
     accepts = [
         Auth(),
-        Site.fields['id'],
+        Site.fields['site_id'],
         site_fields
         ]
 
@@ -45,7 +45,7 @@ class UpdateSite(Method):
         # If we are not an admin, make sure that the caller is a
         # member of the site.
         if 'admin' not in self.caller['roles']:
-            if site['id'] not in self.caller['tenant_id']:
+            if site['site_id'] not in self.caller['site_id']:
                 raise PLCPermissionDenied, "Not allowed to modify specified site"
 
             # Remove admin only fields
diff --git a/PLC/Methods/UpdateSlice.py b/PLC/Methods/UpdateSlice.py
index 9129ba06..5ebca666 100644
--- a/PLC/Methods/UpdateSlice.py
+++ b/PLC/Methods/UpdateSlice.py
@@ -34,9 +34,6 @@ class UpdateSlice(Method):
     roles = ['admin', 'pi', 'user']
 
     accepted_fields = Row.accepted_fields(can_update, Slice.fields)
-    # xxx check the related_fields feature
-    accepted_fields.update(Slice.related_fields)
-    accepted_fields.update(Slice.tags)
 
     accepts = [
         Auth(),
@@ -49,14 +46,6 @@ class UpdateSlice(Method):
 
     def call(self, auth, slice_id_or_name, slice_fields):
 
-        # split provided fields
-        [native,related,tags,rejected] = Row.split_fields(slice_fields,[Slice.fields,Slice.related_fields,Slice.tags])
-
-        # type checking
-        native = Row.check_fields (native, self.accepted_fields)
-        if rejected:
-            raise PLCInvalidArgument, "Cannot update Slice column(s) %r"%rejected
-
         slices = Slices(self.api, [slice_id_or_name])
         if not slices:
             raise PLCInvalidArgument, "No such slice %r"%slice_id_or_name
@@ -110,34 +99,7 @@ class UpdateSlice(Method):
                'pi' not in self.caller['roles']:
                 raise PLCInvalidArgument, "Only admins and PIs may update max_nodes"
 
-        # Make requested associations
-        for (k,v) in related.iteritems():
-            slice.associate(auth,k,v)
-
         slice.update(slice_fields)
         slice.sync(commit=True)
 
-        for (tagname,value) in tags.iteritems():
-            # the tagtype instance is assumed to exist, just check that
-            if not TagTypes(self.api,{'tagname':tagname}):
-                raise PLCInvalidArgument,"No such TagType %s"%tagname
-            slice_tags=SliceTags(self.api,{'tagname':tagname,'slice_id':slice['slice_id']})
-            if not slice_tags:
-                AddSliceTag(self.api).__call__(auth,slice['slice_id'],tagname,value)
-            else:
-                UpdateSliceTag(self.api).__call__(auth,slice_tags[0]['slice_tag_id'],value)
-
-        self.event_objects = {'Slice': [slice['slice_id']]}
-        if 'name' in slice:
-            self.message='Slice %s updated'%slice['name']
-        else:
-            self.message='Slice %d updated'%slice['slice_id']
-        if renewing:
-            # it appears that slice['expires'] may be either an int, or a formatted string
-            try:
-                expire_date=time.strftime('%Y-%m-%d:%H:%M',time.localtime(float(slice['expires'])))
-            except:
-                expire_date=slice['expires']
-            self.message += ' renewed until %s'%expire_date
-
         return 1
diff --git a/PLC/NodeGroups.py b/PLC/NodeGroups.py
index bbe20a8a..01a28d35 100644
--- a/PLC/NodeGroups.py
+++ b/PLC/NodeGroups.py
@@ -11,7 +11,7 @@ from PLC.Debug import profile
 from PLC.Storage.AlchemyObject import AlchemyObj
 from PLC.Nodes import Node, Nodes
 
-class NodeGroup(Row):
+class NodeGroup(AlchemyObj):
     """
     Representation of a row in the nodegroups table. To use, optionally
     instantiate with a dict of values. Update as you would a
@@ -21,7 +21,7 @@ class NodeGroup(Row):
     tablename = 'nodegroups'
     join_tables = ['conf_file_nodegroup']
     fields = {
-        'nodegroup_id': Parameter(int, "Node group identifier", primary_key),
+        'nodegroup_id': Parameter(int, "Node group identifier", primary_key=True),
         'groupname': Parameter(str, "Node group name", max = 50),
         'tag_type_id': Parameter (int, "Node tag type id"),
         'value' : Parameter(str, "value that the nodegroup definition is based upon"),
@@ -29,8 +29,6 @@ class NodeGroup(Row):
         'conf_file_ids': Parameter([int], "List of configuration files specific to this node group", joined=True),
         'node_ids' : Parameter([int], "List of node_ids that belong to this nodegroup", joined=True),
         }
-    related_fields = {
-        }
 
     def validate_name(self, name):
         # Make sure name is not blank
diff --git a/PLC/Nodes.py b/PLC/Nodes.py
index ba939067..2932d1a7 100644
--- a/PLC/Nodes.py
+++ b/PLC/Nodes.py
@@ -121,14 +121,14 @@ class Node(AlchemyObj):
         """
         Update col_name field with current time
         """
-
         assert 'node_id' in self
-        assert self.table_name
-
-        self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
-                       " where node_id = %d" % (self['node_id']) )
-        self.sync(commit)
-
+        self[col_name] = datetime.now()
+        fields = {
+            'node_id': self['node_id'],         
+            col_name: datetime.now()
+        } 
+        Node(self.api, fields).sync()
+        
     def update_last_boot(self, commit = True):
         self.update_timestamp('last_boot', commit)
     def update_last_download(self, commit = True):
diff --git a/PLC/PCUTypes.py b/PLC/PCUTypes.py
index bd419360..554da901 100644
--- a/PLC/PCUTypes.py
+++ b/PLC/PCUTypes.py
@@ -9,7 +9,7 @@ from types import StringTypes
 
 from PLC.Faults import *
 from PLC.Parameter import Parameter
-from PLC.Storage.AlchemyObj import AlchemyObj
+from PLC.Storage.AlchemyObject import AlchemyObj
 
 class PCUType(AlchemyObj):
     """
diff --git a/PLC/PersonTags.py b/PLC/PersonTags.py
index 5581a698..f6beefcc 100644
--- a/PLC/PersonTags.py
+++ b/PLC/PersonTags.py
@@ -16,7 +16,7 @@ class PersonTag(AlchemyObj):
     tablename = 'person_tag'
     fields = {
         'person_tag_id': Parameter(int, "Person setting identifier", primary_key=True),
-        'person_id': Person.fields['id'],
+        'person_id': Person.fields['person_id'],
         'email': Person.fields['email'],
         'tag_type_id': TagType.fields['tag_type_id'],
         'tagname': TagType.fields['tagname'],
diff --git a/PLC/Shell.py b/PLC/Shell.py
index d6c3a54b..fb401d38 100644
--- a/PLC/Shell.py
+++ b/PLC/Shell.py
@@ -144,10 +144,6 @@ class Shell:
             if role is not None:
                 self.auth['Role'] = role
 
-        self.load_methods()
- 
-    def load_methods(self):
-
         for method in PLC.API.PLCAPI.all_methods:
             api_function = self.api.callable(method)
 
diff --git a/PLC/Slices.py b/PLC/Slices.py
index a79f86fd..1219c2e8 100644
--- a/PLC/Slices.py
+++ b/PLC/Slices.py
@@ -71,11 +71,58 @@ class Slice(AlchemyObj):
         check_future = not ('is_deleted' in self and self['is_deleted'])
         return Timestamp.sql_validate( expires, check_future = check_future)
 
-    #add_person = Row.add_object(Person, 'slice_person')
-    #remove_person = Row.remove_object(Person, 'slice_person')
+    def add_person(self, person_filter, role_name=None):
+        assert 'slice_id' in self
+        assert 'tenant_id' in self
+        if not role_name:
+            role_name = 'user'
+        roles = Roles(self.api, role_name)
+        if not roles:
+            raise PLCInvalidArgument, "No such role %s" % role_name
+        role = roles[0]
+        tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
+        persons = Persons(self.api, person_filter)
+        for person in persons:
+            keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
+            tenant.add_user(keystone_user, role.object)
+            slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
+                                                  'person_id': person['person_id']})
+            slice_person.sync()
+
+    def remove_person(self, person_filter, role=None):
+        assert 'slice_id' in self
+        assert 'tenant_id' in self
+        if not role_name:
+            role_name = 'user'
+        roles = Roles(self.api, role_name)
+        if not roles:
+            raise PLCInvalidArgument, "No such role %s" % role_name
+        role = roles[0]
+        tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
+        persons = Persons(self.api, person_filter)
+        for person in persons:
+            keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
+            tenant.remove_user(keystone_user, role.object)
+            slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
+                                                  'person_id': person['person_id']})
+            slice_person.delete()
+ 
 
-    #add_node = Row.add_object(Node, 'slice_node')
-    #remove_node = Row.remove_object(Node, 'slice_node')
+    def add_node(self, node_filter):
+        assert 'slice_id' in self
+        nodes = Nodes(self.api, node_filter)
+        for node in nodes:
+            slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
+                                              'node_id': node['node_id']})
+            slice_node.sync()
+ 
+    def remove_node(self, node_filter):
+        assert 'slice_id' in self
+        nodes = Nodes(self.api, node_filter)
+        for node in nodes:
+            slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
+                                              'node_id': node['node_id']})
+            slice_node.delete()
 
     #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
     #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
diff --git a/PLCAPI.spec b/PLCAPI.spec
index 28c968ad..8922f661 100644
--- a/PLCAPI.spec
+++ b/PLCAPI.spec
@@ -25,6 +25,7 @@ Requires: python >= 2.4
 Requires: python-pycurl
 Requires: httpd
 Requires: python-gevent
+Requires: python-lxml
 # mod_wsgi will replace mod_python when we are ready
 Requires: mod_wsgi
 Requires: mod_ssl
-- 
2.47.0