From 40d33ca5c3e174e3ac07e3519c07e43825b4c797 Mon Sep 17 00:00:00 2001 From: Tony Mack Date: Fri, 6 Oct 2006 19:21:25 +0000 Subject: [PATCH] - removed 'Adm' prefix --- PLC/Methods/AddNodeToNodeGroup.py | 46 ++++++++++++ PLC/Methods/AddPerson.py | 43 +++++++++++ PLC/Methods/AddPersonToSite.py | 47 ++++++++++++ PLC/Methods/AddRoleToPerson.py | 63 ++++++++++++++++ PLC/Methods/AddSite.py | 43 +++++++++++ PLC/Methods/DeleteNode.py | 46 ++++++++++++ PLC/Methods/DeleteNodeGroup.py | 36 ++++++++++ PLC/Methods/DeleteNodeNetwork.py | 60 ++++++++++++++++ PLC/Methods/DeletePerson.py | 45 ++++++++++++ PLC/Methods/DeleteSite.py | 39 ++++++++++ PLC/Methods/GetNodeGroups.py | 34 +++++++++ PLC/Methods/GetNodeNetworkBandwidthLimits.py | 21 ++++++ PLC/Methods/GetNodeNetworks.py | 46 ++++++++++++ PLC/Methods/GetNodes.py | 63 ++++++++++++++++ PLC/Methods/GetPersons.py | 71 ++++++++++++++++++ PLC/Methods/GetSites.py | 52 ++++++++++++++ PLC/Methods/RemoveNodeFromNodeGroup.py | 46 ++++++++++++ PLC/Methods/RemovePersonFromSite.py | 47 ++++++++++++ PLC/Methods/RemoveRoleFromPerson.py | 57 +++++++++++++++ PLC/Methods/SetPersonPrimarySite.py | 56 +++++++++++++++ PLC/Methods/UpdateNode.py | 68 ++++++++++++++++++ PLC/Methods/UpdateNodeGroup.py | 43 +++++++++++ PLC/Methods/UpdateNodeNetwork.py | 76 ++++++++++++++++++++ PLC/Methods/UpdatePerson.py | 68 ++++++++++++++++++ PLC/Methods/UpdateSite.py | 75 +++++++++++++++++++ 25 files changed, 1291 insertions(+) create mode 100644 PLC/Methods/AddNodeToNodeGroup.py create mode 100644 PLC/Methods/AddPerson.py create mode 100644 PLC/Methods/AddPersonToSite.py create mode 100644 PLC/Methods/AddRoleToPerson.py create mode 100644 PLC/Methods/AddSite.py create mode 100644 PLC/Methods/DeleteNode.py create mode 100644 PLC/Methods/DeleteNodeGroup.py create mode 100644 PLC/Methods/DeleteNodeNetwork.py create mode 100644 PLC/Methods/DeletePerson.py create mode 100644 PLC/Methods/DeleteSite.py create mode 100644 PLC/Methods/GetNodeGroups.py create mode 100644 PLC/Methods/GetNodeNetworkBandwidthLimits.py create mode 100644 PLC/Methods/GetNodeNetworks.py create mode 100644 PLC/Methods/GetNodes.py create mode 100644 PLC/Methods/GetPersons.py create mode 100644 PLC/Methods/GetSites.py create mode 100644 PLC/Methods/RemoveNodeFromNodeGroup.py create mode 100644 PLC/Methods/RemovePersonFromSite.py create mode 100644 PLC/Methods/RemoveRoleFromPerson.py create mode 100644 PLC/Methods/SetPersonPrimarySite.py create mode 100644 PLC/Methods/UpdateNode.py create mode 100644 PLC/Methods/UpdateNodeGroup.py create mode 100644 PLC/Methods/UpdateNodeNetwork.py create mode 100644 PLC/Methods/UpdatePerson.py create mode 100644 PLC/Methods/UpdateSite.py diff --git a/PLC/Methods/AddNodeToNodeGroup.py b/PLC/Methods/AddNodeToNodeGroup.py new file mode 100644 index 0000000..04e92be --- /dev/null +++ b/PLC/Methods/AddNodeToNodeGroup.py @@ -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 index 0000000..fb4b887 --- /dev/null +++ b/PLC/Methods/AddPerson.py @@ -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 index 0000000..71d1d67 --- /dev/null +++ b/PLC/Methods/AddPersonToSite.py @@ -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 index 0000000..25b54e2 --- /dev/null +++ b/PLC/Methods/AddRoleToPerson.py @@ -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 index 0000000..171670d --- /dev/null +++ b/PLC/Methods/AddSite.py @@ -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 index 0000000..9555317 --- /dev/null +++ b/PLC/Methods/DeleteNode.py @@ -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 index 0000000..cf6dd8a --- /dev/null +++ b/PLC/Methods/DeleteNodeGroup.py @@ -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 index 0000000..d2c6d4a --- /dev/null +++ b/PLC/Methods/DeleteNodeNetwork.py @@ -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 index 0000000..8375439 --- /dev/null +++ b/PLC/Methods/DeletePerson.py @@ -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 index 0000000..d16946d --- /dev/null +++ b/PLC/Methods/DeleteSite.py @@ -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 index 0000000..ffd6eb3 --- /dev/null +++ b/PLC/Methods/GetNodeGroups.py @@ -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 index 0000000..d2766cf --- /dev/null +++ b/PLC/Methods/GetNodeNetworkBandwidthLimits.py @@ -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 index 0000000..14cdd48 --- /dev/null +++ b/PLC/Methods/GetNodeNetworks.py @@ -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 index 0000000..90a2c3b --- /dev/null +++ b/PLC/Methods/GetNodes.py @@ -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 index 0000000..3805adc --- /dev/null +++ b/PLC/Methods/GetPersons.py @@ -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 index 0000000..2fb73e0 --- /dev/null +++ b/PLC/Methods/GetSites.py @@ -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 index 0000000..715db90 --- /dev/null +++ b/PLC/Methods/RemoveNodeFromNodeGroup.py @@ -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 index 0000000..6d70006 --- /dev/null +++ b/PLC/Methods/RemovePersonFromSite.py @@ -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 index 0000000..a32f465 --- /dev/null +++ b/PLC/Methods/RemoveRoleFromPerson.py @@ -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 index 0000000..ada63c5 --- /dev/null +++ b/PLC/Methods/SetPersonPrimarySite.py @@ -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 index 0000000..f80b779 --- /dev/null +++ b/PLC/Methods/UpdateNode.py @@ -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 index 0000000..c953449 --- /dev/null +++ b/PLC/Methods/UpdateNodeGroup.py @@ -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 index 0000000..8e0b267 --- /dev/null +++ b/PLC/Methods/UpdateNodeNetwork.py @@ -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 index 0000000..a79a461 --- /dev/null +++ b/PLC/Methods/UpdatePerson.py @@ -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 index 0000000..3b3a908 --- /dev/null +++ b/PLC/Methods/UpdateSite.py @@ -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 -- 2.45.2