- removed 'Adm' prefix
authorTony Mack <tmack@cs.princeton.edu>
Fri, 6 Oct 2006 19:21:25 +0000 (19:21 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Fri, 6 Oct 2006 19:21:25 +0000 (19:21 +0000)
25 files changed:
PLC/Methods/AddNodeToNodeGroup.py [new file with mode: 0644]
PLC/Methods/AddPerson.py [new file with mode: 0644]
PLC/Methods/AddPersonToSite.py [new file with mode: 0644]
PLC/Methods/AddRoleToPerson.py [new file with mode: 0644]
PLC/Methods/AddSite.py [new file with mode: 0644]
PLC/Methods/DeleteNode.py [new file with mode: 0644]
PLC/Methods/DeleteNodeGroup.py [new file with mode: 0644]
PLC/Methods/DeleteNodeNetwork.py [new file with mode: 0644]
PLC/Methods/DeletePerson.py [new file with mode: 0644]
PLC/Methods/DeleteSite.py [new file with mode: 0644]
PLC/Methods/GetNodeGroups.py [new file with mode: 0644]
PLC/Methods/GetNodeNetworkBandwidthLimits.py [new file with mode: 0644]
PLC/Methods/GetNodeNetworks.py [new file with mode: 0644]
PLC/Methods/GetNodes.py [new file with mode: 0644]
PLC/Methods/GetPersons.py [new file with mode: 0644]
PLC/Methods/GetSites.py [new file with mode: 0644]
PLC/Methods/RemoveNodeFromNodeGroup.py [new file with mode: 0644]
PLC/Methods/RemovePersonFromSite.py [new file with mode: 0644]
PLC/Methods/RemoveRoleFromPerson.py [new file with mode: 0644]
PLC/Methods/SetPersonPrimarySite.py [new file with mode: 0644]
PLC/Methods/UpdateNode.py [new file with mode: 0644]
PLC/Methods/UpdateNodeGroup.py [new file with mode: 0644]
PLC/Methods/UpdateNodeNetwork.py [new file with mode: 0644]
PLC/Methods/UpdatePerson.py [new file with mode: 0644]
PLC/Methods/UpdateSite.py [new file with mode: 0644]

diff --git a/PLC/Methods/AddNodeToNodeGroup.py b/PLC/Methods/AddNodeToNodeGroup.py
new file mode 100644 (file)
index 0000000..04e92be
--- /dev/null
@@ -0,0 +1,46 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import PasswordAuth
+
+class AddNodeToNodeGroup(Method):
+    """
+    Add a node to the specified node group. If the node is
+    already a member of the nodegroup, no errors are returned.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(NodeGroup.fields['nodegroup_id'],
+             NodeGroup.fields['name']),
+       Mixed(Node.fields['node_id'],
+             Node.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, nodegroup_id_or_name, node_id_or_hostname):
+        # Get node info
+       nodes = Nodes(self.api, [node_id_or_hostname])
+       if not nodes:
+               raise PLCInvalidArgument, "No such node"
+       node = nodes.values()[0]
+
+       # Get nodegroup info
+        nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+        if not nodegroups:
+            raise PLCInvalidArgument, "No such nodegroup"
+
+        nodegroup = nodegroups.values()[0]
+       
+       # add node to nodegroup
+        if node['node_id'] not in nodegroup['node_ids']:
+            nodegroup.add_node(node)
+
+        return 1
diff --git a/PLC/Methods/AddPerson.py b/PLC/Methods/AddPerson.py
new file mode 100644 (file)
index 0000000..fb4b887
--- /dev/null
@@ -0,0 +1,43 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+
+class AddPerson(Method):
+    """
+    Adds a new account. Any fields specified in optional_vals are
+    used, otherwise defaults are used.
+
+    Accounts are disabled by default. To enable an account, use
+    SetPersonEnabled() or UpdatePerson().
+
+    Returns the new person_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    can_update = lambda (field, value): field in \
+                 ['title', 'email', 'password', 'phone', 'url', 'bio']
+    update_fields = dict(filter(can_update, Person.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Person.fields['first_name'],
+        Person.fields['last_name'],
+        update_fields
+        ]
+
+    returns = Parameter(int, 'New person_id (> 0) if successful')
+
+    def call(self, auth, first_name, last_name, optional_vals = {}):
+        if filter(lambda field: field not in self.update_fields, optional_vals):
+            raise PLCInvalidArgument, "Invalid fields specified"
+
+        person = Person(self.api, optional_vals)
+        person['first_name'] = first_name
+        person['last_name'] = last_name
+        person['enabled'] = False
+        person.sync()
+
+        return person['person_id']
diff --git a/PLC/Methods/AddPersonToSite.py b/PLC/Methods/AddPersonToSite.py
new file mode 100644 (file)
index 0000000..71d1d67
--- /dev/null
@@ -0,0 +1,47 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Auth import PasswordAuth
+
+class AddPersonToSite(Method):
+    """
+    Adds the specified person to the specified site. If the person is
+    already a member of the site, no errors are returned. Does not
+    change the person's primary site.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, site_id_or_login_base):
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites.values()[0]
+
+        if site['site_id'] not in person['site_ids']:
+            site.add_person(person)
+
+        return 1
diff --git a/PLC/Methods/AddRoleToPerson.py b/PLC/Methods/AddRoleToPerson.py
new file mode 100644 (file)
index 0000000..25b54e2
--- /dev/null
@@ -0,0 +1,63 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+from PLC.Roles import Roles
+
+class AddRoleToPerson(Method):
+    """
+    Grants the specified role to the person.
+    
+    PIs can only grant the tech and user roles to users and techs at
+    their sites. ins can grant any role to any user.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Mixed(Parameter(int, "Role identifier"),
+              Parameter(str, "Role name"))
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, role_id_or_name):
+        # Get all roles
+        roles = Roles(self.api)
+        if role_id_or_name not in roles:
+            raise PLCInvalidArgument, "Invalid role identifier or name"
+
+        if isinstance(role_id_or_name, int):
+            role_id = role_id_or_name
+        else:
+            role_id = roles[role_id_or_name]
+
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Check if we can update this account
+        if not self.caller.can_update(person):
+            raise PLCPermissionDenied, "Not allowed to update specified account"
+
+        # Can only grant lesser (higher) roles to others
+        if 'admin' not in self.caller['roles'] and \
+           role_id <= min(self.caller['role_ids']):
+            raise PLCInvalidArgument, "Not allowed to grant that role"
+
+        if role_id not in person['role_ids']:
+            person.add_role(role_id)
+
+        return 1
diff --git a/PLC/Methods/AddSite.py b/PLC/Methods/AddSite.py
new file mode 100644 (file)
index 0000000..171670d
--- /dev/null
@@ -0,0 +1,43 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Sites import Site, Sites
+from PLC.Auth import PasswordAuth
+
+class AddSite(Method):
+    """
+    Adds a new site, and creates a node group for that site. Any
+    fields specified in optional_vals are used, otherwise defaults are
+    used.
+
+    Returns the new site_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    can_update = lambda (field, value): field in \
+                 ['is_public', 'latitude', 'longitude', 'url',
+                  'organization_id', 'ext_consortium_id']
+    update_fields = dict(filter(can_update, Site.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Site.fields['name'],
+        Site.fields['abbreviated_name'],
+        Site.fields['login_base'],
+        update_fields
+        ]
+
+    returns = Parameter(int, 'New site_id (> 0) if successful')
+
+    def call(self, auth, name, abbreviated_name, login_base, optional_vals = {}):
+        if filter(lambda field: field not in self.update_fields, optional_vals):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        site = Site(self.api, optional_vals)
+        site['name'] = name
+        site['abbreviated_name'] = abbreviated_name
+        site['login_base'] = login_base
+        site.sync()
+
+        return site['site_id']
diff --git a/PLC/Methods/DeleteNode.py b/PLC/Methods/DeleteNode.py
new file mode 100644 (file)
index 0000000..9555317
--- /dev/null
@@ -0,0 +1,46 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.Nodes import Node, Nodes
+
+class DeleteNode(Method):
+    """
+    Mark an existing node as deleted.
+
+    PIs and techs may only delete nodes at their own sites. ins may
+    delete nodes at any site.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, node_id_or_hostname):
+        # Get account information
+        nodes = Nodes(self.api, [node_id_or_hostname])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node"
+
+        node = nodes.values()[0]
+
+        # 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']:
+            # Authenticated function
+            assert self.caller is not None
+
+            if node['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Not allowed to delete nodes from specified site"
+
+        node.delete()
+
+        return 1
diff --git a/PLC/Methods/DeleteNodeGroup.py b/PLC/Methods/DeleteNodeGroup.py
new file mode 100644 (file)
index 0000000..cf6dd8a
--- /dev/null
@@ -0,0 +1,36 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+class DeleteNodeGroup(Method):
+    """
+    Delete an existing Node Group.
+
+    ins may delete any node group
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(NodeGroup.fields['nodegroup_id'],
+             NodeGroup.fields['name'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, node_group_id_or_name):
+        # Get account information
+        nodegroups = NodeGroups(self.api, [node_group_id_or_name])
+        if not nodegroups:
+            raise PLCInvalidArgument, "No such node group"
+
+        nodegroup = nodegroups.values()[0]
+
+        nodegroup.delete()
+
+        return 1
diff --git a/PLC/Methods/DeleteNodeNetwork.py b/PLC/Methods/DeleteNodeNetwork.py
new file mode 100644 (file)
index 0000000..d2c6d4a
--- /dev/null
@@ -0,0 +1,60 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+class DeleteNodeNetwork(Method):
+    """
+    Delete an existing Node Network. Nodenetwork_id must be associated to 
+    node_id and not be associated with a different node.
+
+    ins may delete any node network. PIs and techs can only delete 
+    nodenetworks for thier nodes.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Node.fields['node_id'],
+             Node.fields['hostname']),
+       Mixed(NodeNetwork.fields['nodenetwork_id'],
+             NodeNetwork.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, node_id_or_hostname, nodenetwork_id_or_hostname):
+        # Get node network information
+        nodenetworks = NodeNetworks(self.api, [nodenetwork_id_or_hostname]).values()
+        if not nodenetworks:
+            raise PLCInvalidArgument, "No such node network"
+       nodenetwork = nodenetworks[0]
+       
+       # Get node information
+       nodes = Nodes(self.api, [node_id_or_hostname]).values()
+       if not nodes:
+               raise PLCInvalidArgument, "No such node"
+       node = nodes[0]
+
+       # Check if node network is associated with specified node
+       if node['node_id'] != nodenetwork['node_id'] or \
+           nodenetwork['nodenetwork_id'] not in node['nodenetwork_ids']:
+            raise PLCInvalidArgument, "Node network not associated with node"
+
+        # Authenticated functino
+       assert self.caller is not None
+
+        # 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 this node network"
+
+        nodenetwork.delete()
+
+        return 1
diff --git a/PLC/Methods/DeletePerson.py b/PLC/Methods/DeletePerson.py
new file mode 100644 (file)
index 0000000..8375439
--- /dev/null
@@ -0,0 +1,45 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+
+class DeletePerson(Method):
+    """
+    Mark an existing account as deleted.
+
+    Users and techs can only delete themselves. PIs can only delete
+    themselves and other non-PIs at their sites. ins can delete
+    anyone.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email):
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Check if we can update this account
+        if not self.caller.can_update(person):
+            raise PLCPermissionDenied, "Not allowed to delete specified account"
+
+        person.delete()
+
+        return 1
diff --git a/PLC/Methods/DeleteSite.py b/PLC/Methods/DeleteSite.py
new file mode 100644 (file)
index 0000000..d16946d
--- /dev/null
@@ -0,0 +1,39 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Sites import Site, Sites
+from PLC.Persons import Person, Persons
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import PasswordAuth
+
+class DeleteSite(Method):
+    """
+    Mark an existing site as deleted. The accounts of people who are
+    not members of at least one other non-deleted site will also be
+    marked as deleted. Nodes, PCUs, and slices associated with the
+    site will be deleted.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, site_id_or_login_base):
+        # Get account information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites.values()[0]
+        site.delete()
+
+        return 1
diff --git a/PLC/Methods/GetNodeGroups.py b/PLC/Methods/GetNodeGroups.py
new file mode 100644 (file)
index 0000000..ffd6eb3
--- /dev/null
@@ -0,0 +1,34 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+class GetNodeGroups(Method):
+    """
+    Returns an array of structs containing details about all node
+    groups. If nodegroup_id_or_name_list is specified, only the
+    specified node groups will be queried.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        [Mixed(NodeGroup.fields['nodegroup_id'],
+              NodeGroup.fields['name'])]
+        ]
+
+    returns = [NodeGroup.fields]
+  
+    def call(self, auth, nodegroup_id_or_name_list = None):
+        # Get node group details
+       nodegroups = NodeGroups(self.api, nodegroup_id_or_name_list).values()
+
+       # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each nodegroup into a real dict.
+        valid_return_fields_only = lambda (key, value): value is not None
+        nodegroups = [dict(filter(valid_return_fields_only, nodegroup.items())) \
+                      for nodegroup in nodegroups]
+
+        return nodegroups
diff --git a/PLC/Methods/GetNodeNetworkBandwidthLimits.py b/PLC/Methods/GetNodeNetworkBandwidthLimits.py
new file mode 100644 (file)
index 0000000..d2766cf
--- /dev/null
@@ -0,0 +1,21 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Auth import PasswordAuth
+
+class GetNodeNetworkBandwidthLimits(Method):
+    """
+    Returns an array of all the valid bandwith limits for node networks.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth()
+        ]
+
+    returns = [NodeNetwork.fields['bwlimit']]
+
+    def call(self, auth):
+        return NodeNetwork.bwlimits          
diff --git a/PLC/Methods/GetNodeNetworks.py b/PLC/Methods/GetNodeNetworks.py
new file mode 100644 (file)
index 0000000..14cdd48
--- /dev/null
@@ -0,0 +1,46 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import PasswordAuth
+
+class GetNodeNetworks(Method):
+    """
+    Returns all the networks this node is connected to, as an array of
+    structs.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname'])
+        ]
+
+    returns = [NodeNetwork.fields]
+
+    def call(self, auth, node_id_or_hostname):
+        # Authenticated function
+        assert self.caller is not None
+
+        # Get node information
+        nodes = Nodes(self.api, [node_id_or_hostname]).values()
+       if not nodes:
+            raise PLCInvalidArgument, "No such node"
+       node = nodes[0]
+
+       # Get node networks for this node
+        if node['nodenetwork_ids']:
+            nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids']).values()
+        else:
+            nodenetworks = []
+
+       # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each node into a real dict.
+        valid_return_fields_only = lambda (key, value): value is not None
+        nodenetworks = [dict(filter(valid_return_fields_only, nodenetwork.items())) \
+                        for nodenetwork in nodenetworks]
+               
+       return nodenetworks
diff --git a/PLC/Methods/GetNodes.py b/PLC/Methods/GetNodes.py
new file mode 100644 (file)
index 0000000..90a2c3b
--- /dev/null
@@ -0,0 +1,63 @@
+import os
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import PasswordAuth
+
+class GetNodes(Method):
+    """
+    Return an array of dictionaries containing details about the
+    specified nodes.
+
+    If return_fields is specified, only the specified fields will be
+    returned. Only admins may retrieve certain fields. Otherwise, the
+    default set of fields returned is:
+
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        [Mixed(Node.fields['node_id'],
+               Node.fields['hostname'])],
+        Parameter([str], 'List of fields to return')
+        ]
+
+    returns = [Node.fields]
+
+    def __init__(self, *args, **kwds):
+        Method.__init__(self, *args, **kwds)
+        # Update documentation with list of default fields returned
+        self.__doc__ += os.linesep.join(Node.fields.keys())
+
+    def call(self, auth, node_id_or_hostname_list = None, return_fields = None):
+        # Authenticated function
+        assert self.caller is not None
+
+        valid_fields = Node.fields.keys()
+
+        # Remove admin only fields
+        if 'admin' not in self.caller['roles']:
+            for key in ['boot_nonce', 'key', 'session', 'root_person_ids']:
+                valid_fields.remove(key)
+
+        # Make sure that only valid fields are specified
+        if return_fields is None:
+            return_fields = valid_fields
+        elif filter(lambda field: field not in valid_fields, return_fields):
+            raise PLCInvalidArgument, "Invalid return field specified"
+
+        # Get node information
+        nodes = Nodes(self.api, node_id_or_hostname_list).values()
+
+        # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each node into a real dict.
+        valid_return_fields_only = lambda (key, value): \
+                                   key in return_fields and value is not None
+        nodes = [dict(filter(valid_return_fields_only, node.items())) \
+                 for node in nodes]
+                    
+        return nodes
diff --git a/PLC/Methods/GetPersons.py b/PLC/Methods/GetPersons.py
new file mode 100644 (file)
index 0000000..3805adc
--- /dev/null
@@ -0,0 +1,71 @@
+import os
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+
+class GetPersons(Method):
+    """
+    Return an array of dictionaries containing details about the
+    specified accounts.
+
+    ins may retrieve details about all accounts by not specifying
+    person_id_or_email_list or by specifying an empty list. Users and
+    techs may only retrieve details about themselves. PIs may retrieve
+    details about themselves and others at their sites.
+
+    If return_fields is specified, only the specified fields will be
+    returned, if set. Otherwise, the default set of fields returned is:
+
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        [Mixed(Person.fields['person_id'],
+               Person.fields['email'])],
+        Parameter([str], 'List of fields to return')
+        ]
+
+    # Filter out password field
+    can_return = lambda (field, value): field not in ['password']
+    return_fields = dict(filter(can_return, Person.fields.items()))
+    returns = [return_fields]
+
+    def __init__(self, *args, **kwds):
+        Method.__init__(self, *args, **kwds)
+        # Update documentation with list of default fields returned
+        self.__doc__ += os.linesep.join(self.return_fields.keys())
+
+    def call(self, auth, person_id_or_email_list = None, return_fields = None):
+        # Make sure that only valid fields are specified
+        if return_fields is None:
+            return_fields = self.return_fields
+        elif filter(lambda field: field not in self.return_fields, return_fields):
+            raise PLCInvalidArgument, "Invalid return field specified"
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Only admins can not specify person_id_or_email_list or
+        # specify an empty list.
+        if not person_id_or_email_list and 'admin' not in self.caller['roles']:
+            raise PLCInvalidArgument, "List of accounts to retrieve not specified"
+
+        # Get account information
+        persons = Persons(self.api, person_id_or_email_list)
+
+        # Filter out accounts that are not viewable and turn into list
+        persons = filter(self.caller.can_view, persons.values())
+
+        # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each person into a real dict.
+        valid_return_fields_only = lambda (key, value): \
+                                   key in return_fields and value is not None
+        persons = [dict(filter(valid_return_fields_only, person.items())) \
+                   for person in persons]
+                    
+        return persons
diff --git a/PLC/Methods/GetSites.py b/PLC/Methods/GetSites.py
new file mode 100644 (file)
index 0000000..2fb73e0
--- /dev/null
@@ -0,0 +1,52 @@
+import os
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.Sites import Site, Sites
+
+class GetSites(Method):
+    """
+    Return an array of structs containing details about all sites. If
+    site_id_list is specified, only the specified sites will be
+    queried.
+
+    If return_fields is specified, only the specified fields will be
+    returned, if set. Otherwise, the default set of fields returned is:
+
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        [Mixed(Site.fields['site_id'],
+               Site.fields['login_base'])],
+        Parameter([str], 'List of fields to return')
+        ]
+
+    returns = [Site.fields]
+
+    def __init__(self, *args, **kwds):
+        Method.__init__(self, *args, **kwds)
+        # Update documentation with list of default fields returned
+        self.__doc__ += os.linesep.join(Site.fields.keys())
+
+    def call(self, auth, site_id_or_login_base_list = None, return_fields = None):
+        # Make sure that only valid fields are specified
+        if return_fields is None:
+            return_fields = Site.fields
+        elif filter(lambda field: field not in Site.fields, return_fields):
+            raise PLCInvalidArgument, "Invalid return field specified"
+
+        # Get site information
+        sites = Sites(self.api, site_id_or_login_base_list)
+
+        # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each site into a real dict.
+        valid_return_fields_only = lambda (key, value): \
+                                   key in return_fields and value is not None
+        sites = [dict(filter(valid_return_fields_only, site.items())) \
+                 for site in sites.values()]
+
+        return sites
diff --git a/PLC/Methods/RemoveNodeFromNodeGroup.py b/PLC/Methods/RemoveNodeFromNodeGroup.py
new file mode 100644 (file)
index 0000000..715db90
--- /dev/null
@@ -0,0 +1,46 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import PasswordAuth
+
+class RemoveNodeFromNodeGroup(Method):
+    """
+    Removes a node from the specified node group. 
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(NodeGroup.fields['nodegroup_id'],
+             NodeGroup.fields['name']),
+       Mixed(Node.fields['node_id'],
+             Node.fields['hostname'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, nodegroup_id_or_name, node_id_or_hostname):
+        # Get node info
+       nodes = Nodes(self.api, [node_id_or_hostname])
+       if not nodes:
+               raise PLCInvalidArgument, "No such node"
+
+       node = nodes.values()[0]
+
+       # Get nodegroup info
+        nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+        if not nodegroups:
+            raise PLCInvalidArgument, "No such nodegroup"
+
+        nodegroup = nodegroups.values()[0]
+
+       # Remove node from nodegroup
+        if node['node_id'] in nodegroup['node_ids']:
+            nodegroup.remove_node(node)
+
+        return 1
diff --git a/PLC/Methods/RemovePersonFromSite.py b/PLC/Methods/RemovePersonFromSite.py
new file mode 100644 (file)
index 0000000..6d70006
--- /dev/null
@@ -0,0 +1,47 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Auth import PasswordAuth
+
+class RemovePersonFromSite(Method):
+    """
+    Removes the specified person from the specified site. If the
+    person is not a member of the specified site, no error is
+    returned.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, site_id_or_login_base):
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites.values()[0]
+
+        if site['site_id'] in person['site_ids']:
+            site.remove_person(person)
+
+        return 1
diff --git a/PLC/Methods/RemoveRoleFromPerson.py b/PLC/Methods/RemoveRoleFromPerson.py
new file mode 100644 (file)
index 0000000..a32f465
--- /dev/null
@@ -0,0 +1,57 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+from PLC.Roles import Roles
+
+class RemoveRoleFromPerson(Method):
+    """
+    Revokes the specified role from the person.
+    
+    PIs can only revoke the tech and user roles from users and techs
+    at their sites. ins can revoke any role from any user.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Parameter(int, 'Role ID')
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, role_id):
+        # Get all roles
+        roles = Roles(self.api)
+        if role_id not in roles:
+            raise PLCInvalidArgument, "Invalid role ID"
+
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Check if we can update this account
+        if not self.caller.can_update(person):
+            raise PLCPermissionDenied, "Not allowed to update specified account"
+
+        # Can only revoke lesser (higher) roles from others
+        if 'admin' not in self.caller['roles'] and \
+           role_id <= min(self.caller['role_ids']):
+            raise PLCPermissionDenied, "Not allowed to revoke that role"
+
+        if role_id in person['role_ids']:
+            person.remove_role(role_id)
+
+        return 1
diff --git a/PLC/Methods/SetPersonPrimarySite.py b/PLC/Methods/SetPersonPrimarySite.py
new file mode 100644 (file)
index 0000000..ada63c5
--- /dev/null
@@ -0,0 +1,56 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Auth import PasswordAuth
+
+class SetPersonPrimarySite(Method):
+    """
+    Makes the specified site the person's primary site. The person
+    must already be a member of the site.
+
+    ins may update anyone. All others may only update themselves.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, site_id_or_login_base):
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Non-admins can only update their own primary site
+        if 'admin' not in self.caller['roles'] and \
+           self.caller['person_id'] != person['person_id']:
+            raise PLCPermissionDenied, "Not allowed to update specified account"
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites.values()[0]
+
+        if site['site_id'] not in person['site_ids']:
+            raise PLCInvalidArgument, "Not a member of the specified site"
+
+        person.set_primary_site(site)
+
+        return 1
diff --git a/PLC/Methods/UpdateNode.py b/PLC/Methods/UpdateNode.py
new file mode 100644 (file)
index 0000000..f80b779
--- /dev/null
@@ -0,0 +1,68 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import PasswordAuth
+
+class UpdateNode(Method):
+    """
+    Updates a node. Only the fields specified in update_fields are
+    updated, all other fields are left untouched.
+
+    To remove a value without setting a new one in its place (for
+    example, to remove an address from the node), specify -1 for int
+    and double fields and 'null' for string fields. hostname and
+    boot_state cannot be unset.
+    
+    PIs and techs can only update the nodes at their sites.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    can_update = lambda (field, value): field in \
+                 ['hostname', 'boot_state', 'model', 'version']
+    update_fields = dict(filter(can_update, Node.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Node.fields['node_id'],
+              Node.fields['hostname']),
+        update_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, node_id_or_hostname, update_fields):
+        if filter(lambda field: field not in self.update_fields, update_fields):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        # XML-RPC cannot marshal None, so we need special values to
+        # represent "unset".
+        for key, value in update_fields.iteritems():
+            if value == -1 or value == "null":
+                if key in ['hostname', 'boot_state']:
+                    raise PLCInvalidArgument, "hostname and boot_state cannot be unset"
+                update_fields[key] = None
+
+        # Get account information
+        nodes = Nodes(self.api, [node_id_or_hostname])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node"
+
+        node = nodes.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # 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"
+
+        node.update(update_fields)
+        node.sync()
+
+        return 1
diff --git a/PLC/Methods/UpdateNodeGroup.py b/PLC/Methods/UpdateNodeGroup.py
new file mode 100644 (file)
index 0000000..c953449
--- /dev/null
@@ -0,0 +1,43 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Auth import PasswordAuth
+
+class UpdateNodeGroup(Method):
+    """
+    Updates a custom node group.
+     
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(NodeGroup.fields['nodegroup_id'],
+             NodeGroup.fields['name']),
+        NodeGroup.fields['name'],
+       NodeGroup.fields['description']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, nodegroup_id_or_name, name, description):
+       # Get nodegroup information
+       nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+       if not nodegroups:
+            raise PLCInvalidArgument, "No such nodegroup"
+
+       nodegroup = nodegroups.values()[0]
+       
+       # Modify node group
+        update_fields = {
+            'name': name,
+            'description': description
+            }
+
+       nodegroup.update(update_fields)
+        nodegroup.sync()
+
+        return 1
diff --git a/PLC/Methods/UpdateNodeNetwork.py b/PLC/Methods/UpdateNodeNetwork.py
new file mode 100644 (file)
index 0000000..8e0b267
--- /dev/null
@@ -0,0 +1,76 @@
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Auth import PasswordAuth
+
+class UpdateNodeNetwork(Method):
+    """
+    Updates an existing node network. Any values specified in update_fields 
+    are used, otherwise defaults are used. Acceptable values for method are
+    dhcp and static. If type is static, the parameter update_fields must
+    be present and ip, gateway, network, broadcast, netmask, and dns1 must
+    all be specified. If type is dhcp, these parameters, even if
+    specified, are ignored.
+    
+    PIs and techs may only update networks associated with their own
+    nodes. ins may update any node network.
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    can_update = lambda (field, value): field not in \
+                 ['nodenetwork_id']
+    update_fields = dict(filter(can_update, NodeNetwork.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+       Mixed(NodeNetwork.fields['nodenetwork_id'],
+             NodeNetwork.fields['hostname']),
+       update_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, nodenetwork_id_or_hostname, update_fields):
+        # Check for invalid fields
+        if filter(lambda field: field not in self.update_fields, update_fields):
+            raise PLCInvalidArgument, "Invalid fields specified"
+
+        # XML-RPC cannot marshal None, so we need special values to
+        # represent "unset".
+        for key, value in update_fields.iteritems():
+            if value == -1 or value == "null":
+                if key in ['method', 'type', 'mac', 'ip', 'bwlimit']:
+                    raise PLCInvalidArgument, "%s cannot be unset" % key
+                update_fields[key] = None
+
+       # Get node network information
+       nodenetworks = NodeNetworks(self.api, [nodenetwork_id_or_hostname]).values()
+       if not nodenetworks:
+            raise PLCInvalidArgument, "No such node network"
+
+       nodenetwork = nodenetworks[0]
+               
+        # Authenticated function
+        assert self.caller is not None
+
+       # If we are not an admin, make sure that the caller is a
+        # member of the site where the node exists.
+        if 'admin' not in self.caller['roles']:
+            nodes = Nodes(self.api, [nodenetwork['node_id']]).values()
+            if not nodes:
+                raise PLCPermissionDenied, "Node network is not associated with a node"
+            node = nodes[0]
+            if node['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Not allowed to update node network"
+
+       # Update node network
+       nodenetwork.update(update_fields)
+        nodenetwork.sync()
+       
+        return 1
diff --git a/PLC/Methods/UpdatePerson.py b/PLC/Methods/UpdatePerson.py
new file mode 100644 (file)
index 0000000..a79a461
--- /dev/null
@@ -0,0 +1,68 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import PasswordAuth
+
+class UpdatePerson(Method):
+    """
+    Updates a person. Only the fields specified in update_fields are
+    updated, all other fields are left untouched.
+
+    To remove a value without setting a new one in its place (for
+    example, to remove an address from the person), specify -1 for int
+    and double fields and 'null' for string fields. first_name and
+    last_name cannot be unset.
+    
+    Users and techs can only update themselves. PIs can only update
+    themselves and other non-PIs at their sites.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    can_update = lambda (field, value): field in \
+                 ['first_name', 'last_name', 'title', 'email',
+                  'password', 'phone', 'url', 'bio', 'accepted_aup']
+    update_fields = dict(filter(can_update, Person.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        update_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, update_fields):
+        if filter(lambda field: field not in self.update_fields, update_fields):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        # XML-RPC cannot marshal None, so we need special values to
+        # represent "unset".
+        for key, value in update_fields.iteritems():
+            if value == -1 or value == "null":
+                if key in ['first_name', 'last_name']:
+                    raise PLCInvalidArgument, "first_name and last_name cannot be unset"
+                update_fields[key] = None
+
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+
+        person = persons.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Check if we can update this account
+        if not self.caller.can_update(person):
+            raise PLCPermissionDenied, "Not allowed to update specified account"
+
+        person.update(update_fields)
+        person.sync()
+
+        return 1
diff --git a/PLC/Methods/UpdateSite.py b/PLC/Methods/UpdateSite.py
new file mode 100644 (file)
index 0000000..3b3a908
--- /dev/null
@@ -0,0 +1,75 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Sites import Site, Sites
+from PLC.Auth import PasswordAuth
+
+class UpdateSite(Method):
+    """
+    Updates a site. Only the fields specified in update_fields are
+    updated, all other fields are left untouched.
+
+    To remove a value without setting a new one in its place (for
+    example, to remove an address from the node), specify -1 for int
+    and double fields and 'null' for string fields. hostname and
+    boot_state cannot be unset.
+    
+    PIs can only update sites they are a member of. Only admins can 
+    update max_slices.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    can_update = lambda (field, value): field in \
+                 ['name', 'abbreviated_name',
+                  'is_public', 'latitude', 'longitude', 'url',
+                  'max_slices', 'max_slivers']
+    update_fields = dict(filter(can_update, Site.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base']),
+        update_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, site_id_or_login_base, update_fields):
+       # Check for invalid fields
+        if filter(lambda field: field not in self.update_fields, update_fields):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        # XML-RPC cannot marshal None, so we need special values to
+        # represent "unset".
+        for key, value in update_fields.iteritems():
+            if value == -1 or value == "null":
+                if key not in ['latitude', 'longitude', 'url']:
+                    raise PLCInvalidArgument, "%s cannot be unset" % key
+                update_fields[key] = None
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites.values()[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # 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['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Not allowed to modify specified site"
+
+            if 'max_slices' or 'max_slivers' in update_fields:
+                raise PLCInvalidArgument, "Only admins can update max_slices and max_slivers"
+
+        site.update(update_fields)
+       site.sync()
+       
+       return 1