--- /dev/null
+#
+# PLCAPI XML-RPC and SOAP interfaces
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+# $Id: API.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import sys
+import traceback
+import string
+
+import xmlrpclib
+
+# See "2.2 Characters" in the XML specification:
+#
+# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# avoiding
+# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
+
+invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
+xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
+
+def xmlrpclib_escape(s, replace = string.replace):
+ """
+ xmlrpclib does not handle invalid 7-bit control characters. This
+ function augments xmlrpclib.escape, which by default only replaces
+ '&', '<', and '>' with entities.
+ """
+
+ # This is the standard xmlrpclib.escape function
+ s = replace(s, "&", "&")
+ s = replace(s, "<", "<")
+ s = replace(s, ">", ">",)
+
+ # Replace invalid 7-bit control characters with '?'
+ return s.translate(xml_escape_table)
+
+def xmlrpclib_dump(self, value, write):
+ """
+ xmlrpclib cannot marshal instances of subclasses of built-in
+ types. This function overrides xmlrpclib.Marshaller.__dump so that
+ any value that is an instance of one of its acceptable types is
+ marshalled as that type.
+
+ xmlrpclib also cannot handle invalid 7-bit control characters. See
+ above.
+ """
+
+ # Use our escape function
+ args = [self, value, write]
+ if isinstance(value, (str, unicode)):
+ args.append(xmlrpclib_escape)
+
+ try:
+ # Try for an exact match first
+ f = self.dispatch[type(value)]
+ except KeyError:
+ # Try for an isinstance() match
+ for Type, f in self.dispatch.iteritems():
+ if isinstance(value, Type):
+ f(*args)
+ return
+ raise TypeError, "cannot marshal %s objects" % type(value)
+ else:
+ f(*args)
+
+# You can't hide from me!
+xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
+
+# SOAP support is optional
+try:
+ import SOAPpy
+ from SOAPpy.Parser import parseSOAPRPC
+ from SOAPpy.Types import faultType
+ from SOAPpy.NS import NS
+ from SOAPpy.SOAPBuilder import buildSOAP
+except ImportError:
+ SOAPpy = None
+
+from PLC.Config import Config
+from PLC.Faults import *
+import PLC.Methods
+
+class PLCAPI:
+ methods = PLC.Methods.methods
+
+ def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"):
+ self.encoding = encoding
+
+ # Better just be documenting the API
+ if config is None:
+ return
+
+ # Load configuration
+ self.config = Config(config)
+
+ # Initialize database connection
+ if self.config.PLC_DB_TYPE == "postgresql":
+ from PLC.PostgreSQL import PostgreSQL
+ self.db = PostgreSQL(self)
+
+ else:
+ raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE
+
+ def callable(self, method):
+ """
+ Return a new instance of the specified method.
+ """
+
+ # Look up method
+ if method not in self.methods:
+ raise PLCInvalidAPIMethod, method
+
+ # Get new instance of method
+ try:
+ classname = method.split(".")[-1]
+ module = __import__("PLC.Methods." + method, globals(), locals(), [classname])
+ return getattr(module, classname)(self)
+ except ImportError, AttributeError:
+ raise PLCInvalidAPIMethod, method
+
+ def call(self, source, method, *args):
+ """
+ Call the named method from the specified source with the
+ specified arguments.
+ """
+
+ function = self.callable(method)
+ function.source = source
+ return function(*args)
+
+ def handle(self, source, data):
+ """
+ Handle an XML-RPC or SOAP request from the specified source.
+ """
+
+ # Parse request into method name and arguments
+ try:
+ interface = xmlrpclib
+ (args, method) = xmlrpclib.loads(data)
+ methodresponse = True
+ except Exception, e:
+ if SOAPpy is not None:
+ interface = SOAPpy
+ (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
+ method = r._name
+ args = r._aslist()
+ # XXX Support named arguments
+ else:
+ raise e
+
+ try:
+ result = self.call(source, method, *args)
+ except PLCFault, fault:
+ # Handle expected faults
+ if interface == xmlrpclib:
+ result = fault
+ methodresponse = None
+ elif interface == SOAPpy:
+ result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
+ result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
+
+ # Return result
+ if interface == xmlrpclib:
+ if not isinstance(result, PLCFault):
+ result = (result,)
+ data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
+ elif interface == SOAPpy:
+ data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
+
+ return data
--- /dev/null
+#
+# Functions for interacting with the address_types table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: AddressTypes.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from types import StringTypes
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+
+class AddressType(Row):
+ """
+ Representation of a row in the address_types table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'address_types'
+ primary_key = 'address_type_id'
+ join_tables = ['address_address_type']
+ fields = {
+ 'address_type_id': Parameter(int, "Address type identifier"),
+ 'name': Parameter(str, "Address type", max = 20),
+ 'description': Parameter(str, "Address type description", max = 254),
+ }
+
+ def validate_name(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Address type must be specified"
+
+ # Make sure address type does not already exist
+ conflicts = AddressTypes(self.api, [name])
+ for address_type_id in conflicts:
+ if 'address_type_id' not in self or self['address_type_id'] != address_type_id:
+ raise PLCInvalidArgument, "Address type name already in use"
+
+ return name
+
+class AddressTypes(Table):
+ """
+ Representation of the address_types table in the database.
+ """
+
+ def __init__(self, api, address_type_filter = None, columns = None):
+ Table.__init__(self, api, AddressType, columns)
+
+ sql = "SELECT %s FROM address_types WHERE True" % \
+ ", ".join(self.columns)
+
+ if address_type_filter is not None:
+ if isinstance(address_type_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), address_type_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), address_type_filter)
+ address_type_filter = Filter(AddressType.fields, {'address_type_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % address_type_filter.sql(api, "OR")
+ elif isinstance(address_type_filter, dict):
+ address_type_filter = Filter(AddressType.fields, address_type_filter)
+ sql += " AND (%s) %s" % address_type_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+from PLC.Filter import Filter
+from PLC.AddressTypes import AddressType, AddressTypes
+
+class Address(Row):
+ """
+ Representation of a row in the addresses table. To use, instantiate
+ with a dict of values.
+ """
+
+ table_name = 'addresses'
+ primary_key = 'address_id'
+ join_tables = ['address_address_type', 'site_address']
+ fields = {
+ 'address_id': Parameter(int, "Address identifier"),
+ 'line1': Parameter(str, "Address line 1", max = 254),
+ 'line2': Parameter(str, "Address line 2", max = 254, nullok = True),
+ 'line3': Parameter(str, "Address line 3", max = 254, nullok = True),
+ 'city': Parameter(str, "City", max = 254),
+ 'state': Parameter(str, "State or province", max = 254),
+ 'postalcode': Parameter(str, "Postal code", max = 64),
+ 'country': Parameter(str, "Country", max = 128),
+ 'address_type_ids': Parameter([int], "Address type identifiers"),
+ 'address_types': Parameter([str], "Address types"),
+ }
+
+ def add_address_type(self, address_type, commit = True):
+ """
+ Add address type to existing address.
+ """
+
+ assert 'address_id' in self
+ assert isinstance(address_type, AddressType)
+ assert 'address_type_id' in address_type
+
+ address_id = self['address_id']
+ address_type_id = address_type['address_type_id']
+
+ if address_type_id not in self['address_type_ids']:
+ assert address_type['name'] not in self['address_types']
+
+ self.api.db.do("INSERT INTO address_address_type (address_id, address_type_id)" \
+ " VALUES(%(address_id)d, %(address_type_id)d)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['address_type_ids'].append(address_type_id)
+ self['address_types'].append(address_type['name'])
+
+ def remove_address_type(self, address_type, commit = True):
+ """
+ Add address type to existing address.
+ """
+
+ assert 'address_id' in self
+ assert isinstance(address_type, AddressType)
+ assert 'address_type_id' in address_type
+
+ address_id = self['address_id']
+ address_type_id = address_type['address_type_id']
+
+ if address_type_id in self['address_type_ids']:
+ assert address_type['name'] in self['address_types']
+
+ self.api.db.do("DELETE FROM address_address_type" \
+ " WHERE address_id = %(address_id)d" \
+ " AND address_type_id = %(address_type_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['address_type_ids'].remove(address_type_id)
+ self['address_types'].remove(address_type['name'])
+
+class Addresses(Table):
+ """
+ Representation of row(s) from the addresses table in the
+ database.
+ """
+
+ def __init__(self, api, address_filter = None, columns = None):
+ Table.__init__(self, api, Address, columns)
+
+ sql = "SELECT %s FROM view_addresses WHERE True" % \
+ ", ".join(self.columns)
+
+ if address_filter is not None:
+ if isinstance(address_filter, (list, tuple, set)):
+ address_filter = Filter(Address.fields, {'address_id': address_filter})
+ elif isinstance(address_filter, dict):
+ address_filter = Filter(Address.fields, address_filter)
+ sql += " AND (%s) %s" % address_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+#
+# PLCAPI authentication parameters
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Auth.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import crypt
+import sha
+import hmac
+import time
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Persons
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Sessions import Session, Sessions
+from PLC.Peers import Peer, Peers
+from PLC.Boot import notify_owners
+
+class Auth(Parameter):
+ """
+ Base class for all API authentication methods, as well as a class
+ that can be used to represent all supported API authentication
+ methods.
+ """
+
+ def __init__(self, auth = None):
+ if auth is None:
+ auth = {'AuthMethod': Parameter(str, "Authentication method to use", optional = False)}
+ Parameter.__init__(self, auth, "API authentication structure")
+
+ def check(self, method, auth, *args):
+ # Method.type_check() should have checked that all of the
+ # mandatory fields were present.
+ assert 'AuthMethod' in auth
+
+ if auth['AuthMethod'] == "session":
+ expected = SessionAuth()
+ elif auth['AuthMethod'] == "password" or \
+ auth['AuthMethod'] == "capability":
+ expected = PasswordAuth()
+ elif auth['AuthMethod'] == "gpg":
+ expected = GPGAuth()
+ elif auth['AuthMethod'] == "hmac":
+ expected = BootAuth()
+ elif auth['AuthMethod'] == "anonymous":
+ expected = AnonymousAuth()
+ else:
+ raise PLCInvalidArgument("must be 'session', 'password', 'gpg', 'hmac', or 'anonymous'", "AuthMethod")
+
+ # Re-check using the specified authentication method
+ method.type_check("auth", auth, expected, (auth,) + args)
+
+class GPGAuth(Auth):
+ """
+ Proposed PlanetLab federation authentication structure.
+ """
+
+ def __init__(self):
+ Auth.__init__(self, {
+ 'AuthMethod': Parameter(str, "Authentication method to use, always 'gpg'", optional = False),
+ 'name': Parameter(str, "Peer or user name", optional = False),
+ 'signature': Parameter(str, "Message signature", optional = False)
+ })
+
+ def check(self, method, auth, *args):
+ try:
+ peers = Peers(method.api, [auth['name']])
+ if peers:
+ if 'peer' not in method.roles:
+ raise PLCAuthenticationFailure, "Not allowed to call method"
+
+ method.caller = peer = peers[0]
+ keys = [peer['key']]
+ else:
+ persons = Persons(method.api, {'email': auth['name'], 'enabled': True, 'peer_id': None})
+ if not persons:
+ raise PLCAuthenticationFailure, "No such user '%s'" % auth['name']
+
+ if not set(person['roles']).intersection(method.roles):
+ raise PLCAuthenticationFailure, "Not allowed to call method"
+
+ method.caller = person = persons[0]
+ keys = Keys(method.api, {'key_id': person['key_ids'], 'key_type': "gpg", 'peer_id': None})
+
+ if not keys:
+ raise PLCAuthenticationFailure, "No GPG key on record for peer or user '%s'"
+
+ for key in keys:
+ try:
+ from PLC.GPG import gpg_verify
+ gpg_verify(args, key, auth['signature'], method.name)
+ return
+ except PLCAuthenticationFailure, fault:
+ pass
+
+ raise fault
+
+ except PLCAuthenticationFailure, fault:
+ # XXX Send e-mail
+ raise fault
+
+class SessionAuth(Auth):
+ """
+ Secondary authentication method. After authenticating with a
+ primary authentication method, call GetSession() to generate a
+ session key that may be used for subsequent calls.
+ """
+
+ def __init__(self):
+ Auth.__init__(self, {
+ 'AuthMethod': Parameter(str, "Authentication method to use, always 'session'", optional = False),
+ 'session': Parameter(str, "Session key", optional = False)
+ })
+
+ def check(self, method, auth, *args):
+ # Method.type_check() should have checked that all of the
+ # mandatory fields were present.
+ assert auth.has_key('session')
+
+ # Get session record
+ sessions = Sessions(method.api, [auth['session']], expires = None)
+ if not sessions:
+ raise PLCAuthenticationFailure, "No such session"
+ session = sessions[0]
+
+ try:
+ if session['node_id'] is not None:
+ nodes = Nodes(method.api, {'node_id': session['node_id'], 'peer_id': None})
+ if not nodes:
+ raise PLCAuthenticationFailure, "No such node"
+ node = nodes[0]
+
+ if 'node' not in method.roles:
+ raise PLCAuthenticationFailure, "Not allowed to call method"
+
+ method.caller = node
+
+ elif session['person_id'] is not None and session['expires'] > time.time():
+ persons = Persons(method.api, {'person_id': session['person_id'], 'enabled': True, 'peer_id': None})
+ if not persons:
+ raise PLCAuthenticationFailure, "No such account"
+ person = persons[0]
+
+ if not set(person['roles']).intersection(method.roles):
+ raise PLCPermissionDenied, "Not allowed to call method"
+
+ method.caller = persons[0]
+
+ else:
+ raise PLCAuthenticationFailure, "Invalid session"
+
+ except PLCAuthenticationFailure, fault:
+ session.delete()
+ raise fault
+
+class BootAuth(Auth):
+ """
+ PlanetLab version 3.x node authentication structure. Used by the
+ Boot Manager to make authenticated calls to the API based on a
+ unique node key or boot nonce value.
+
+ The original parameter serialization code did not define the byte
+ encoding of strings, or the string encoding of all other types. We
+ define the byte encoding to be UTF-8, and the string encoding of
+ all other types to be however Python version 2.3 unicode() encodes
+ them.
+ """
+
+ def __init__(self):
+ Auth.__init__(self, {
+ 'AuthMethod': Parameter(str, "Authentication method to use, always 'hmac'", optional = False),
+ 'node_id': Parameter(int, "Node identifier", optional = False),
+ 'value': Parameter(str, "HMAC of node key and method call", optional = False)
+ })
+
+ def canonicalize(self, args):
+ values = []
+
+ for arg in args:
+ if isinstance(arg, list) or isinstance(arg, tuple):
+ # The old implementation did not recursively handle
+ # lists of lists. But neither did the old API itself.
+ values += self.canonicalize(arg)
+ elif isinstance(arg, dict):
+ # Yes, the comments in the old implementation are
+ # misleading. Keys of dicts are not included in the
+ # hash.
+ values += self.canonicalize(arg.values())
+ else:
+ # We use unicode() instead of str().
+ values.append(unicode(arg))
+
+ return values
+
+ def check(self, method, auth, *args):
+ # Method.type_check() should have checked that all of the
+ # mandatory fields were present.
+ assert auth.has_key('node_id')
+
+ if 'node' not in method.roles:
+ raise PLCAuthenticationFailure, "Not allowed to call method"
+
+ try:
+ nodes = Nodes(method.api, {'node_id': auth['node_id'], 'peer_id': None})
+ if not nodes:
+ raise PLCAuthenticationFailure, "No such node"
+ node = nodes[0]
+
+ if node['key']:
+ key = node['key']
+ elif node['boot_nonce']:
+ # Allow very old nodes that do not have a node key in
+ # their configuration files to use their "boot nonce"
+ # instead. The boot nonce is a random value generated
+ # by the node itself and POSTed by the Boot CD when it
+ # requests the Boot Manager. This is obviously not
+ # very secure, so we only allow it to be used if the
+ # requestor IP is the same as the IP address we have
+ # on record for the node.
+ key = node['boot_nonce']
+
+ nodenetwork = None
+ if node['nodenetwork_ids']:
+ nodenetworks = NodeNetworks(method.api, node['nodenetwork_ids'])
+ for nodenetwork in nodenetworks:
+ if nodenetwork['is_primary']:
+ break
+
+ if not nodenetwork or not nodenetwork['is_primary']:
+ raise PLCAuthenticationFailure, "No primary network interface on record"
+
+ if method.source is None:
+ raise PLCAuthenticationFailure, "Cannot determine IP address of requestor"
+
+ if nodenetwork['ip'] != method.source[0]:
+ raise PLCAuthenticationFailure, "Requestor IP %s does not match node IP %s" % \
+ (method.source[0], nodenetwork['ip'])
+ else:
+ raise PLCAuthenticationFailure, "No node key or boot nonce"
+
+ # Yes, this is the "canonicalization" method used.
+ args = self.canonicalize(args)
+ args.sort()
+ msg = "[" + "".join(args) + "]"
+
+ # We encode in UTF-8 before calculating the HMAC, which is
+ # an 8-bit algorithm.
+ digest = hmac.new(key, msg.encode('utf-8'), sha).hexdigest()
+
+ if digest != auth['value']:
+ raise PLCAuthenticationFailure, "Call could not be authenticated"
+
+ method.caller = node
+
+ except PLCAuthenticationFailure, fault:
+ if nodes:
+ notify_owners(method, node, 'authfail', include_pis = True, include_techs = True, fault = fault)
+ raise fault
+
+class AnonymousAuth(Auth):
+ """
+ PlanetLab version 3.x anonymous authentication structure.
+ """
+
+ def __init__(self):
+ Auth.__init__(self, {
+ 'AuthMethod': Parameter(str, "Authentication method to use, always 'anonymous'", False),
+ })
+
+ def check(self, method, auth, *args):
+ if 'anonymous' not in method.roles:
+ raise PLCAuthenticationFailure, "Not allowed to call method anonymously"
+
+ method.caller = None
+
+class PasswordAuth(Auth):
+ """
+ PlanetLab version 3.x password authentication structure.
+ """
+
+ def __init__(self):
+ Auth.__init__(self, {
+ 'AuthMethod': Parameter(str, "Authentication method to use, always 'password' or 'capability'", optional = False),
+ 'Username': Parameter(str, "PlanetLab username, typically an e-mail address", optional = False),
+ 'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
+ })
+
+ def check(self, method, auth, *args):
+ # Method.type_check() should have checked that all of the
+ # mandatory fields were present.
+ assert auth.has_key('Username')
+
+ # Get record (must be enabled)
+ persons = Persons(method.api, {'email': auth['Username'].lower(), 'enabled': True, 'peer_id': None})
+ if len(persons) != 1:
+ raise PLCAuthenticationFailure, "No such account"
+
+ person = persons[0]
+
+ if auth['Username'] == method.api.config.PLC_API_MAINTENANCE_USER:
+ # "Capability" authentication, whatever the hell that was
+ # supposed to mean. It really means, login as the special
+ # "maintenance user" using password authentication. Can
+ # only be used on particular machines (those in a list).
+ sources = method.api.config.PLC_API_MAINTENANCE_SOURCES.split()
+ if method.source is not None and method.source[0] not in sources:
+ raise PLCAuthenticationFailure, "Not allowed to login to maintenance account"
+
+ # Not sure why this is not stored in the DB
+ password = method.api.config.PLC_API_MAINTENANCE_PASSWORD
+
+ if auth['AuthString'] != password:
+ raise PLCAuthenticationFailure, "Maintenance account password verification failed"
+ else:
+ # Compare encrypted plaintext against encrypted password stored in the DB
+ plaintext = auth['AuthString'].encode(method.api.encoding)
+ password = person['password']
+
+ # Protect against blank passwords in the DB
+ if password is None or password[:12] == "" or \
+ crypt.crypt(plaintext, password[:12]) != password:
+ raise PLCAuthenticationFailure, "Password verification failed"
+
+ if not set(person['roles']).intersection(method.roles):
+ raise PLCAuthenticationFailure, "Not allowed to call method"
+
+ method.caller = person
--- /dev/null
+#
+# Boot Manager support
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2007 The Trustees of Princeton University
+#
+# $Id: Boot.py 6955 2007-11-19 15:40:41Z soltesz $
+#
+
+from PLC.Faults import *
+from PLC.Debug import log
+from PLC.Messages import Message, Messages
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.sendmail import sendmail
+
+def notify_owners(method, node, message_id,
+ include_pis = False, include_techs = False, include_support = False,
+ fault = None):
+ messages = Messages(method.api, [message_id], enabled = True)
+ if not messages:
+ print >> log, "No such message template '%s'" % message_id
+ return 1
+ message = messages[0]
+
+ To = []
+
+ if method.api.config.PLC_MAIL_BOOT_ADDRESS:
+ To.append(("Boot Messages", method.api.config.PLC_MAIL_BOOT_ADDRESS))
+
+ if include_support and method.api.config.PLC_MAIL_SUPPORT_ADDRESS:
+ To.append(("%s Support" % method.api.config.PLC_NAME,
+ method.api.config.PLC_MAIL_SUPPORT_ADDRESS))
+
+ if include_pis or include_techs:
+ sites = Sites(method.api, [node['site_id']])
+ if not sites:
+ raise PLCAPIError, "No site associated with node"
+ site = sites[0]
+
+ persons = Persons(method.api, site['person_ids'])
+ for person in persons:
+ if (include_pis and 'pi' in person['roles'] and person['enabled']) or \
+ (include_techs and 'tech' in person['roles'] and person['enabled']) :
+ To.append(("%s %s" % (person['first_name'], person['last_name']), person['email']))
+
+ # Send email
+ params = {'node_id': node['node_id'],
+ 'hostname': node['hostname'],
+ 'PLC_WWW_HOST': method.api.config.PLC_WWW_HOST,
+ 'PLC_WWW_SSL_PORT': method.api.config.PLC_WWW_SSL_PORT,
+ 'fault': fault}
+
+ sendmail(method.api, To = To,
+ Subject = message['subject'] % params,
+ Body = message['template'] % params)
+
+ # Logging variables
+ method.object_type = "Node"
+ method.object_ids = [node['node_id']]
+ method.message = "Sent message %s" % message_id
--- /dev/null
+#
+# Functions for interacting with the boot_states table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: BootStates.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class BootState(Row):
+ """
+ Representation of a row in the boot_states table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'boot_states'
+ primary_key = 'boot_state'
+ join_tables = ['nodes']
+ fields = {
+ 'boot_state': Parameter(str, "Boot state", max = 20),
+ }
+
+ def validate_boot_state(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Boot state must be specified"
+
+ # Make sure boot state does not alredy exist
+ conflicts = BootStates(self.api, [name])
+ if conflicts:
+ raise PLCInvalidArgument, "Boot state name already in use"
+
+ return name
+
+class BootStates(Table):
+ """
+ Representation of the boot_states table in the database.
+ """
+
+ def __init__(self, api, boot_states = None):
+ Table.__init__(self, api, BootState)
+
+ sql = "SELECT %s FROM boot_states" % \
+ ", ".join(BootState.fields)
+
+ if boot_states:
+ sql += " WHERE boot_state IN (%s)" % ", ".join(map(api.db.quote, boot_states))
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the conf_files table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: ConfFiles.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Nodes import Node, Nodes
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+class ConfFile(Row):
+ """
+ Representation of a row in the conf_files table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'conf_files'
+ primary_key = 'conf_file_id'
+ join_tables = ['conf_file_node', 'conf_file_nodegroup']
+ fields = {
+ 'conf_file_id': Parameter(int, "Configuration file identifier"),
+ 'enabled': Parameter(bool, "Configuration file is active"),
+ 'source': Parameter(str, "Relative path on the boot server where file can be downloaded", max = 255),
+ 'dest': Parameter(str, "Absolute path where file should be installed", max = 255),
+ 'file_permissions': Parameter(str, "chmod(1) permissions", max = 20),
+ 'file_owner': Parameter(str, "chown(1) owner", max = 50),
+ 'file_group': Parameter(str, "chgrp(1) owner", max = 50),
+ 'preinstall_cmd': Parameter(str, "Shell command to execute prior to installing", max = 1024, nullok = True),
+ 'postinstall_cmd': Parameter(str, "Shell command to execute after installing", max = 1024, nullok = True),
+ 'error_cmd': Parameter(str, "Shell command to execute if any error occurs", max = 1024, nullok = True),
+ 'ignore_cmd_errors': Parameter(bool, "Install file anyway even if an error occurs"),
+ 'always_update': Parameter(bool, "Always attempt to install file even if unchanged"),
+ 'node_ids': Parameter(int, "List of nodes linked to this file"),
+ 'nodegroup_ids': Parameter(int, "List of node groups linked to this file"),
+ }
+
+ def add_node(self, node, commit = True):
+ """
+ Add configuration file to node.
+ """
+
+ assert 'conf_file_id' in self
+ assert isinstance(node, Node)
+ assert 'node_id' in node
+
+ conf_file_id = self['conf_file_id']
+ node_id = node['node_id']
+
+ if node_id not in self['node_ids']:
+ self.api.db.do("INSERT INTO conf_file_node (conf_file_id, node_id)" \
+ " VALUES(%(conf_file_id)d, %(node_id)d)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].append(node_id)
+ node['conf_file_ids'].append(conf_file_id)
+
+ def remove_node(self, node, commit = True):
+ """
+ Remove configuration file from node.
+ """
+
+ assert 'conf_file_id' in self
+ assert isinstance(node, Node)
+ assert 'node_id' in node
+
+ conf_file_id = self['conf_file_id']
+ node_id = node['node_id']
+
+ if node_id in self['node_ids']:
+ self.api.db.do("DELETE FROM conf_file_node" \
+ " WHERE conf_file_id = %(conf_file_id)d" \
+ " AND node_id = %(node_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].remove(node_id)
+ node['conf_file_ids'].remove(conf_file_id)
+
+ def add_nodegroup(self, nodegroup, commit = True):
+ """
+ Add configuration file to node group.
+ """
+
+ assert 'conf_file_id' in self
+ assert isinstance(nodegroup, NodeGroup)
+ assert 'nodegroup_id' in nodegroup
+
+ conf_file_id = self['conf_file_id']
+ nodegroup_id = nodegroup['nodegroup_id']
+
+ if nodegroup_id not in self['nodegroup_ids']:
+ self.api.db.do("INSERT INTO conf_file_nodegroup (conf_file_id, nodegroup_id)" \
+ " VALUES(%(conf_file_id)d, %(nodegroup_id)d)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['nodegroup_ids'].append(nodegroup_id)
+ nodegroup['conf_file_ids'].append(conf_file_id)
+
+ def remove_nodegroup(self, nodegroup, commit = True):
+ """
+ Remove configuration file from node group.
+ """
+
+ assert 'conf_file_id' in self
+ assert isinstance(nodegroup, NodeGroup)
+ assert 'nodegroup_id' in nodegroup
+
+ conf_file_id = self['conf_file_id']
+ nodegroup_id = nodegroup['nodegroup_id']
+
+ if nodegroup_id in self['nodegroup_ids']:
+ self.api.db.do("DELETE FROM conf_file_nodegroup" \
+ " WHERE conf_file_id = %(conf_file_id)d" \
+ " AND nodegroup_id = %(nodegroup_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['nodegroup_ids'].remove(nodegroup_id)
+ nodegroup['conf_file_ids'].remove(conf_file_id)
+
+class ConfFiles(Table):
+ """
+ Representation of the conf_files table in the database.
+ """
+
+ def __init__(self, api, conf_file_filter = None, columns = None):
+ Table.__init__(self, api, ConfFile, columns)
+
+ sql = "SELECT %s FROM view_conf_files WHERE True" % \
+ ", ".join(self.columns)
+
+ if conf_file_filter is not None:
+ if isinstance(conf_file_filter, (list, tuple, set)):
+ conf_file_filter = Filter(ConfFile.fields, {'conf_file_id': conf_file_filter})
+ elif isinstance(conf_file_filter, dict):
+ conf_file_filter = Filter(ConfFile.fields, conf_file_filter)
+ sql += " AND (%s) %s" % conf_file_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+#!/usr/bin/python
+#
+# PLCAPI configuration store. Supports XML-based configuration file
+# format exported by MyPLC.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+# $Id: Config.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import os
+import sys
+
+from PLC.Faults import *
+from PLC.Debug import profile
+
+# If we have been checked out into a directory at the same
+# level as myplc, where plc_config.py lives. If we are in a
+# MyPLC environment, plc_config.py has already been installed
+# in site-packages.
+myplc = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + \
+ os.sep + "myplc"
+
+class Config:
+ """
+ Parse the bash/Python/PHP version of the configuration file. Very
+ fast but no type conversions.
+ """
+
+ def __init__(self, file = "/etc/planetlab/plc_config"):
+ # Load plc_config
+ try:
+ execfile(file, self.__dict__)
+ except:
+ # Try myplc directory
+ try:
+ execfile(myplc + os.sep + "plc_config", self.__dict__)
+ except:
+ raise PLCAPIError("Could not find plc_config in " + \
+ file + ", " + \
+ myplc + os.sep + "plc_config")
+
+class XMLConfig:
+ """
+ Parse the XML configuration file directly. Takes longer but is
+ presumably more accurate.
+ """
+
+ def __init__(self, file = "/etc/planetlab/plc_config.xml"):
+ try:
+ from plc_config import PLCConfiguration
+ except:
+ sys.path.append(myplc)
+ from plc_config import PLCConfiguration
+
+ # Load plc_config.xml
+ try:
+ cfg = PLCConfiguration(file)
+ except:
+ # Try myplc directory
+ try:
+ cfg = PLCConfiguration(myplc + os.sep + "plc_config.xml")
+ except:
+ raise PLCAPIError("Could not find plc_config.xml in " + \
+ file + ", " + \
+ myplc + os.sep + "plc_config.xml")
+
+ for (category, variablelist) in cfg.variables().values():
+ for variable in variablelist.values():
+ # Try to cast each variable to an appropriate Python
+ # type.
+ if variable['type'] == "int":
+ value = int(variable['value'])
+ elif variable['type'] == "double":
+ value = float(variable['value'])
+ elif variable['type'] == "boolean":
+ if variable['value'] == "true":
+ value = True
+ else:
+ value = False
+ else:
+ value = variable['value']
+
+ # Variables are split into categories such as
+ # "plc_api", "plc_db", etc. Within each category are
+ # variables such as "host", "port", etc. For backward
+ # compatibility, refer to variables by their shell
+ # names.
+ shell_name = category['id'].upper() + "_" + variable['id'].upper()
+ setattr(self, shell_name, value)
+
+if __name__ == '__main__':
+ import pprint
+ pprint = pprint.PrettyPrinter()
+ pprint.pprint(Config().__dict__.items())
--- /dev/null
+import time
+import sys
+import syslog
+
+class unbuffered:
+ """
+ Write to /var/log/httpd/error_log. See
+
+ http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp
+ """
+
+ def write(self, data):
+ sys.stderr.write(data)
+ sys.stderr.flush()
+
+log = unbuffered()
+
+def profile(callable):
+ """
+ Prints the runtime of the specified callable. Use as a decorator, e.g.,
+
+ @profile
+ def foo(...):
+ ...
+
+ Or, equivalently,
+
+ def foo(...):
+ ...
+ foo = profile(foo)
+
+ Or inline:
+
+ result = profile(foo)(...)
+ """
+
+ def wrapper(*args, **kwds):
+ start = time.time()
+ result = callable(*args, **kwds)
+ end = time.time()
+ args = map(str, args)
+ args += ["%s = %s" % (name, str(value)) for (name, value) in kwds.items()]
+ print >> log, "%s (%s): %f s" % (callable.__name__, ", ".join(args), end - start)
+ return result
+
+ return wrapper
+
+if __name__ == "__main__":
+ def sleep(seconds = 1):
+ time.sleep(seconds)
+
+ sleep = profile(sleep)
+
+ sleep(1)
--- /dev/null
+#
+# Functions for interacting with the events table in the database
+#
+# Tony Mack <tmack@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: EventObjects.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+
+class EventObject(Row):
+ """
+ Representation of a row in the event_object table.
+ """
+
+ table_name = 'event_object'
+ primary_key = 'event_id'
+ fields = {
+ 'event_id': Parameter(int, "Event identifier"),
+ 'person_id': Parameter(int, "Identifier of person responsible for event, if any"),
+ 'node_id': Parameter(int, "Identifier of node responsible for event, if any"),
+ 'fault_code': Parameter(int, "Event fault code"),
+ 'call_name': Parameter(str, "Call responsible for this event"),
+ 'call': Parameter(str, "Call responsible for this event, including paramters"),
+ 'message': Parameter(str, "High level description of this event"),
+ 'runtime': Parameter(float, "Runtime of event"),
+ 'time': Parameter(int, "Date and time that the event took place, in seconds since UNIX epoch", ro = True),
+ 'object_id': Parameter(int, "ID of objects affected by this event"),
+ 'object_type': Parameter(str, "What type of object is this event affecting")
+ }
+
+class EventObjects(Table):
+ """
+ Representation of row(s) from the event_object table in the database.
+ """
+
+ def __init__(self, api, event_filter = None, columns = None):
+ Table.__init__(self, api, EventObject, columns)
+
+ sql = "SELECT %s FROM view_event_objects WHERE True" % \
+ ", ".join(self.columns)
+
+ if event_filter is not None:
+ if isinstance(event_filter, (list, tuple, set)):
+ event_filter = Filter(EventObject.fields, {'event_id': event_filter})
+ sql += " AND (%s) %s" % event_filter.sql(api, "OR")
+ elif isinstance(event_filter, dict):
+ event_filter = Filter(EventObject.fields, event_filter)
+ sql += " AND (%s) %s" % event_filter.sql(api, "AND")
+ elif isinstance (event_filter, int):
+ event_filter = Filter(EventObject.fields, {'event_id':[event_filter]})
+ sql += " AND (%s) %s" % event_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong event object filter %r"%event_filter
+# with new filtering, caller needs to set this explicitly
+# sql += " ORDER BY %s" % EventObject.primary_key
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the events table in the database
+#
+# Tony Mack <tmack@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Events.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+
+class Event(Row):
+ """
+ Representation of a row in the events table.
+ """
+
+ table_name = 'events'
+ primary_key = 'event_id'
+ fields = {
+ 'event_id': Parameter(int, "Event identifier"),
+ 'person_id': Parameter(int, "Identifier of person responsible for event, if any"),
+ 'node_id': Parameter(int, "Identifier of node responsible for event, if any"),
+ 'auth_type': Parameter(int, "Type of auth used. i.e. AuthMethod"),
+ 'fault_code': Parameter(int, "Event fault code"),
+ 'call_name': Parameter(str, "Call responsible for this event"),
+ 'call': Parameter(str, "Call responsible for this event, including paramters"),
+ 'message': Parameter(str, "High level description of this event"),
+ 'runtime': Parameter(float, "Runtime of event"),
+ 'time': Parameter(int, "Date and time that the event took place, in seconds since UNIX epoch", ro = True),
+ 'object_ids': Parameter([int], "IDs of objects affected by this event"),
+ 'object_types': Parameter([str], "What type of object were affected by this event")
+ }
+
+ def add_object(self, object_type, object_id, commit = True):
+ """
+ Relate object to this event.
+ """
+
+ assert 'event_id' in self
+
+ event_id = self['event_id']
+
+ if 'object_ids' not in self:
+ self['object_ids'] = []
+
+ if object_id not in self['object_ids']:
+ self.api.db.do("INSERT INTO event_object (event_id, object_id, object_type)" \
+ " VALUES(%(event_id)d, %(object_id)d, %(object_type)s)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['object_ids'].append(object_id)
+
+class Events(Table):
+ """
+ Representation of row(s) from the events table in the database.
+ """
+
+ def __init__(self, api, event_filter = None, columns = None):
+ Table.__init__(self, api, Event, columns)
+
+ sql = "SELECT %s FROM view_events WHERE True" % \
+ ", ".join(self.columns)
+
+ if event_filter is not None:
+ if isinstance(event_filter, (list, tuple, set)):
+ event_filter = Filter(Event.fields, {'event_id': event_filter})
+ elif isinstance(event_filter, dict):
+ event_filter = Filter(Event.fields, event_filter)
+ sql += " AND (%s) %s" % event_filter.sql(api)
+# with new filtering, caller needs to set this explicitly
+# sql += " ORDER BY %s" % Event.primary_key
+ self.selectall(sql)
--- /dev/null
+#
+# PLCAPI XML-RPC faults
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+# $Id: Faults.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import xmlrpclib
+
+class PLCFault(xmlrpclib.Fault):
+ def __init__(self, faultCode, faultString, extra = None):
+ if extra:
+ faultString += ": " + extra
+ xmlrpclib.Fault.__init__(self, faultCode, faultString)
+
+class PLCInvalidAPIMethod(PLCFault):
+ def __init__(self, method, role = None, extra = None):
+ faultString = "Invalid method " + method
+ if role:
+ faultString += " for role " + role
+ PLCFault.__init__(self, 100, faultString, extra)
+
+class PLCInvalidArgumentCount(PLCFault):
+ def __init__(self, got, min, max = min, extra = None):
+ if min != max:
+ expected = "%d-%d" % (min, max)
+ else:
+ expected = "%d" % min
+ faultString = "Expected %s arguments, got %d" % \
+ (expected, got)
+ PLCFault.__init__(self, 101, faultString, extra)
+
+class PLCInvalidArgument(PLCFault):
+ def __init__(self, extra = None, name = None):
+ if name is not None:
+ faultString = "Invalid %s value" % name
+ else:
+ faultString = "Invalid argument"
+ PLCFault.__init__(self, 102, faultString, extra)
+
+class PLCAuthenticationFailure(PLCFault):
+ def __init__(self, extra = None):
+ faultString = "Failed to authenticate call"
+ PLCFault.__init__(self, 103, faultString, extra)
+
+class PLCDBError(PLCFault):
+ def __init__(self, extra = None):
+ faultString = "Database error"
+ PLCFault.__init__(self, 106, faultString, extra)
+
+class PLCPermissionDenied(PLCFault):
+ def __init__(self, extra = None):
+ faultString = "Permission denied"
+ PLCFault.__init__(self, 108, faultString, extra)
+
+class PLCNotImplemented(PLCFault):
+ def __init__(self, extra = None):
+ faultString = "Not fully implemented"
+ PLCFault.__init__(self, 109, faultString, extra)
+
+class PLCAPIError(PLCFault):
+ def __init__(self, extra = None):
+ faultString = "Internal API error"
+ PLCFault.__init__(self, 111, faultString, extra)
--- /dev/null
+from types import StringTypes
+try:
+ set
+except NameError:
+ from sets import Set
+ set = Set
+
+import time
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed, python_type
+
+class Filter(Parameter, dict):
+ """
+ A type of parameter that represents a filter on one or more
+ columns of a database table.
+ Special features provide support for negation, upper and lower bounds,
+ as well as sorting and clipping.
+
+
+ fields should be a dictionary of field names and types
+ Only filters on non-sequence type fields are supported.
+ example : fields = {'node_id': Parameter(int, "Node identifier"),
+ 'hostname': Parameter(int, "Fully qualified hostname", max = 255),
+ ...}
+
+
+ filter should be a dictionary of field names and values
+ representing the criteria for filtering.
+ example : filter = { 'hostname' : '*.edu' , site_id : [34,54] }
+ Whether the filter represents an intersection (AND) or a union (OR)
+ of these criteria is determined by the join_with argument
+ provided to the sql method below
+
+ Special features:
+
+ * a field starting with the ~ character means negation.
+ example : filter = { '~peer_id' : None }
+
+ * a field starting with < [ ] or > means lower than or greater than
+ < > uses strict comparison
+ [ ] is for using <= or >= instead
+ example : filter = { ']event_id' : 2305 }
+ example : filter = { '>time' : 1178531418 }
+ in this example the integer value denotes a unix timestamp
+
+ * if a value is a sequence type, then it should represent
+ a list of possible values for that field
+ example : filter = { 'node_id' : [12,34,56] }
+
+ * a (string) value containing either a * or a % character is
+ treated as a (sql) pattern; * are replaced with % that is the
+ SQL wildcard character.
+ example : filter = { 'hostname' : '*.jp' }
+
+ * fields starting with - are special and relate to row selection, i.e. sorting and clipping
+ * '-SORT' : a field name, or an ordered list of field names that are used for sorting
+ these fields may start with + (default) or - for denoting increasing or decreasing order
+ example : filter = { '-SORT' : [ '+node_id', '-hostname' ] }
+ * '-OFFSET' : the number of first rows to be ommitted
+ * '-LIMIT' : the amount of rows to be returned
+ example : filter = { '-OFFSET' : 100, '-LIMIT':25}
+
+ A realistic example would read
+ GetNodes ( { 'hostname' : '*.edu' , '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 } )
+ and that would return nodes matching '*.edu' in alphabetical order from 31th to 55th
+ """
+
+ def __init__(self, fields = {}, filter = {}, doc = "Attribute filter"):
+ # Store the filter in our dict instance
+ dict.__init__(self, filter)
+
+ # Declare ourselves as a type of parameter that can take
+ # either a value or a list of values for each of the specified
+ # fields.
+ self.fields = {}
+
+ for field, expected in fields.iteritems():
+ # Cannot filter on sequences
+ if python_type(expected) in (list, tuple, set):
+ continue
+
+ # Accept either a value or a list of values of the specified type
+ self.fields[field] = Mixed(expected, [expected])
+
+ # Null filter means no filter
+ Parameter.__init__(self, self.fields, doc = doc, nullok = True)
+
+ # this code is not used anymore
+ # at some point the select in the DB for event objects was done on
+ # the events table directly, that is stored as a timestamp, thus comparisons
+ # needed to be done based on SQL timestamps as well
+ def unix2timestamp (self,unix):
+ s = time.gmtime(unix)
+ return "TIMESTAMP'%04d-%02d-%02d %02d:%02d:%02d'" % (s.tm_year,s.tm_mon,s.tm_mday,
+ s.tm_hour,s.tm_min,s.tm_sec)
+
+ def sql(self, api, join_with = "AND"):
+ """
+ Returns a SQL conditional that represents this filter.
+ """
+
+ # So that we always return something
+ if join_with == "AND":
+ conditionals = ["True"]
+ elif join_with == "OR":
+ conditionals = ["False"]
+ else:
+ assert join_with in ("AND", "OR")
+
+ # init
+ sorts = []
+ clips = []
+
+ for field, value in self.iteritems():
+ # handle negation, numeric comparisons
+ # simple, 1-depth only mechanism
+
+ modifiers={'~' : False,
+ '<' : False, '>' : False,
+ '[' : False, ']' : False,
+ '-' : False,
+ }
+
+ for char in modifiers.keys():
+ if field[0] == char:
+ modifiers[char]=True;
+ field = field[1:]
+ break
+
+ # filter on fields
+ if not modifiers['-']:
+ if field not in self.fields:
+ raise PLCInvalidArgument, "Invalid filter field '%s'" % field
+
+ if isinstance(value, (list, tuple, set)):
+ # Turn empty list into (NULL) instead of invalid ()
+ if not value:
+ value = [None]
+
+ operator = "IN"
+ value = map(str, map(api.db.quote, value))
+ value = "(%s)" % ", ".join(value)
+ else:
+ if value is None:
+ operator = "IS"
+ value = "NULL"
+ elif isinstance(value, StringTypes) and \
+ (value.find("*") > -1 or value.find("%") > -1):
+ operator = "LIKE"
+ value = str(api.db.quote(value.replace("*", "%")))
+ else:
+ operator = "="
+ if modifiers['<']:
+ operator='<'
+ if modifiers['>']:
+ operator='>'
+ if modifiers['[']:
+ operator='<='
+ if modifiers[']']:
+ operator='>='
+ else:
+ value = str(api.db.quote(value))
+
+ clause = "%s %s %s" % (field, operator, value)
+
+ if modifiers['~']:
+ clause = " ( NOT %s ) " % (clause)
+
+ conditionals.append(clause)
+ # sorting and clipping
+ else:
+ if field not in ('SORT','OFFSET','LIMIT'):
+ raise PLCInvalidArgument, "Invalid filter, unknown sort and clip field %r"%field
+ # sorting
+ if field == 'SORT':
+ if not isinstance(value,(list,tuple,set)):
+ value=[value]
+ for field in value:
+ order = 'ASC'
+ if field[0] == '+':
+ field = field[1:]
+ elif field[0] == '-':
+ field = field[1:]
+ order = 'DESC'
+ if field not in self.fields:
+ raise PLCInvalidArgument, "Invalid field %r in SORT filter"%field
+ sorts.append("%s %s"%(field,order))
+ # clipping
+ elif field == 'OFFSET':
+ clips.append("OFFSET %d"%value)
+ # clipping continued
+ elif field == 'LIMIT' :
+ clips.append("LIMIT %d"%value)
+
+ where_part = (" %s " % join_with).join(conditionals)
+ clip_part = ""
+ if sorts:
+ clip_part += " ORDER BY " + ",".join(sorts)
+ if clips:
+ clip_part += " " + " ".join(clips)
+# print 'where_part=',where_part,'clip_part',clip_part
+ return (where_part,clip_part)
--- /dev/null
+#
+# Python "binding" for GPG. I'll write GPGME bindings eventually. The
+# intent is to use GPG to sign method calls, as a way of identifying
+# and authenticating peers. Calls should still go over an encrypted
+# transport such as HTTPS, with certificate checking.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: GPG.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import os
+import xmlrpclib
+import shutil
+from types import StringTypes
+from StringIO import StringIO
+from xml.dom import minidom
+from xml.dom.ext import Canonicalize
+from subprocess import Popen, PIPE, call
+from tempfile import NamedTemporaryFile, mkdtemp
+
+from PLC.Faults import *
+
+def canonicalize(args, methodname = None, methodresponse = False):
+ """
+ Returns a canonicalized XML-RPC representation of the specified
+ method call (methodname != None) or response (methodresponse =
+ True).
+ """
+
+ xml = xmlrpclib.dumps(args, methodname, methodresponse, encoding = 'utf-8', allow_none = 1)
+ dom = minidom.parseString(xml)
+
+ # Canonicalize(), though it claims to, does not encode unicode
+ # nodes to UTF-8 properly and throws an exception unless you write
+ # the stream to a file object, so just encode it ourselves.
+ buf = StringIO()
+ Canonicalize(dom, output = buf)
+ xml = buf.getvalue().encode('utf-8')
+
+ return xml
+
+def gpg_export(keyring, armor = True):
+ """
+ Exports the specified public keyring file.
+ """
+
+ homedir = mkdtemp()
+ args = ["gpg", "--batch", "--no-tty",
+ "--homedir", homedir,
+ "--no-default-keyring",
+ "--keyring", keyring,
+ "--export"]
+ if armor:
+ args.append("--armor")
+
+ p = Popen(args, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
+ export = p.stdout.read()
+ err = p.stderr.read()
+ rc = p.wait()
+
+ # Clean up
+ shutil.rmtree(homedir)
+
+ if rc:
+ raise PLCAuthenticationFailure, "GPG export failed with return code %d: %s" % (rc, err)
+
+ return export
+
+def gpg_sign(args, secret_keyring, keyring, methodname = None, methodresponse = False, detach_sign = True):
+ """
+ Signs the specified method call (methodname != None) or response
+ (methodresponse == True) using the specified GPG keyring files. If
+ args is not a tuple representing the arguments to the method call
+ or the method response value, then it should be a string
+ representing a generic message to sign (detach_sign == True) or
+ sign/encrypt (detach_sign == False) specified). Returns the
+ detached signature (detach_sign == True) or signed/encrypted
+ message (detach_sign == False).
+ """
+
+ # Accept either an opaque string blob or a Python tuple
+ if isinstance(args, StringTypes):
+ message = args
+ elif isinstance(args, tuple):
+ message = canonicalize(args, methodname, methodresponse)
+
+ # Use temporary trustdb
+ homedir = mkdtemp()
+
+ cmd = ["gpg", "--batch", "--no-tty",
+ "--homedir", homedir,
+ "--no-default-keyring",
+ "--secret-keyring", secret_keyring,
+ "--keyring", keyring,
+ "--armor"]
+
+ if detach_sign:
+ cmd.append("--detach-sign")
+ else:
+ cmd.append("--sign")
+
+ p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
+ p.stdin.write(message)
+ p.stdin.close()
+ signature = p.stdout.read()
+ err = p.stderr.read()
+ rc = p.wait()
+
+ # Clean up
+ shutil.rmtree(homedir)
+
+ if rc:
+ raise PLCAuthenticationFailure, "GPG signing failed with return code %d: %s" % (rc, err)
+
+ return signature
+
+def gpg_verify(args, key, signature = None, methodname = None, methodresponse = False):
+ """
+ Verifies the signature of the specified method call (methodname !=
+ None) or response (methodresponse = True) using the specified
+ public key material. If args is not a tuple representing the
+ arguments to the method call or the method response value, then it
+ should be a string representing a generic message to verify (if
+ signature is specified) or verify/decrypt (if signature is not
+ specified).
+ """
+
+ # Accept either an opaque string blob or a Python tuple
+ if isinstance(args, StringTypes):
+ message = args
+ else:
+ message = canonicalize(args, methodname, methodresponse)
+
+ # Write public key to temporary file
+ if os.path.exists(key):
+ keyfile = None
+ keyfilename = key
+ else:
+ keyfile = NamedTemporaryFile(suffix = '.pub')
+ keyfile.write(key)
+ keyfile.flush()
+ keyfilename = keyfile.name
+
+ # Import public key into temporary keyring
+ homedir = mkdtemp()
+ call(["gpg", "--batch", "--no-tty", "--homedir", homedir, "--import", keyfilename],
+ stdin = PIPE, stdout = PIPE, stderr = PIPE)
+
+ cmd = ["gpg", "--batch", "--no-tty",
+ "--homedir", homedir]
+
+ if signature is not None:
+ # Write detached signature to temporary file
+ sigfile = NamedTemporaryFile()
+ sigfile.write(signature)
+ sigfile.flush()
+ cmd += ["--verify", sigfile.name, "-"]
+ else:
+ # Implicit signature
+ sigfile = None
+ cmd.append("--decrypt")
+
+ p = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
+ p.stdin.write(message)
+ p.stdin.close()
+ if signature is None:
+ message = p.stdout.read()
+ err = p.stderr.read()
+ rc = p.wait()
+
+ # Clean up
+ shutil.rmtree(homedir)
+ if sigfile:
+ sigfile.close()
+ if keyfile:
+ keyfile.close()
+
+ if rc:
+ raise PLCAuthenticationFailure, "GPG verification failed with return code %d: %s" % (rc, err)
+
+ return message
--- /dev/null
+#
+# Functions for interacting with the initscripts table in the database
+#
+# Tony Mack <tmack@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+#
+
+from types import StringTypes
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+
+class InitScript(Row):
+ """
+ Representation of a row in the initscripts table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'initscripts'
+ primary_key = 'initscript_id'
+ join_tables = []
+ fields = {
+ 'initscript_id': Parameter(int, "Initscript identifier"),
+ 'name': Parameter(str, "Initscript name", max = 254),
+ 'enabled': Parameter(bool, "Initscript is active"),
+ 'script': Parameter(str, "Initscript"),
+ }
+
+ def validate_name(self, name):
+ """
+ validates the script name
+ """
+
+ conflicts = InitScripts(self.api, [name])
+ for initscript in conflicts:
+ if 'initscript_id' not in self or self['initscript_id'] != initscript['initscript_id']:
+ raise PLCInvalidArgument, "Initscript name already in use"
+
+ return name
+
+
+class InitScripts(Table):
+ """
+ Representation of the initscipts table in the database.
+ """
+
+ def __init__(self, api, initscript_filter = None, columns = None):
+ Table.__init__(self, api, InitScript, columns)
+
+ sql = "SELECT %s FROM initscripts WHERE True" % \
+ ", ".join(self.columns)
+
+ if initscript_filter is not None:
+ if isinstance(initscript_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), initscript_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), initscript_filter)
+ initscript_filter = Filter(InitScript.fields, {'initscript_id': ints, 'name': strs })
+ sql += " AND (%s) %s" % initscript_filter.sql(api, "OR")
+ elif isinstance(initscript_filter, dict):
+ initscript_filter = Filter(InitScript.fields, initscript_filter)
+ sql += " AND (%s) %s" % initscript_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the key_types table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: KeyTypes.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class KeyType(Row):
+ """
+ Representation of a row in the key_types table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'key_types'
+ primary_key = 'key_type'
+ join_tables = ['keys']
+ fields = {
+ 'key_type': Parameter(str, "Key type", max = 20),
+ }
+
+ def validate_key_type(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Key type must be specified"
+
+ # Make sure key type does not alredy exist
+ conflicts = KeyTypes(self.api, [name])
+ if conflicts:
+ raise PLCInvalidArgument, "Key type name already in use"
+
+ return name
+
+class KeyTypes(Table):
+ """
+ Representation of the key_types table in the database.
+ """
+
+ def __init__(self, api, key_types = None):
+ Table.__init__(self, api, KeyType)
+
+ sql = "SELECT %s FROM key_types" % \
+ ", ".join(KeyType.fields)
+
+ if key_types:
+ sql += " WHERE key_type IN (%s)" % ", ".join(map(api.db.quote, key_types))
+
+ self.selectall(sql)
--- /dev/null
+import re
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.KeyTypes import KeyType, KeyTypes
+
+class Key(Row):
+ """
+ Representation of a row in the keys table. To use, instantiate with a
+ dict of values. Update as you would a dict. Commit to the database
+ with sync().
+ """
+
+ table_name = 'keys'
+ primary_key = 'key_id'
+ join_tables = ['person_key', 'peer_key']
+ fields = {
+ 'key_id': Parameter(int, "Key identifier"),
+ 'key_type': Parameter(str, "Key type"),
+ 'key': Parameter(str, "Key value", max = 4096),
+ 'person_id': Parameter(int, "User to which this key belongs", nullok = True),
+ 'peer_id': Parameter(int, "Peer to which this key belongs", nullok = True),
+ 'peer_key_id': Parameter(int, "Foreign key identifier at peer", nullok = True),
+ }
+
+ # for Cache
+ class_key= 'key'
+ foreign_fields = ['key_type']
+ foreign_xrefs = []
+
+ def validate_key_type(self, key_type):
+ key_types = [row['key_type'] for row in KeyTypes(self.api)]
+ if key_type not in key_types:
+ raise PLCInvalidArgument, "Invalid key type"
+ return key_type
+
+ def validate_key(self, key):
+ # Key must not be blacklisted
+ rows = self.api.db.selectall("SELECT 1 from keys" \
+ " WHERE key = %(key)s" \
+ " AND is_blacklisted IS True",
+ locals())
+ if rows:
+ raise PLCInvalidArgument, "Key is blacklisted and cannot be used"
+
+ return key
+
+ def validate(self):
+ # Basic validation
+ Row.validate(self)
+
+ assert 'key' in self
+ key = self['key']
+
+ if self['key_type'] == 'ssh':
+ # Accept only SSH version 2 keys without options. From
+ # sshd(8):
+ #
+ # Each protocol version 2 public key consists of: options,
+ # keytype, base64 encoded key, comment. The options field
+ # is optional...The comment field is not used for anything
+ # (but may be convenient for the user to identify the
+ # key). For protocol version 2 the keytype is ``ssh-dss''
+ # or ``ssh-rsa''.
+
+ good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
+ if not re.match(good_ssh_key, key, re.IGNORECASE):
+ raise PLCInvalidArgument, "Invalid SSH version 2 public key"
+
+ def blacklist(self, commit = True):
+ """
+ Permanently blacklist key (and all other identical keys),
+ preventing it from ever being added again. Because this could
+ affect multiple keys associated with multiple accounts, it
+ should be admin only.
+ """
+
+ assert 'key_id' in self
+ assert 'key' in self
+
+ # Get all matching keys
+ rows = self.api.db.selectall("SELECT key_id FROM keys WHERE key = %(key)s",
+ self)
+ key_ids = [row['key_id'] for row in rows]
+ assert key_ids
+ assert self['key_id'] in key_ids
+
+ # Keep the keys in the table
+ self.api.db.do("UPDATE keys SET is_blacklisted = True" \
+ " WHERE key_id IN (%s)" % ", ".join(map(str, key_ids)))
+
+ # But disassociate them from all join tables
+ for table in self.join_tables:
+ self.api.db.do("DELETE FROM %s WHERE key_id IN (%s)" % \
+ (table, ", ".join(map(str, key_ids))))
+
+ if commit:
+ self.api.db.commit()
+
+class Keys(Table):
+ """
+ Representation of row(s) from the keys table in the
+ database.
+ """
+
+ def __init__(self, api, key_filter = None, columns = None):
+ Table.__init__(self, api, Key, columns)
+
+ sql = "SELECT %s FROM view_keys WHERE is_blacklisted IS False" % \
+ ", ".join(self.columns)
+
+ if key_filter is not None:
+ if isinstance(key_filter, (list, tuple, set)):
+ key_filter = Filter(Key.fields, {'key_id': key_filter})
+ elif isinstance(key_filter, dict):
+ key_filter = Filter(Key.fields, key_filter)
+ sql += " AND (%s) %s" % key_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the messages table in the database
+#
+# Tony Mack <tmack@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Messages.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+from PLC.Filter import Filter
+
+class Message(Row):
+ """
+ Representation of a row in the messages table.
+ """
+
+ table_name = 'messages'
+ primary_key = 'message_id'
+ fields = {
+ 'message_id': Parameter(str, "Message identifier"),
+ 'subject': Parameter(str, "Message summary", nullok = True),
+ 'template': Parameter(str, "Message template", nullok = True),
+ 'enabled': Parameter(bool, "Message is enabled"),
+ }
+
+class Messages(Table):
+ """
+ Representation of row(s) from the messages table in the database.
+ """
+
+ def __init__(self, api, message_filter = None, columns = None, enabled = None):
+ Table.__init__(self, api, Message, columns)
+
+ sql = "SELECT %s from messages WHERE True" % \
+ ", ".join(self.columns)
+
+ if enabled is not None:
+ sql += " AND enabled IS %s" % enabled
+
+ if message_filter is not None:
+ if isinstance(message_filter, (list, tuple, set)):
+ message_filter = Filter(Message.fields, {'message_id': message_filter})
+ sql += " AND (%s) %s" % message_filter.sql(api, "OR")
+ elif isinstance(message_filter, dict):
+ message_filter = Filter(Message.fields, message_filter)
+ sql += " AND (%s) %s" % message_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+#
+# Base class for all PLCAPI functions
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Method.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import xmlrpclib
+from types import *
+import textwrap
+import os
+import time
+import pprint
+
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed, python_type, xmlrpc_type
+from PLC.Auth import Auth
+from PLC.Debug import profile, log
+from PLC.Events import Event, Events
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+
+class Method:
+ """
+ Base class for all PLCAPI functions. At a minimum, all PLCAPI
+ functions must define:
+
+ roles = [list of roles]
+ accepts = [Parameter(arg1_type, arg1_doc), Parameter(arg2_type, arg2_doc), ...]
+ returns = Parameter(return_type, return_doc)
+ call(arg1, arg2, ...): method body
+
+ Argument types may be Python types (e.g., int, bool, etc.), typed
+ values (e.g., 1, True, etc.), a Parameter, or lists or
+ dictionaries of possibly mixed types, values, and/or Parameters
+ (e.g., [int, bool, ...] or {'arg1': int, 'arg2': bool}).
+
+ Once function decorators in Python 2.4 are fully supported,
+ consider wrapping calls with accepts() and returns() functions
+ instead of performing type checking manually.
+ """
+
+ # Defaults. Could implement authentication and type checking with
+ # decorators, but they are not supported in Python 2.3 and it
+ # would be hard to generate documentation without writing a code
+ # parser.
+
+ roles = []
+ accepts = []
+ returns = bool
+ status = "current"
+
+ def call(self, *args):
+ """
+ Method body for all PLCAPI functions. Must override.
+ """
+
+ return True
+
+ def __init__(self, api):
+ self.name = self.__class__.__name__
+ self.api = api
+
+ # Auth may set this to a Person instance (if an anonymous
+ # method, will remain None).
+ self.caller = None
+
+ # API may set this to a (addr, port) tuple if known
+ self.source = None
+
+ def __call__(self, *args, **kwds):
+ """
+ Main entry point for all PLCAPI functions. Type checks
+ arguments, authenticates, and executes call().
+ """
+
+ try:
+ start = time.time()
+ (min_args, max_args, defaults) = self.args()
+
+ # Check that the right number of arguments were passed in
+ if len(args) < len(min_args) or len(args) > len(max_args):
+ raise PLCInvalidArgumentCount(len(args), len(min_args), len(max_args))
+
+ for name, value, expected in zip(max_args, args, self.accepts):
+ self.type_check(name, value, expected, args)
+
+ result = self.call(*args, **kwds)
+ runtime = time.time() - start
+
+ if self.api.config.PLC_API_DEBUG or hasattr(self, 'message'):
+ self.log(None, runtime, *args)
+
+ return result
+
+ except PLCFault, fault:
+
+ caller = ""
+ if isinstance(self.caller, Person):
+ caller = 'person_id %s' % self.caller['person_id']
+ elif isinstance(self.caller, Node):
+ caller = 'node_id %s' % self.caller['node_id']
+
+ # Prepend caller and method name to expected faults
+ fault.faultString = caller + ": " + self.name + ": " + fault.faultString
+ runtime = time.time() - start
+ self.log(fault, runtime, *args)
+ raise fault
+
+ def log(self, fault, runtime, *args):
+ """
+ Log the transaction
+ """
+
+ # Do not log system or Get calls
+ #if self.name.startswith('system') or self.name.startswith('Get'):
+ # return False
+
+ # Create a new event
+ event = Event(self.api)
+ event['fault_code'] = 0
+ if fault:
+ event['fault_code'] = fault.faultCode
+ event['runtime'] = runtime
+
+ # Redact passwords and sessions
+ if args and isinstance(args[0], dict):
+ # what type of auth this is
+ if args[0].has_key('AuthMethod'):
+ auth_methods = ['session', 'password', 'capability', 'gpg', 'hmac','anonymous']
+ auth_method = args[0]['AuthMethod']
+ if auth_method in auth_methods:
+ event['auth_type'] = auth_method
+ for password in 'AuthString', 'session':
+ if args[0].has_key(password):
+ auth = args[0].copy()
+ auth[password] = "Removed by API"
+ args = (auth,) + args[1:]
+
+ # Log call representation
+ # XXX Truncate to avoid DoS
+ event['call'] = self.name + pprint.saferepr(args)
+ event['call_name'] = self.name
+
+ # Both users and nodes can call some methods
+ if isinstance(self.caller, Person):
+ event['person_id'] = self.caller['person_id']
+ elif isinstance(self.caller, Node):
+ event['node_id'] = self.caller['node_id']
+
+ event.sync(commit = False)
+
+ if hasattr(self, 'event_objects') and isinstance(self.event_objects, dict):
+ for key in self.event_objects.keys():
+ for object_id in self.event_objects[key]:
+ event.add_object(key, object_id, commit = False)
+
+
+ # Set the message for this event
+ if fault:
+ event['message'] = fault.faultString
+ elif hasattr(self, 'message'):
+ event['message'] = self.message
+
+ # Commit
+ event.sync()
+
+ def help(self, indent = " "):
+ """
+ Text documentation for the method.
+ """
+
+ (min_args, max_args, defaults) = self.args()
+
+ text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
+
+ text += "Description:\n\n"
+ lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
+ text += "\n".join(lines) + "\n\n"
+
+ text += "Allowed Roles:\n\n"
+ if not self.roles:
+ roles = ["any"]
+ else:
+ roles = self.roles
+ text += indent + ", ".join(roles) + "\n\n"
+
+ def param_text(name, param, indent, step):
+ """
+ Format a method parameter.
+ """
+
+ text = indent
+
+ # Print parameter name
+ if name:
+ param_offset = 32
+ text += name.ljust(param_offset - len(indent))
+ else:
+ param_offset = len(indent)
+
+ # Print parameter type
+ param_type = python_type(param)
+ text += xmlrpc_type(param_type) + "\n"
+
+ # Print parameter documentation right below type
+ if isinstance(param, Parameter):
+ wrapper = textwrap.TextWrapper(width = 70,
+ initial_indent = " " * param_offset,
+ subsequent_indent = " " * param_offset)
+ text += "\n".join(wrapper.wrap(param.doc)) + "\n"
+ param = param.type
+
+ text += "\n"
+
+ # Indent struct fields and mixed types
+ if isinstance(param, dict):
+ for name, subparam in param.iteritems():
+ text += param_text(name, subparam, indent + step, step)
+ elif isinstance(param, Mixed):
+ for subparam in param:
+ text += param_text(name, subparam, indent + step, step)
+ elif isinstance(param, (list, tuple, set)):
+ for subparam in param:
+ text += param_text("", subparam, indent + step, step)
+
+ return text
+
+ text += "Parameters:\n\n"
+ for name, param in zip(max_args, self.accepts):
+ text += param_text(name, param, indent, indent)
+
+ text += "Returns:\n\n"
+ text += param_text("", self.returns, indent, indent)
+
+ return text
+
+ def args(self):
+ """
+ Returns a tuple:
+
+ ((arg1_name, arg2_name, ...),
+ (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
+ (None, None, ..., optional1_default, optional2_default, ...))
+
+ That represents the minimum and maximum sets of arguments that
+ this function accepts and the defaults for the optional arguments.
+ """
+
+ # Inspect call. Remove self from the argument list.
+ max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
+ defaults = self.call.func_defaults
+ if defaults is None:
+ defaults = ()
+
+ min_args = max_args[0:len(max_args) - len(defaults)]
+ defaults = tuple([None for arg in min_args]) + defaults
+
+ return (min_args, max_args, defaults)
+
+ def type_check(self, name, value, expected, args):
+ """
+ Checks the type of the named value against the expected type,
+ which may be a Python type, a typed value, a Parameter, a
+ Mixed type, or a list or dictionary of possibly mixed types,
+ values, Parameters, or Mixed types.
+
+ Extraneous members of lists must be of the same type as the
+ last specified type. For example, if the expected argument
+ type is [int, bool], then [1, False] and [14, True, False,
+ True] are valid, but [1], [False, 1] and [14, True, 1] are
+ not.
+
+ Extraneous members of dictionaries are ignored.
+ """
+
+ # If any of a number of types is acceptable
+ if isinstance(expected, Mixed):
+ for item in expected:
+ try:
+ self.type_check(name, value, item, args)
+ return
+ except PLCInvalidArgument, fault:
+ pass
+ raise fault
+
+ # If an authentication structure is expected, save it and
+ # authenticate after basic type checking is done.
+ if isinstance(expected, Auth):
+ auth = expected
+ else:
+ auth = None
+
+ # Get actual expected type from within the Parameter structure
+ if isinstance(expected, Parameter):
+ min = expected.min
+ max = expected.max
+ nullok = expected.nullok
+ expected = expected.type
+ else:
+ min = None
+ max = None
+ nullok = False
+
+ expected_type = python_type(expected)
+
+ # If value can be NULL
+ if value is None and nullok:
+ return
+
+ # Strings are a special case. Accept either unicode or str
+ # types if a string is expected.
+ if expected_type in StringTypes and isinstance(value, StringTypes):
+ pass
+
+ # Integers and long integers are also special types. Accept
+ # either int or long types if an int or long is expected.
+ elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
+ pass
+
+ elif not isinstance(value, expected_type):
+ raise PLCInvalidArgument("expected %s, got %s" % \
+ (xmlrpc_type(expected_type),
+ xmlrpc_type(type(value))),
+ name)
+
+ # If a minimum or maximum (length, value) has been specified
+ if expected_type in StringTypes:
+ if min is not None and \
+ len(value.encode(self.api.encoding)) < min:
+ raise PLCInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+ if max is not None and \
+ len(value.encode(self.api.encoding)) > max:
+ raise PLCInvalidArgument, "%s must be at most %d bytes long" % (name, max)
+ elif expected_type in (list, tuple, set):
+ if min is not None and len(value) < min:
+ raise PLCInvalidArgument, "%s must contain at least %d items" % (name, min)
+ if max is not None and len(value) > max:
+ raise PLCInvalidArgument, "%s must contain at most %d items" % (name, max)
+ else:
+ if min is not None and value < min:
+ raise PLCInvalidArgument, "%s must be > %s" % (name, str(min))
+ if max is not None and value > max:
+ raise PLCInvalidArgument, "%s must be < %s" % (name, str(max))
+
+ # If a list with particular types of items is expected
+ if isinstance(expected, (list, tuple, set)):
+ for i in range(len(value)):
+ if i >= len(expected):
+ j = len(expected) - 1
+ else:
+ j = i
+ self.type_check(name + "[]", value[i], expected[j], args)
+
+ # If a struct with particular (or required) types of items is
+ # expected.
+ elif isinstance(expected, dict):
+ for key in value.keys():
+ if key in expected:
+ self.type_check(name + "['%s']" % key, value[key], expected[key], args)
+ for key, subparam in expected.iteritems():
+ if isinstance(subparam, Parameter) and \
+ subparam.optional is not None and \
+ not subparam.optional and key not in value.keys():
+ raise PLCInvalidArgument("'%s' not specified" % key, name)
+
+ if auth is not None:
+ auth.check(self, *args)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in ['address_type_id']
+
+class AddAddressType(Method):
+ """
+ Adds a new address type. Fields specified in address_type_fields
+ are used.
+
+ Returns the new address_type_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ address_type_fields = dict(filter(can_update, AddressType.fields.items()))
+
+ accepts = [
+ Auth(),
+ address_type_fields
+ ]
+
+ returns = Parameter(int, 'New address_type_id (> 0) if successful')
+
+
+ def call(self, auth, address_type_fields):
+ address_type_fields = dict(filter(can_update, address_type_fields.items()))
+ address_type = AddressType(self.api, address_type_fields)
+ address_type.sync()
+
+ self.event_objects = {'AddressType' : [address_type['address_type_id']]}
+
+ return address_type['address_type_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+
+class AddAddressTypeToAddress(Method):
+ """
+ Adds an address type to the specified address.
+
+ PIs may only update addresses of their own sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(AddressType.fields['address_type_id'],
+ AddressType.fields['name']),
+ Address.fields['address_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, address_type_id_or_name, address_id):
+ address_types = AddressTypes(self.api, [address_type_id_or_name])
+ if not address_types:
+ raise PLCInvalidArgument, "No such address type"
+ address_type = address_types[0]
+
+ addresses = Addresses(self.api, [address_id])
+ if not addresses:
+ raise PLCInvalidArgument, "No such address"
+ address = addresses[0]
+
+ if 'admin' not in self.caller['roles']:
+ if address['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Address must be associated with one of your sites"
+
+ address.add_address_type(address_type)
+ self.event_objects = {'Address': [address['address_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.BootStates import BootState, BootStates
+from PLC.Auth import Auth
+
+class AddBootState(Method):
+ """
+ Adds a new node boot state.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ BootState.fields['boot_state']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ boot_state = BootState(self.api)
+ boot_state['boot_state'] = name
+ boot_state.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in \
+ ['conf_file_id', 'node_ids', 'nodegroup_ids']
+
+class AddConfFile(Method):
+ """
+ Adds a new node configuration file. Any fields specified in
+ conf_file_fields are used, otherwise defaults are used.
+
+ Returns the new conf_file_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ conf_file_fields = dict(filter(can_update, ConfFile.fields.items()))
+
+ accepts = [
+ Auth(),
+ conf_file_fields
+ ]
+
+ returns = Parameter(int, 'New conf_file_id (> 0) if successful')
+
+
+ def call(self, auth, conf_file_fields):
+ conf_file_fields = dict(filter(can_update, conf_file_fields.items()))
+ conf_file = ConfFile(self.api, conf_file_fields)
+ conf_file.sync()
+
+ self.event_objects = {'ConfFile': [conf_file['conf_file_id']]}
+
+ return conf_file['conf_file_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import Auth
+
+class AddConfFileToNode(Method):
+ """
+ Adds a configuration file to the specified node. If the node is
+ already linked to the configuration file, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id'],
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, conf_file_id, node_id_or_hostname):
+ # Get configuration file
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+ conf_file = conf_files[0]
+
+ # Get node
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # Link configuration file to node
+ if node['node_id'] not in conf_file['node_ids']:
+ conf_file.add_node(node)
+
+ # Log affected objects
+ self.event_objects = {'ConfFile': [conf_file_id],
+ 'Node': [node['node_id']] }
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Auth import Auth
+
+class AddConfFileToNodeGroup(Method):
+ """
+ Adds a configuration file to the specified node group. If the node
+ group is already linked to the configuration file, no errors are
+ returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id'],
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, conf_file_id, nodegroup_id_or_name):
+ # Get configuration file
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+ conf_file = conf_files[0]
+
+ # Get node
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such node group"
+ nodegroup = nodegroups[0]
+
+ # Link configuration file to node
+ if nodegroup['nodegroup_id'] not in conf_file['nodegroup_ids']:
+ conf_file.add_nodegroup(nodegroup)
+
+ # Log affected objects
+ self.event_objects = {'ConfFile': [conf_file_id],
+ 'NodeGroup': [nodegroup['nodegroup_id']] }
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in \
+ ['initscript_id']
+
+class AddInitScript(Method):
+ """
+ Adds a new initscript. Any fields specified in initscript_fields
+ are used, otherwise defaults are used.
+
+ Returns the new initscript_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ initscript_fields = dict(filter(can_update, InitScript.fields.items()))
+
+ accepts = [
+ Auth(),
+ initscript_fields
+ ]
+
+ returns = Parameter(int, 'New initscript_id (> 0) if successful')
+
+
+ def call(self, auth, initscript_fields):
+ initscript_fields = dict(filter(can_update, initscript_fields.items()))
+ initscript = InitScript(self.api, initscript_fields)
+ initscript.sync()
+
+ self.event_objects = {'InitScript': [initscript['initscript_id']]}
+
+ return initscript['initscript_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.KeyTypes import KeyType, KeyTypes
+from PLC.Auth import Auth
+
+class AddKeyType(Method):
+ """
+ Adds a new key type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ KeyType.fields['key_type']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ key_type = KeyType(self.api)
+ key_type['key_type'] = name
+ key_type.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter
+from PLC.Messages import Message, Messages
+from PLC.Auth import Auth
+
+class AddMessage(Method):
+ """
+ Adds a new message template. Any values specified in
+ message_fields are used, otherwise defaults are used.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Message.fields,
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, message_fields):
+ message = Message(self.api, message_fields)
+ message.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkMethods import NetworkMethod, NetworkMethods
+from PLC.Auth import Auth
+
+class AddNetworkMethod(Method):
+ """
+ Adds a new network method.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ NetworkMethod.fields['method']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ network_method = NetworkMethod(self.api)
+ network_method['method'] = name
+ network_method.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkTypes import NetworkType, NetworkTypes
+from PLC.Auth import Auth
+
+class AddNetworkType(Method):
+ """
+ Adds a new network type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ NetworkType.fields['type']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ network_type = NetworkType(self.api)
+ network_type['type'] = name
+ network_type.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['hostname', 'boot_state', 'model', 'version']
+
+class AddNode(Method):
+ """
+ Adds a new node. Any values specified in node_fields are used,
+ otherwise defaults are used.
+
+ PIs and techs may only add nodes to their own sites. Admins may
+ add nodes to any site.
+
+ Returns the new node_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ node_fields = dict(filter(can_update, Node.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base']),
+ node_fields
+ ]
+
+ returns = Parameter(int, 'New node_id (> 0) if successful')
+
+ def call(self, auth, site_id_or_login_base, node_fields):
+ node_fields = dict(filter(can_update, node_fields.items()))
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+
+ site = sites[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']:
+ assert self.caller['person_id'] not in site['person_ids']
+ raise PLCPermissionDenied, "Not allowed to add nodes to specified site"
+ else:
+ assert self.caller['person_id'] in site['person_ids']
+
+ node = Node(self.api, node_fields)
+ node['site_id'] = site['site_id']
+ node.sync()
+
+ self.event_objects = {'Site': [site['site_id']],
+ 'Node': [node['node_id']]}
+ self.message = "Node %s created" % node['node_id']
+
+ return node['node_id']
--- /dev/null
+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 Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'description']
+
+class AddNodeGroup(Method):
+ """
+ Adds a new node group. Any values specified in nodegroup_fields
+ are used, otherwise defaults are used.
+
+ Returns the new nodegroup_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ nodegroup_fields = dict(filter(can_update, NodeGroup.fields.items()))
+
+ accepts = [
+ Auth(),
+ nodegroup_fields
+ ]
+
+ returns = Parameter(int, 'New nodegroup_id (> 0) if successful')
+
+
+ def call(self, auth, nodegroup_fields):
+ nodegroup_fields = dict(filter(can_update, nodegroup_fields.items()))
+ nodegroup = NodeGroup(self.api, nodegroup_fields)
+ nodegroup.sync()
+
+ # Logging variables
+ self.event_objects = {'NodeGroup': [nodegroup['nodegroup_id']]}
+ self.message = 'Node group %d created' % nodegroup['nodegroup_id']
+
+ return nodegroup['nodegroup_id']
--- /dev/null
+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 Auth
+
+can_update = lambda (field, value): field not in ['nodenetwork_id', 'node_id']
+
+class AddNodeNetwork(Method):
+ """
+
+ Adds a new network for a node. Any values specified in
+ nodenetwork_fields are used, otherwise defaults are
+ used. Acceptable values for method may be retrieved via
+ GetNetworkMethods. Acceptable values for type may be retrieved via
+ GetNetworkTypes.
+
+ If type is static, ip, gateway, network, broadcast, netmask, and
+ dns1 must all be specified in nodenetwork_fields. If type is dhcp,
+ these parameters, even if specified, are ignored.
+
+ PIs and techs may only add networks to their own nodes. Admins may
+ add networks to any node.
+
+ Returns the new nodenetwork_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ nodenetwork_fields = dict(filter(can_update, NodeNetwork.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ nodenetwork_fields
+ ]
+
+ returns = Parameter(int, 'New nodenetwork_id (> 0) if successful')
+
+
+ def call(self, auth, node_id_or_hostname, nodenetwork_fields):
+ nodenetwork_fields = dict(filter(can_update, nodenetwork_fields.items()))
+
+ # Check if node exists
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[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']:
+ if node['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to add node network for specified node"
+
+ # Add node network
+ nodenetwork = NodeNetwork(self.api, nodenetwork_fields)
+ nodenetwork['node_id'] = node['node_id']
+ # if this is the first node network, make it primary
+ if not node['nodenetwork_ids']:
+ nodenetwork['is_primary'] = True
+ nodenetwork.sync()
+
+ # Logging variables
+ self.object_ids = [node['node_id'], nodenetwork['nodenetwork_id']]
+ self.messgage = "Node network %d added" % nodenetwork['nodenetwork_id']
+
+ return nodenetwork['nodenetwork_id']
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+from PLC.Nodes import Nodes
+from PLC.Sites import Sites
+
+class AddNodeNetworkSetting(Method):
+ """
+ Sets the specified setting for the specified nodenetwork
+ to the specified value.
+
+ In general only tech(s), PI(s) and of course admin(s) are allowed to
+ do the change, but this is defined in the nodenetwork setting type object.
+
+ Returns the new nodenetwork_setting_id (> 0) if successful, faults
+ otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ accepts = [
+ Auth(),
+ # no other way to refer to a nodenetwork
+ NodeNetworkSetting.fields['nodenetwork_id'],
+ Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'],
+ NodeNetworkSettingType.fields['name']),
+ NodeNetworkSetting.fields['value'],
+ ]
+
+ returns = Parameter(int, 'New nodenetwork_setting_id (> 0) if successful')
+
+ object_type = 'NodeNetwork'
+
+
+ def call(self, auth, nodenetwork_id, nodenetwork_setting_type_id_or_name, value):
+ nodenetworks = NodeNetworks(self.api, [nodenetwork_id])
+ if not nodenetworks:
+ raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_id
+ nodenetwork = nodenetworks[0]
+
+ nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name])
+ if not nodenetwork_setting_types:
+ raise PLCInvalidArgument, "No such nodenetwork setting type %r"%nodenetwork_setting_type_id_or_name
+ nodenetwork_setting_type = nodenetwork_setting_types[0]
+
+ # checks for existence - does not allow several different settings
+ conflicts = NodeNetworkSettings(self.api,
+ {'nodenetwork_id':nodenetwork['nodenetwork_id'],
+ 'nodenetwork_setting_type_id':nodenetwork_setting_type['nodenetwork_setting_type_id']})
+
+ if len(conflicts) :
+ raise PLCInvalidArgument, "Nodenetwork %d already has setting %d"%(nodenetwork['nodenetwork_id'],
+ nodenetwork_setting_type['nodenetwork_setting_type_id'])
+
+ # check permission : it not admin, is the user affiliated with the right site
+ if 'admin' not in self.caller['roles']:
+ # locate node
+ node = Nodes (self.api,[nodenetwork['node_id']])[0]
+ # locate site
+ site = Sites (self.api, [node['site_id']])[0]
+ # check caller is affiliated with this site
+ if self.caller['person_id'] not in site['person_ids']:
+ raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+
+ required_min_role = nodenetwork_setting_type ['min_role_id']
+ if required_min_role is not None and \
+ min(self.caller['role_ids']) > required_min_role:
+ raise PLCPermissionDenied, "Not allowed to modify the specified nodenetwork setting, requires role %d",required_min_role
+
+ nodenetwork_setting = NodeNetworkSetting(self.api)
+ nodenetwork_setting['nodenetwork_id'] = nodenetwork['nodenetwork_id']
+ nodenetwork_setting['nodenetwork_setting_type_id'] = nodenetwork_setting_type['nodenetwork_setting_type_id']
+ nodenetwork_setting['value'] = value
+
+ nodenetwork_setting.sync()
+ self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']]
+
+ return nodenetwork_setting['nodenetwork_setting_id']
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'description', 'category', 'min_role_id']
+
+class AddNodeNetworkSettingType(Method):
+ """
+ Adds a new type of nodenetwork setting.
+ Any fields specified are used, otherwise defaults are used.
+
+ Returns the new nodenetwork_setting_id (> 0) if successful,
+ faults otherwise.
+ """
+
+ roles = ['admin']
+
+ nodenetwork_setting_type_fields = dict(filter(can_update, NodeNetworkSettingType.fields.items()))
+
+ accepts = [
+ Auth(),
+ nodenetwork_setting_type_fields
+ ]
+
+ returns = Parameter(int, 'New nodenetwork_setting_id (> 0) if successful')
+
+
+ def call(self, auth, nodenetwork_setting_type_fields):
+ nodenetwork_setting_type_fields = dict(filter(can_update, nodenetwork_setting_type_fields.items()))
+ nodenetwork_setting_type = NodeNetworkSettingType(self.api, nodenetwork_setting_type_fields)
+ nodenetwork_setting_type.sync()
+
+ self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']]
+
+ return nodenetwork_setting_type['nodenetwork_setting_type_id']
--- /dev/null
+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 Auth
+
+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 = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, node_id_or_hostname, nodegroup_id_or_name):
+ # Get node info
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # Get nodegroup info
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such nodegroup"
+
+ nodegroup = nodegroups[0]
+
+ # add node to nodegroup
+ if node['node_id'] not in nodegroup['node_ids']:
+ nodegroup.add_node(node)
+
+ # Logging variables
+ self.event_objects = {'NodeGroup': [nodegroup['nodegroup_id']],
+ 'Node': [node['node_id']]}
+ self.message = 'Node %d added to node group %d' % \
+ (node['node_id'], nodegroup['nodegroup_id'])
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+
+class AddNodeToPCU(Method):
+ """
+ Adds a node to a port on a PCU. Faults if the node has already
+ been added to the PCU or if the port is already in use.
+
+ Non-admins may only update PCUs at their sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ PCU.fields['pcu_id'],
+ Parameter(int, 'PCU port number')
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, node_id_or_hostname, pcu_id, port):
+ # Get node
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # Get PCU
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+ pcu = pcus[0]
+
+ if 'admin' not in self.caller['roles']:
+ ok = False
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ if pcu['pcu_id'] in site['pcu_ids']:
+ ok = True
+ break
+ if not ok:
+ raise PLCPermissionDenied, "Not allowed to update that PCU"
+
+ # Add node to PCU
+ if node['node_id'] in pcu['node_ids']:
+ raise PLCInvalidArgument, "Node already controlled by PCU"
+
+ if node['site_id'] != pcu['site_id']:
+ raise PLCInvalidArgument, "Node is at a different site than this PCU"
+
+ if port in pcu['ports']:
+ raise PLCInvalidArgument, "PCU port already in use"
+
+ pcu.add_node(node, port)
+
+ # Logging variables
+ self.event_objects = {'Node': [node['node_id']],
+ 'PCU': [pcu['pcu_id']]}
+ self.message = 'Node %d added to pcu %d on port %d' % \
+ (node['node_id'], pcu['pcu_id'], port)
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+
+can_update = lambda (field, value): field in \
+ ['ip', 'hostname', 'protocol',
+ 'username', 'password',
+ 'model', 'notes']
+
+class AddPCU(Method):
+ """
+ Adds a new power control unit (PCU) to the specified site. Any
+ fields specified in pcu_fields are used, otherwise defaults are
+ used.
+
+ PIs and technical contacts may only add PCUs to their own sites.
+
+ Returns the new pcu_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ pcu_fields = dict(filter(can_update, PCU.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base']),
+ pcu_fields
+ ]
+
+ returns = Parameter(int, 'New pcu_id (> 0) if successful')
+
+
+ def call(self, auth, site_id_or_login_base, pcu_fields):
+ pcu_fields = dict(filter(can_update, pcu_fields.items()))
+
+ # Get associated site details
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if 'admin' not in self.caller['roles']:
+ if site['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to add a PCU to that site"
+
+ pcu = PCU(self.api, pcu_fields)
+ pcu['site_id'] = site['site_id']
+ pcu.sync()
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']],
+ 'PCU': [pcu['pcu_id']]}
+ self.message = 'PCU %d added site %s' % \
+ (pcu['pcu_id'], site['site_id'])
+
+ return pcu['pcu_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUProtocolTypes import PCUProtocolType, PCUProtocolTypes
+from PLC.PCUTypes import PCUType, PCUTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['pcu_type_id', 'port', 'protocol', 'supported']
+
+class AddPCUProtocolType(Method):
+ """
+ Adds a new pcu protocol type.
+
+ Returns the new pcu_protocol_type_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ protocol_type_fields = dict(filter(can_update, PCUProtocolType.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(PCUType.fields['pcu_type_id'],
+ PCUType.fields['model']),
+ protocol_type_fields
+ ]
+
+ returns = Parameter(int, 'New pcu_protocol_type_id (> 0) if successful')
+
+ def call(self, auth, pcu_type_id_or_model, protocol_type_fields):
+
+ # Check if pcu type exists
+ pcu_types = PCUTypes(self.api, [pcu_type_id_or_model])
+ if not pcu_types:
+ raise PLCInvalidArgument, "No such pcu type"
+ pcu_type = pcu_types[0]
+
+
+ # Check if this port is already used
+ if 'port' not in protocol_type_fields:
+ raise PLCInvalidArgument, "Must specify a port"
+ else:
+ protocol_types = PCUProtocolTypes(self.api, {'pcu_type_id': pcu_type['pcu_type_id']})
+ for protocol_type in protocol_types:
+ if protocol_type['port'] == protocol_type_fields['port']:
+ raise PLCInvalidArgument, "Port alreay in use"
+
+ protocol_type_fields = dict(filter(can_update, protocol_type_fields.items()))
+ protocol_type = PCUProtocolType(self.api, protocol_type_fields)
+ protocol_type['pcu_type_id'] = pcu_type['pcu_type_id']
+ protocol_type.sync()
+ self.event_object = {'PCUProtocolType': [protocol_type['pcu_protocol_type_id']]}
+
+ return protocol_type['pcu_protocol_type_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUTypes import PCUType, PCUTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['model', 'name']
+
+class AddPCUType(Method):
+ """
+ Adds a new pcu type.
+
+ Returns the new pcu_type_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ pcu_type_fields = dict(filter(can_update, PCUType.fields.items()))
+
+ accepts = [
+ Auth(),
+ pcu_type_fields
+ ]
+
+ returns = Parameter(int, 'New pcu_type_id (> 0) if successful')
+
+
+ def call(self, auth, pcu_type_fields):
+ pcu_type_fields = dict(filter(can_update, pcu_type_fields.items()))
+ pcu_type = PCUType(self.api, pcu_type_fields)
+ pcu_type.sync()
+ self.event_object = {'PCUType': [pcu_type['pcu_type_id']]}
+
+ return pcu_type['pcu_type_id']
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Peers import Peer, Peers
+
+can_update = lambda (field, value): field in \
+ ['peername', 'peer_url', 'key', 'cacert']
+
+class AddPeer(Method):
+ """
+ Adds a new peer.
+
+ Returns the new peer_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ peer_fields = dict(filter(can_update, Peer.fields.items()))
+
+ accepts = [
+ Auth(),
+ peer_fields
+ ]
+
+ returns = Parameter(int, "New peer_id (> 0) if successful")
+
+ def call(self, auth, peer_fields):
+ peer = Peer(self.api, peer_fields);
+ peer.sync()
+ self.event_objects = {'Peer': [peer['peer_id']]}
+
+ return peer['peer_id']
--- /dev/null
+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 Auth
+
+can_update = lambda (field, value): field in \
+ ['first_name', 'last_name', 'title',
+ 'email', 'password', 'phone', 'url', 'bio']
+
+class AddPerson(Method):
+ """
+ Adds a new account. Any fields specified in person_fields are
+ used, otherwise defaults are used.
+
+ Accounts are disabled by default. To enable an account, use
+ UpdatePerson().
+
+ Returns the new person_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ person_fields = dict(filter(can_update, Person.fields.items()))
+
+ accepts = [
+ Auth(),
+ person_fields
+ ]
+
+ returns = Parameter(int, 'New person_id (> 0) if successful')
+
+ def call(self, auth, person_fields):
+ person_fields = dict(filter(can_update, person_fields.items()))
+ person_fields['enabled'] = False
+ person = Person(self.api, person_fields)
+ person.sync()
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']]}
+ self.message = 'Person %d added' % person['person_id']
+
+ return person['person_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Keys import Key, Keys
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in ['key_type','key']
+
+class AddPersonKey(Method):
+ """
+ Adds a new key to the specified account.
+
+ Non-admins can only modify their own keys.
+
+ Returns the new key_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ key_fields = dict(filter(can_update, Key.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ key_fields
+ ]
+
+ returns = Parameter(int, 'New key_id (> 0) if successful')
+
+ def call(self, auth, person_id_or_email, key_fields):
+ key_fields = dict(filter(can_update, key_fields.items()))
+
+ # Get account details
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # If we are not admin, make sure caller is adding a key to their account
+ if 'admin' not in self.caller['roles']:
+ if person['person_id'] != self.caller['person_id']:
+ raise PLCPermissionDenied, "You may only modify your own keys"
+
+ key = Key(self.api, key_fields)
+ key.sync(commit = False)
+ person.add_key(key, commit = True)
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']],
+ 'Key': [key['key_id']]}
+ self.message = 'Key %d added to person %d' % \
+ (key['key_id'], person['person_id'])
+
+ return key['key_id']
--- /dev/null
+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 Auth
+
+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 = [
+ Auth(),
+ 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[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if site['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local site"
+
+ if site['site_id'] not in person['site_ids']:
+ site.add_person(person)
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']],
+ 'Person': [person['person_id']]}
+ self.message = 'Person %d added to site %d' % \
+ (person['person_id'], site['site_id'])
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class AddPersonToSlice(Method):
+ """
+ Adds the specified person to the specified slice. If the person is
+ already a member of the slice, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, person_id_or_email, slice_id_or_name):
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ # N.B. Allow foreign users to be added to local slices and
+ # local users to be added to foreign slices (and, of course,
+ # local users to be added to local slices).
+ if person['peer_id'] is not None and slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Cannot add foreign users to foreign slices"
+
+ # If we are not admin, make sure the caller is a PI
+ # of the site associated with the slice
+ if 'admin' not in self.caller['roles']:
+ if slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to add users to this slice"
+
+ if slice['slice_id'] not in person['slice_ids']:
+ slice.add_person(person)
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']],
+ 'Slice': [slice['slice_id']]}
+ self.object_ids = [slice['slice_id']]
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Roles import Role, Roles
+from PLC.Auth import Auth
+
+class AddRole(Method):
+ """
+ Adds a new role.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Role.fields['role_id'],
+ Role.fields['name']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, role_id, name):
+ role = Role(self.api)
+ role['role_id'] = role_id
+ role['name'] = name
+ role.sync(insert = True)
+ self.event_objects = {'Role': [role['role_id']]}
+
+ return 1
--- /dev/null
+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 Auth
+from PLC.Roles import Role, 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. Admins can grant any role to any user.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(Role.fields['role_id'],
+ Role.fields['name']),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, role_id_or_name, person_id_or_email):
+ # Get role
+ roles = Roles(self.api, [role_id_or_name])
+ if not roles:
+ raise PLCInvalidArgument, "Invalid role '%s'" % unicode(role_id_or_name)
+ role = roles[0]
+
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # Authenticated function
+ assert self.caller is not None
+
+ # 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['role_id'] <= min(self.caller['role_ids']):
+ raise PLCInvalidArgument, "Not allowed to grant that role"
+
+ if role['role_id'] not in person['role_ids']:
+ person.add_role(role)
+
+ self.event_objects = {'Person': [person['person_id']],
+ 'Role': [role['role_id']]}
+ self.message = "Role %d granted to person %d" % \
+ (role['role_id'], person['person_id'])
+
+ return 1
--- /dev/null
+import time
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Sessions import Session, Sessions
+from PLC.Persons import Person, Persons
+
+class AddSession(Method):
+ """
+ Creates and returns a new session key for the specified user.
+ (Used for website 'user sudo')
+ """
+
+ roles = ['admin']
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email'])
+ ]
+ returns = Session.fields['session_id']
+
+
+ def call(self, auth, person_id_or_email):
+
+ persons = Persons(self.api, [person_id_or_email], ['person_id', 'email'])
+
+ if not persons:
+ raise PLCInvalidArgument, "No such person"
+
+ person = persons[0]
+ session = Session(self.api)
+ session['expires'] = int(time.time()) + (24 * 60 * 60)
+ session.sync(commit = False)
+ session.add_person(person, commit = True)
+
+ return session['session_id']
--- /dev/null
+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 Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'abbreviated_name', 'login_base',
+ 'is_public', 'latitude', 'longitude', 'url',
+ 'max_slices', 'max_slivers', 'enabled']
+
+class AddSite(Method):
+ """
+ Adds a new site, and creates a node group for that site. Any
+ fields specified in site_fields are used, otherwise defaults are
+ used.
+
+ Returns the new site_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ site_fields = dict(filter(can_update, Site.fields.items()))
+
+ accepts = [
+ Auth(),
+ site_fields
+ ]
+
+ returns = Parameter(int, 'New site_id (> 0) if successful')
+
+ def call(self, auth, site_fields):
+ site_fields = dict(filter(can_update, site_fields.items()))
+ site = Site(self.api, site_fields)
+ site.sync()
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']]}
+ self.message = 'Site %d created' % site['site_id']
+
+ return site['site_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+
+can_update = lambda (field, value): field in \
+ ['line1', 'line2', 'line3',
+ 'city', 'state', 'postalcode', 'country']
+
+class AddSiteAddress(Method):
+ """
+ Adds a new address to a site. Fields specified in
+ address_fields are used; some are not optional.
+
+ PIs may only add addresses to their own sites.
+
+ Returns the new address_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ address_fields = dict(filter(can_update, Address.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base']),
+ address_fields
+ ]
+
+ returns = Parameter(int, 'New address_id (> 0) if successful')
+
+ def call(self, auth, site_id_or_login_base, address_fields):
+ address_fields = dict(filter(can_update, address_fields.items()))
+
+ # Get associated site details
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if 'admin' not in self.caller['roles']:
+ if site['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Address must be associated with one of your sites"
+
+ address = Address(self.api, address_fields)
+ address.sync(commit = False)
+ site.add_address(address, commit = True)
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']],
+ 'Address': [address['address_id']]}
+ self.message = 'Address %d assigned to Site %d' % \
+ (address['address_id'], site['site_id'])
+
+ return address['address_id']
--- /dev/null
+import re
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+
+can_update = lambda (field, value): field in \
+ ['name', 'instantiation', 'url', 'description', 'max_nodes']
+
+class AddSlice(Method):
+ """
+ Adds a new slice. Any fields specified in slice_fields are used,
+ otherwise defaults are used.
+
+ Valid slice names are lowercase and begin with the login_base
+ (slice prefix) of a valid site, followed by a single
+ underscore. Thereafter, only letters, numbers, or additional
+ underscores may be used.
+
+ PIs may only add slices associated with their own sites (i.e.,
+ slice prefixes must always be the login_base of one of their
+ sites).
+
+ Returns the new slice_id (> 0) if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ slice_fields = dict(filter(can_update, Slice.fields.items()))
+
+ accepts = [
+ Auth(),
+ slice_fields
+ ]
+
+ returns = Parameter(int, 'New slice_id (> 0) if successful')
+
+ def call(self, auth, slice_fields):
+ slice_fields = dict(filter(can_update, slice_fields.items()))
+
+ # 1. Lowercase.
+ # 2. Begins with login_base (letters or numbers).
+ # 3. Then single underscore after login_base.
+ # 4. Then letters, numbers, or underscores.
+ name = slice_fields['name']
+ good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
+ if not name or \
+ not re.match(good_name, name):
+ raise PLCInvalidArgument, "Invalid slice name"
+
+ # Get associated site details
+ login_base = name.split("_")[0]
+ sites = Sites(self.api, [login_base])
+ if not sites:
+ raise PLCInvalidArgument, "Invalid slice prefix %s in %s"%(login_base,name)
+ site = sites[0]
+
+ if 'admin' not in self.caller['roles']:
+ if site['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Slice prefix %s must be the same as the login_base of one of your sites"%login_base
+
+ if len(site['slice_ids']) >= site['max_slices']:
+ raise PLCInvalidArgument, "Site %s has reached (%d) its maximum allowable slice count (%d)"%(site['name'],
+ len(site['slice_ids']),
+ site['max_slices'])
+
+ if not site['enabled']:
+ raise PLCInvalidArgument, "Site %s is disabled can cannot create slices" % (site['name'])
+
+ slice = Slice(self.api, slice_fields)
+ slice['creator_person_id'] = self.caller['person_id']
+ slice['site_id'] = site['site_id']
+ slice.sync()
+
+ self.event_objects = {'Slice': [slice['slice_id']]}
+
+ return slice['slice_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class AddSliceAttribute(Method):
+ """
+ Sets the specified attribute of the slice (or sliver, if
+ node_id_or_hostname is specified) to the specified value.
+
+ Attributes may require the caller to have a particular role in
+ order to be set or changed. Users may only set attributes of
+ slices or slivers of which they are members. PIs may only set
+ attributes of slices or slivers at their sites, or of which they
+ are members. Admins may set attributes of any slice or sliver.
+
+ Returns the new slice_attribute_id (> 0) if successful, faults
+ otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Mixed(SliceAttribute.fields['slice_id'],
+ SliceAttribute.fields['name']),
+ Mixed(SliceAttribute.fields['attribute_type_id'],
+ SliceAttribute.fields['name']),
+ Mixed(SliceAttribute.fields['value'],
+ InitScript.fields['name']),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname'],
+ None),
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name'])
+ ]
+
+ returns = Parameter(int, 'New slice_attribute_id (> 0) if successful')
+
+ def call(self, auth, slice_id_or_name, attribute_type_id_or_name, value, node_id_or_hostname = None, nodegroup_id_or_name = None):
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ attribute_types = SliceAttributeTypes(self.api, [attribute_type_id_or_name])
+ if not attribute_types:
+ raise PLCInvalidArgument, "No such slice attribute type"
+ attribute_type = attribute_types[0]
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ if attribute_type['min_role_id'] is not None and \
+ min(self.caller['role_ids']) > attribute_type['min_role_id']:
+ raise PLCPermissionDenied, "Not allowed to set the specified slice attribute"
+
+ # if initscript is specified, validate value
+ if attribute_type['name'] in ['initscript']:
+ initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
+ if not initscripts:
+ raise PLCInvalidArgument, "No such plc initscript"
+
+ slice_attribute = SliceAttribute(self.api)
+ slice_attribute['slice_id'] = slice['slice_id']
+ slice_attribute['attribute_type_id'] = attribute_type['attribute_type_id']
+ slice_attribute['value'] = unicode(value)
+
+ # Sliver attribute if node is specified
+ if node_id_or_hostname is not None:
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['node_id'] not in slice['node_ids']:
+ raise PLCInvalidArgument, "Node not in the specified slice"
+ slice_attribute['node_id'] = node['node_id']
+
+ # Sliver attribute shared accross nodes if nodegroup is sepcified
+ if nodegroup_id_or_name is not None:
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such nodegroup"
+ nodegroup = nodegroups[0]
+
+ slice_attribute['nodegroup_id'] = nodegroup['nodegroup_id']
+
+ # Check if slice attribute alreay exists
+ slice_attributes_check = SliceAttributes(self.api, {'slice_id': slice['slice_id'], 'name': attribute_type['name'], 'value': value})
+ for slice_attribute_check in slice_attributes_check:
+ if 'node_id' in slice_attribute and slice_attribute['node_id'] == slice_attribute_check['node_id']:
+ raise PLCInvalidArgument, "Sliver attribute already exists"
+ if 'nodegroup_id' in slice_attribute and slice_attribute['nodegroup_id'] == slice_attribute_check['nodegroup_id']:
+ raise PLCInvalidArgument, "Slice attribute already exists for this nodegroup"
+ if node_id_or_hostname is None and nodegroup_id_or_name is None:
+ raise PLCInvalidArgument, "Slice attribute already exists"
+
+ slice_attribute.sync()
+ self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
+
+ return slice_attribute['slice_attribute_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'description', 'min_role_id']
+
+class AddSliceAttributeType(Method):
+ """
+ Adds a new type of slice attribute. Any fields specified in
+ attribute_type_fields are used, otherwise defaults are used.
+
+ Returns the new attribute_type_id (> 0) if successful, faults
+ otherwise.
+ """
+
+ roles = ['admin']
+
+ attribute_type_fields = dict(filter(can_update, SliceAttributeType.fields.items()))
+
+ accepts = [
+ Auth(),
+ attribute_type_fields
+ ]
+
+ returns = Parameter(int, 'New attribute_id (> 0) if successful')
+
+
+ def call(self, auth, attribute_type_fields):
+ attribute_type_fields = dict(filter(can_update, attribute_type_fields.items()))
+ attribute_type = SliceAttributeType(self.api, attribute_type_fields)
+ attribute_type.sync()
+
+ self.event_objects = {'AttributeType': [attribute_type['attribute_type_id']]}
+
+ return attribute_type['attribute_type_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
+from PLC.Auth import Auth
+
+class AddSliceInstantiation(Method):
+ """
+ Adds a new slice instantiation state.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ SliceInstantiation.fields['instantiation']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ slice_instantiation = SliceInstantiation(self.api)
+ slice_instantiation['instantiation'] = name
+ slice_instantiation.sync(insert = True)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+class AddSliceToNodes(Method):
+ """
+ Adds the specified slice to the specified nodes. Nodes may be
+ either local or foreign nodes.
+
+ If the slice is already associated with a node, no errors are
+ returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ [Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name, node_id_or_hostname_list):
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ # Get specified nodes, add them to the slice
+ nodes = Nodes(self.api, node_id_or_hostname_list, ['node_id', 'hostname', 'slice_ids', 'slice_ids_whitelist', 'site_id'])
+
+ for node in nodes:
+ # check the slice whitelist on each node first
+ # allow users at site to add node to slice, ignoring whitelist
+ if node['slice_ids_whitelist'] and \
+ slice['slice_id'] not in node['slice_ids_whitelist'] and \
+ not set(self.caller['site_ids']).intersection([node['site_id']]):
+ raise PLCInvalidArgument, "%s is not allowed on %s (not on the whitelist)" % \
+ (slice['name'], node['hostname'])
+ if slice['slice_id'] not in node['slice_ids']:
+ slice.add_node(node, commit = False)
+
+ slice.sync()
+
+ self.event_objects = {'Node': [node['node_id'] for node in nodes],
+ 'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class AddSliceToNodesWhitelist(Method):
+ """
+ Adds the specified slice to the whitelist on the specified nodes. Nodes may be
+ either local or foreign nodes.
+
+ If the slice is already associated with a node, no errors are
+ returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ [Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name, node_id_or_hostname_list):
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ # Get specified nodes, add them to the slice
+ nodes = Nodes(self.api, node_id_or_hostname_list)
+ for node in nodes:
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "%s not a local node" % node['hostname']
+ if slice['slice_id'] not in node['slice_ids_whitelist']:
+ slice.add_to_node_whitelist(node, commit = False)
+
+ slice.sync()
+
+ self.event_objects = {'Node': [node['node_id'] for node in nodes],
+ 'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Auth import Auth
+from PLC.Methods.AddAddressType import AddAddressType
+
+class AdmAddAddressType(AddAddressType):
+ """
+ Deprecated. See AddAddressType.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ AddressType.fields['name']
+ ]
+
+ def call(self, auth, name):
+ return AddAddressType.call(self, auth, {'name': name})
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+from PLC.Methods.AddNode import AddNode
+
+can_update = lambda (field, value): field in \
+ ['model', 'version']
+
+class AdmAddNode(AddNode):
+ """
+ Deprecated. See AddNode.
+ """
+
+ status = "deprecated"
+
+ node_fields = dict(filter(can_update, Node.fields.items()))
+
+ accepts = [
+ Auth(),
+ Site.fields['site_id'],
+ Node.fields['hostname'],
+ Node.fields['boot_state'],
+ node_fields
+ ]
+
+ def call(self, auth, site_id, hostname, boot_state, node_fields = {}):
+ node_fields['site_id'] = site_id
+ node_fields['hostname'] = hostname
+ node_fields['boot_state'] = boot_state
+ return AddNode.call(self, auth, node_fields)
--- /dev/null
+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 Auth
+from PLC.Methods.AddNodeGroup import AddNodeGroup
+
+class AdmAddNodeGroup(AddNodeGroup):
+ """
+ Deprecated. See AddNodeGroup.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ NodeGroup.fields['name'],
+ NodeGroup.fields['description']
+ ]
+
+ def call(self, auth, name, description):
+ return AddNodeGroup.call(self, auth, {'name': name, 'description': description})
--- /dev/null
+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 Auth
+from PLC.Methods.AddNodeNetwork import AddNodeNetwork
+
+can_update = lambda (field, value): field not in ['nodenetwork_id', 'node_id', 'method', 'type']
+
+class AdmAddNodeNetwork(AddNodeNetwork):
+ """
+ Deprecated. See AddNodeNetwork.
+ """
+
+ status = "deprecated"
+
+ nodenetwork_fields = dict(filter(can_update, NodeNetwork.fields.items()))
+
+ accepts = [
+ Auth(),
+ NodeNetwork.fields['node_id'],
+ NodeNetwork.fields['method'],
+ NodeNetwork.fields['type'],
+ nodenetwork_fields
+ ]
+
+ def call(self, auth, node_id, method, type, nodenetwork_fields = {}):
+ nodenetwork_fields['node_id'] = node_id
+ nodenetwork_fields['method'] = method
+ nodenetwork_fields['type'] = type
+ return AddNodeNetwork.call(self, auth, nodenetwork_fields)
--- /dev/null
+from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
+
+class AdmAddNodeToNodeGroup(AddNodeToNodeGroup):
+ """
+ Deprecated. See AddNodeToNodeGroup.
+ """
+
+ status = "deprecated"
--- /dev/null
+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 Auth
+from PLC.Methods.AddPerson import AddPerson
+
+can_update = lambda (field, value): field in \
+ ['title', 'email', 'password', 'phone', 'url', 'bio']
+
+class AdmAddPerson(AddPerson):
+ """
+ Deprecated. See AddPerson.
+ """
+
+ status = "deprecated"
+
+ person_fields = dict(filter(can_update, Person.fields.items()))
+
+ accepts = [
+ Auth(),
+ Person.fields['first_name'],
+ Person.fields['last_name'],
+ person_fields
+ ]
+
+ def call(self, auth, first_name, last_name, person_fields = {}):
+ person_fields['first_name'] = first_name
+ person_fields['last_name'] = last_name
+ return AddPerson.call(self, auth, person_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Keys import Key, Keys
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+from PLC.Methods.AddPersonKey import AddPersonKey
+
+class AdmAddPersonKey(AddPersonKey):
+ """
+ Deprecated. See AddPersonKey. Keys can no longer be marked as
+ primary, i.e. the is_primary argument does nothing.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Key.fields['key_type'],
+ Key.fields['key'],
+ Parameter(int, "Make this key the primary key")
+ ]
+
+ def call(self, auth, person_id_or_email, key_type, key_value, is_primary):
+ key_fields = {'key_type': key_type, 'key_value': key_value}
+ return AddPersonKey.call(self, auth, person_id_or_email, key_fields)
--- /dev/null
+from PLC.Methods.AddPersonToSite import AddPersonToSite
+
+class AdmAddPersonToSite(AddPersonToSite):
+ """
+ Deprecated. See AddPersonToSite.
+ """
+
+ status = "deprecated"
--- /dev/null
+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 Auth
+from PLC.Methods.AddSite import AddSite
+
+can_update = lambda (field, value): field in \
+ ['is_public', 'latitude', 'longitude', 'url']
+
+class AdmAddSite(AddSite):
+ """
+ Deprecated. See AddSite.
+ """
+
+ status = "deprecated"
+
+ site_fields = dict(filter(can_update, Site.fields.items()))
+
+ accepts = [
+ Auth(),
+ Site.fields['name'],
+ Site.fields['abbreviated_name'],
+ Site.fields['login_base'],
+ site_fields
+ ]
+
+ def call(self, auth, name, abbreviated_name, login_base, site_fields = {}):
+ site_fields['name'] = name
+ site_fields['abbreviated_name'] = abbreviated_name
+ site_fields['login_base'] = login_base
+ return AddSite.call(self, auth, site_fields)
--- /dev/null
+from PLC.Methods.AddPCU import AddPCU
+
+class AdmAddSitePowerControlUnit(AddPCU):
+ """
+ Deprecated. See AddPCU.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+from PLC.Methods.AddNodeToPCU import AddNodeToPCU
+
+class AdmAssociateNodeToPowerControlUnitPort(AddNodeToPCU):
+ """
+ Deprecated. See AddNodeToPCU.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ PCU.fields['pcu_id'],
+ Parameter(int, 'PCU port number'),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, pcu_id, port, node_id_or_hostname):
+ return AddNodeToPCU(self, auth, node_id_or_hostname, pcu_id, port)
--- /dev/null
+from PLC.Methods.AuthCheck import AuthCheck
+
+class AdmAuthCheck(AuthCheck):
+ """
+ Deprecated. See AuthCheck.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.DeleteAddressType import DeleteAddressType
+
+class AdmDeleteAddressType(DeleteAddressType):
+ """
+ Deprecated. See DeleteAddressType.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+class AdmDeleteAllPersonKeys(Method):
+ """
+ Deprecated. Functionality can be implemented with GetPersons and
+ DeleteKey.
+
+ Deletes all of the keys associated with an account. Non-admins may
+ only delete their own keys.
+
+ Non-admins may only delete their own keys.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ accepts = [
+ Auth(),
+ 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[0]
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] != person['person_id']:
+ raise PLCPermissionDenied, "Not allowed to update specified account"
+
+ key_ids = person['key_ids']
+ if not key_ids:
+ return 1
+
+ # Get associated key details
+ keys = Keys(self.api, key_ids)
+
+ for key in keys:
+ key.delete()
+
+ return 1
--- /dev/null
+from PLC.Methods.DeleteNode import DeleteNode
+
+class AdmDeleteNode(DeleteNode):
+ """
+ Deprecated. See DeleteNode.
+ """
+
+ status = "deprecated"
+
--- /dev/null
+from PLC.Methods.DeleteNodeGroup import DeleteNodeGroup
+
+class AdmDeleteNodeGroup(DeleteNodeGroup):
+ """
+ Deprecated. See DeleteNodeGroup.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Methods.DeleteNodeNetwork import DeleteNodeNetwork
+
+class AdmDeleteNodeNetwork(DeleteNodeNetwork):
+ """
+ Deprecated. See DeleteNodeNetwork.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ NodeNetwork.fields['nodenetwork_id']
+ ]
+
+ def call(self, auth, node_id_or_hostname, nodenetwork_id):
+ return DeleteNodeNetwork.call(self, auth, nodenetwork_id)
--- /dev/null
+from PLC.Methods.DeletePerson import DeletePerson
+
+class AdmDeletePerson(DeletePerson):
+ """
+ Deprecated. See DeletePerson.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+class AdmDeletePersonKeys(Method):
+ """
+ Deprecated. Functionality can be implemented with GetPersons and
+ DeleteKey.
+
+ Deletes the specified keys. Non-admins may only delete their own
+ keys.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ [Key.fields['key_id']]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, person_id_or_email, key_ids):
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] != person['person_id']:
+ raise PLCPermissionDenied, "Not allowed to update specified account"
+
+ key_ids = set(key_ids).intersection(person['key_ids'])
+ if not key_ids:
+ return 1
+
+ # Get associated key details
+ keys = Keys(self.api, key_ids)
+
+ for key in keys:
+ key.delete()
+
+ return 1
--- /dev/null
+from PLC.Methods.DeleteSite import DeleteSite
+
+class AdmDeleteSite(DeleteSite):
+ """
+ Deprecated. See DeleteSite.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.DeletePCU import DeletePCU
+
+class AdmDeleteSitePowerControlUnit(DeletePCU):
+ """
+ Deprecated. See DeletePCU.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+from PLC.Methods.DeleteNodeFromPCU import DeleteNodeFromPCU
+
+class AdmDisassociatePowerControlUnitPort(DeleteNodeFromPCU):
+ """
+ Deprecated. See DeleteNodeFromPCU.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ PCU.fields['pcu_id'],
+ Parameter(int, 'PCU port number'),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, pcu_id, port):
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+
+ pcu = pcus[0]
+
+ ports = dict(zip(pcu['ports'], pcu['node_ids']))
+ if port not in ports:
+ raise PLCInvalidArgument, "No node on that port or no such port"
+
+ return DeleteNodeFromPCU(self, auth, ports[port], pcu_id)
--- /dev/null
+import random
+import base64
+
+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 Auth
+
+class AdmGenerateNodeConfFile(Method):
+ """
+ Deprecated. Functionality can be implemented with GetNodes,
+ GetNodeNetworks, and UpdateNode.
+
+ Creates a new node configuration file if all network settings are
+ present. This function will generate a new node key for the
+ specified node, effectively invalidating any old configuration
+ files.
+
+ Non-admins can only generate files for nodes at their sites.
+
+ Returns the contents of the file if successful, faults otherwise.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])
+ ]
+
+ returns = Parameter(str, "Node configuration file")
+
+ def call(self, auth, node_id_or_hostname):
+ # Get node information
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # 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 generate a configuration file for that node"
+
+ # Get node networks for this node
+ primary = None
+ nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids'])
+ for nodenetwork in nodenetworks:
+ if nodenetwork['is_primary']:
+ primary = nodenetwork
+ break
+ if primary is None:
+ raise PLCInvalidArgument, "No primary network configured"
+
+ # Split hostname into host and domain parts
+ parts = node['hostname'].split(".", 1)
+ if len(parts) < 2:
+ raise PLCInvalidArgument, "Node hostname is invalid"
+ host = parts[0]
+ domain = parts[1]
+
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ node['key'] = base64.b64encode("".join(map(chr, bytes)))
+ # XXX Boot Manager cannot handle = in the key
+ node['key'] = node['key'].replace("=", "")
+ # Save it
+ node.sync()
+
+ # Generate node configuration file suitable for BootCD
+ file = ""
+
+ file += 'NODE_ID="%d"\n' % node['node_id']
+ file += 'NODE_KEY="%s"\n' % node['key']
+
+ if primary['mac']:
+ file += 'NET_DEVICE="%s"\n' % primary['mac'].lower()
+
+ file += 'IP_METHOD="%s"\n' % primary['method']
+
+ if primary['method'] == 'static':
+ file += 'IP_ADDRESS="%s"\n' % primary['ip']
+ file += 'IP_GATEWAY="%s"\n' % primary['gateway']
+ file += 'IP_NETMASK="%s"\n' % primary['netmask']
+ file += 'IP_NETADDR="%s"\n' % primary['network']
+ file += 'IP_BROADCASTADDR="%s"\n' % primary['broadcast']
+ file += 'IP_DNS1="%s"\n' % primary['dns1']
+ file += 'IP_DNS2="%s"\n' % (primary['dns2'] or "")
+
+ file += 'HOST_NAME="%s"\n' % host
+ file += 'DOMAIN_NAME="%s"\n' % domain
+
+ for nodenetwork in nodenetworks:
+ if nodenetwork['method'] == 'ipmi':
+ file += 'IPMI_ADDRESS="%s"\n' % nodenetwork['ip']
+ if nodenetwork['mac']:
+ file += 'IPMI_MAC="%s"\n' % nodenetwork['mac'].lower()
+ break
+
+ return file
--- /dev/null
+from PLC.Methods.GetAddressTypes import GetAddressTypes
+
+class AdmGetAllAddressTypes(GetAddressTypes):
+ """
+ Deprecated. See GetAddressTypes.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.GetKeyTypes import GetKeyTypes
+
+class AdmGetAllKeyTypes(GetKeyTypes):
+ """
+ Deprecated. See GetKeyTypes.
+ """
+
+ status = "deprecated"
--- /dev/null
+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 Auth
+from PLC.Methods.GetNodeNetworks import GetNodeNetworks
+
+class AdmGetAllNodeNetworks(GetNodeNetworks):
+ """
+ Deprecated. Functionality can be implemented with GetNodes and
+ GetNodeNetworks.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])
+ ]
+
+ returns = [NodeNetwork.fields]
+
+ def call(self, auth, node_id_or_hostname):
+ # Get node information
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if not node['nodenetwork_ids']:
+ return []
+
+ return GetNodeNetworks.call(self, auth, node['nodenetwork_ids'])
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter
+from PLC.Auth import Auth
+from PLC.Methods.GetRoles import GetRoles
+
+class AdmGetAllRoles(GetRoles):
+ """
+ Deprecated. See GetRoles.
+
+ Return all possible roles as a struct:
+
+ {'10': 'admin', '20': 'pi', '30': 'user', '40': 'tech'}
+
+ Note that because of XML-RPC marshalling limitations, the keys to
+ this struct are string representations of the integer role
+ identifiers.
+ """
+
+ status = "deprecated"
+
+ returns = dict
+
+ def call(self, auth):
+ roles_list = GetRoles.call(self, auth)
+
+ roles_dict = {}
+ for role in roles_list:
+ # Stringify the keys!
+ roles_dict[str(role['role_id'])] = role['name']
+
+ return roles_dict
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+class AdmGetNodeGroupNodes(Method):
+ """
+ Deprecated. See GetNodeGroups.
+
+ Returns a list of node_ids for the node group specified.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name'])
+ ]
+
+ returns = NodeGroup.fields['node_ids']
+
+ def call(self, auth, nodegroup_id_or_name):
+ # Get nodes in this nodegroup
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such node group"
+
+ # Get the info for the node group specified
+ nodegroup = nodegroups[0]
+
+ # Return the list of node_ids
+ return nodegroup['node_ids']
--- /dev/null
+from PLC.Methods.GetNodeGroups import GetNodeGroups
+
+class AdmGetNodeGroups(GetNodeGroups):
+ """
+ Deprecated. See GetNodeGroups.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.GetNodes import GetNodes
+
+class AdmGetNodes(GetNodes):
+ """
+ Deprecated. See GetNodes. All fields are now always returned.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, node_id_or_hostname_list = None, return_fields = None):
+ return GetNodes.call(self, auth, node_id_or_hostname_list)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+from PLC.Methods.GetKeys import GetKeys
+
+class AdmGetPersonKeys(GetKeys):
+ """
+ Deprecated. Functionality can be implemented with GetPersons and
+ GetKeys.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ [Key.fields['key_id']]
+ ]
+
+ returns = [Key.fields]
+
+ 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[0]
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] != person['person_id']:
+ raise PLCPermissionDenied, "Not allowed to view keys for specified account"
+
+ return GetKeys.call(self, auth, person['key_ids'])
--- /dev/null
+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 Auth
+
+class AdmGetPersonRoles(Method):
+ """
+ Deprecated. See GetPersons.
+
+ Return the roles that the specified person has as a struct:
+
+ {'10': 'admin', '30': 'user', '20': 'pi', '40': 'tech'}
+
+ Admins can get the roles for any user. PIs can only get the roles
+ for members of their sites. All others may only get their own
+ roles.
+
+ Note that because of XML-RPC marshalling limitations, the keys to
+ this struct are string representations of the integer role
+ identifiers.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email'])
+ ]
+
+ returns = dict
+
+ 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[0]
+
+ # Authenticated function
+ assert self.caller is not None
+
+ # Check if we can view this account
+ if not self.caller.can_view(person):
+ raise PLCPermissionDenied, "Not allowed to view specified account"
+
+ # Stringify the keys!
+ role_ids = map(str, person['role_ids'])
+ roles = person['roles']
+
+ return dict(zip(role_ids, roles))
--- /dev/null
+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 Auth
+
+class AdmGetPersonSites(Method):
+ """
+ Deprecated. See GetPersons.
+
+ Returns the sites that the specified person is associated with as
+ an array of site identifiers.
+
+ Admins may retrieve details about anyone. Users and techs may only
+ retrieve details about themselves. PIs may retrieve details about
+ themselves and others at their sites.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email'])
+ ]
+
+ returns = Person.fields['site_ids']
+
+ 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[0]
+
+ # Authenticated function
+ assert self.caller is not None
+
+ # Check if we can view this account
+ if not self.caller.can_view(person):
+ raise PLCPermissionDenied, "Not allowed to view specified account"
+
+ return person['site_ids']
--- /dev/null
+from PLC.Methods.GetPersons import GetPersons
+
+class AdmGetPersons(GetPersons):
+ """
+ Deprecated. See GetPersons.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, person_id_or_email_list = None, return_fields = None):
+ return GetPersons.call(self, auth, person_id_or_email_list)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+
+class AdmGetPowerControlUnitNodes(Method):
+ """
+ Deprecated. See GetPCUs.
+
+ Returns a list of the nodes, and the ports they are assigned to,
+ on the specified PCU.
+
+ Admin may query all PCUs. Non-admins may only query the PCUs at
+ their sites.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ PCU.fields['pcu_id']
+ ]
+
+ returns = [{'node_id': Parameter(int, "Node identifier"),
+ 'port_number': Parameter(int, "Port number")}]
+
+ def call(self, auth, pcu_id):
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+ pcu = pcus[0]
+
+ if 'admin' not in self.caller['roles']:
+ if pcu['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to view that PCU"
+
+ return [{'node_id': node_id, 'port_number': port} \
+ for (node_id, port) in zip(pcu['node_ids'], pcu['ports'])]
--- /dev/null
+from PLC.Methods.GetPCUs import GetPCUs
+
+class AdmGetPowerControlUnits(GetPCUs):
+ """
+ Deprecated. See GetPCUs.
+ """
+
+ status = "deprecated"
--- /dev/null
+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 Auth
+
+class AdmGetSiteNodes(Method):
+ """
+ Deprecated. See GetSites.
+
+ Return a struct containing an array of node_ids for each of the
+ sites specified. Note that the keys of the struct are strings, not
+ integers, because of XML-RPC marshalling limitations.
+
+ Admins may retrieve details about all nodes on a site by not specifying
+ site_id_or_name 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.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ [Mixed(Site.fields['site_id'],
+ Site.fields['name'])],
+ ]
+
+ returns = dict
+
+ def call(self, auth, site_id_or_name_list = None):
+ # Get site information
+ sites = Sites(self.api, site_id_or_name_list)
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+
+ # Convert to {str(site_id): [node_id]}
+ site_nodes = {}
+ for site in sites:
+ site_nodes[str(site['site_id'])] = site['node_ids']
+
+ return site_nodes
--- /dev/null
+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.Auth import Auth
+
+class AdmGetSitePIs(Method):
+ """
+ Deprecated. Functionality can be implemented with GetSites and
+ GetPersons.
+
+ Return a list of person_ids of the PIs for the site specified.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])
+ ]
+
+ returns = Site.fields['person_ids']
+
+ def call(self, auth, site_id_or_login_base):
+ # Authenticated function
+ assert self.caller is not None
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+
+ site = sites[0]
+
+ persons = Persons(self.api, site['person_ids'])
+
+ has_pi_role = lambda person: 'pi' in person['roles']
+ pis = filter(has_pi_role, persons)
+
+ return [pi['person_id'] for pi in pis]
--- /dev/null
+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 Auth
+
+class AdmGetSitePersons(Method):
+ """
+ Deprecated. See GetSites.
+
+ Return a list of person_ids for the site specified.
+
+ PIs may only retrieve the person_ids of accounts at their
+ site. Admins may retrieve the person_ids of accounts at any site.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])
+ ]
+
+ returns = Site.fields['person_ids']
+
+ def call(self, auth, site_id_or_login_base):
+ # Authenticated function
+ assert self.caller is not None
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+
+ site = sites[0]
+
+ if 'admin' not in self.caller['roles']:
+ if site['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to view accounts at that site"
+
+ return site['person_ids']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+
+class AdmGetSitePowerControlUnits(Method):
+ """
+ Deprecated. Functionality can be implemented with GetSites and GetPCUs.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])
+ ]
+
+ returns = [PCU.fields]
+
+ def call(self, auth, site_id_or_login_base):
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if 'admin' not in self.caller['roles']:
+ if site['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to view the PCUs at that site"
+
+ return PCUs(self.api, site['pcu_ids'])
--- /dev/null
+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.Auth import Auth
+
+class AdmGetSiteTechContacts(Method):
+ """
+ Deprecated. Functionality can be implemented with GetSites and
+ GetPersons.
+
+ Return a list of person_ids of the technical contacts for the site
+ specified.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])
+ ]
+
+ returns = Site.fields['person_ids']
+
+ def call(self, auth, site_id_or_login_base):
+ # Authenticated function
+ assert self.caller is not None
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+
+ site = sites[0]
+
+ persons = Persons(self.api, site['person_ids'])
+
+ has_tech_role = lambda person: 'tech' in person['roles']
+ techs = filter(has_tech_role, persons)
+
+ return [tech['person_id'] for tech in techs]
--- /dev/null
+from PLC.Methods.GetSites import GetSites
+
+class AdmGetSites(GetSites):
+ """
+ Deprecated. See GetSites.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, site_id_or_login_base_list = None, return_fields = None):
+ return GetSites.call(self, auth, site_id_or_login_base_list)
--- /dev/null
+from PLC.Methods.AddRoleToPerson import AddRoleToPerson
+
+class AdmGrantRoleToPerson(AddRoleToPerson):
+ """
+ Deprecated. See AddRoleToPerson.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, person_id_or_email, role_id_or_name):
+ return AddRoleToPerson.call(self, auth, role_id_or_name, person_id_or_email)
--- /dev/null
+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 Auth
+from PLC.Roles import Role, Roles
+
+class AdmIsPersonInRole(Method):
+ """
+ Deprecated. Functionality can be implemented with GetPersons.
+
+ Returns 1 if the specified account has the specified role, 0
+ otherwise. This function differs from AdmGetPersonRoles() in that
+ any authorized user can call it. It is currently restricted to
+ verifying PI roles.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Mixed(Parameter(int, "Role identifier"),
+ Parameter(str, "Role name"))
+ ]
+
+ returns = Parameter(int, "1 if account has role, 0 otherwise")
+
+ def call(self, auth, person_id_or_email, role_id_or_name):
+ # This is a totally fucked up function. I have no idea why it
+ # exists or who calls it, but here is how it is supposed to
+ # work.
+
+ # Only allow PI roles to be checked
+ roles = {}
+ for role in Roles(self.api):
+ roles[role['role_id']] = role['name']
+ roles[role['name']] = role['role_id']
+
+ 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]
+
+ if roles[role_id] != "pi":
+ raise PLCInvalidArgument, "Only the PI role may be checked"
+
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+
+ # Rather than raise an error, and indicate whether or not
+ # the person is real, return 0.
+ if not persons:
+ return 0
+
+ person = persons[0]
+
+ if role_id in person['role_ids']:
+ return 1
+
+ return 0
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Auth import Auth
+
+class AdmQueryConfFile(Method):
+ """
+ Deprecated. See GetConfFiles.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ {'node_id': Node.fields['node_id']}
+ ]
+
+ returns = [ConfFile.fields['conf_file_id']]
+
+ def call(self, auth, search_vals):
+ if 'node_id' in search_vals:
+ conf_files = ConfFiles(self.api)
+
+ conf_files = filter(lambda conf_file: \
+ search_vals['node_id'] in conf_file['node_ids'],
+ conf_files)
+
+ if conf_files:
+ return [conf_file['conf_file_id'] for conf_file in conf_files]
+
+ return []
--- /dev/null
+import socket
+
+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, valid_ip
+from PLC.Auth import Auth
+
+class AdmQueryNode(Method):
+ """
+ Deprecated. Functionality can be implemented with GetNodes and
+ GetNodeNetworks.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ {'node_hostname': Node.fields['hostname'],
+ 'nodenetwork_ip': NodeNetwork.fields['ip'],
+ 'nodenetwork_mac': NodeNetwork.fields['mac'],
+ 'nodenetwork_method': NodeNetwork.fields['method']}
+ ]
+
+ returns = [Node.fields['node_id']]
+
+ def call(self, auth, search_vals):
+ # Get possible nodenetworks
+ if 'node_hostname' in search_vals:
+ nodes = Nodes(self.api, [search_vals['node_hostname']])
+ if not nodes:
+ return []
+
+ # No network interface filters specified
+ if 'nodenetwork_ip' not in search_vals and \
+ 'nodenetwork_mac' not in search_vals and \
+ 'nodenetwork_method' not in search_vals:
+ return [nodes[0]['node_id']]
+
+ if nodes[0]['nodenetwork_ids']:
+ nodenetworks = NodeNetworks(self.api, nodes[0]['nodenetwork_ids'])
+ else:
+ nodenetworks = []
+ else:
+ nodenetworks = NodeNetworks(self.api)
+
+ if 'nodenetwork_ip' in search_vals:
+ if not valid_ip(search_vals['nodenetwork_ip']):
+ raise PLCInvalidArgument, "Invalid IP address"
+ nodenetworks = filter(lambda nodenetwork: \
+ socket.inet_aton(nodenetwork['ip']) == socket.inet_aton(search_vals['nodenetwork_ip']),
+ nodenetworks)
+
+ if 'nodenetwork_mac' in search_vals:
+ nodenetworks = filter(lambda nodenetwork: \
+ nodenetwork['mac'].lower() == search_vals['nodenetwork_mac'].lower(),
+ nodenetworks)
+
+ if 'nodenetwork_method' in search_vals:
+ nodenetworks = filter(lambda nodenetwork: \
+ nodenetwork['method'].lower() == search_vals['nodenetwork_method'].lower(),
+ nodenetworks)
+
+ return [nodenetwork['node_id'] for nodenetwork in nodenetworks]
--- /dev/null
+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 Auth
+
+class AdmQueryPerson(Method):
+ """
+ Deprecated. See GetPersons.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ {'email': Person.fields['email']}
+ ]
+
+ returns = [Person.fields['person_id']]
+
+ def call(self, auth, search_vals):
+ if 'email' in search_vals:
+ persons = Persons(self.api, [search_vals['email']])
+ if persons:
+ return [persons[0]['person_id']]
+
+ return []
--- /dev/null
+import socket
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks, valid_ip
+from PLC.Auth import Auth
+
+class AdmQueryPowerControlUnit(Method):
+ """
+ Deprecated. Functionality can be implemented with GetPCUs or
+ GetNodes.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ {'pcu_hostname': PCU.fields['hostname'],
+ 'pcu_ip': PCU.fields['ip'],
+ 'node_hostname': Node.fields['hostname'],
+ 'node_id': Node.fields['node_id']}
+ ]
+
+ returns = [PCU.fields['pcu_id']]
+
+ def call(self, auth, search_vals):
+ # Get all PCUs. This is a stupid function. The API should not
+ # be used for DB mining.
+ pcus = PCUs(self.api)
+
+ if 'pcu_hostname' in search_vals:
+ pcus = filter(lambda pcu: \
+ pcu['hostname'].lower() == search_vals['pcu_hostname'].lower(),
+ pcus)
+
+ if 'pcu_ip' in search_vals:
+ if not valid_ip(search_vals['pcu_ip']):
+ raise PLCInvalidArgument, "Invalid IP address"
+ pcus = filter(lambda pcu: \
+ socket.inet_aton(pcu['ip']) == socket.inet_aton(search_vals['pcu_ip']),
+ pcus)
+
+ if 'node_id' in search_vals:
+ pcus = filter(lambda pcu: \
+ search_vals['node_id'] in pcu['node_ids'],
+ pcus)
+
+ if 'node_hostname' in search_vals:
+ pcus = filter(lambda pcu: \
+ search_vals['node_hostname'] in \
+ [node['hostname'] for node in Nodes(self.api, pcu['node_ids'])],
+ pcus)
+
+ return [pcu['pcu_id'] for pcu in pcus]
--- /dev/null
+import socket
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Sites import Site, Sites
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks, valid_ip
+from PLC.Auth import Auth
+
+class AdmQuerySite(Method):
+ """
+ Deprecated. Functionality can be implemented with GetSites and
+ GetNodes.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ {'site_name': Site.fields['name'],
+ 'site_abbreviatedname': Site.fields['abbreviated_name'],
+ 'site_loginbase': Site.fields['login_base'],
+ 'node_hostname': Node.fields['hostname'],
+ 'node_id': Node.fields['node_id'],
+ 'nodenetwork_ip': NodeNetwork.fields['ip'],
+ 'nodenetwork_mac': NodeNetwork.fields['mac']}
+ ]
+
+ returns = [Site.fields['site_id']]
+
+ def call(self, auth, search_vals):
+ if 'site_loginbase' in search_vals:
+ sites = Sites(self.api, [search_vals['site_loginbase']])
+ else:
+ sites = Sites(self.api)
+
+ if 'site_name' in search_vals:
+ sites = filter(lambda site: \
+ site['name'] == search_vals['site_name'],
+ sites)
+
+ if 'site_abbreviatedname' in search_vals:
+ sites = filter(lambda site: \
+ site['abbreviatedname'] == search_vals['site_abbreviatedname'],
+ sites)
+
+ if 'node_id' in search_vals:
+ sites = filter(lambda site: \
+ search_vals['node_id'] in site['node_ids'],
+ sites)
+
+ if 'node_hostname' in search_vals or \
+ 'nodenetwork_ip' in search_vals or \
+ 'nodenetwork_mac' in search_vals:
+ for site in sites:
+ site['hostnames'] = []
+ site['ips'] = []
+ site['macs'] = []
+ if site['node_ids']:
+ nodes = Nodes(self.api, site['node_ids'])
+ for node in nodes:
+ site['hostnames'].append(node['hostname'])
+ if 'nodenetwork_ip' in search_vals or \
+ 'nodenetwork_mac' in search_vals:
+ nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids'])
+ site['ips'] += [nodenetwork['ip'] for nodenetwork in nodenetworks]
+ site['macs'] += [nodenetwork['mac'] for nodenetwork in nodenetworks]
+
+ if 'node_hostname' in search_vals:
+ sites = filter(lambda site: \
+ search_vals['node_hostname'] in site['hostnames'],
+ sites)
+
+ if 'nodenetwork_ip' in search_vals:
+ sites = filter(lambda site: \
+ search_vals['nodenetwork_ip'] in site['ips'],
+ sites)
+
+ if 'nodenetwork_mac' in search_vals:
+ sites = filter(lambda site: \
+ search_vals['nodenetwork_mac'] in site['macs'],
+ sites)
+
+ return [site['site_id'] for site in sites]
--- /dev/null
+from PLC.Methods.RebootNode import RebootNode
+
+class AdmRebootNode(RebootNode):
+ """
+ Deprecated. See RebootNode.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
+
+class AdmRemoveNodeFromNodeGroup(DeleteNodeFromNodeGroup):
+ """
+ Deprecated. See DeleteNodeFromNodeGroup.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
+
+class AdmRemovePersonFromSite(DeletePersonFromSite):
+ """
+ Deprecated. See DeletePersonFromSite.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.DeleteRoleFromPerson import DeleteRoleFromPerson
+
+class AdmRevokeRoleFromPerson(DeleteRoleFromPerson):
+ """
+ Deprecated. See DeleteRoleFromPerson.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, person_id_or_email, role_id_or_name):
+ return DeleteRoleFromPerson.call(self, auth, role_id_or_name, person_id_or_email)
--- /dev/null
+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 Auth
+from PLC.Methods.UpdatePerson import UpdatePerson
+
+class AdmSetPersonEnabled(UpdatePerson):
+ """
+ Deprecated. See UpdatePerson.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Person.fields['enabled']
+ ]
+
+ def call(self, auth, person_id_or_email, enabled):
+ return UpdatePerson.call(self, auth, person_id_or_email, {'enabled': enabled})
--- /dev/null
+from PLC.Methods.SetPersonPrimarySite import SetPersonPrimarySite
+
+class AdmSetPersonPrimarySite(SetPersonPrimarySite):
+ """
+ Deprecated. See SetPersonPrimarySite.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.UpdateNode import UpdateNode
+
+class AdmUpdateNode(UpdateNode):
+ """
+ Deprecated. See UpdateNode.
+ """
+
+ status = "deprecated"
--- /dev/null
+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 Auth
+from PLC.Methods.UpdateNodeGroup import UpdateNodeGroup
+
+class AdmUpdateNodeGroup(UpdateNodeGroup):
+ """
+ Deprecated. See UpdateNodeGroup.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ 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):
+ return UpdateNodeGroup.call(self, auth, nodegroup_id_or_name,
+ {'name': name, 'description': description})
--- /dev/null
+from PLC.Methods.UpdateNodeNetwork import UpdateNodeNetwork
+
+class AdmUpdateNodeNetwork(UpdateNodeNetwork):
+ """
+ Deprecated. See UpdateNodeNetwork.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.UpdatePerson import UpdatePerson
+
+class AdmUpdatePerson(UpdatePerson):
+ """
+ Deprecated. See UpdatePerson.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.UpdateSite import UpdateSite
+
+class AdmUpdateSite(UpdateSite):
+ """
+ Deprecated. See UpdateSite.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.UpdatePCU import UpdatePCU
+
+class AdmUpdateSitePowerControlUnit(UpdatePCU):
+ """
+ Deprecated. See UpdatePCU.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Methods.GetNodeGroups import GetNodeGroups
+
+class AnonAdmGetNodeGroups(GetNodeGroups):
+ """
+ Deprecated. See GetNodeGroups. All fields are now always returned
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, nodegroup_id_or_name_list = None, return_fields = None):
+ return GetNodeGroups.call(self, auth, nodegroup_id_or_name_list)
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth, BootAuth
+
+class AuthCheck(Method):
+ """
+ Returns 1 if the user or node authenticated successfully, faults
+ otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+ accepts = [Auth()]
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth):
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+class BlacklistKey(Method):
+ """
+ Blacklists a key, disassociating it and all others identical to it
+ from all accounts and preventing it from ever being added again.
+
+ WARNING: Identical keys associated with other accounts with also
+ be blacklisted.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Key.fields['key_id'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, key_id):
+ # Get associated key details
+ keys = Keys(self.api, [key_id])
+ if not keys:
+ raise PLCInvalidArgument, "No such key"
+ key = keys[0]
+
+ # N.B.: Can blacklist any key, even foreign ones
+
+ key.blacklist()
+
+ # Logging variables
+ self.event_objects = {'Key': [key['key_id']]}
+ self.message = 'Key %d blacklisted' % key['key_id']
+
+ return 1
--- /dev/null
+from PLC.Methods.AuthCheck import AuthCheck
+
+class BootCheckAuthentication(AuthCheck):
+ """
+ Deprecated. See AuthCheck.
+ """
+
+ status = "deprecated"
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import BootAuth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Sessions import Session, Sessions
+
+class BootGetNodeDetails(Method):
+ """
+ Returns a set of details about the calling node, including a new
+ node session value.
+ """
+
+ roles = ['node']
+
+ accepts = [BootAuth()]
+
+ returns = {
+ 'hostname': Node.fields['hostname'],
+ 'boot_state': Node.fields['boot_state'],
+ 'model': Node.fields['model'],
+ 'networks': [NodeNetwork.fields],
+ 'session': Session.fields['session_id'],
+ }
+
+ def call(self, auth):
+ details = {
+ 'hostname': self.caller['hostname'],
+ 'boot_state': self.caller['boot_state'],
+ # XXX Boot Manager cannot unmarshal None
+ 'model': self.caller['model'] or "",
+ }
+
+ # Generate a new session value
+ session = Session(self.api)
+ session.sync(commit = False)
+ session.add_node(self.caller, commit = True)
+
+ details['session'] = session['session_id']
+
+ if self.caller['nodenetwork_ids']:
+ details['networks'] = NodeNetworks(self.api, self.caller['nodenetwork_ids'])
+ # XXX Boot Manager cannot unmarshal None
+ for network in details['networks']:
+ for field in network:
+ if network[field] is None:
+ if isinstance(network[field], (int, long)):
+ network[field] = -1
+ else:
+ network[field] = ""
+
+ self.messge = "Node request boot_state (%s) and networks" % \
+ (details['boot_state'])
+ return details
+
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth, BootAuth, SessionAuth
+from PLC.Nodes import Node, Nodes
+from PLC.Messages import Message, Messages
+
+from PLC.Boot import notify_owners
+
+class BootNotifyOwners(Method):
+ """
+ Notify the owners of the node, and/or support about an event that
+ happened on the machine.
+
+ Returns 1 if successful.
+ """
+
+ roles = ['node']
+
+ accepts = [
+ Mixed(BootAuth(), SessionAuth()),
+ Message.fields['message_id'],
+ Parameter(int, "Notify PIs"),
+ Parameter(int, "Notify technical contacts"),
+ Parameter(int, "Notify support")
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, message_id, include_pis, include_techs, include_support):
+ assert isinstance(self.caller, Node)
+ notify_owners(self, self.caller, message_id, include_pis, include_techs, include_support)
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth, BootAuth, SessionAuth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+can_update = lambda (field, value): field in \
+ ['method', 'mac', 'gateway', 'network',
+ 'broadcast', 'netmask', 'dns1', 'dns2']
+
+class BootUpdateNode(Method):
+ """
+ Allows the calling node to update its own record. Only the primary
+ network can be updated, and the node IP cannot be changed.
+
+ Returns 1 if updated successfully.
+ """
+
+ roles = ['node']
+
+ nodenetwork_fields = dict(filter(can_update, NodeNetwork.fields.items()))
+
+ accepts = [
+ Mixed(BootAuth(), SessionAuth()),
+ {'boot_state': Node.fields['boot_state'],
+ 'primary_network': nodenetwork_fields,
+ 'ssh_host_key': Node.fields['ssh_rsa_key']}
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, node_fields):
+ # Update node state
+ if node_fields.has_key('boot_state'):
+ self.caller['boot_state'] = node_fields['boot_state']
+ if node_fields.has_key('ssh_host_key'):
+ self.caller['ssh_rsa_key'] = node_fields['ssh_host_key']
+
+ # Update primary node network state
+ if node_fields.has_key('primary_network'):
+ primary_network = node_fields['primary_network']
+
+ if 'nodenetwork_id' not in primary_network:
+ raise PLCInvalidArgument, "Node network not specified"
+ if primary_network['nodenetwork_id'] not in self.caller['nodenetwork_ids']:
+ raise PLCInvalidArgument, "Node network not associated with calling node"
+
+ nodenetworks = NodeNetworks(self.api, [primary_network['nodenetwork_id']])
+ if not nodenetworks:
+ raise PLCInvalidArgument, "No such node network"
+ nodenetwork = nodenetworks[0]
+
+ if not nodenetwork['is_primary']:
+ raise PLCInvalidArgument, "Not the primary node network on record"
+
+ nodenetwork_fields = dict(filter(can_update, primary_network.items()))
+ nodenetwork.update(nodenetwork_fields)
+ nodenetwork.sync(commit = False)
+
+ self.caller.sync(commit = True)
+ self.message = "Node updated: %s" % ", ".join(node_fields.keys())
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+
+class DeleteAddress(Method):
+ """
+ Deletes an address.
+
+ PIs may only delete addresses from their own sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Address.fields['address_id'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, address_id):
+ # Get associated address details
+ addresses = Addresses(self.api, [address_id])
+ if not addresses:
+ raise PLCInvalidArgument, "No such address"
+ address = addresses[0]
+
+ if 'admin' not in self.caller['roles']:
+ if address['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Address must be associated with one of your sites"
+
+ address.delete()
+
+ # Logging variables
+ self.event_objects = {'Address': [address['address_id']]}
+ self.message = 'Address %d deleted' % address['address_id']
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Auth import Auth
+
+class DeleteAddressType(Method):
+ """
+ Deletes an address type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(AddressType.fields['address_type_id'],
+ AddressType.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, address_type_id_or_name):
+ address_types = AddressTypes(self.api, [address_type_id_or_name])
+ if not address_types:
+ raise PLCInvalidArgument, "No such address type"
+ address_type = address_types[0]
+ address_type.delete()
+ self.event_objects = {'AddressType': [address_type['address_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+
+class DeleteAddressTypeFromAddress(Method):
+ """
+ Deletes an address type from the specified address.
+
+ PIs may only update addresses of their own sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(AddressType.fields['address_type_id'],
+ AddressType.fields['name']),
+ Address.fields['address_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, address_type_id_or_name, address_id):
+ address_types = AddressTypes(self.api, [address_type_id_or_name])
+ if not address_types:
+ raise PLCInvalidArgument, "No such address type"
+ address_type = address_types[0]
+
+ addresses = Addresses(self.api, [address_id])
+ if not addresses:
+ raise PLCInvalidArgument, "No such address"
+ address = addresses[0]
+
+ if 'admin' not in self.caller['roles']:
+ if address['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Address must be associated with one of your sites"
+
+ address.remove_address_type(address_type)
+ self.event_objects = {'Address' : [address['address_id']],
+ 'AddressType': [address_type['address_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.BootStates import BootState, BootStates
+from PLC.Auth import Auth
+
+class DeleteBootState(Method):
+ """
+ Deletes a node boot state.
+
+ WARNING: This will cause the deletion of all nodes in this boot
+ state.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ BootState.fields['boot_state']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ boot_states = BootStates(self.api, [name])
+ if not boot_states:
+ raise PLCInvalidArgument, "No such boot state"
+ boot_state = boot_states[0]
+
+ boot_state.delete()
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Auth import Auth
+
+class DeleteConfFile(Method):
+ """
+ Returns an array of structs containing details about node
+ configuration files. If conf_file_ids is specified, only the
+ specified configuration files will be queried.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, conf_file_id):
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+
+ conf_file = conf_files[0]
+ conf_file.delete()
+ self.event_objects = {'ConfFile': [conf_file['conf_file_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import Auth
+
+class DeleteConfFileFromNode(Method):
+ """
+ Deletes a configuration file from the specified node. If the node
+ is not linked to the configuration file, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id'],
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, conf_file_id, node_id_or_hostname):
+ # Get configuration file
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+ conf_file = conf_files[0]
+
+ # Get node
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ # Link configuration file to node
+ if node['node_id'] in conf_file['node_ids']:
+ conf_file.remove_node(node)
+
+ # Log affected objects
+ self.event_objects = {'ConfFile': [conf_file_id],
+ 'Node': [node['node_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Auth import Auth
+
+class DeleteConfFileFromNodeGroup(Method):
+ """
+ Deletes a configuration file from the specified nodegroup. If the nodegroup
+ is not linked to the configuration file, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id'],
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, conf_file_id, nodegroup_id_or_name):
+ # Get configuration file
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+ conf_file = conf_files[0]
+
+ # Get nodegroup
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such nodegroup"
+ nodegroup = nodegroups[0]
+
+ # Link configuration file to nodegroup
+ if nodegroup['nodegroup_id'] in conf_file['nodegroup_ids']:
+ conf_file.remove_nodegroup(nodegroup)
+
+ # Log affected objects
+ self.event_objects = {'ConfFile': [conf_file_id],
+ 'NodeGroup': [nodegroup['nodegroup_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class DeleteInitScript(Method):
+ """
+ Deletes an existing initscript.
+
+ Returns 1 if successfuli, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ InitScript.fields['initscript_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, initscript_id):
+ initscripts = InitScripts(self.api, [initscript_id])
+ if not initscripts:
+ raise PLCInvalidArgument, "No such initscript"
+
+ initscript = initscripts[0]
+ initscript.delete()
+ self.event_objects = {'InitScript': [initscript['initscript_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+class DeleteKey(Method):
+ """
+ Deletes a key.
+
+ Non-admins may only delete their own keys.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ accepts = [
+ Auth(),
+ Key.fields['key_id'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, key_id):
+ # Get associated key details
+ keys = Keys(self.api, [key_id])
+ if not keys:
+ raise PLCInvalidArgument, "No such key"
+ key = keys[0]
+
+ if key['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local key"
+
+ if 'admin' not in self.caller['roles']:
+ if key['key_id'] not in self.caller['key_ids']:
+ raise PLCPermissionDenied, "Key must be associated with your account"
+
+ key.delete()
+
+ # Logging variables
+ self.event_objects = {'Key': [key['key_id']]}
+ self.message = 'Key %d deleted' % key['key_id']
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.KeyTypes import KeyType, KeyTypes
+from PLC.Auth import Auth
+
+class DeleteKeyType(Method):
+ """
+ Deletes a key type.
+
+ WARNING: This will cause the deletion of all keys of this type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ KeyType.fields['key_type']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ key_types = KeyTypes(self.api, [name])
+ if not key_types:
+ raise PLCInvalidArgument, "No such key type"
+ key_type = key_types[0]
+
+ key_type.delete()
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Messages import Message, Messages
+from PLC.Auth import Auth
+
+class DeleteMessage(Method):
+ """
+ Deletes a message template.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Message.fields['message_id'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, message_id):
+ # Get message information
+ messages = Messages(self.api, [message_id])
+ if not messages:
+ raise PLCInvalidArgument, "No such message"
+ message = messages[0]
+
+ message.delete()
+ self.event_objects = {'Message': [message['message_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkMethods import NetworkMethod, NetworkMethods
+from PLC.Auth import Auth
+
+class DeleteNetworkMethod(Method):
+ """
+ Deletes a network method.
+
+ WARNING: This will cause the deletion of all network interfaces
+ that use this method.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ NetworkMethod.fields['method']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ network_methods = NetworkMethods(self.api, [name])
+ if not network_methods:
+ raise PLCInvalidArgument, "No such network method"
+ network_method = network_methods[0]
+
+ network_method.delete()
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkTypes import NetworkType, NetworkTypes
+from PLC.Auth import Auth
+
+class DeleteNetworkType(Method):
+ """
+ Deletes a network type.
+
+ WARNING: This will cause the deletion of all network interfaces
+ that use this type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ NetworkType.fields['type']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, name):
+ network_types = NetworkTypes(self.api, [name])
+ if not network_types:
+ raise PLCInvalidArgument, "No such network type"
+ network_type = network_types[0]
+
+ network_type.delete()
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+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 = [
+ Auth(),
+ 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[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # 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()
+
+ # Logging variables
+ self.event_objects = {'Node': [node['node_id']]}
+ self.message = "Node %d deleted" % node['node_id']
+
+ return 1
--- /dev/null
+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 Auth
+
+class DeleteNodeFromNodeGroup(Method):
+ """
+ Removes a node from the specified node group.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, node_id_or_hostname, nodegroup_id_or_name):
+ # Get node info
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+
+ node = nodes[0]
+
+ # Get nodegroup info
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such nodegroup"
+
+ nodegroup = nodegroups[0]
+
+ # Remove node from nodegroup
+ if node['node_id'] in nodegroup['node_ids']:
+ nodegroup.remove_node(node)
+
+ # Logging variables
+ self.event_objects = {'NodeGroup': [nodegroup['nodegroup_id']],
+ 'Node': [node['node_id']]}
+ self.message = 'node %d deleted from node group %d' % \
+ (node['node_id'], nodegroup['nodegroup_id'])
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.PCUs import PCU, PCUs
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+
+class DeleteNodeFromPCU(Method):
+ """
+ Deletes a node from a PCU.
+
+ Non-admins may only update PCUs at their sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ PCU.fields['pcu_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, node_id_or_hostname, pcu_id):
+ # Get node
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+
+ node = nodes[0]
+
+ # Get PCU
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+
+ pcu = pcus[0]
+
+ if 'admin' not in self.caller['roles']:
+ ok = False
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ if pcu['pcu_id'] in site['pcu_ids']:
+ ok = True
+ break
+ if not ok:
+ raise PLCPermissionDenied, "Not allowed to update that PCU"
+
+ # Removed node from PCU
+
+ if node['node_id'] in pcu['node_ids']:
+ pcu.remove_node(node)
+
+ # Logging variables
+ self.event_objects = {'PCU': [pcu['pcu_id']],
+ 'Node': [node['node_id']]}
+ self.message = 'Node %d removed from PCU %d' % \
+ (node['node_id'], pcu['pcu_id'])
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+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 = [
+ Auth(),
+ 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[0]
+
+ nodegroup.delete()
+
+ # Logging variables
+ self.event_objects = {'NodeGroup': [nodegroup['nodegroup_id']]}
+ self.message = 'Node group %d deleted' % nodegroup['nodegroup_id']
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+class DeleteNodeNetwork(Method):
+ """
+ Deletes an existing node network interface.
+
+ Admins may delete any node network. PIs and techs may only delete
+ node network interfaces associated with nodes at their sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ NodeNetwork.fields['nodenetwork_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, nodenetwork_id):
+
+ # Get node network information
+ nodenetworks = NodeNetworks(self.api, [nodenetwork_id])
+ if not nodenetworks:
+ raise PLCInvalidArgument, "No such node network"
+ nodenetwork = nodenetworks[0]
+
+ # Get node information
+ nodes = Nodes(self.api, [nodenetwork['node_id']])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ # 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()
+
+ # Logging variables
+ self.event_objects = {'NodeNetwork': [nodenetwork['nodenetwork_id']]}
+ self.message = "Node network %d deleted" % nodenetwork['nodenetwork_id']
+
+ return 1
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+from PLC.Nodes import Node, Nodes
+from PLC.Sites import Site, Sites
+
+class DeleteNodeNetworkSetting(Method):
+ """
+ Deletes the specified nodenetwork setting
+
+ Attributes may require the caller to have a particular role in order
+ to be deleted, depending on the related nodenetwork setting type.
+ Admins may delete attributes of any slice or sliver.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ NodeNetworkSetting.fields['nodenetwork_setting_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ object_type = 'NodeNetwork'
+
+
+ def call(self, auth, nodenetwork_setting_id):
+ nodenetwork_settings = NodeNetworkSettings(self.api, [nodenetwork_setting_id])
+ if not nodenetwork_settings:
+ raise PLCInvalidArgument, "No such nodenetwork setting %r"%nodenetwork_setting_id
+ nodenetwork_setting = nodenetwork_settings[0]
+
+ ### reproducing a check from UpdateSliceAttribute, looks dumb though
+ nodenetworks = NodeNetworks(self.api, [nodenetwork_setting['nodenetwork_id']])
+ if not nodenetworks:
+ raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_setting['nodenetwork_id']
+ nodenetwork = nodenetworks[0]
+
+ assert nodenetwork_setting['nodenetwork_setting_id'] in nodenetwork['nodenetwork_setting_ids']
+
+ # check permission : it not admin, is the user affiliated with the right site
+ if 'admin' not in self.caller['roles']:
+ # locate node
+ node = Nodes (self.api,[nodenetwork['node_id']])[0]
+ # locate site
+ site = Sites (self.api, [node['site_id']])[0]
+ # check caller is affiliated with this site
+ if self.caller['person_id'] not in site['person_ids']:
+ raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+
+ required_min_role = nodenetwork_setting_type ['min_role_id']
+ if required_min_role is not None and \
+ min(self.caller['role_ids']) > required_min_role:
+ raise PLCPermissionDenied, "Not allowed to modify the specified nodenetwork setting, requires role %d",required_min_role
+
+ nodenetwork_setting.delete()
+ self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']]
+
+ return 1
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 7365 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+from PLC.Auth import Auth
+
+class DeleteNodeNetworkSettingType(Method):
+ """
+ Deletes the specified nodenetwork setting type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'],
+ NodeNetworkSettingType.fields['name']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, nodenetwork_setting_type_id_or_name):
+ nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name])
+ if not nodenetwork_setting_types:
+ raise PLCInvalidArgument, "No such nodenetwork setting type"
+ nodenetwork_setting_type = nodenetwork_setting_types[0]
+
+ nodenetwork_setting_type.delete()
+ self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']]
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+
+class DeletePCU(Method):
+ """
+ Deletes a PCU.
+
+ Non-admins may only delete PCUs at their sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ PCU.fields['pcu_id'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, pcu_id):
+ # Get associated PCU details
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+ pcu = pcus[0]
+
+ if 'admin' not in self.caller['roles']:
+ if pcu['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to update that PCU"
+
+ pcu.delete()
+
+ # Logging variables
+ self.event_objects = {'PCU': [pcu['pcu_id']]}
+ self.message = 'PCU %d deleted' % pcu['pcu_id']
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUProtocolTypes import PCUProtocolType, PCUProtocolTypes
+from PLC.Auth import Auth
+
+class DeletePCUProtocolType(Method):
+ """
+ Deletes a PCU protocol type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ PCUProtocolType.fields['pcu_protocol_type_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, protocol_type_id):
+ protocol_types = PCUProtocolTypes(self.api, [protocol_type_id])
+ if not protocol_types:
+ raise PLCInvalidArgument, "No such pcu protocol type"
+
+ protocol_type = protocol_types[0]
+ protocol_type.delete()
+ self.event_objects = {'PCUProtocolType': [protocol_type['pcu_protocol_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUTypes import PCUType, PCUTypes
+from PLC.Auth import Auth
+
+class DeletePCUType(Method):
+ """
+ Deletes a PCU type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ PCUType.fields['pcu_type_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, pcu_type_id):
+ pcu_types = PCUTypes(self.api, [pcu_type_id])
+ if not pcu_types:
+ raise PLCInvalidArgument, "No such pcu type"
+
+ pcu_type = pcu_types[0]
+ pcu_type.delete()
+ self.event_objects = {'PCUType': [pcu_type['pcu_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Peers import Peer, Peers
+
+class DeletePeer(Method):
+ """
+ Mark an existing peer as deleted. All entities (e.g., slices,
+ keys, nodes, etc.) for which this peer is authoritative will also
+ be deleted or marked as deleted.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Peer.fields['peer_id'],
+ Peer.fields['peername'])
+ ]
+
+ returns = Parameter(int, "1 if successful")
+
+ def call(self, auth, peer_id_or_name):
+ # Get account information
+ peers = Peers(self.api, [peer_id_or_name])
+ if not peers:
+ raise PLCInvalidArgument, "No such peer"
+
+ peer = peers[0]
+ peer.delete()
+
+ # Log affected objects
+ self.event_objects = {'Peer': [peer['peer_id']]}
+
+ return 1
--- /dev/null
+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 Auth
+
+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 = [
+ Auth(),
+ 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[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # 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()
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']]}
+ self.message = 'Person %d deleted' % person['person_id']
+
+ return 1
--- /dev/null
+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 Auth
+
+class DeletePersonFromSite(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 = [
+ Auth(),
+ 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[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if site['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local site"
+
+ if site['site_id'] in person['site_ids']:
+ site.remove_person(person)
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']],
+ 'Person': [person['person_id']]}
+ self.message = 'Person %d deleted from site %d ' % \
+ (person['person_id'], site['site_id'])
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class DeletePersonFromSlice(Method):
+ """
+ Deletes the specified person from the specified slice. If the person is
+ not a member of the slice, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, person_id_or_email, slice_id_or_name):
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ # N.B. Allow foreign users to be added to local slices and
+ # local users to be added to foreign slices (and, of course,
+ # local users to be added to local slices).
+ if person['peer_id'] is not None and slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Cannot delete foreign users from foreign slices"
+
+ # If we are not admin, make sure the caller is a pi
+ # of the site associated with the slice
+ if 'admin' not in self.caller['roles']:
+ if slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to delete users from this slice"
+
+ if slice['slice_id'] in person['slice_ids']:
+ slice.remove_person(person)
+
+ self.event_objects = {'Slice': [slice['slice_id']],
+ 'Person': [person['person_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Roles import Role, Roles
+from PLC.Auth import Auth
+
+class DeleteRole(Method):
+ """
+ Deletes a role.
+
+ WARNING: This will remove the specified role from all accounts
+ that possess it, and from all node and slice attributes that refer
+ to it.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Role.fields['role_id'],
+ Role.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, role_id_or_name):
+ roles = Roles(self.api, [role_id_or_name])
+ if not roles:
+ raise PLCInvalidArgument, "No such role"
+ role = roles[0]
+
+ role.delete()
+ self.event_objects = {'Role': [role['role_id']]}
+
+ return 1
--- /dev/null
+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 Auth
+from PLC.Roles import Role, Roles
+
+class DeleteRoleFromPerson(Method):
+ """
+ Deletes 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 = [
+ Auth(),
+ Mixed(Role.fields['role_id'],
+ Role.fields['name']),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, role_id_or_name, person_id_or_email):
+ # Get role
+ roles = Roles(self.api, [role_id_or_name])
+ if not roles:
+ raise PLCInvalidArgument, "Invalid role '%s'" % unicode(role_id_or_name)
+ role = roles[0]
+
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # Authenticated function
+ assert self.caller is not None
+
+ # 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['role_id'] <= min(self.caller['role_ids']):
+ raise PLCPermissionDenied, "Not allowed to revoke that role"
+
+ if role['role_id'] in person['role_ids']:
+ person.remove_role(role)
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']],
+ 'Role': [role['role_id']]}
+ self.message = "Role %d revoked from person %d" % \
+ (role['role_id'], person['person_id'])
+
+ return 1
--- /dev/null
+import time
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import SessionAuth
+from PLC.Sessions import Session, Sessions
+
+class DeleteSession(Method):
+ """
+ Invalidates the current session.
+
+ Returns 1 if successful.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+ accepts = [SessionAuth()]
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth):
+ assert auth.has_key('session')
+
+ sessions = Sessions(self.api, [auth['session']])
+ if not sessions:
+ raise PLCAPIError, "No such session"
+ session = sessions[0]
+
+ session.delete()
+
+ return 1
--- /dev/null
+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 Auth
+
+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 = [
+ Auth(),
+ 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[0]
+
+ if site['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local site"
+
+ site.delete()
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']]}
+ self.message = 'Site %d deleted' % site['site_id']
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class DeleteSlice(Method):
+ """
+ Deletes the specified slice.
+
+ Users may only delete slices of which they are members. PIs may
+ delete any of the slices at their sites, or any slices of which
+ they are members. Admins may delete any slice.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name):
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ slice.delete()
+ self.event_objects = {'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.Auth import Auth
+
+class DeleteSliceAttribute(Method):
+ """
+ Deletes the specified slice or sliver attribute.
+
+ Attributes may require the caller to have a particular role in
+ order to be deleted. Users may only delete attributes of
+ slices or slivers of which they are members. PIs may only delete
+ attributes of slices or slivers at their sites, or of which they
+ are members. Admins may delete attributes of any slice or sliver.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ SliceAttribute.fields['slice_attribute_id']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_attribute_id):
+ slice_attributes = SliceAttributes(self.api, [slice_attribute_id])
+ if not slice_attributes:
+ raise PLCInvalidArgument, "No such slice attribute"
+ slice_attribute = slice_attributes[0]
+
+ slices = Slices(self.api, [slice_attribute['slice_id']])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ assert slice_attribute['slice_attribute_id'] in slice['slice_attribute_ids']
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ if slice_attribute['min_role_id'] is not None and \
+ min(self.caller['role_ids']) > slice_attribute['min_role_id']:
+ raise PLCPermissioinDenied, "Not allowed to delete the specified attribute"
+
+ slice_attribute.delete()
+ self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.Auth import Auth
+
+class DeleteSliceAttributeType(Method):
+ """
+ Deletes the specified slice attribute.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(SliceAttributeType.fields['attribute_type_id'],
+ SliceAttributeType.fields['name']),
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, attribute_type_id_or_name):
+ attribute_types = SliceAttributeTypes(self.api, [attribute_type_id_or_name])
+ if not attribute_types:
+ raise PLCInvalidArgument, "No such slice attribute type"
+ attribute_type = attribute_types[0]
+
+ attribute_type.delete()
+ self.event_objects = {'AttributeType': [attribute_type['attribute_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class DeleteSliceFromNodes(Method):
+ """
+ Deletes the specified slice from the specified nodes. If the slice is
+ not associated with a node, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ [Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name, node_id_or_hostname_list):
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ # Remove slice from all nodes found
+
+ # Get specified nodes
+ nodes = Nodes(self.api, node_id_or_hostname_list)
+ for node in nodes:
+ if slice['peer_id'] is not None and node['peer_id'] is not None:
+ raise PLCPermissionDenied, "Not allowed to remove peer slice from peer node"
+ if slice['slice_id'] in node['slice_ids']:
+ slice.remove_node(node, commit = False)
+
+ slice.sync()
+
+ self.event_objects = {'Node': [node['node_id'] for node in nodes],
+ 'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class DeleteSliceFromNodesWhitelist(Method):
+ """
+ Deletes the specified slice from the whitelist on the specified nodes. Nodes may be
+ either local or foreign nodes.
+
+ If the slice is already associated with a node, no errors are
+ returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ [Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name, node_id_or_hostname_list):
+ # Get slice information
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ # Get specified nodes, add them to the slice
+ nodes = Nodes(self.api, node_id_or_hostname_list)
+ for node in nodes:
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "%s not a local node" % node['hostname']
+ if slice['slice_id'] in node['slice_ids_whitelist']:
+ slice.delete_from_node_whitelist(node, commit = False)
+
+ slice.sync()
+
+ self.event_objects = {'Node': [node['node_id'] for node in nodes],
+ 'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
+from PLC.Auth import Auth
+
+class DeleteSliceInstantiation(Method):
+ """
+ Deletes a slice instantiation state.
+
+ WARNING: This will cause the deletion of all slices of this instantiation.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ SliceInstantiation.fields['instantiation']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+
+ def call(self, auth, instantiation):
+ slice_instantiations = SliceInstantiations(self.api, [instantiation])
+ if not slice_instantiations:
+ raise PLCInvalidArgument, "No such slice instantiation state"
+ slice_instantiation = slice_instantiations[0]
+
+ slice_instantiation.delete()
+
+ return 1
--- /dev/null
+import random
+import base64
+
+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 Auth
+
+class GenerateNodeConfFile(Method):
+ """
+ Creates a new node configuration file if all network settings are
+ present. This function will generate a new node key for the
+ specified node, effectively invalidating any old configuration
+ files.
+
+ Non-admins can only generate files for nodes at their sites.
+
+ Returns the contents of the file if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ Parameter(bool, "True if you want to regenerate node key")
+ ]
+
+ returns = Parameter(str, "Node configuration file")
+
+ def call(self, auth, node_id_or_hostname, regenerate_node_key = True):
+ # Get node information
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # 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 generate a configuration file for that node"
+
+ # Get node networks for this node
+ primary = None
+ nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids'])
+ for nodenetwork in nodenetworks:
+ if nodenetwork['is_primary']:
+ primary = nodenetwork
+ break
+ if primary is None:
+ raise PLCInvalidArgument, "No primary network configured"
+
+ # Split hostname into host and domain parts
+ parts = node['hostname'].split(".", 1)
+ if len(parts) < 2:
+ raise PLCInvalidArgument, "Node hostname is invalid"
+ host = parts[0]
+ domain = parts[1]
+
+ if regenerate_node_key:
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ node['key'] = base64.b64encode("".join(map(chr, bytes)))
+ # XXX Boot Manager cannot handle = in the key
+ node['key'] = node['key'].replace("=", "")
+ # Save it
+ node.sync()
+
+ # Generate node configuration file suitable for BootCD
+ file = ""
+
+ file += 'NODE_ID="%d"\n' % node['node_id']
+ file += 'NODE_KEY="%s"\n' % node['key']
+
+ if primary['mac']:
+ file += 'NET_DEVICE="%s"\n' % primary['mac'].lower()
+
+ file += 'IP_METHOD="%s"\n' % primary['method']
+
+ if primary['method'] == 'static':
+ file += 'IP_ADDRESS="%s"\n' % primary['ip']
+ file += 'IP_GATEWAY="%s"\n' % primary['gateway']
+ file += 'IP_NETMASK="%s"\n' % primary['netmask']
+ file += 'IP_NETADDR="%s"\n' % primary['network']
+ file += 'IP_BROADCASTADDR="%s"\n' % primary['broadcast']
+ file += 'IP_DNS1="%s"\n' % primary['dns1']
+ file += 'IP_DNS2="%s"\n' % (primary['dns2'] or "")
+
+ file += 'HOST_NAME="%s"\n' % host
+ file += 'DOMAIN_NAME="%s"\n' % domain
+
+ for nodenetwork in nodenetworks:
+ if nodenetwork['method'] == 'ipmi':
+ file += 'IPMI_ADDRESS="%s"\n' % nodenetwork['ip']
+ if nodenetwork['mac']:
+ file += 'IPMI_MAC="%s"\n' % nodenetwork['mac'].lower()
+ break
+
+ return file
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Auth import Auth
+
+class GetAddressTypes(Method):
+ """
+ Returns an array of structs containing details about address
+ types. If address_type_filter is specified and is an array of
+ address type identifiers, or a struct of address type attributes,
+ only address types matching the filter will be returned. If
+ return_fields is specified, only the specified details will be
+ returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(AddressType.fields['address_type_id'],
+ AddressType.fields['name'])],
+ Filter(AddressType.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [AddressType.fields]
+
+
+ def call(self, auth, address_type_filter = None, return_fields = None):
+ return AddressTypes(self.api, address_type_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+
+class GetAddresses(Method):
+ """
+ Returns an array of structs containing details about addresses. If
+ address_filter is specified and is an array of address
+ identifiers, or a struct of address attributes, only addresses
+ matching the filter will be returned. If return_fields is
+ specified, only the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Address.fields['address_id']],
+ Filter(Address.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Address.fields]
+
+
+ def call(self, auth, address_filter = None, return_fields = None):
+ return Addresses(self.api, address_filter, return_fields)
--- /dev/null
+# $Id: GetBootMedium.py 9562 2008-06-13 14:00:10Z thierry $
+import random
+import base64
+import os
+import os.path
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+# could not define this in the class..
+boot_medium_actions = [ 'node-preview',
+ 'node-floppy',
+ 'node-iso',
+ 'node-usb',
+ 'generic-iso',
+ 'generic-usb',
+ ]
+
+# compute a new key
+# xxx used by GetDummyBoxMedium
+def compute_key():
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ key = base64.b64encode("".join(map(chr, bytes)))
+ # Boot Manager cannot handle = in the key
+ # XXX this sounds wrong, as it might prevent proper decoding
+ key = key.replace("=", "")
+ return key
+
+class GetBootMedium(Method):
+ """
+ This method is a redesign based on former, supposedly dedicated,
+ AdmGenerateNodeConfFile
+
+ As compared with its ancestor, this method provides a much more detailed
+ detailed interface, that allows to
+ (*) either just preview the node config file -- in which case
+ the node key is NOT recomputed, and NOT provided in the output
+ (*) or regenerate the node config file for storage on a floppy
+ that is, exactly what the ancestor method used todo,
+ including renewing the node's key
+ (*) or regenerate the config file and bundle it inside an ISO or USB image
+ (*) or just provide the generic ISO or USB boot images
+ in which case of course the node_id_or_hostname parameter is not used
+
+ action is expected among the following string constants
+ (*) node-preview
+ (*) node-floppy
+ (*) node-iso
+ (*) node-usb
+ (*) generic-iso
+ (*) generic-usb
+
+ Apart for the preview mode, this method generates a new node key for the
+ specified node, effectively invalidating any old boot medium.
+
+ In addition, two return mechanisms are supported.
+ (*) The default behaviour is that the file's content is returned as a
+ base64-encoded string. This is how the ancestor method used to work.
+ To use this method, pass an empty string as the file parameter.
+
+ (*) Or, for efficiency -- this makes sense only when the API is used
+ by the web pages that run on the same host -- the caller may provide
+ a filename, in which case the resulting file is stored in that location instead.
+ The filename argument can use the following markers, that are expanded
+ within the method
+ - %d : default root dir (some builtin dedicated area under /var/tmp/)
+ Using this is recommended, and enforced for non-admin users
+ - %n : the node's name when this makes sense, or a mktemp-like name when
+ generic media is requested
+ - %s : a file suffix appropriate in the context (.txt, .iso or the like)
+ - %v : the bootcd version string (e.g. 4.0)
+ - %p : the PLC name
+ - %f : the nodefamily
+ - %a : arch
+ With the file-based return mechanism, the method returns the full pathname
+ of the result file;
+ ** WARNING **
+ It is the caller's responsability to remove this file after use.
+
+ Options: an optional array of keywords.
+ options are not supported for generic images
+ Currently supported are
+ - 'partition' - for USB actions only
+ - 'cramfs'
+ - 'serial' or 'serial:<console_spec>'
+ console_spec (or 'default') is passed as-is to bootcd/build.sh
+ it is expected to be a colon separated string denoting
+ tty - baudrate - parity - bits
+ e.g. ttyS0:115200:n:8
+
+ Security:
+ - Non-admins can only generate files for nodes at their sites.
+ - Non-admins, when they provide a filename, *must* specify it in the %d area
+
+ Housekeeping:
+ Whenever needed, the method stores intermediate files in a
+ private area, typically not located under the web server's
+ accessible area, and are cleaned up by the method.
+
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ Parameter (str, "Action mode, expected in " + "|".join(boot_medium_actions)),
+ Parameter (str, "Empty string for verbatim result, resulting file full path otherwise"),
+ Parameter ([str], "Options"),
+ ]
+
+ returns = Parameter(str, "Node boot medium, either inlined, or filename, depending on the filename parameter")
+
+ BOOTCDDIR = "/usr/share/bootcd-@NODEFAMILY@/"
+ BOOTCDBUILD = "/usr/share/bootcd-@NODEFAMILY@/build.sh"
+ GENERICDIR = "/var/www/html/download-@NODEFAMILY@/"
+ WORKDIR = "/var/tmp/bootmedium"
+ DEBUG = False
+ # uncomment this to preserve temporary area and bootcustom logs
+ #DEBUG = True
+
+ ### returns (host, domain) :
+ # 'host' : host part of the hostname
+ # 'domain' : domain part of the hostname
+ def split_hostname (self, node):
+ # Split hostname into host and domain parts
+ parts = node['hostname'].split(".", 1)
+ if len(parts) < 2:
+ raise PLCInvalidArgument, "Node hostname %s is invalid"%node['hostname']
+ return parts
+
+ # plnode.txt content
+ def floppy_contents (self, node, renew_key):
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # 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 generate a configuration file for %s"%node['hostname']
+
+ # Get node networks for this node
+ primary = None
+ nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids'])
+ for nodenetwork in nodenetworks:
+ if nodenetwork['is_primary']:
+ primary = nodenetwork
+ break
+ if primary is None:
+ raise PLCInvalidArgument, "No primary network configured on %s"%node['hostname']
+
+ ( host, domain ) = self.split_hostname (node)
+
+ if renew_key:
+ node['key'] = compute_key()
+ # Save it
+ node.sync()
+
+ # Generate node configuration file suitable for BootCD
+ file = ""
+
+ if renew_key:
+ file += 'NODE_ID="%d"\n' % node['node_id']
+ file += 'NODE_KEY="%s"\n' % node['key']
+
+ if primary['mac']:
+ file += 'NET_DEVICE="%s"\n' % primary['mac'].lower()
+
+ file += 'IP_METHOD="%s"\n' % primary['method']
+
+ if primary['method'] == 'static':
+ file += 'IP_ADDRESS="%s"\n' % primary['ip']
+ file += 'IP_GATEWAY="%s"\n' % primary['gateway']
+ file += 'IP_NETMASK="%s"\n' % primary['netmask']
+ file += 'IP_NETADDR="%s"\n' % primary['network']
+ file += 'IP_BROADCASTADDR="%s"\n' % primary['broadcast']
+ file += 'IP_DNS1="%s"\n' % primary['dns1']
+ file += 'IP_DNS2="%s"\n' % (primary['dns2'] or "")
+
+ file += 'HOST_NAME="%s"\n' % host
+ file += 'DOMAIN_NAME="%s"\n' % domain
+
+ # define various nodenetwork settings attached to the primary nodenetwork
+ settings = NodeNetworkSettings (self.api, {'nodenetwork_id':nodenetwork['nodenetwork_id']})
+
+ categories = set()
+ for setting in settings:
+ if setting['category'] is not None:
+ categories.add(setting['category'])
+
+ for category in categories:
+ category_settings = NodeNetworkSettings(self.api,{'nodenetwork_id':nodenetwork['nodenetwork_id'],
+ 'category':category})
+ if category_settings:
+ file += '### Category : %s\n'%category
+ for setting in category_settings:
+ file += '%s_%s="%s"\n'%(category.upper(),setting['name'].upper(),setting['value'])
+
+ for nodenetwork in nodenetworks:
+ if nodenetwork['method'] == 'ipmi':
+ file += 'IPMI_ADDRESS="%s"\n' % nodenetwork['ip']
+ if nodenetwork['mac']:
+ file += 'IPMI_MAC="%s"\n' % nodenetwork['mac'].lower()
+ break
+
+ return file
+
+ # see also InstallBootstrapFS in bootmanager that does similar things
+ def get_nodefamily (self, node):
+ try:
+ (pldistro,arch) = file("/etc/planetlab/nodefamily").read().strip().split("-")
+ except:
+ (pldistro,arch) = ("planetlab","i386")
+
+ if not node:
+ return (pldistro,arch)
+
+ known_archs = [ 'i386', 'x86_64' ]
+ nodegroupnames = [ ng['name'] for ng in NodeGroups (self.api, node['nodegroup_ids'],['name'])]
+ # (1) if groupname == arch, nodefamily becomes pldistro-groupname
+ # (2) else if groupname looks like pldistro-arch, it is taken as a nodefamily
+ # (3) otherwise groupname is taken as an extension
+ for nodegroupname in nodegroupnames:
+ if nodegroupname in known_archs:
+ arch = nodegroupname
+ else:
+ for known_arch in known_archs:
+ try:
+ (api_pldistro,api_arch)=nodegroupname.split("-")
+ # sanity check
+ if api_arch != known_arch: raise Exception,"mismatch"
+ (pldistro,arch) = (api_pldistro, api_arch)
+ break
+ except:
+ pass
+ return (pldistro,arch)
+
+ def bootcd_version (self):
+ try:
+ return file(self.BOOTCDDIR + "/build/version.txt").readline().strip()
+ except:
+ raise Exception,"Unknown boot cd version - probably wrong bootcd dir : %s"%self.BOOTCDDIR
+
+ def cleantrash (self):
+ for file in self.trash:
+ if self.DEBUG:
+ print 'DEBUG -- preserving',file
+ else:
+ os.unlink(file)
+
+ def call(self, auth, node_id_or_hostname, action, filename, options = []):
+
+ self.trash=[]
+ ### check action
+ if action not in boot_medium_actions:
+ raise PLCInvalidArgument, "Unknown action %s"%action
+
+ ### compute file suffix and type
+ if action.find("-iso") >= 0 :
+ suffix=".iso"
+ type = "iso"
+ elif action.find("-usb") >= 0:
+ suffix=".usb"
+ type = "usb"
+ else:
+ suffix=".txt"
+ type = "txt"
+
+ # handle / caconicalize options
+ if type == "txt":
+ if options:
+ raise PLCInvalidArgument, "Options are not supported for node configs"
+ else:
+ # create a dict for build.sh
+ optdict={}
+ for option in options:
+ if option == "cramfs":
+ optdict['cramfs']=True
+ elif option == 'partition':
+ if type != "usb":
+ raise PLCInvalidArgument, "option 'partition' is for USB images only"
+ else:
+ type="usb_partition"
+ elif option == "serial":
+ optdict['serial']='default'
+ elif option.find("serial:") == 0:
+ optdict['serial']=option.replace("serial:","")
+ else:
+ raise PLCInvalidArgument, "unknown option %s"%option
+
+ ### check node if needed
+ if action.find("node-") == 0:
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node %r"%node_id_or_hostname
+ node = nodes[0]
+ nodename = node['hostname']
+
+ else:
+ node = None
+ # compute a 8 bytes random number
+ tempbytes = random.sample (xrange(0,256), 8);
+ def hexa2 (c): return chr((c>>4)+65) + chr ((c&16)+65)
+ nodename = "".join(map(hexa2,tempbytes))
+
+ # get nodefamily
+ (pldistro,arch) = self.get_nodefamily(node)
+ self.nodefamily="%s-%s"%(pldistro,arch)
+ # apply on globals
+ for attr in [ "BOOTCDDIR", "BOOTCDBUILD", "GENERICDIR" ]:
+ setattr(self,attr,getattr(self,attr).replace("@NODEFAMILY@",self.nodefamily))
+
+ ### handle filename
+ # allow to set filename to None or any other empty value
+ if not filename: filename=''
+ filename = filename.replace ("%d",self.WORKDIR)
+ filename = filename.replace ("%n",nodename)
+ filename = filename.replace ("%s",suffix)
+ filename = filename.replace ("%p",self.api.config.PLC_NAME)
+ # let's be cautious
+ try: filename = filename.replace ("%f", self.nodefamily)
+ except: pass
+ try: filename = filename.replace ("%a", arch)
+ except: pass
+ try: filename = filename.replace ("%v",self.bootcd_version())
+ except: pass
+
+ ### Check filename location
+ if filename != '':
+ if 'admin' not in self.caller['roles']:
+ if ( filename.index(self.WORKDIR) != 0):
+ raise PLCInvalidArgument, "File %s not under %s"%(filename,self.WORKDIR)
+
+ ### output should not exist (concurrent runs ..)
+ if os.path.exists(filename):
+ raise PLCInvalidArgument, "Resulting file %s already exists"%filename
+
+ ### we can now safely create the file,
+ ### either we are admin or under a controlled location
+ filedir=os.path.dirname(filename)
+ # dirname does not return "." for a local filename like its shell counterpart
+ if filedir:
+ if not os.path.exists(filedir):
+ try:
+ os.makedirs (filedir,0777)
+ except:
+ raise PLCPermissionDenied, "Could not create dir %s"%filedir
+
+
+ ### generic media
+ if action == 'generic-iso' or action == 'generic-usb':
+ if options:
+ raise PLCInvalidArgument, "Options are not supported for generic images"
+ # this raises an exception if bootcd is missing
+ version = self.bootcd_version()
+ generic_name = "%s-BootCD-%s%s"%(self.api.config.PLC_NAME,
+ version,
+ suffix)
+ generic_path = "%s/%s" % (self.GENERICDIR,generic_name)
+
+ if filename:
+ ret=os.system ("cp %s %s"%(generic_path,filename))
+ if ret==0:
+ return filename
+ else:
+ raise PLCPermissionDenied, "Could not copy %s into"%(generic_path,filename)
+ else:
+ ### return the generic medium content as-is, just base64 encoded
+ return base64.b64encode(file(generic_path).read())
+
+ ### config file preview or regenerated
+ if action == 'node-preview' or action == 'node-floppy':
+ renew_key = (action == 'node-floppy')
+ floppy = self.floppy_contents (node,renew_key)
+ if filename:
+ try:
+ file(filename,'w').write(floppy)
+ except:
+ raise PLCPermissionDenied, "Could not write into %s"%filename
+ return filename
+ else:
+ return floppy
+
+ ### we're left with node-iso and node-usb
+ if action == 'node-iso' or action == 'node-usb':
+
+ ### check we've got required material
+ version = self.bootcd_version()
+
+ if not os.path.isfile(self.BOOTCDBUILD):
+ raise PLCAPIError, "Cannot locate bootcd/build.sh script %s"%self.BOOTCDBUILD
+
+ # create the workdir if needed
+ if not os.path.isdir(self.WORKDIR):
+ try:
+ os.makedirs(self.WORKDIR,0777)
+ os.chmod(self.WORKDIR,0777)
+ except:
+ raise PLCPermissionDenied, "Could not create dir %s"%self.WORKDIR
+
+ try:
+ # generate floppy config
+ floppy_text = self.floppy_contents(node,True)
+ # store it
+ floppy_file = "%s/%s.txt"%(self.WORKDIR,nodename)
+ try:
+ file(floppy_file,"w").write(floppy_text)
+ except:
+ raise PLCPermissionDenied, "Could not write into %s"%floppy_file
+
+ self.trash.append(floppy_file)
+
+ node_image = "%s/%s%s"%(self.WORKDIR,nodename,suffix)
+
+ # make build's arguments
+ serial_arg=""
+ if "cramfs" in optdict: type += "_cramfs"
+ if "serial" in optdict: serial_arg = "-s %s"%optdict['serial']
+ log_file="%s.log"%node_image
+ # invoke build.sh
+ build_command = '%s -f "%s" -o "%s" -t "%s" %s &> %s' % (self.BOOTCDBUILD,
+ floppy_file,
+ node_image,
+ type,
+ serial_arg,
+ log_file)
+ if self.DEBUG:
+ print 'build command:',build_command
+ ret=os.system(build_command)
+ if ret != 0:
+ raise PLCAPIError,"bootcd/build.sh failed\n%s\n%s"%(
+ build_command,file(log_file).read())
+
+ self.trash.append(log_file)
+ if not os.path.isfile (node_image):
+ raise PLCAPIError,"Unexpected location of build.sh output - %s"%node_image
+
+ # handle result
+ if filename:
+ ret=os.system("mv %s %s"%(node_image,filename))
+ if ret != 0:
+ self.trash.append(node_image)
+ self.cleantrash()
+ raise PLCAPIError, "Could not move node image %s into %s"%(node_image,filename)
+ self.cleantrash()
+ return filename
+ else:
+ result = file(node_image).read()
+ self.trash.append(node_image)
+ self.cleantrash()
+ return base64.b64encode(result)
+ except:
+ self.cleantrash()
+ raise
+
+ # we're done here, or we missed something
+ raise PLCAPIError,'Unhandled action %s'%action
+
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.BootStates import BootState, BootStates
+from PLC.Auth import Auth
+
+class GetBootStates(Method):
+ """
+ Returns an array of all valid node boot states.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [BootState.fields['boot_state']]
+
+
+ def call(self, auth):
+ return [boot_state['boot_state'] for boot_state in BootStates(self.api)]
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Auth import Auth
+
+class GetConfFiles(Method):
+ """
+ Returns an array of structs containing details about configuration
+ files. If conf_file_filter is specified and is an array of
+ configuration file identifiers, or a struct of configuration file
+ attributes, only configuration files matching the filter will be
+ returned. If return_fields is specified, only the specified
+ details will be returned.
+ """
+
+ roles = ['admin', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([ConfFile.fields['conf_file_id']],
+ Filter(ConfFile.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [ConfFile.fields]
+
+
+ def call(self, auth, conf_file_filter = None, return_fields = None):
+ return ConfFiles(self.api, conf_file_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.EventObjects import EventObject, EventObjects
+from PLC.Auth import Auth
+
+class GetEventObjects(Method):
+ """
+ Returns an array of structs containing details about events and
+ faults. If event_filter is specified and is an array of event
+ identifiers, or a struct of event attributes, only events matching
+ the filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Filter(EventObject.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [EventObject.fields]
+
+ def call(self, auth, event_filter = None, return_fields = None):
+ return EventObjects(self.api, event_filter, return_fields)
+
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Events import Event, Events
+from PLC.Auth import Auth
+
+class GetEvents(Method):
+ """
+ Returns an array of structs containing details about events and
+ faults. If event_filter is specified and is an array of event
+ identifiers, or a struct of event attributes, only events matching
+ the filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed([Event.fields['event_id']],
+ Filter(Event.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Event.fields]
+
+ def call(self, auth, event_filter = None, return_fields = None):
+ return Events(self.api, event_filter, return_fields)
+
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class GetInitScripts(Method):
+ """
+ Returns an array of structs containing details about initscripts.
+ If initscript_filter is specified and is an array of initscript
+ identifiers, or a struct of initscript attributes, only initscripts
+ matching the filter will be returned. If return_fields is specified,
+ only the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(InitScript.fields['initscript_id'],
+ InitScript.fields['name'])],
+ Filter(InitScript.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [InitScript.fields]
+
+
+ def call(self, auth, initscript_filter = None, return_fields = None):
+ return InitScripts(self.api, initscript_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.KeyTypes import KeyType, KeyTypes
+from PLC.Auth import Auth
+
+class GetKeyTypes(Method):
+ """
+ Returns an array of all valid key types.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [KeyType.fields['key_type']]
+
+
+ def call(self, auth):
+ return [key_type['key_type'] for key_type in KeyTypes(self.api)]
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+class GetKeys(Method):
+ """
+ Returns an array of structs containing details about keys. If
+ key_filter is specified and is an array of key identifiers, or a
+ struct of key attributes, only keys matching the filter will be
+ returned. If return_fields is specified, only the specified
+ details will be returned.
+
+ Admin may query all keys. Non-admins may only query their own
+ keys.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Key.fields['key_id'])],
+ Filter(Key.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Key.fields]
+
+
+ def call(self, auth, key_filter = None, return_fields = None):
+ keys = Keys(self.api, key_filter, return_fields)
+
+ # If we are not admin, make sure to only return our own keys
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ keys = filter(lambda key: key['key_id'] in self.caller['key_ids'], keys)
+
+ return keys
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Messages import Message, Messages
+from PLC.Auth import Auth
+
+class GetMessages(Method):
+ """
+ Returns an array of structs containing details about message
+ templates. If message template_filter is specified and is an array
+ of message template identifiers, or a struct of message template
+ attributes, only message templates matching the filter will be
+ returned. If return_fields is specified, only the specified
+ details will be returned.
+ """
+
+ roles = ['admin', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Message.fields['message_id']],
+ Filter(Message.fields)),
+ Parameter([str], "List of fields to return", nullok = True),
+ ]
+
+ returns = [Message.fields]
+
+
+ def call(self, auth, message_filter = None, return_fields = None):
+ return Messages(self.api, message_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkMethods import NetworkMethod, NetworkMethods
+from PLC.Auth import Auth
+
+class GetNetworkMethods(Method):
+ """
+ Returns a list of all valid network methods.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [NetworkMethod.fields['method']]
+
+
+ def call(self, auth):
+ return [network_method['method'] for network_method in NetworkMethods(self.api)]
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NetworkTypes import NetworkType, NetworkTypes
+from PLC.Auth import Auth
+
+class GetNetworkTypes(Method):
+ """
+ Returns a list of all valid network types.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [NetworkType.fields['type']]
+
+
+ def call(self, auth):
+ return [network_type['type'] for network_type in NetworkTypes(self.api)]
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.NodeGroups import NodeGroup, NodeGroups
+
+class GetNodeGroups(Method):
+ """
+ Returns an array of structs containing details about node groups.
+ If nodegroup_filter is specified and is an array of node group
+ identifiers or names, or a struct of node group attributes, only
+ node groups matching the filter will be returned. If return_fields
+ is specified, only the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name'])],
+ Filter(NodeGroup.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [NodeGroup.fields]
+
+ def call(self, auth, nodegroup_filter = None, return_fields = None):
+ return NodeGroups(self.api, nodegroup_filter, return_fields)
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+
+class GetNodeNetworkSettingTypes(Method):
+ """
+ Returns an array of structs containing details about
+ nodenetwork setting types.
+
+ The usual filtering scheme applies on this method.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'],
+ NodeNetworkSettingType.fields['name'])],
+ Filter(NodeNetworkSettingType.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [NodeNetworkSettingType.fields]
+
+ def call(self, auth, nodenetwork_setting_type_filter = None, return_fields = None):
+ return NodeNetworkSettingTypes(self.api, nodenetwork_setting_type_filter, return_fields)
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings
+from PLC.Sites import Site, Sites
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+class GetNodeNetworkSettings(Method):
+ """
+ Returns an array of structs containing details about
+ nodenetworks and related settings.
+
+ If nodenetwork_setting_filter is specified and is an array of
+ nodenetwork setting identifiers, only nodenetwork settings matching
+ the filter will be returned. If return_fields is specified, only
+ the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([NodeNetworkSetting.fields['nodenetwork_setting_id']],
+ Parameter(int,"Nodenetwork setting id"),
+ Filter(NodeNetworkSetting.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [NodeNetworkSetting.fields]
+
+
+ def call(self, auth, nodenetwork_setting_filter = None, return_fields = None):
+
+ nodenetwork_settings = NodeNetworkSettings(self.api, nodenetwork_setting_filter, return_fields)
+
+ return nodenetwork_settings
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.Auth import Auth
+
+class GetNodeNetworks(Method):
+ """
+ Returns an array of structs containing details about node network
+ interfacess. If nodenetworks_filter is specified and is an array
+ of node network identifiers, or a struct of node network
+ fields and values, only node network interfaces matching the filter
+ will be returned.
+
+ If return_fields is given, only the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ Mixed([NodeNetwork.fields['nodenetwork_id']],
+ Parameter (int, "nodenetwork id"),
+ Filter(NodeNetwork.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [NodeNetwork.fields]
+
+ def call(self, auth, nodenetwork_filter = None, return_fields = None):
+ return NodeNetworks(self.api, nodenetwork_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+class GetNodes(Method):
+ """
+ Returns an array of structs containing details about nodes. If
+ node_filter is specified and is an array of node identifiers or
+ hostnames, or a struct of node attributes, only nodes matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Some fields may only be viewed by admins.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])],
+ Parameter(str,"hostname"),
+ Parameter(int,"node_id"),
+ Filter(Node.fields)),
+ Parameter([str], "List of fields to return", nullok = True),
+ ]
+
+ returns = [Node.fields]
+
+
+ def call(self, auth, node_filter = None, return_fields = None):
+
+ # Must query at least slice_ids_whitelist
+ if return_fields is not None:
+ added_fields = set(['slice_ids_whitelist', 'site_id']).difference(return_fields)
+ return_fields += added_fields
+ else:
+ added_fields =[]
+
+ # Get node information
+ nodes = Nodes(self.api, node_filter, return_fields)
+
+ # Remove admin only fields
+ if not isinstance(self.caller, Person) or \
+ 'admin' not in self.caller['roles']:
+ slice_ids = set()
+ site_ids = set()
+
+ if self.caller:
+ slice_ids.update(self.caller['slice_ids'])
+ if isinstance(self.caller, Node):
+ site_ids.update([self.caller['site_id']])
+ else:
+ site_ids.update(self.caller['site_ids'])
+
+ # if node has whitelist, only return it if users is at
+ # the same site or user has a slice on the whitelist
+ for node in nodes[:]:
+ if 'site_id' in node and \
+ site_ids.intersection([node['site_id']]):
+ continue
+ if 'slice_ids_whitelist' in node and \
+ node['slice_ids_whitelist'] and \
+ not slice_ids.intersection(node['slice_ids_whitelist']):
+ nodes.remove(node)
+
+ # remove remaining admin only fields
+ for node in nodes:
+ for field in ['boot_nonce', 'key', 'session', 'root_person_ids']:
+ if field in node:
+ del node[field]
+
+ # remove added fields if not specified
+ if added_fields:
+ for node in nodes:
+ for field in added_fields:
+ del node[field]
+
+ return nodes
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUProtocolTypes import PCUProtocolType, PCUProtocolTypes
+from PLC.Auth import Auth
+from PLC.Filter import Filter
+
+class GetPCUProtocolTypes(Method):
+ """
+ Returns an array of PCU Types.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([PCUProtocolType.fields['pcu_type_id']],
+ Filter(PCUProtocolType.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [PCUProtocolType.fields]
+
+
+ def call(self, auth, protocol_type_filter = None, return_fields = None):
+
+ #Must query at least pcu_type_id
+ if return_fields is not None and 'pcu_protocol_type_id' not in return_fields:
+ return_fields.append('pcu_protocol_type_id')
+ added_fields = ['pcu_protocol_type_id']
+ else:
+ added_fields = []
+
+ protocol_types = PCUProtocolTypes(self.api, protocol_type_filter, return_fields)
+
+ for added_field in added_fields:
+ for protocol_type in protocol_types:
+ del protocol_type[added_field]
+
+ return protocol_types
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUTypes import PCUType, PCUTypes
+from PLC.Auth import Auth
+from PLC.Filter import Filter
+
+class GetPCUTypes(Method):
+ """
+ Returns an array of PCU Types.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(PCUType.fields['pcu_type_id'],
+ PCUType.fields['model'])],
+ Parameter(str, 'model'),
+ Parameter(int, 'node_id'),
+ Filter(PCUType.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [PCUType.fields]
+
+
+ def call(self, auth, pcu_type_filter = None, return_fields = None):
+
+ #Must query at least pcu_type_id
+ if return_fields is not None:
+ added_fields = []
+ if 'pcu_type_id' not in return_fields:
+ return_fields.append('pcu_type_id')
+ added_fields.append('pcu_type_id')
+ if 'pcu_protocol_types' in return_fields and \
+ 'pcu_protocol_type_ids' not in return_fields:
+ return_fields.append('pcu_protocol_type_ids')
+ added_fields.append('pcu_protocol_type_ids')
+ else:
+ added_fields = []
+
+ pcu_types = PCUTypes(self.api, pcu_type_filter, return_fields)
+
+ # remove added fields and protocol_types
+ for added_field in added_fields:
+ for pcu_type in pcu_types:
+ del pcu_type[added_field]
+
+ return pcu_types
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+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 Auth
+
+class GetPCUs(Method):
+ """
+ Returns an array of structs containing details about power control
+ units (PCUs). If pcu_filter is specified and is an array of PCU
+ identifiers, or a struct of PCU attributes, only PCUs matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Admin may query all PCUs. Non-admins may only query the PCUs at
+ their sites.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([PCU.fields['pcu_id']],
+ Filter(PCU.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [PCU.fields]
+
+ def call(self, auth, pcu_filter = None, return_fields = None):
+ # If we are not admin
+ if not (isinstance(self.caller, Person) and 'admin' in self.caller['roles']):
+ # Return only the PCUs at our site
+ valid_pcu_ids = []
+
+ if isinstance(self.caller, Person):
+ site_ids = self.caller['site_ids']
+ elif isinstance(self.caller, Node):
+ site_ids = [self.caller['site_id']]
+
+ for site in Sites(self.api, site_ids):
+ valid_pcu_ids += site['pcu_ids']
+
+ if not valid_pcu_ids:
+ return []
+
+ if pcu_filter is None:
+ pcu_filter = valid_pcu_ids
+
+ # Must query at least slice_id (see below)
+ if return_fields is not None and 'pcu_id' not in return_fields:
+ return_fields.append('pcu_id')
+ added_fields = True
+ else:
+ added_fields = False
+
+ pcus = PCUs(self.api, pcu_filter, return_fields)
+
+ # Filter out PCUs that are not viewable
+ if not (isinstance(self.caller, Person) and 'admin' in self.caller['roles']):
+ pcus = filter(lambda pcu: pcu['pcu_id'] in valid_pcu_ids, pcus)
+
+ # Remove pcu_id if not specified
+ if added_fields:
+ for pcu in pcus:
+ if 'pcu_id' in pcu:
+ del pcu['pcu_id']
+
+ return pcus
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Id: GetPeerData.py 5574 2007-10-25 20:33:17Z thierry $
+
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Peers import Peer, Peers
+
+from PLC.Sites import Site, Sites
+from PLC.Keys import Key, Keys
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.Slices import Slice, Slices
+from PLC.SliceAttributes import SliceAttributes
+
+class GetPeerData(Method):
+ """
+ Returns lists of local objects that a peer should cache in its
+ database as foreign objects. Also returns the list of foreign
+ nodes in this database, for which the calling peer is
+ authoritative, to assist in synchronization of slivers.
+
+ See the implementation of RefreshPeer for how this data is used.
+ """
+
+ roles = ['admin', 'peer']
+
+ accepts = [Auth()]
+
+ returns = {
+ 'Sites': Parameter([dict], "List of local sites"),
+ 'Keys': Parameter([dict], "List of local keys"),
+ 'Nodes': Parameter([dict], "List of local nodes"),
+ 'Persons': Parameter([dict], "List of local users"),
+ 'Slices': Parameter([dict], "List of local slices"),
+ 'db_time': Parameter(float, "(Debug) Database fetch time"),
+ }
+
+ def call (self, auth):
+ start = time.time()
+
+ # Filter out various secrets
+ node_fields = filter(lambda field: field not in \
+ ['boot_nonce', 'key', 'session', 'root_person_ids'],
+ Node.fields)
+ nodes = Nodes(self.api, {'peer_id': None}, node_fields);
+ # filter out whitelisted nodes
+ nodes = [ n for n in nodes if not n['slice_ids_whitelist']]
+
+
+ person_fields = filter(lambda field: field not in \
+ ['password', 'verification_key', 'verification_expires'],
+ Person.fields)
+
+ # XXX Optimize to return only those Persons, Keys, and Slices
+ # necessary for slice creation on the calling peer's nodes.
+
+ # filter out special person
+ persons = Persons(self.api, {'~email':[self.api.config.PLC_API_MAINTENANCE_USER,
+ self.api.config.PLC_ROOT_USER],
+ 'peer_id': None}, person_fields)
+
+ # filter out system slices
+ system_slice_ids = SliceAttributes(self.api, {'name': 'system', 'value': '1'}).dict('slice_id')
+ slices = Slices(self.api, {'peer_id': None,
+ '~slice_id':system_slice_ids.keys()})
+
+ result = {
+ 'Sites': Sites(self.api, {'peer_id': None}),
+ 'Keys': Keys(self.api, {'peer_id': None}),
+ 'Nodes': nodes,
+ 'Persons': persons,
+ 'Slices': slices,
+ }
+
+ if isinstance(self.caller, Peer):
+ result['PeerNodes'] = Nodes(self.api, {'peer_id': self.caller['peer_id']})
+
+ result['db_time'] = time.time() - start
+
+ return result
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter
+from PLC.Auth import Auth
+
+from PLC.Peers import Peer, Peers
+
+class GetPeerName (Method):
+ """
+ Returns this peer's name, as defined in the config as PLC_NAME
+ """
+
+ roles = ['admin', 'peer', 'node']
+
+ accepts = [Auth()]
+
+ returns = Peer.fields['peername']
+
+ def call (self, auth):
+ return self.api.config.PLC_NAME
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+
+from PLC.Persons import Person
+from PLC.Peers import Peer, Peers
+
+class GetPeers (Method):
+ """
+ Returns an array of structs containing details about peers. If
+ person_filter is specified and is an array of peer identifiers or
+ peer names, or a struct of peer attributes, only peers matching
+ the filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+ """
+
+ roles = ['admin', 'node','pi','user']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Peer.fields['peer_id'],
+ Peer.fields['peername'])],
+ Filter(Peer.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Peer.fields]
+
+ def call (self, auth, peer_filter = None, return_fields = None):
+
+ peers = Peers(self.api, peer_filter, return_fields)
+
+ # Remove admin only fields
+ if not isinstance(self.caller, Person) or \
+ 'admin' not in self.caller['roles']:
+ for peer in peers:
+ for field in ['key', 'cacert']:
+ if field in peer:
+ del peer[field]
+
+ return peers
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+
+hidden_fields = ['password', 'verification_key', 'verification_expires']
+
+class GetPersons(Method):
+ """
+ Returns an array of structs containing details about users. If
+ person_filter is specified and is an array of user identifiers or
+ usernames, or a struct of user attributes, only users matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Users and techs may only retrieve details about themselves. PIs
+ may retrieve details about themselves and others at their
+ sites. Admins and nodes may retrieve details about all accounts.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Person.fields['person_id'],
+ Person.fields['email'])],
+ Parameter(str,"email"),
+ Parameter(int,"person_id"),
+ Filter(Person.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ # Filter out password field
+ return_fields = dict(filter(lambda (field, value): field not in hidden_fields,
+ Person.fields.items()))
+ returns = [return_fields]
+
+ def call(self, auth, person_filter = None, return_fields = None):
+ # If we are not admin, make sure to only return viewable accounts
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ # Get accounts that we are able to view
+ valid_person_ids = [self.caller['person_id']]
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_person_ids += site['person_ids']
+
+ if not valid_person_ids:
+ return []
+
+ if person_filter is None:
+ person_filter = valid_person_ids
+
+ # Filter out password field
+ if return_fields:
+ return_fields = filter(lambda field: field not in hidden_fields,
+ return_fields)
+ else:
+ return_fields = self.return_fields.keys()
+
+ # Must query at least person_id, site_ids, and role_ids (see
+ # Person.can_view() and below).
+ if return_fields is not None:
+ added_fields = set(['person_id', 'site_ids', 'role_ids']).difference(return_fields)
+ return_fields += added_fields
+ else:
+ added_fields = []
+
+ persons = Persons(self.api, person_filter, return_fields)
+
+ # Filter out accounts that are not viewable
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ persons = filter(self.caller.can_view, persons)
+
+ # Remove added fields if not specified
+ if added_fields:
+ for person in persons:
+ for field in added_fields:
+ if field in person:
+ del person[field]
+
+ return persons
--- /dev/null
+from PLC.Method import Method
+from PLC.Auth import Auth
+from PLC.Faults import *
+
+import re
+
+comment_regexp = '\A\s*#.|\A\s*\Z|\Axxxxx'
+
+regexps = { 'build' : '\A[bB]uild\s+(?P<key>[^:]+)\s*:\s*(?P<value>.*)\Z',
+ 'tags' : '\A(?P<key>[^:]+)\s*:=\s*(?P<value>.*)\Z',
+ 'rpms' : '\A(?P<key>[^:]+)\s*::\s*(?P<value>.*)\Z',
+}
+
+class GetPlcRelease(Method):
+ """
+ Returns various information about the current myplc installation.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ ]
+
+ # for now only return /etc/myplc-release verbatim
+ returns = { 'build' : 'information about the build',
+ 'tags' : 'describes the codebase location and tags used for building',
+ 'rpms' : 'details the rpm installed in the myplc chroot jail' }
+
+ def call(self, auth):
+
+ comment_matcher = re.compile(comment_regexp)
+
+ matchers = {}
+ result = {}
+ for field in regexps.keys():
+ matchers[field] = re.compile(regexps[field])
+ result[field]={}
+
+ try:
+ release = open('/etc/myplc-release')
+ for line in release.readlines():
+ line=line.strip()
+ if comment_matcher.match(line):
+ continue
+ for field in regexps.keys():
+ m=matchers[field].match(line)
+ if m:
+ (key,value)=m.groups(['key','value'])
+ result[field][key]=value
+ break
+ else:
+ if not result.has_key('unexpected'):
+ result['unexpected']=""
+ result['unexpected'] += (line+"\n")
+ except:
+ raise PLCNotImplemented, 'Cannot open /etc/myplc-release'
+ return result
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Roles import Role, Roles
+from PLC.Auth import Auth
+
+class GetRoles(Method):
+ """
+ Get an array of structs containing details about all roles.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [Role.fields]
+
+ def call(self, auth):
+ return Roles(self.api)
--- /dev/null
+import time
+
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Sessions import Session, Sessions
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+
+class GetSession(Method):
+ """
+ Returns a new session key if a user or node authenticated
+ successfully, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+ accepts = [Auth()]
+ returns = Session.fields['session_id']
+
+
+ def call(self, auth):
+ # Authenticated with a session key, just return it
+ if auth.has_key('session'):
+ return auth['session']
+
+ session = Session(self.api)
+
+ if isinstance(self.caller, Person):
+ # XXX Make this configurable
+ session['expires'] = int(time.time()) + (24 * 60 * 60)
+
+ session.sync(commit = False)
+
+ if isinstance(self.caller, Node):
+ session.add_node(self.caller, commit = True)
+ elif isinstance(self.caller, Person):
+ session.add_person(self.caller, commit = True)
+
+ return session['session_id']
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Sessions import Session, Sessions
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+class GetSessions(Method):
+ """
+ Returns an array of structs containing details about users sessions. If
+ session_filter is specified and is an array of user identifiers or
+ session_keys, or a struct of session attributes, only sessions matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Session.fields['person_id'],
+ Session.fields['session_id'])],
+ Filter(Session.fields))
+ ]
+
+ returns = [Session.fields]
+
+ def call(self, auth, session_filter = None):
+
+ sessions = Sessions(self.api, session_filter)
+
+ return sessions
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+
+class GetSites(Method):
+ """
+ Returns an array of structs containing details about sites. If
+ site_filter is specified and is an array of site identifiers or
+ hostnames, or a struct of site attributes, only sites matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])],
+ Parameter(str,"login_base"),
+ Parameter(int,"site_id"),
+ Filter(Site.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Site.fields]
+
+ def call(self, auth, site_filter = None, return_fields = None):
+ return Sites(self.api, site_filter, return_fields)
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+
+class GetSliceAttributeTypes(Method):
+ """
+ Returns an array of structs containing details about slice
+ attribute types. If attribute_type_filter is specified and
+ is an array of slice attribute type identifiers, or a
+ struct of slice attribute type attributes, only slice attribute
+ types matching the filter will be returned. If return_fields is
+ specified, only the specified details will be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(SliceAttributeType.fields['attribute_type_id'],
+ SliceAttributeType.fields['name'])],
+ Filter(SliceAttributeType.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [SliceAttributeType.fields]
+
+ def call(self, auth, attribute_type_filter = None, return_fields = None):
+ return SliceAttributeTypes(self.api, attribute_type_filter, return_fields)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+
+class GetSliceAttributes(Method):
+ """
+ Returns an array of structs containing details about slice and
+ sliver attributes. An attribute is a sliver attribute if the
+ node_id field is set. If slice_attribute_filter is specified and
+ is an array of slice attribute identifiers, or a struct of slice
+ attribute attributes, only slice attributes matching the filter
+ will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Users may only query attributes of slices or slivers of which they
+ are members. PIs may only query attributes of slices or slivers at
+ their sites, or of which they are members. Admins may query
+ attributes of any slice or sliver.
+ """
+
+ roles = ['admin', 'pi', 'user', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([SliceAttribute.fields['slice_attribute_id']],
+ Filter(SliceAttribute.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [SliceAttribute.fields]
+
+
+ def call(self, auth, slice_attribute_filter = None, return_fields = None):
+ # If we are not admin, make sure to only return our own slice
+ # and sliver attributes.
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ # Get slices that we are able to view
+ valid_slice_ids = self.caller['slice_ids']
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_slice_ids += site['slice_ids']
+
+ if not valid_slice_ids:
+ return []
+
+ # Get slice attributes that we are able to view
+ valid_slice_attribute_ids = []
+ slices = Slices(self.api, valid_slice_ids)
+ for slice in slices:
+ valid_slice_attribute_ids += slice['slice_attribute_ids']
+
+ if not valid_slice_attribute_ids:
+ return []
+
+ if slice_attribute_filter is None:
+ slice_attribute_filter = valid_slice_attribute_ids
+
+ # Must query at least slice_attribute_id (see below)
+ if return_fields is not None and 'slice_attribute_id' not in return_fields:
+ return_fields.append('slice_attribute_id')
+ added_fields = True
+ else:
+ added_fields = False
+
+ slice_attributes = SliceAttributes(self.api, slice_attribute_filter, return_fields)
+
+ # Filter out slice attributes that are not viewable
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ slice_attributes = filter(lambda slice_attribute: \
+ slice_attribute['slice_attribute_id'] in valid_slice_attribute_ids,
+ slice_attributes)
+
+ # Remove slice_attribute_id if not specified
+ if added_fields:
+ for slice_attribute in slice_attributes:
+ if 'slice_attribute_id' in slice_attribute:
+ del slice_attribute['slice_attribute_id']
+
+ return slice_attributes
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
+from PLC.Auth import Auth
+
+class GetSliceInstantiations(Method):
+ """
+ Returns an array of all valid slice instantiation states.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth()
+ ]
+
+ returns = [SliceInstantiation.fields['instantiation']]
+
+ def call(self, auth):
+ return [slice_instantiation['instantiation'] for slice_instantiation in SliceInstantiations(self.api)]
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Slices import Slice, Slices
+from PLC.Keys import Key, Keys
+
+class GetSliceKeys(Method):
+ """
+ Returns an array of structs containing public key info for users in
+ the specified slices. If slice_filter is specified and is an array
+ of slice identifiers or slice names, or a struct of slice
+ attributes, only slices matching the filter will be returned. If
+ return_fields is specified, only the specified details will be
+ returned.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins and nodes may query
+ any slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Slice.fields['slice_id'],
+ Slice.fields['name'])],
+ Filter(Slice.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [
+ {
+ 'slice_id': Slice.fields['slice_id'],
+ 'name': Slice.fields['name'],
+ 'person_id': Person.fields['person_id'],
+ 'email': Person.fields['email'],
+ 'key': Key.fields['key']
+ }]
+
+ def call(self, auth, slice_filter = None, return_fields = None):
+ slice_fields = ['slice_id', 'name']
+ person_fields = ['person_id', 'email']
+ key_fields = ['key']
+
+ # If we are not admin, make sure to return only viewable
+ # slices.
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ # Get slices that we are able to view
+ valid_slice_ids = self.caller['slice_ids']
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_slice_ids += site['slice_ids']
+
+ if not valid_slice_ids:
+ return []
+
+ if slice_filter is None:
+ slice_filter = valid_slice_ids
+
+ if return_fields:
+ slice_return_fields = filter(lambda field: field in slice_fields, return_fields)
+ person_return_fields = filter(lambda field: field in person_fields, return_fields)
+ key_return_fields = filter(lambda field: field in key_fields, return_fields)
+ else:
+ slice_return_fields = slice_fields
+ person_return_fields = person_fields
+ key_return_fields = key_fields
+
+ # Must query at least Slice.slice_id, Slice.person_ids,
+ # and Person.person_id and Person.key_ids so we can join data correctly
+ slice_added_fields = set(['slice_id', 'person_ids']).difference(slice_return_fields)
+ slice_return_fields += slice_added_fields
+ person_added_fields = set(['person_id', 'key_ids']).difference(person_return_fields)
+ person_return_fields += person_added_fields
+ key_added_fields = set(['key_id']).difference(key_return_fields)
+ key_return_fields += key_added_fields
+
+ # Get the slices
+ all_slices = Slices(self.api, slice_filter, slice_return_fields).dict('slice_id')
+ slice_ids = all_slices.keys()
+ slices = all_slices.values()
+
+ # Filter out slices that are not viewable
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ slices = filter(lambda slice: slice['slice_id'] in valid_slice_ids, slices)
+
+ # Get the persons
+ person_ids = set()
+ for slice in slices:
+ person_ids.update(slice['person_ids'])
+
+ all_persons = Persons(self.api, list(person_ids), person_return_fields).dict('person_id')
+ person_ids = all_persons.keys()
+ persons = all_persons.values()
+
+ # Get the keys
+ key_ids = set()
+ for person in persons:
+ key_ids.update(person['key_ids'])
+
+ all_keys = Keys(self.api, list(key_ids), key_return_fields).dict('key_id')
+ key_ids = all_keys.keys()
+ keys = all_keys.values()
+
+ # Create slice_keys list
+ slice_keys = []
+ slice_fields = list(set(slice_return_fields).difference(slice_added_fields))
+ person_fields = list(set(person_return_fields).difference(person_added_fields))
+ key_fields = list(set(key_return_fields).difference(key_added_fields))
+
+ for slice in slices:
+ slice_key = dict.fromkeys(slice_fields + person_fields + key_fields)
+ if not slice['person_ids']:
+ continue
+ for person_id in slice['person_ids']:
+ person = all_persons[person_id]
+ if not person['key_ids']:
+ continue
+ for key_id in person['key_ids']:
+ key = all_keys[key_id]
+ slice_key.update(dict(filter(lambda (k, v): k in slice_fields, slice.items())))
+ slice_key.update(dict(filter(lambda (k, v): k in person_fields, person.items())))
+ slice_key.update(dict(filter(lambda (k, v): k in key_fields, key.items())))
+ slice_keys.append(slice_key.copy())
+
+ return slice_keys
+
--- /dev/null
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.GPG import gpg_sign, gpg_verify
+from PLC.InitScripts import InitScript, InitScripts
+
+from PLC.Methods.GetSlivers import get_slivers
+
+class GetSliceTicket(Method):
+ """
+ Returns a ticket for, or signed representation of, the specified
+ slice. Slice tickets may be used to manually instantiate or update
+ a slice on a node. Present this ticket to the local Node Manager
+ interface to redeem it.
+
+ If the slice has not been added to a node with AddSliceToNodes,
+ and the ticket is redeemed on that node, it will be deleted the
+ next time the Node Manager contacts the API.
+
+ Users may only obtain tickets for slices of which they are
+ members. PIs may obtain tickets for any of the slices at their
+ sites, or any slices of which they are members. Admins may obtain
+ tickets for any slice.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user', 'peer']
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ ]
+
+ returns = Parameter(str, 'Signed slice ticket')
+
+ def call(self, auth, slice_id_or_name):
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ # Allow peers to obtain tickets for their own slices
+ if slice['peer_id'] is not None:
+ if not isinstance(self.caller, Peer):
+ raise PLCInvalidArgument, "Not a local slice"
+ elif slice['peer_id'] != self.caller['peer_id']:
+ raise PLCInvalidArgument, "Only the authoritative peer may obtain tickets for that slice"
+
+ # Tickets are the canonicalized XML-RPC methodResponse
+ # representation of a partial GetSlivers() response, i.e.,
+
+ initscripts = InitScripts(self.api, {'enabled': True})
+
+ data = {
+ 'timestamp': int(time.time()),
+ 'initscripts': initscripts,
+ 'slivers': get_slivers(self.api, [slice['slice_id']]),
+ }
+
+ # Sign ticket
+ signed_ticket = gpg_sign((data,),
+ self.api.config.PLC_ROOT_GPG_KEY,
+ self.api.config.PLC_ROOT_GPG_KEY_PUB,
+ methodresponse = True,
+ detach_sign = False)
+
+ # Verify ticket
+ gpg_verify(signed_ticket,
+ self.api.config.PLC_ROOT_GPG_KEY_PUB)
+
+ return signed_ticket
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Slices import Slice, Slices
+
+class GetSlices(Method):
+ """
+ Returns an array of structs containing details about slices. If
+ slice_filter is specified and is an array of slice identifiers or
+ slice names, or a struct of slice attributes, only slices matching
+ the filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins and nodes may query
+ any slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ roles = ['admin', 'pi', 'user', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Slice.fields['slice_id'],
+ Slice.fields['name'])],
+ Parameter(str,"name"),
+ Parameter(int,"slice_id"),
+ Filter(Slice.fields)),
+ Parameter([str], "List of fields to return", nullok = True)
+ ]
+
+ returns = [Slice.fields]
+
+ def call(self, auth, slice_filter = None, return_fields = None):
+ # If we are not admin, make sure to return only viewable
+ # slices.
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ # Get slices that we are able to view
+ valid_slice_ids = self.caller['slice_ids']
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_slice_ids += site['slice_ids']
+
+ if not valid_slice_ids:
+ return []
+
+ if slice_filter is None:
+ slice_filter = valid_slice_ids
+
+ # Must query at least slice_id (see below)
+ if return_fields is not None and 'slice_id' not in return_fields:
+ return_fields.append('slice_id')
+ added_fields = True
+ else:
+ added_fields = False
+
+ slices = Slices(self.api, slice_filter, return_fields)
+
+ # Filter out slices that are not viewable
+ if isinstance(self.caller, Person) and \
+ 'admin' not in self.caller['roles']:
+ slices = filter(lambda slice: slice['slice_id'] in valid_slice_ids, slices)
+
+ # Remove slice_id if not specified
+ if added_fields:
+ for slice in slices:
+ if 'slice_id' in slice:
+ del slice['slice_id']
+
+ return slices
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+class GetSlicesMD5(Method):
+ """
+ Returns the current md5 hash of slices.xml file
+ (slices-0.5.xml.md5)
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+ accepts = [
+ Auth(),
+ ]
+
+ returns = Parameter(str, "MD5 hash of slices.xml")
+
+
+ def call(self, auth):
+ try:
+ file_path = '/var/www/html/xml/slices-0.5.xml.md5'
+ slices_md5 = file(file_path).readline().strip()
+ if slices_md5 <> "":
+ return slices_md5
+ raise PLCInvalidArgument, "File is empty"
+ except IOError:
+ raise PLCInvalidArgument, "No such file"
+
--- /dev/null
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Nodes import Node, Nodes
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Slices import Slice, Slices
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.InitScripts import InitScript, InitScripts
+
+def get_slivers(api, slice_filter, node = None):
+ # Get slice information
+ slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_attribute_ids'])
+
+ # Build up list of users and slice attributes
+ person_ids = set()
+ slice_attribute_ids = set()
+ for slice in slices:
+ person_ids.update(slice['person_ids'])
+ slice_attribute_ids.update(slice['slice_attribute_ids'])
+
+ # Get user information
+ all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
+
+ # Build up list of keys
+ key_ids = set()
+ for person in all_persons.values():
+ key_ids.update(person['key_ids'])
+
+ # Get user account keys
+ all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
+
+ # Get slice attributes
+ all_slice_attributes = SliceAttributes(api, slice_attribute_ids).dict()
+
+ slivers = []
+ for slice in slices:
+ keys = []
+ for person_id in slice['person_ids']:
+ if person_id in all_persons:
+ person = all_persons[person_id]
+ if not person['enabled']:
+ continue
+ for key_id in person['key_ids']:
+ if key_id in all_keys:
+ key = all_keys[key_id]
+ keys += [{'key_type': key['key_type'],
+ 'key': key['key']}]
+
+ attributes = []
+
+ # All (per-node and global) attributes for this slice
+ slice_attributes = []
+ for slice_attribute_id in slice['slice_attribute_ids']:
+ if slice_attribute_id in all_slice_attributes:
+ slice_attributes.append(all_slice_attributes[slice_attribute_id])
+
+ # Per-node sliver attributes take precedence over global
+ # slice attributes, so set them first.
+ # Then comes nodegroup slice attributes
+ # Followed by global slice attributes
+ sliver_attributes = []
+
+ if node is not None:
+ for sliver_attribute in filter(lambda a: a['node_id'] == node['node_id'], slice_attributes):
+ sliver_attributes.append(sliver_attribute['name'])
+ attributes.append({'name': sliver_attribute['name'],
+ 'value': sliver_attribute['value']})
+
+ # set nodegroup slice attributes
+ for slice_attribute in filter(lambda a: a['nodegroup_id'] in node['nodegroup_ids'], slice_attributes):
+ # Do not set any nodegroup slice attributes for
+ # which there is at least one sliver attribute
+ # already set.
+ if slice_attribute['name'] not in slice_attributes:
+ attributes.append({'name': slice_attribute['name'],
+ 'value': slice_attribute['value']})
+
+ for slice_attribute in filter(lambda a: a['node_id'] is None, slice_attributes):
+ # Do not set any global slice attributes for
+ # which there is at least one sliver attribute
+ # already set.
+ if slice_attribute['name'] not in sliver_attributes:
+ attributes.append({'name': slice_attribute['name'],
+ 'value': slice_attribute['value']})
+
+ slivers.append({
+ 'name': slice['name'],
+ 'slice_id': slice['slice_id'],
+ 'instantiation': slice['instantiation'],
+ 'expires': slice['expires'],
+ 'keys': keys,
+ 'attributes': attributes
+ })
+
+ return slivers
+
+class GetSlivers(Method):
+ """
+ Returns a struct containing information about the specified node
+ (or calling node, if called by a node and node_id_or_hostname is
+ not specified), including the current set of slivers bound to the
+ node.
+
+ All of the information returned by this call can be gathered from
+ other calls, e.g. GetNodes, GetNodeNetworks, GetSlices, etc. This
+ function exists almost solely for the benefit of Node Manager.
+ """
+
+ roles = ['admin', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ ]
+
+ returns = {
+ 'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
+ 'node_id': Node.fields['node_id'],
+ 'hostname': Node.fields['hostname'],
+ 'networks': [NodeNetwork.fields],
+ 'groups': [NodeGroup.fields['name']],
+ 'conf_files': [ConfFile.fields],
+ 'initscripts': [InitScript.fields],
+ 'slivers': [{
+ 'name': Slice.fields['name'],
+ 'slice_id': Slice.fields['slice_id'],
+ 'instantiation': Slice.fields['instantiation'],
+ 'expires': Slice.fields['expires'],
+ 'keys': [{
+ 'key_type': Key.fields['key_type'],
+ 'key': Key.fields['key']
+ }],
+ 'attributes': [{
+ 'name': SliceAttribute.fields['name'],
+ 'value': SliceAttribute.fields['value']
+ }]
+ }]
+ }
+
+ def call(self, auth, node_id_or_hostname = None):
+ timestamp = int(time.time())
+
+ # Get node
+ if node_id_or_hostname is None:
+ if isinstance(self.caller, Node):
+ node = self.caller
+ else:
+ raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
+ else:
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # Get nodenetwork information
+ networks = NodeNetworks(self.api, node['nodenetwork_ids'])
+
+ # Get node group information
+ nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('name')
+ groups = nodegroups.keys()
+
+ # Get all (enabled) configuration files
+ all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
+ conf_files = {}
+
+ # Global configuration files are the default. If multiple
+ # entries for the same global configuration file exist, it is
+ # undefined which one takes precedence.
+ for conf_file in all_conf_files.values():
+ if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
+ conf_files[conf_file['dest']] = conf_file
+
+ # Node group configuration files take precedence over global
+ # ones. If a node belongs to multiple node groups for which
+ # the same configuration file is defined, it is undefined
+ # which one takes precedence.
+ for nodegroup in nodegroups.values():
+ for conf_file_id in nodegroup['conf_file_ids']:
+ if conf_file_id in all_conf_files:
+ conf_file = all_conf_files[conf_file_id]
+ conf_files[conf_file['dest']] = conf_file
+
+ # Node configuration files take precedence over node group
+ # configuration files.
+ for conf_file_id in node['conf_file_ids']:
+ if conf_file_id in all_conf_files:
+ conf_file = all_conf_files[conf_file_id]
+ conf_files[conf_file['dest']] = conf_file
+
+ # Get all (enabled) initscripts
+ initscripts = InitScripts(self.api, {'enabled': True})
+
+ # Get system slices
+ system_slice_attributes = SliceAttributes(self.api, {'name': 'system', 'value': '1'}).dict('slice_id')
+ system_slice_ids = system_slice_attributes.keys()
+
+ # Get nm-controller slices
+ controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
+ controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
+ slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
+
+ slivers = get_slivers(self.api, slice_ids, node)
+
+ node.update_last_contact()
+
+ return {
+ 'timestamp': timestamp,
+ 'node_id': node['node_id'],
+ 'hostname': node['hostname'],
+ 'networks': networks,
+ 'groups': groups,
+ 'conf_files': conf_files.values(),
+ 'initscripts': initscripts,
+ 'slivers': slivers
+ }
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+
+class GetWhitelist(Method):
+ """
+ Returns an array of structs containing details about the specified nodes
+ whitelists. If node_filter is specified and is an array of node identifiers or
+ hostnames, or a struct of node attributes, only nodes matching the
+ filter will be returned. If return_fields is specified, only the
+ specified details will be returned.
+
+ Some fields may only be viewed by admins.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech', 'node', 'anonymous']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Node.fields['node_id'],
+ Node.fields['hostname'])],
+ Filter(Node.fields)),
+ Parameter([str], "List of fields to return", nullok = True),
+ ]
+
+ returns = [Node.fields]
+
+
+ def call(self, auth, node_filter = None, return_fields = None):
+
+ # Must query at least slice_ids_whitelist
+ if return_fields is not None:
+ added_fields = set(['slice_ids_whitelist']).difference(return_fields)
+ return_fields += added_fields
+ else:
+ added_fields =[]
+
+ # Get node information
+ nodes = Nodes(self.api, node_filter, return_fields)
+
+ # Remove all nodes without a whitelist
+ for node in nodes[:]:
+ if not node['slice_ids_whitelist']:
+ nodes.remove(node)
+
+ # Remove admin only fields
+ if not isinstance(self.caller, Person) or \
+ 'admin' not in self.caller['roles']:
+ slice_ids = set()
+ if self.caller:
+ slice_ids.update(self.caller['slice_ids'])
+ #if node has whitelist, make sure the user has a slice on the whitelist
+ for node in nodes[:]:
+ if 'slice_ids_whitelist' in node and \
+ node['slice_ids_whitelist'] and \
+ not slice_ids.intersection(node['slice_ids_whitelist']):
+ nodes.remove(node)
+ for node in nodes:
+ for field in ['boot_nonce', 'key', 'session', 'root_person_ids']:
+ if field in node:
+ del node[field]
+
+ # remove added fields if not specified
+ if added_fields:
+ for node in nodes:
+ for field in added_fields:
+ del node[field]
+
+ return nodes
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Persons import Person, Persons
+from PLC.sendmail import sendmail
+
+class NotifyPersons(Method):
+ """
+ Sends an e-mail message to the specified users. If person_filter
+ is specified and is an array of user identifiers or usernames, or
+ a struct of user attributes, only users matching the filter will
+ receive the message.
+
+ Returns 1 if successful.
+ """
+
+ roles = ['admin', 'node']
+
+ accepts = [
+ Auth(),
+ Mixed([Mixed(Person.fields['person_id'],
+ Person.fields['email'])],
+ Filter(Person.fields)),
+ Parameter(str, "E-mail subject"),
+ Parameter(str, "E-mail body")
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, person_filter, subject, body):
+ persons = Persons(self.api, person_filter,
+ ['person_id', 'first_name', 'last_name', 'email'])
+ if not persons:
+ raise PLCInvalidArgument, "No such user(s)"
+
+ # Send email
+ sendmail(self.api,
+ To = [("%s %s" % (person['first_name'], person['last_name']),
+ person['email']) for person in persons],
+ Subject = subject,
+ Body = body)
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id'] for person in persons]}
+ self.message = subject
+
+ return 1
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.sendmail import sendmail
+
+class NotifySupport(Method):
+ """
+ Sends an e-mail message to the configured support address.
+
+ Returns 1 if successful.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Parameter(str, "E-mail subject"),
+ Parameter(str, "E-mail body")
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, subject, body):
+ to_name="%s Support"%self.api.config.PLC_NAME
+ to_address=self.api.config.PLC_MAIL_SUPPORT_ADDRESS
+
+ # Send email
+ sendmail(self.api, To=(to_name,to_address),
+ Subject = subject,
+ Body = body)
+
+ # Logging variables
+ #self.event_objects = {'Person': [person['person_id'] for person in persons]}
+ self.message = subject
+
+ return 1
--- /dev/null
+import socket
+
+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 Auth
+from PLC.POD import udp_pod
+
+class RebootNode(Method):
+ """
+ Sends the specified node a specially formatted UDP packet which
+ should cause it to reboot immediately.
+
+ Admins can reboot any node. Techs and PIs can only reboot nodes at
+ their site.
+
+ Returns 1 if the packet was successfully sent (which only whether
+ the packet was sent, not whether the reboot was successful).
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ accepts = [
+ Auth(),
+ 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[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"
+
+ session = node['session']
+ if not session:
+ raise PLCInvalidArgument, "No session key on record for that node (i.e., has never successfully booted)"
+ session = session.strip()
+
+ # Only use the hostname as a backup, try to use the primary ID
+ # address instead.
+ host = node['hostname']
+ nodenetworks = NodeNetworks(self.api, node['nodenetwork_ids'])
+ for nodenetwork in nodenetworks:
+ if nodenetwork['is_primary'] == 1:
+ host = nodenetwork['ip']
+ break
+
+ try:
+ udp_pod(host, session)
+ except socket.error, e:
+ # Ignore socket errors
+ pass
+
+ self.event_objects = {'Node': [node['node_id']]}
+ self.message = "RebootNode called"
+
+ return 1
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Id: RefreshPeer.py 5574 2007-10-25 20:33:17Z thierry $
+
+import time
+
+from PLC.Debug import log
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Peers import Peer, Peers
+from PLC.Sites import Site, Sites
+from PLC.Persons import Person, Persons
+from PLC.KeyTypes import KeyType, KeyTypes
+from PLC.Keys import Key, Keys
+from PLC.BootStates import BootState, BootStates
+from PLC.Nodes import Node, Nodes
+from PLC.SliceInstantiations import SliceInstantiations
+from PLC.Slices import Slice, Slices
+
+verbose=False
+
+class RefreshPeer(Method):
+ """
+ Fetches site, node, slice, person and key data from the specified peer
+ and caches it locally; also deletes stale entries.
+ Upon successful completion, returns a dict reporting various timers.
+ Faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Peer.fields['peer_id'],
+ Peer.fields['peername']),
+ ]
+
+ returns = Parameter(int, "1 if successful")
+
+ def call(self, auth, peer_id_or_peername):
+ # Get peer
+ peers = Peers(self.api, [peer_id_or_peername])
+ if not peers:
+ raise PLCInvalidArgument, "No such peer '%s'" % unicode(peer_id_or_peername)
+ peer = peers[0]
+ peer_id = peer['peer_id']
+
+ # Connect to peer API
+ peer.connect()
+
+ timers = {}
+
+ # Get peer data
+ start = time.time()
+ print >>log, 'Issuing GetPeerData'
+ peer_tables = peer.GetPeerData()
+ timers['transport'] = time.time() - start - peer_tables['db_time']
+ timers['peer_db'] = peer_tables['db_time']
+ if verbose:
+ print >>log, 'GetPeerData returned -> db=%d transport=%d'%(timers['peer_db'],timers['transport'])
+
+ def sync(objects, peer_objects, classobj):
+ """
+ Synchronizes two dictionaries of objects. objects should
+ be a dictionary of local objects keyed on their foreign
+ identifiers. peer_objects should be a dictionary of
+ foreign objects keyed on their local (i.e., foreign to us)
+ identifiers. Returns a final dictionary of local objects
+ keyed on their foreign identifiers.
+ """
+
+ if verbose:
+ print >>log, 'Entering sync on',classobj(self.api).__class__.__name__
+
+ synced = {}
+
+ # Delete stale objects
+ for peer_object_id, object in objects.iteritems():
+ if peer_object_id not in peer_objects:
+ object.delete(commit = False)
+ print >> log, peer['peername'],classobj(self.api).__class__.__name__, object[object.primary_key],"deleted"
+
+ # Add/update new/existing objects
+ for peer_object_id, peer_object in peer_objects.iteritems():
+ if peer_object_id in objects:
+ # Update existing object
+ object = objects[peer_object_id]
+
+ # Replace foreign identifier with existing local
+ # identifier temporarily for the purposes of
+ # comparison.
+ peer_object[object.primary_key] = object[object.primary_key]
+
+ # Must use __eq__() instead of == since
+ # peer_object may be a raw dict instead of a Peer
+ # object.
+ if not object.__eq__(peer_object):
+ # Only update intrinsic fields
+ object.update(object.db_fields(peer_object))
+ sync = True
+ dbg = "changed"
+ else:
+ sync = False
+ dbg = None
+
+ # Restore foreign identifier
+ peer_object[object.primary_key] = peer_object_id
+ else:
+ # Add new object
+ object = classobj(self.api, peer_object)
+ # Replace foreign identifier with new local identifier
+ del object[object.primary_key]
+ sync = True
+ dbg = "added"
+
+ if sync:
+ try:
+ object.sync(commit = False)
+ except PLCInvalidArgument, err:
+ # Skip if validation fails
+ # XXX Log an event instead of printing to logfile
+ print >> log, "Warning: Skipping invalid", \
+ peer['peername'], object.__class__.__name__, \
+ ":", peer_object, ":", err
+ continue
+
+ synced[peer_object_id] = object
+
+ if dbg:
+ print >> log, peer['peername'], classobj(self.api).__class__.__name__, object[object.primary_key], dbg
+
+ if verbose:
+ print >>log, 'Exiting sync on',classobj(self.api).__class__.__name__
+
+ return synced
+
+ #
+ # Synchronize foreign sites
+ #
+
+ start = time.time()
+
+ print >>log, 'Dealing with Sites'
+
+ # Compare only the columns returned by the GetPeerData() call
+ if peer_tables['Sites']:
+ columns = peer_tables['Sites'][0].keys()
+ else:
+ columns = None
+
+ # Keyed on foreign site_id
+ old_peer_sites = Sites(self.api, {'peer_id': peer_id}, columns).dict('peer_site_id')
+ sites_at_peer = dict([(site['site_id'], site) for site in peer_tables['Sites']])
+
+ # Synchronize new set (still keyed on foreign site_id)
+ peer_sites = sync(old_peer_sites, sites_at_peer, Site)
+
+ for peer_site_id, site in peer_sites.iteritems():
+ # Bind any newly cached sites to peer
+ if peer_site_id not in old_peer_sites:
+ peer.add_site(site, peer_site_id, commit = False)
+ site['peer_id'] = peer_id
+ site['peer_site_id'] = peer_site_id
+
+ timers['site'] = time.time() - start
+
+ #
+ # XXX Synchronize foreign key types
+ #
+
+ print >>log, 'Dealing with Keys'
+
+ key_types = KeyTypes(self.api).dict()
+
+ #
+ # Synchronize foreign keys
+ #
+
+ start = time.time()
+
+ # Compare only the columns returned by the GetPeerData() call
+ if peer_tables['Keys']:
+ columns = peer_tables['Keys'][0].keys()
+ else:
+ columns = None
+
+ # Keyed on foreign key_id
+ old_peer_keys = Keys(self.api, {'peer_id': peer_id}, columns).dict('peer_key_id')
+ keys_at_peer = dict([(key['key_id'], key) for key in peer_tables['Keys']])
+
+ # Fix up key_type references
+ for peer_key_id, key in keys_at_peer.items():
+ if key['key_type'] not in key_types:
+ # XXX Log an event instead of printing to logfile
+ print >> log, "Warning: Skipping invalid %s key:" % peer['peername'], \
+ key, ": invalid key type", key['key_type']
+ del keys_at_peer[peer_key_id]
+ continue
+
+ # Synchronize new set (still keyed on foreign key_id)
+ peer_keys = sync(old_peer_keys, keys_at_peer, Key)
+ for peer_key_id, key in peer_keys.iteritems():
+ # Bind any newly cached keys to peer
+ if peer_key_id not in old_peer_keys:
+ peer.add_key(key, peer_key_id, commit = False)
+ key['peer_id'] = peer_id
+ key['peer_key_id'] = peer_key_id
+
+ timers['keys'] = time.time() - start
+
+ #
+ # Synchronize foreign users
+ #
+
+ start = time.time()
+
+ print >>log, 'Dealing with Persons'
+
+ # Compare only the columns returned by the GetPeerData() call
+ if peer_tables['Persons']:
+ columns = peer_tables['Persons'][0].keys()
+ else:
+ columns = None
+
+ # Keyed on foreign person_id
+ old_peer_persons = Persons(self.api, {'peer_id': peer_id}, columns).dict('peer_person_id')
+
+ # artificially attach the persons returned by GetPeerData to the new peer
+ # this is because validate_email needs peer_id to be correct when checking for duplicates
+ for person in peer_tables['Persons']:
+ person['peer_id']=peer_id
+ persons_at_peer = dict([(peer_person['person_id'], peer_person) \
+ for peer_person in peer_tables['Persons']])
+
+ # XXX Do we care about membership in foreign site(s)?
+
+ # Synchronize new set (still keyed on foreign person_id)
+ peer_persons = sync(old_peer_persons, persons_at_peer, Person)
+
+ # transcoder : retrieve a local key_id from a peer_key_id
+ key_transcoder = dict ( [ (key['key_id'],peer_key_id) \
+ for peer_key_id,key in peer_keys.iteritems()])
+
+ for peer_person_id, person in peer_persons.iteritems():
+ # Bind any newly cached users to peer
+ if peer_person_id not in old_peer_persons:
+ peer.add_person(person, peer_person_id, commit = False)
+ person['peer_id'] = peer_id
+ person['peer_person_id'] = peer_person_id
+ person['key_ids'] = []
+
+ # User as viewed by peer
+ peer_person = persons_at_peer[peer_person_id]
+
+ # Foreign keys currently belonging to the user
+ old_person_key_ids = [key_transcoder[key_id] for key_id in person['key_ids'] \
+ if key_transcoder[key_id] in peer_keys]
+
+ # Foreign keys that should belong to the user
+ # this is basically peer_person['key_ids'], we just check it makes sense
+ # (e.g. we might have failed importing it)
+ person_key_ids = [ key_id for key_id in peer_person['key_ids'] if key_id in peer_keys]
+
+ # Remove stale keys from user
+ for key_id in (set(old_person_key_ids) - set(person_key_ids)):
+ person.remove_key(peer_keys[key_id], commit = False)
+ print >> log, peer['peername'], 'Key', key_id, 'removed from', person['email']
+
+ # Add new keys to user
+ for key_id in (set(person_key_ids) - set(old_person_key_ids)):
+ person.add_key(peer_keys[key_id], commit = False)
+ print >> log, peer['peername'], 'Key', key_id, 'added into', person['email']
+
+ timers['persons'] = time.time() - start
+
+ #
+ # XXX Synchronize foreign boot states
+ #
+
+ boot_states = BootStates(self.api).dict()
+
+ #
+ # Synchronize foreign nodes
+ #
+
+ start = time.time()
+
+ print >>log, 'Dealing with Nodes'
+
+ # Compare only the columns returned by the GetPeerData() call
+ if peer_tables['Nodes']:
+ columns = peer_tables['Nodes'][0].keys()
+ else:
+ columns = None
+
+ # Keyed on foreign node_id
+ old_peer_nodes = Nodes(self.api, {'peer_id': peer_id}, columns).dict('peer_node_id')
+ nodes_at_peer = dict([(node['node_id'], node) \
+ for node in peer_tables['Nodes']])
+
+ # Fix up site_id and boot_states references
+ for peer_node_id, node in nodes_at_peer.items():
+ errors = []
+ if node['site_id'] not in peer_sites:
+ errors.append("invalid site %d" % node['site_id'])
+ if node['boot_state'] not in boot_states:
+ errors.append("invalid boot state %s" % node['boot_state'])
+ if errors:
+ # XXX Log an event instead of printing to logfile
+ print >> log, "Warning: Skipping invalid %s node:" % peer['peername'], \
+ node, ":", ", ".join(errors)
+ del nodes_at_peer[peer_node_id]
+ continue
+ else:
+ node['site_id'] = peer_sites[node['site_id']]['site_id']
+
+ # Synchronize new set
+ peer_nodes = sync(old_peer_nodes, nodes_at_peer, Node)
+
+ for peer_node_id, node in peer_nodes.iteritems():
+ # Bind any newly cached foreign nodes to peer
+ if peer_node_id not in old_peer_nodes:
+ peer.add_node(node, peer_node_id, commit = False)
+ node['peer_id'] = peer_id
+ node['peer_node_id'] = peer_node_id
+
+ timers['nodes'] = time.time() - start
+
+ #
+ # Synchronize local nodes
+ #
+
+ start = time.time()
+
+ # Keyed on local node_id
+ local_nodes = Nodes(self.api).dict()
+
+ for node in peer_tables['PeerNodes']:
+ # Foreign identifier for our node as maintained by peer
+ peer_node_id = node['node_id']
+ # Local identifier for our node as cached by peer
+ node_id = node['peer_node_id']
+ if node_id in local_nodes:
+ # Still a valid local node, add it to the synchronized
+ # set of local node objects keyed on foreign node_id.
+ peer_nodes[peer_node_id] = local_nodes[node_id]
+
+ timers['local_nodes'] = time.time() - start
+
+ #
+ # XXX Synchronize foreign slice instantiation states
+ #
+
+ slice_instantiations = SliceInstantiations(self.api).dict()
+
+ #
+ # Synchronize foreign slices
+ #
+
+ start = time.time()
+
+ print >>log, 'Dealing with Slices'
+
+ # Compare only the columns returned by the GetPeerData() call
+ if peer_tables['Slices']:
+ columns = peer_tables['Slices'][0].keys()
+ else:
+ columns = None
+
+ # Keyed on foreign slice_id
+ old_peer_slices = Slices(self.api, {'peer_id': peer_id}, columns).dict('peer_slice_id')
+ slices_at_peer = dict([(slice['slice_id'], slice) \
+ for slice in peer_tables['Slices']])
+
+ # Fix up site_id, instantiation, and creator_person_id references
+ for peer_slice_id, slice in slices_at_peer.items():
+ errors = []
+ if slice['site_id'] not in peer_sites:
+ errors.append("invalid site %d" % slice['site_id'])
+ if slice['instantiation'] not in slice_instantiations:
+ errors.append("invalid instantiation %s" % slice['instantiation'])
+ if slice['creator_person_id'] not in peer_persons:
+ # Just NULL it out
+ slice['creator_person_id'] = None
+ else:
+ slice['creator_person_id'] = peer_persons[slice['creator_person_id']]['person_id']
+ if errors:
+ print >> log, "Warning: Skipping invalid %s slice:" % peer['peername'], \
+ slice, ":", ", ".join(errors)
+ del slices_at_peer[peer_slice_id]
+ continue
+ else:
+ slice['site_id'] = peer_sites[slice['site_id']]['site_id']
+
+ # Synchronize new set
+ peer_slices = sync(old_peer_slices, slices_at_peer, Slice)
+
+ # transcoder : retrieve a local node_id from a peer_node_id
+ node_transcoder = dict ( [ (node['node_id'],peer_node_id) \
+ for peer_node_id,node in peer_nodes.iteritems()])
+ person_transcoder = dict ( [ (person['person_id'],peer_person_id) \
+ for peer_person_id,person in peer_persons.iteritems()])
+
+ for peer_slice_id, slice in peer_slices.iteritems():
+ # Bind any newly cached foreign slices to peer
+ if peer_slice_id not in old_peer_slices:
+ peer.add_slice(slice, peer_slice_id, commit = False)
+ slice['peer_id'] = peer_id
+ slice['peer_slice_id'] = peer_slice_id
+ slice['node_ids'] = []
+ slice['person_ids'] = []
+
+ # Slice as viewed by peer
+ peer_slice = slices_at_peer[peer_slice_id]
+
+ # Nodes that are currently part of the slice
+ old_slice_node_ids = [ node_transcoder[node_id] for node_id in slice['node_ids'] \
+ if node_transcoder[node_id] in peer_nodes]
+
+ # Nodes that should be part of the slice
+ slice_node_ids = [ node_id for node_id in peer_slice['node_ids'] if node_id in peer_nodes]
+
+ # Remove stale nodes from slice
+ for node_id in (set(old_slice_node_ids) - set(slice_node_ids)):
+ slice.remove_node(peer_nodes[node_id], commit = False)
+ print >> log, peer['peername'], 'Node', peer_nodes[node_id]['hostname'], 'removed from', slice['name']
+
+ # Add new nodes to slice
+ for node_id in (set(slice_node_ids) - set(old_slice_node_ids)):
+ slice.add_node(peer_nodes[node_id], commit = False)
+ print >> log, peer['peername'], 'Node', peer_nodes[node_id]['hostname'], 'added into', slice['name']
+
+ # N.B.: Local nodes that may have been added to the slice
+ # by hand, are removed. In other words, don't do this.
+
+ # Foreign users that are currently part of the slice
+ #old_slice_person_ids = [ person_transcoder[person_id] for person_id in slice['person_ids'] \
+ # if person_transcoder[person_id] in peer_persons]
+ # An issue occurred with a user who registered on both sites (same email)
+ # So the remote person could not get cached locally
+ # The one-line map/filter style is nicer but ineffective here
+ old_slice_person_ids = []
+ for person_id in slice['person_ids']:
+ if not person_transcoder.has_key(person_id):
+ print >> log, 'WARNING : person_id %d in %s not transcodable (1) - skipped'%(person_id,slice['name'])
+ elif person_transcoder[person_id] not in peer_persons:
+ print >> log, 'WARNING : person_id %d in %s not transcodable (2) - skipped'%(person_id,slice['name'])
+ else:
+ old_slice_person_ids += [person_transcoder[person_id]]
+
+ # Foreign users that should be part of the slice
+ slice_person_ids = [ person_id for person_id in peer_slice['person_ids'] if person_id in peer_persons ]
+
+ # Remove stale users from slice
+ for person_id in (set(old_slice_person_ids) - set(slice_person_ids)):
+ slice.remove_person(peer_persons[person_id], commit = False)
+ print >> log, peer['peername'], 'User', peer_persons[person_id]['email'], 'removed from', slice['name']
+
+ # Add new users to slice
+ for person_id in (set(slice_person_ids) - set(old_slice_person_ids)):
+ slice.add_person(peer_persons[person_id], commit = False)
+ print >> log, peer['peername'], 'User', peer_persons[person_id]['email'], 'added into', slice['name']
+
+ # N.B.: Local users that may have been added to the slice
+ # by hand, are not touched.
+
+ timers['slices'] = time.time() - start
+
+ # Update peer itself and commit
+ peer.sync(commit = True)
+
+ return timers
--- /dev/null
+import random
+import base64
+import time
+import urllib
+
+from types import StringTypes
+
+from PLC.Debug import log
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Messages import Message, Messages
+from PLC.Auth import Auth
+from PLC.sendmail import sendmail
+
+class ResetPassword(Method):
+ """
+ If verification_key is not specified, then a new verification_key
+ will be generated and stored with the user's account. The key will
+ be e-mailed to the user in the form of a link to a web page.
+
+ The web page should verify the key by calling this function again
+ and specifying verification_key. If the key matches what has been
+ stored in the user's account, a new random password will be
+ e-mailed to the user.
+
+ Returns 1 if verification_key was not specified, or was specified
+ and is valid, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Person.fields['verification_key'],
+ Person.fields['verification_expires']
+ ]
+
+ returns = Parameter(int, '1 if verification_key is valid')
+
+ def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None):
+ # Get account information
+ # we need to search in local objects only
+ if isinstance (person_id_or_email,StringTypes):
+ filter={'email':person_id_or_email}
+ else:
+ filter={'person_id':person_id_or_email}
+ filter['peer_id']=None
+ persons = Persons(self.api, filter)
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ if not person['enabled']:
+ raise PLCInvalidArgument, "Account must be enabled"
+
+ # Be paranoid and deny password resets for admins
+ if 'admin' in person['roles']:
+ raise PLCInvalidArgument, "Cannot reset admin passwords"
+
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ random_key = base64.b64encode("".join(map(chr, bytes)))
+
+ if verification_key is not None:
+ if person['verification_key'] is None or \
+ person['verification_expires'] is None or \
+ person['verification_expires'] < time.time():
+ raise PLCPermissionDenied, "Verification key has expired"
+ elif person['verification_key'] != verification_key:
+ raise PLCPermissionDenied, "Verification key incorrect"
+ else:
+ # Reset password to random string
+ person['password'] = random_key
+ person['verification_key'] = None
+ person['verification_expires'] = None
+ person.sync()
+
+ message_id = 'Password reset'
+ else:
+ # Only allow one reset at a time
+ if person['verification_expires'] is not None and \
+ person['verification_expires'] > time.time():
+ raise PLCPermissionDenied, "Password reset request already pending"
+
+ if verification_expires is None:
+ verification_expires = int(time.time() + (24 * 60 * 60))
+
+ person['verification_key'] = random_key
+ person['verification_expires'] = verification_expires
+ person.sync()
+
+ message_id = 'Password reset requested'
+
+ messages = Messages(self.api, [message_id])
+ if messages:
+ # Send password to user
+ message = messages[0]
+
+ params = {'PLC_NAME': self.api.config.PLC_NAME,
+ 'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
+ 'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
+ 'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
+ 'person_id': person['person_id'],
+ # Will be used in a URL, so must quote appropriately
+ 'verification_key': urllib.quote_plus(random_key),
+ 'password': random_key,
+ 'email': person['email']}
+
+ sendmail(self.api,
+ To = ("%s %s" % (person['first_name'], person['last_name']), person['email']),
+ Subject = message['subject'] % params,
+ Body = message['template'] % params)
+ else:
+ print >> log, "Warning: No message template '%s'" % message_id
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']]}
+ self.message = message_id
+
+ return 1
--- /dev/null
+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 Auth
+
+class SetPersonPrimarySite(Method):
+ """
+ Makes the specified site the person's primary site. The person
+ must already be a member of the site.
+
+ Admins may update anyone. All others may only update themselves.
+ """
+
+ roles = ['admin', 'pi', 'user', 'tech']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ object_type = 'Person'
+
+ 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[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # 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[0]
+
+ if site['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local site"
+
+ 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
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Methods.AddSlice import AddSlice
+
+class SliceCreate(AddSlice):
+ """
+ Deprecated. See AddSlice.
+ """
+
+ status = "deprecated"
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ AddSlice.accepts[1]
+ ]
+
+ returns = Parameter(int, 'New slice_id (> 0) if successful')
+
+ def call(self, auth, name, slice_fields = {}):
+ slice_fields['name'] = name
+ return AddSlice.call(self, auth, slice_fields)
--- /dev/null
+import re
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.DeleteSlice import DeleteSlice
+
+class SliceDelete(DeleteSlice):
+ """
+ Deprecated. See DeleteSlice.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name']
+ ]
+
+ returns = Parameter(int, 'Returns 1 if successful, a fault otherwise.')
+
+ def call(self, auth, slice_name):
+
+ return DeleteSlice.call(self, auth, slice_name)
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Sites import Site, Sites
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+
+class SliceExtendedInfo(Method):
+ """
+ Deprecated. Can be implemented with GetSlices.
+
+ Returns an array of structs containing details about slices.
+ The summary can optionally include the list of nodes in and
+ users of each slice.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins may query any
+ slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ [Slice.fields['name']],
+ Parameter(bool, "Whether or not to return users for the slices", nullok = True),
+ Parameter(bool, "Whether or not to return nodes for the slices", nullok = True)
+ ]
+
+ returns = [Slice.fields]
+
+
+ def call(self, auth, slice_name_list=None, return_users=None, return_nodes=None, return_attributes=None):
+ # If we are not admin, make sure to return only viewable
+ # slices.
+ slice_filter = slice_name_list
+ slices = Slices(self.api, slice_filter)
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+
+ if 'admin' not in self.caller['roles']:
+ # Get slices that we are able to view
+ valid_slice_ids = self.caller['slice_ids']
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_slice_ids += site['slice_ids']
+
+ if not valid_slice_ids:
+ return []
+
+ slices = filter(lambda slice: slice['slice_id'] in valid_slice_ids, slices)
+
+ for slice in slices:
+ index = slices.index(slice)
+ node_ids = slices[index].pop('node_ids')
+ person_ids = slices[index].pop('person_ids')
+ attribute_ids = slices[index].pop('slice_attribute_ids')
+ if return_users or return_users is None:
+ persons = Persons(self.api, person_ids)
+ person_info = [{'email': person['email'],
+ 'person_id': person['person_id']} \
+ for person in persons]
+ slices[index]['users'] = person_info
+ if return_nodes or return_nodes is None:
+ nodes = Nodes(self.api, node_ids)
+ node_info = [{'hostname': node['hostname'],
+ 'node_id': node['node_id']} \
+ for node in nodes]
+ slices[index]['nodes'] = node_info
+ if return_attributes or return_attributes is None:
+ attributes = SliceAttributes(self.api, attribute_ids)
+ attribute_info = [{'name': attribute['name'],
+ 'value': attribute['value']} \
+ for attribute in attributes]
+ slices[index]['attributes'] = attribute_info
+
+ return slices
--- /dev/null
+import os
+import sys
+from subprocess import Popen, PIPE, call
+from tempfile import NamedTemporaryFile
+from xml.sax.saxutils import escape, quoteattr, XMLGenerator
+
+from PLC.Faults import *
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+
+from PLC.Methods.GetSliceTicket import GetSliceTicket
+
+class PrettyXMLGenerator(XMLGenerator):
+ """
+ Adds indentation to the beginning and newlines to the end of
+ opening and closing tags.
+ """
+
+ def __init__(self, out = sys.stdout, encoding = "utf-8", indent = "", addindent = "", newl = ""):
+ XMLGenerator.__init__(self, out, encoding)
+ # XMLGenerator does not export _write()
+ self.write = self.ignorableWhitespace
+ self.indents = [indent]
+ self.addindent = addindent
+ self.newl = newl
+
+ def startDocument(self):
+ XMLGenerator.startDocument(self)
+
+ def startElement(self, name, attrs, indent = True, newl = True):
+ if indent:
+ self.ignorableWhitespace("".join(self.indents))
+ self.indents.append(self.addindent)
+
+ XMLGenerator.startElement(self, name, attrs)
+
+ if newl:
+ self.ignorableWhitespace(self.newl)
+
+ def characters(self, content):
+ # " to "
+ # ' to '
+ self.write(escape(content, {
+ '"': '"',
+ "'": ''',
+ }))
+
+ def endElement(self, name, indent = True, newl = True):
+ self.indents.pop()
+ if indent:
+ self.ignorableWhitespace("".join(self.indents))
+
+ XMLGenerator.endElement(self, name)
+
+ if newl:
+ self.ignorableWhitespace(self.newl)
+
+ def simpleElement(self, name, attrs = {}, indent = True, newl = True):
+ if indent:
+ self.ignorableWhitespace("".join(self.indents))
+
+ self.write('<' + name)
+ for (name, value) in attrs.items():
+ self.write(' %s=%s' % (name, quoteattr(value)))
+ self.write('/>')
+
+ if newl:
+ self.ignorableWhitespace(self.newl)
+
+class SliceGetTicket(GetSliceTicket):
+ """
+ Deprecated. See GetSliceTicket.
+
+ Warning: This function exists solely for backward compatibility
+ with the old public PlanetLab 3.0 Node Manager, which will be
+ removed from service by 2007. This call is not intended to be used
+ by any other PLC except the public PlanetLab.
+ """
+
+ status = "deprecated"
+
+ def call(self, auth, slice_id_or_name):
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ # Allow peers to obtain tickets for their own slices
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ if slice['instantiation'] != 'delegated':
+ raise PLCInvalidArgument, "Not in delegated state"
+
+ nodes = Nodes(self.api, slice['node_ids']).dict()
+ persons = Persons(self.api, slice['person_ids']).dict()
+ slice_attributes = SliceAttributes(self.api, slice['slice_attribute_ids']).dict()
+
+ ticket = NamedTemporaryFile()
+
+ xml = PrettyXMLGenerator(out = ticket, encoding = self.api.encoding, indent = "", addindent = " ", newl = "\n")
+ xml.startDocument()
+
+ # <ticket>
+ xml.startElement('ticket', {})
+
+ # <slice name="site_slice" id="12345" expiry="1138712648">
+ xml.startElement('slice',
+ {'id': str(slice['slice_id']),
+ 'name': unicode(slice['name']),
+ 'expiry': unicode(int(slice['expires']))})
+
+ # <nodes>
+ xml.startElement('nodes', {})
+ for node_id in slice['node_ids']:
+ if not nodes.has_key(node_id):
+ continue
+ node = nodes[node_id]
+ # <node id="12345" hostname="node.site.domain"/>
+ xml.simpleElement('node',
+ {'id': str(node['node_id']),
+ 'hostname': unicode(node['hostname'])})
+ # </nodes>
+ xml.endElement('nodes')
+
+ # <users>
+ xml.startElement('users', {})
+ for person_id in slice['person_ids']:
+ if not persons.has_key(person_id):
+ continue
+ user = persons[person_id]
+ # <user person_id="12345" email="user@site.domain"/>
+ xml.simpleElement('user',
+ {'person_id': unicode(user['person_id']),
+ 'email': unicode(user['email'])})
+ # </users>
+ xml.endElement('users')
+
+ # <rspec>
+ xml.startElement('rspec', {})
+ for slice_attribute_id in slice['slice_attribute_ids']:
+ if not slice_attributes.has_key(slice_attribute_id):
+ continue
+ slice_attribute = slice_attributes[slice_attribute_id]
+
+ name = slice_attribute['name']
+ value = slice_attribute['value']
+
+ def kbps_to_bps(kbps):
+ bps = int(kbps) * 1000
+ return bps
+
+ def max_kbyte_to_bps(max_kbyte):
+ bps = int(max_kbyte) * 1000 * 8 / 24 / 60 / 60
+ return bps
+
+ # XXX Used to support multiple named values for each attribute type
+ name_type_cast = {
+ 'cpu_share': ('nm_cpu_share', 'cpu_share', 'integer', int),
+
+ 'net_share': ('nm_net_share', 'rate', 'integer', int),
+ 'net_min_rate': ('nm_net_min_rate', 'rate', 'integer', int),
+ 'net_max_rate': ('nm_net_max_rate', 'rate', 'integer', int),
+ 'net_max_kbyte': ('nm_net_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
+
+ 'net_i2_share': ('nm_net_exempt_share', 'rate', 'integer', int),
+ 'net_i2_min_rate': ('nm_net_exempt_min_rate', 'rate', 'integer', kbps_to_bps),
+ 'net_i2_max_rate': ('nm_net_exempt_max_rate', 'rate', 'integer', kbps_to_bps),
+ 'net_i2_max_kbyte': ('nm_net_exempt_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
+
+ 'disk_max': ('nm_disk_quota', 'quota', 'integer', int),
+ 'plc_agent_version': ('plc_agent_version', 'version', 'string', str),
+ 'plc_slice_type': ('plc_slice_type', 'type', 'string', str),
+ 'plc_ticket_pubkey': ('plc_ticket_pubkey', 'key', 'string', str),
+ }
+
+ if name == 'initscript':
+ (attribute_name, value_name, type) = ('initscript', 'initscript_id', 'integer')
+ value = slice_attribute['slice_attribute_id']
+ elif name in name_type_cast:
+ (attribute_name, value_name, type, cast) = name_type_cast[name]
+ value = cast(value)
+ else:
+ attribute_name = value_name = name
+ type = "string"
+
+ # <resource name="slice_attribute_type">
+ xml.startElement('resource', {'name': unicode(attribute_name)})
+
+ # <value name="element_name" type="element_type">
+ xml.startElement('value',
+ {'name': unicode(value_name),
+ 'type': type},
+ newl = False)
+ # element value
+ xml.characters(unicode(value))
+ # </value>
+ xml.endElement('value', indent = False)
+
+ # </resource>
+ xml.endElement('resource')
+ # </rspec>
+ xml.endElement('rspec')
+
+ # </slice>
+ xml.endElement('slice')
+
+ # Add signature template
+ xml.startElement('Signature', {'xmlns': "http://www.w3.org/2000/09/xmldsig#"})
+ xml.startElement('SignedInfo', {})
+ xml.simpleElement('CanonicalizationMethod', {'Algorithm': "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"})
+ xml.simpleElement('SignatureMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#rsa-sha1"})
+ xml.startElement('Reference', {'URI': ""})
+ xml.startElement('Transforms', {})
+ xml.simpleElement('Transform', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#enveloped-signature"})
+ xml.endElement('Transforms')
+ xml.simpleElement('DigestMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#sha1"})
+ xml.simpleElement('DigestValue', {})
+ xml.endElement('Reference')
+ xml.endElement('SignedInfo')
+ xml.simpleElement('SignatureValue', {})
+ xml.endElement('Signature')
+
+ xml.endElement('ticket')
+ xml.endDocument()
+
+ if not hasattr(self.api.config, 'PLC_API_TICKET_KEY') or \
+ not os.path.exists(self.api.config.PLC_API_TICKET_KEY):
+ raise PLCAPIError, "Slice ticket signing key not found"
+
+ ticket.flush()
+
+ # Sign the ticket
+ p = Popen(["xmlsec1", "--sign",
+ "--privkey-pem", self.api.config.PLC_API_TICKET_KEY,
+ ticket.name],
+ stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
+ signed_ticket = p.stdout.read()
+ err = p.stderr.read()
+ rc = p.wait()
+
+ ticket.close()
+
+ if rc:
+ raise PLCAPIError, err
+
+ return signed_ticket
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Faults import *
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Sites import Site, Sites
+from PLC.Persons import Person, Persons
+from PLC.Nodes import Node, Nodes
+
+class SliceInfo(Method):
+ """
+ Deprecated. Can be implemented with GetSlices.
+
+ Returns an array of structs containing details about slices.
+ The summary can optionally include the list of nodes in and
+ users of each slice.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins may query any
+ slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ [Mixed(Slice.fields['name'])],
+ Parameter(bool, "Whether or not to return users for the slices", nullok = True),
+ Parameter(bool, "Whether or not to return nodes for the slices", nullok = True)
+ ]
+
+ returns = [Slice.fields]
+
+
+ def call(self, auth, slice_name_list=None, return_users=None, return_nodes=None):
+ # If we are not admin, make sure to return only viewable
+ # slices.
+ slice_filter = slice_name_list
+ slices = Slices(self.api, slice_filter)
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+
+ if 'admin' not in self.caller['roles']:
+ # Get slices that we are able to view
+ valid_slice_ids = self.caller['slice_ids']
+ if 'pi' in self.caller['roles'] and self.caller['site_ids']:
+ sites = Sites(self.api, self.caller['site_ids'])
+ for site in sites:
+ valid_slice_ids += site['slice_ids']
+
+ if not valid_slice_ids:
+ return []
+
+ slices = filter(lambda slice: slice['slice_id'] in valid_slice_ids, slices)
+
+
+ for slice in slices:
+ index = slices.index(slice)
+ node_ids = slices[index].pop('node_ids')
+ person_ids = slices[index].pop('person_ids')
+ if return_users or return_users is None:
+ persons = Persons(self.api, person_ids)
+ emails = [person['email'] for person in persons]
+ slices[index]['users'] = emails
+ if return_nodes or return_nodes is None:
+ nodes = Nodes(self.api, node_ids)
+ hostnames = [node['hostname'] for node in nodes]
+ slices[index]['nodes'] = hostnames
+
+
+ return slices
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Methods.GetSlices import GetSlices
+
+class SliceListNames(GetSlices):
+ """
+ Deprecated. Can be implemented with GetSlices.
+
+ List the names of registered slices.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins may query any
+ slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Parameter(str, "Slice prefix", nullok = True)
+ ]
+
+ returns = [Slice.fields['name']]
+
+
+ def call(self, auth, prefix=None):
+
+ slice_filter = None
+ if prefix:
+ slice_filter = {'name': prefix+'*'}
+
+ slices = GetSlices.call(self, auth, slice_filter)
+
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+
+ slice_names = [slice['name'] for slice in slices]
+
+ return slice_names
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Persons import Person, Persons
+from PLC.Methods.GetSlices import GetSlices
+from PLC.Methods.GetPersons import GetPersons
+
+class SliceListUserSlices(GetSlices, GetPersons):
+ """
+ Deprecated. Can be implemented with GetPersons and GetSlices.
+
+ Return the slices the specified user (by email address) is a member of.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins may query any
+ slice. If a slice that cannot be queried is specified in
+ slice_filter, details about that slice will not be returned.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Person.fields['email']
+ ]
+
+ returns = [Slice.fields['name']]
+
+
+ def call(self, auth, email):
+
+ persons = GetPersons.call(self, auth, [email])
+ if not persons:
+ return []
+ person = persons[0]
+ slice_ids = person['slice_ids']
+ if not slice_ids:
+ return []
+
+ slices = GetSlices.call(self, auth, slice_ids)
+ slice_names = [slice['name'] for slice in slices]
+
+ return slice_names
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.AddSliceToNodes import AddSliceToNodes
+
+class SliceNodesAdd(AddSliceToNodes):
+ """
+ Deprecated. See AddSliceToNodes.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ [Node.fields['hostname']]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, nodes_list):
+
+ return AddSliceToNodes.call(self, auth, slice_name, nodes_list)
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
+
+class SliceNodesDel(DeleteSliceFromNodes):
+ """
+ Deprecated. See DeleteSliceFromNodes.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ [Node.fields['hostname']]
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, nodes_list):
+
+ return DeleteSliceFromNodes.call(self, auth, slice_name, nodes_list)
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.Methods.GetSlices import GetSlices
+from PLC.Methods.GetNodes import GetNodes
+
+class SliceNodesList(GetSlices, GetNodes):
+ """
+ Deprecated. Can be implemented with GetSlices and GetNodes.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name']
+ ]
+
+ returns = [Node.fields['hostname']]
+
+
+ def call(self, auth, slice_name):
+ slices = GetSlices.call(self, auth, [slice_name])
+ if not slices:
+ return []
+
+ slice = slices[0]
+ nodes = GetNodes.call(self, auth, slice['node_ids'])
+ if not nodes:
+ return []
+
+ node_hostnames = [node['hostname'] for node in nodes]
+
+ return node_hostnames
--- /dev/null
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.UpdateSlice import UpdateSlice
+
+class SliceRenew(UpdateSlice):
+ """
+ Deprecated. See UpdateSlice.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ Slice.fields['expires']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, slice_expires):
+
+ slice_fields = {}
+ slice_fields['expires'] = slice_expires
+
+ return UpdateSlice.call(self, auth, slice_name, slice_fields)
+
--- /dev/null
+from PLC.Methods.SliceGetTicket import SliceGetTicket
+
+class SliceTicketGet(SliceGetTicket):
+ """
+ Deprecated. See GetSliceTicket.
+
+ Warning: This function exists solely for backward compatibility
+ with the old public PlanetLab 3.0 Node Manager, which will be
+ removed from service by 2007. This call is not intended to be used
+ by any other PLC except the public PlanetLab.
+ """
+
+ status = "deprecated"
--- /dev/null
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.UpdateSlice import UpdateSlice
+
+class SliceUpdate(UpdateSlice):
+ """
+ Deprecated. See UpdateSlice.
+
+ """
+
+ status = 'deprecated'
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ Slice.fields['url'],
+ Slice.fields['description'],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, url, description):
+
+ slice_fields = {}
+ slice_fields['url'] = url
+ slice_fields['description'] = description
+
+ return UpdateSlice.call(self, auth, slice_name, slice_fields)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Methods.AddPersonToSlice import AddPersonToSlice
+
+class SliceUserAdd(AddPersonToSlice):
+ """
+ Deprecated. See AddPersonToSlice.
+
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ [Person.fields['email']],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, user_list):
+
+ for user in user_list:
+ AddPersonToSlice.call(self, auth, user, slice_name)
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Persons import Person, Persons
+from PLC.Slices import Slice, Slices
+from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
+
+class SliceUserDel(Method):
+ """
+ Deprecated. Can be implemented with DeletePersonFromSlice.
+
+ Removes the specified users from the specified slice. If the person is
+ already a member of the slice, no errors are returned.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name'],
+ [Person.fields['email']],
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_name, user_list):
+ for user in user_list:
+ DeletePersonFromSlice.call(self, auth, user, slice_name)
+
+ return 1
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.Slices import Slice, Slices
+from PLC.Persons import Person, Persons
+from PLC.Methods.GetSlices import GetSlices
+from PLC.Methods.GetPersons import GetPersons
+
+class SliceUsersList(GetSlices, GetPersons):
+ """
+ Deprecated. Can be implemented with GetSlices and GetPersons.
+
+ List users that are members of the named slice.
+
+ Users may only query slices of which they are members. PIs may
+ query any of the slices at their sites. Admins may query any
+ slice. If a slice that cannot be queried is specified details
+ about that slice will not be returned.
+ """
+
+ status = "deprecated"
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ Slice.fields['name']
+ ]
+
+ returns = [Person.fields['email']]
+
+
+ def call(self, auth, slice_name):
+
+ slice_filter = [slice_name]
+ slices = GetSlices.call(self, auth, slice_filter)
+ if not slices:
+ return []
+ slice = slices[0]
+
+ persons = GetPersons.call(self, auth, slice['person_ids'])
+ person_emails = [person['email'] for person in persons]
+
+ return person_emails
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Addresses import Address, Addresses
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['line1', 'line2', 'line3',
+ 'city', 'state', 'postalcode', 'country']
+
+class UpdateAddress(Method):
+ """
+ Updates the parameters of an existing address with the values in
+ address_fields.
+
+ PIs may only update addresses of their own sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ address_fields = dict(filter(can_update, Address.fields.items()))
+
+ accepts = [
+ Auth(),
+ Address.fields['address_id'],
+ address_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, address_id, address_fields):
+ address_fields = dict(filter(can_update, address_fields.items()))
+
+ # Get associated address details
+ addresses = Addresses(self.api, [address_id])
+ if not addresses:
+ raise PLCInvalidArgument, "No such address"
+ address = addresses[0]
+
+ if 'admin' not in self.caller['roles']:
+ if address['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Address must be associated with one of your sites"
+
+ address.update(address_fields)
+ address.sync()
+
+ # Logging variables
+ self.event_objects = {'Address': [address['address_id']]}
+ self.message = 'Address %d updated: %s' % \
+ (address['address_id'], ", ".join(address_fields.keys()))
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.AddressTypes import AddressType, AddressTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in ['name', 'description']
+
+class UpdateAddressType(Method):
+ """
+ Updates the parameters of an existing address type with the values
+ in address_type_fields.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ address_type_fields = dict(filter(can_update, AddressType.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(AddressType.fields['address_type_id'],
+ AddressType.fields['name']),
+ address_type_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, address_type_id_or_name, address_type_fields):
+ address_type_fields = dict(filter(can_update, address_type_fields.items()))
+
+ address_types = AddressTypes(self.api, [address_type_id_or_name])
+ if not address_types:
+ raise PLCInvalidArgument, "No such address type"
+ address_type = address_types[0]
+
+ address_type.update(address_type_fields)
+ address_type.sync()
+ self.event_objects = {'AddressType': [address_type['address_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.ConfFiles import ConfFile, ConfFiles
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in \
+ ['conf_file_id', 'node_ids', 'nodegroup_ids']
+
+class UpdateConfFile(Method):
+ """
+ Updates a node configuration file. Only the fields specified in
+ conf_file_fields are updated, all other fields are left untouched.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ conf_file_fields = dict(filter(can_update, ConfFile.fields.items()))
+
+ accepts = [
+ Auth(),
+ ConfFile.fields['conf_file_id'],
+ conf_file_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, conf_file_id, conf_file_fields):
+ conf_file_fields = dict(filter(can_update, conf_file_fields.items()))
+
+ conf_files = ConfFiles(self.api, [conf_file_id])
+ if not conf_files:
+ raise PLCInvalidArgument, "No such configuration file"
+
+ conf_file = conf_files[0]
+ conf_file.update(conf_file_fields)
+ conf_file.sync()
+ self.event_objects = {'ConfFile': [conf_file['conf_file_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in \
+ ['initscript_id']
+
+class UpdateInitScript(Method):
+ """
+ Updates an initscript. Only the fields specified in
+ initscript_fields are updated, all other fields are left untouched.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ initscript_fields = dict(filter(can_update, InitScript.fields.items()))
+
+ accepts = [
+ Auth(),
+ InitScript.fields['initscript_id'],
+ initscript_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, initscript_id, initscript_fields):
+ initscript_fields = dict(filter(can_update, initscript_fields.items()))
+
+ initscripts = InitScripts(self.api, [initscript_id])
+ if not initscripts:
+ raise PLCInvalidArgument, "No such initscript"
+
+ initscript = initscripts[0]
+ initscript.update(initscript_fields)
+ initscript.sync()
+ self.event_objects = {'InitScript': [initscript['initscript_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Keys import Key, Keys
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['key_type', 'key']
+
+class UpdateKey(Method):
+ """
+ Updates the parameters of an existing key with the values in
+ key_fields.
+
+ Non-admins may only update their own keys.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ key_fields = dict(filter(can_update, Key.fields.items()))
+
+ accepts = [
+ Auth(),
+ Key.fields['key_id'],
+ key_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, key_id, key_fields):
+ key_fields = dict(filter(can_update, key_fields.items()))
+
+ # Get key information
+ keys = Keys(self.api, [key_id])
+ if not keys:
+ raise PLCInvalidArgument, "No such key"
+ key = keys[0]
+
+ if key['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local key"
+
+ if 'admin' not in self.caller['roles']:
+ if key['key_id'] not in self.caller['key_ids']:
+ raise PLCPermissionDenied, "Key must be associated with one of your accounts"
+
+ key.update(key_fields)
+ key.sync()
+
+ # Logging variables
+ self.event_objects = {'Key': [key['key_id']]}
+ self.message = 'key %d updated: %s' % \
+ (key['key_id'], ", ".join(key_fields.keys()))
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Messages import Message, Messages
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['template', 'enabled']
+
+class UpdateMessage(Method):
+ """
+ Updates the parameters of an existing message template with the
+ values in message_fields.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ message_fields = dict(filter(can_update, Message.fields.items()))
+
+ accepts = [
+ Auth(),
+ Message.fields['message_id'],
+ message_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, message_id, message_fields):
+ message_fields = dict(filter(can_update, message_fields.items()))
+
+ # Get message information
+ messages = Messages(self.api, [message_id])
+ if not messages:
+ raise PLCInvalidArgument, "No such message"
+ message = messages[0]
+
+ message.update(message_fields)
+ message.sync()
+ self.event_objects = {'Message': [message['message_id']]}
+
+ return 1
--- /dev/null
+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 Auth
+
+related_fields = Node.related_fields.keys()
+can_update = lambda (field, value): field in \
+ ['hostname', 'boot_state', 'model', 'version',
+ 'key', 'session', 'boot_nonce'] + \
+ related_fields
+
+class UpdateNode(Method):
+ """
+ Updates a node. Only the fields specified in node_fields are
+ updated, all other fields are left untouched.
+
+ PIs and techs can update only the nodes at their sites. Only
+ admins can update the key, session, and boot_nonce fields.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ node_fields = dict(filter(can_update, Node.fields.items() + Node.related_fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Node.fields['node_id'],
+ Node.fields['hostname']),
+ node_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, node_id_or_hostname, node_fields):
+ node_fields = dict(filter(can_update, node_fields.items()))
+
+ # Remove admin only fields
+ if 'admin' not in self.caller['roles']:
+ for key in 'key', 'session', 'boot_nonce':
+ if node_fields.has_key(key):
+ del node_fields[key]
+
+ # Get account information
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node"
+ node = nodes[0]
+
+ if node['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local node"
+
+ # 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"
+
+ # Make requested associations
+ for field in related_fields:
+ if field in node_fields:
+ node.associate(auth, field, node_fields[field])
+ node_fields.pop(field)
+
+ node.update(node_fields)
+ node.update_last_updated(False)
+ node.sync()
+
+ # Logging variables
+ self.event_objects = {'Node': [node['node_id']]}
+ self.message = 'Node %d updated: %s.' % \
+ (node['node_id'], ", ".join(node_fields.keys()))
+ if 'boot_state' in node_fields.keys():
+ self.message += ' boot_state updated to %s' % node_fields['boot_state']
+
+ return 1
--- /dev/null
+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 Auth
+
+related_fields = NodeGroup.related_fields.keys()
+can_update = lambda (field, value): field in \
+ ['name', 'description'] + \
+ related_fields
+
+class UpdateNodeGroup(Method):
+ """
+ Updates a custom node group.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ nodegroup_fields = dict(filter(can_update, NodeGroup.fields.items() + NodeGroup.related_fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(NodeGroup.fields['nodegroup_id'],
+ NodeGroup.fields['name']),
+ nodegroup_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, nodegroup_id_or_name, nodegroup_fields):
+ nodegroup_fields = dict(filter(can_update, nodegroup_fields.items()))
+
+ # Get nodegroup information
+ nodegroups = NodeGroups(self.api, [nodegroup_id_or_name])
+ if not nodegroups:
+ raise PLCInvalidArgument, "No such nodegroup"
+ nodegroup = nodegroups[0]
+
+ # Make requested associations
+ for field in related_fields:
+ if field in nodegroup_fields:
+ nodegroup.associate(auth, field, nodegroup_fields[field])
+ nodegroup_fields.pop(field)
+
+ nodegroup.update(nodegroup_fields)
+ nodegroup.sync()
+
+ # Logging variables
+ self.event_objects = {'NodeGroup': [nodegroup['nodegroup_id']]}
+ self.message = 'Node group %d updated: %s' % \
+ (nodegroup['nodegroup_id'], ", ".join(nodegroup_fields.keys()))
+ return 1
--- /dev/null
+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 Auth
+
+can_update = lambda (field, value): field not in \
+ ['nodenetwork_id','node_id']
+
+class UpdateNodeNetwork(Method):
+ """
+ Updates an existing node network. Any values specified in
+ nodenetwork_fields are used, otherwise defaults are
+ used. Acceptable values for method are dhcp and static. If type is
+ static, then ip, gateway, network, broadcast, netmask, and dns1
+ must all be specified in nodenetwork_fields. If type is dhcp,
+ these parameters, even if specified, are ignored.
+
+ PIs and techs may only update networks associated with their own
+ nodes. Admins may update any node network.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ nodenetwork_fields = dict(filter(can_update, NodeNetwork.fields.items()))
+
+ accepts = [
+ Auth(),
+ NodeNetwork.fields['nodenetwork_id'],
+ nodenetwork_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, nodenetwork_id, nodenetwork_fields):
+ nodenetwork_fields = dict(filter(can_update, nodenetwork_fields.items()))
+
+ # Get node network information
+ nodenetworks = NodeNetworks(self.api, [nodenetwork_id])
+ 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']])
+ 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(nodenetwork_fields)
+ nodenetwork.sync()
+
+ self.event_objects = {'NodeNetwork': [nodenetwork['nodenetwork_id']]}
+ self.message = "Node network %d updated: %s " % \
+ (nodenetwork['nodenetwork_id'], ", ".join(nodenetwork_fields.keys()))
+
+ return 1
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.NodeNetworkSettings import NodeNetworkSetting, NodeNetworkSettings
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+
+from PLC.Nodes import Nodes
+from PLC.Sites import Sites
+
+class UpdateNodeNetworkSetting(Method):
+ """
+ Updates the value of an existing nodenetwork setting
+
+ Access rights depend on the nodenetwork setting type.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech', 'user']
+
+ accepts = [
+ Auth(),
+ NodeNetworkSetting.fields['nodenetwork_setting_id'],
+ NodeNetworkSetting.fields['value']
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ object_type = 'NodeNetwork'
+
+ def call(self, auth, nodenetwork_setting_id, value):
+ nodenetwork_settings = NodeNetworkSettings(self.api, [nodenetwork_setting_id])
+ if not nodenetwork_settings:
+ raise PLCInvalidArgument, "No such nodenetwork setting %r"%nodenetwork_setting_id
+ nodenetwork_setting = nodenetwork_settings[0]
+
+ ### reproducing a check from UpdateSliceAttribute, looks dumb though
+ nodenetworks = NodeNetworks(self.api, [nodenetwork_setting['nodenetwork_id']])
+ if not nodenetworks:
+ raise PLCInvalidArgument, "No such nodenetwork %r"%nodenetwork_setting['nodenetwork_id']
+ nodenetwork = nodenetworks[0]
+
+ assert nodenetwork_setting['nodenetwork_setting_id'] in nodenetwork['nodenetwork_setting_ids']
+
+ # check permission : it not admin, is the user affiliated with the right site
+ if 'admin' not in self.caller['roles']:
+ # locate node
+ node = Nodes (self.api,[nodenetwork['node_id']])[0]
+ # locate site
+ site = Sites (self.api, [node['site_id']])[0]
+ # check caller is affiliated with this site
+ if self.caller['person_id'] not in site['person_ids']:
+ raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+
+ required_min_role = nodenetwork_setting_type ['min_role_id']
+ if required_min_role is not None and \
+ min(self.caller['role_ids']) > required_min_role:
+ raise PLCPermissionDenied, "Not allowed to modify the specified nodenetwork setting, requires role %d",required_min_role
+
+ nodenetwork_setting['value'] = value
+ nodenetwork_setting.sync()
+
+ self.object_ids = [nodenetwork_setting['nodenetwork_setting_id']]
+ return 1
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'description', 'category', 'min_role_id']
+
+class UpdateNodeNetworkSettingType(Method):
+ """
+ Updates the parameters of an existing setting type
+ with the values in nodenetwork_setting_type_fields.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ nodenetwork_setting_type_fields = dict(filter(can_update, NodeNetworkSettingType.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(NodeNetworkSettingType.fields['nodenetwork_setting_type_id'],
+ NodeNetworkSettingType.fields['name']),
+ nodenetwork_setting_type_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, nodenetwork_setting_type_id_or_name, nodenetwork_setting_type_fields):
+ nodenetwork_setting_type_fields = dict(filter(can_update, nodenetwork_setting_type_fields.items()))
+
+ nodenetwork_setting_types = NodeNetworkSettingTypes(self.api, [nodenetwork_setting_type_id_or_name])
+ if not nodenetwork_setting_types:
+ raise PLCInvalidArgument, "No such setting type"
+ nodenetwork_setting_type = nodenetwork_setting_types[0]
+
+ nodenetwork_setting_type.update(nodenetwork_setting_type_fields)
+ nodenetwork_setting_type.sync()
+ self.object_ids = [nodenetwork_setting_type['nodenetwork_setting_type_id']]
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUs import PCU, PCUs
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field not in \
+ ['pcu_id', 'site_id']
+
+class UpdatePCU(Method):
+ """
+ Updates the parameters of an existing PCU with the values in
+ pcu_fields.
+
+ Non-admins may only update PCUs at their sites.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'tech']
+
+ update_fields = dict(filter(can_update, PCU.fields.items()))
+
+ accepts = [
+ Auth(),
+ PCU.fields['pcu_id'],
+ update_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, pcu_id, pcu_fields):
+ pcu_fields = dict(filter(can_update, pcu_fields.items()))
+
+ # Get associated PCU details
+ pcus = PCUs(self.api, [pcu_id])
+ if not pcus:
+ raise PLCInvalidArgument, "No such PCU"
+ pcu = pcus[0]
+
+ if 'admin' not in self.caller['roles']:
+ if pcu['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Not allowed to update that PCU"
+
+ pcu.update(pcu_fields)
+ pcu.sync()
+
+ # Logging variables
+ self.event_objects = {'PCU': [pcu['pcu_id']]}
+ self.message = 'PCU %d updated: %s' % \
+ (pcu['pcu_id'], ", ".join(pcu_fields.keys()))
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUProtocolTypes import PCUProtocolType, PCUProtocolTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['pcu_type_id', 'port', 'protocol', 'supported']
+
+class UpdatePCUProtocolType(Method):
+ """
+ Updates a pcu protocol type. Only the fields specified in
+ port_typee_fields are updated, all other fields are left untouched.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ protocol_type_fields = dict(filter(can_update, PCUProtocolType.fields.items()))
+
+ accepts = [
+ Auth(),
+ PCUProtocolType.fields['pcu_protocol_type_id'],
+ protocol_type_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, protocol_type_id, protocol_type_fields):
+ protocol_type_fields = dict(filter(can_update, protocol_type_fields.items()))
+
+ protocol_types = PCUProtocolTypes(self.api, [protocol_type_id])
+ if not protocol_types:
+ raise PLCInvalidArgument, "No such pcu protocol type"
+
+ protocol_type = protocol_types[0]
+ protocol_type.update(protocol_type_fields)
+ protocol_type.sync()
+ self.event_objects = {'PCUProtocolType': [protocol_type['pcu_protocol_type_id']]}
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.PCUTypes import PCUType, PCUTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['model', 'name']
+
+class UpdatePCUType(Method):
+ """
+ Updates a PCU type. Only the fields specified in
+ pcu_typee_fields are updated, all other fields are left untouched.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ pcu_type_fields = dict(filter(can_update, PCUType.fields.items()))
+
+ accepts = [
+ Auth(),
+ PCUType.fields['pcu_type_id'],
+ pcu_type_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, pcu_type_id, pcu_type_fields):
+ pcu_type_fields = dict(filter(can_update, pcu_type_fields.items()))
+
+ pcu_types = PCUTypes(self.api, [pcu_type_id])
+ if not pcu_types:
+ raise PLCInvalidArgument, "No such pcu type"
+
+ pcu_type = pcu_types[0]
+ pcu_type.update(pcu_type_fields)
+ pcu_type.sync()
+ self.event_objects = {'PCUType': [pcu_type['pcu_type_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Peers import Peer, Peers
+
+can_update = lambda (field, value): field in \
+ ['peername', 'peer_url', 'key', 'cacert']
+
+class UpdatePeer(Method):
+ """
+ Updates a peer. Only the fields specified in peer_fields are
+ updated, all other fields are left untouched.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ peer_fields = dict(filter(can_update, Peer.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Peer.fields['peer_id'],
+ Peer.fields['peername']),
+ peer_fields
+ ]
+
+ returns = Parameter(int, "1 if successful")
+
+ def call(self, auth, peer_id_or_name, peer_fields):
+ peer_fields = dict(filter(can_update, peer_fields.items()))
+
+ # Get account information
+ peers = Peers(self.api, [peer_id_or_name])
+ if not peers:
+ raise PLCInvalidArgument, "No such peer"
+ peer = peers[0]
+
+ if isinstance(self.caller, Peer):
+ if self.caller['peer_id'] != peer['peer_id']:
+ raise PLCPermissionDenied, "Not allowed to update specified peer"
+
+ peer.update(peer_fields)
+ peer.sync()
+
+ # Log affected objects
+ self.event_objects = {'Peer': [peer['peer_id']]}
+
+ return 1
--- /dev/null
+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 Auth
+from PLC.sendmail import sendmail
+
+related_fields = Person.related_fields.keys()
+can_update = lambda (field, value): field in \
+ ['first_name', 'last_name', 'title', 'email',
+ 'password', 'phone', 'url', 'bio', 'accepted_aup',
+ 'enabled'] + related_fields
+
+class UpdatePerson(Method):
+ """
+ Updates a person. Only the fields specified in person_fields are
+ updated, all other fields are left untouched.
+
+ 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']
+
+ person_fields = dict(filter(can_update, Person.fields.items() + Person.related_fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ person_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, person_id_or_email, person_fields):
+ person_fields = dict(filter(can_update, person_fields.items()))
+
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account"
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account"
+
+ # Authenticated function
+ assert self.caller is not None
+
+ # Check if we can update this account
+ if not self.caller.can_update(person):
+ raise PLCPermissionDenied, "Not allowed to update specified account"
+
+ # Make requested associations
+ for field in related_fields:
+ if field in person_fields:
+ person.associate(auth, field, person_fields[field])
+ person_fields.pop(field)
+
+ person.update(person_fields)
+ person.update_last_updated(False)
+ person.sync()
+
+ if 'enabled' in person_fields:
+ To = [("%s %s" % (person['first_name'], person['last_name']), person['email'])]
+ Cc = []
+ if person['enabled']:
+ Subject = "%s account enabled" % (self.api.config.PLC_NAME)
+ Body = "Your %s account has been enabled. Please visit %s to access your account." % (self.api.config.PLC_NAME, self.api.config.PLC_WWW_HOST)
+ else:
+ Subject = "%s account disabled" % (self.api.config.PLC_NAME)
+ Body = "Your %s account has been disabled. Please contact your PI or PlanetLab support for more information" % (self.api.config.PLC_NAME)
+ sendmail(self.api, To = To, Cc = Cc, Subject = Subject, Body = Body)
+
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']]}
+
+ # Redact password
+ if 'password' in person_fields:
+ person_fields['password'] = "Removed by API"
+ self.message = 'Person %d updated: %s.' % \
+ (person['person_id'], person_fields.keys())
+ if 'enabled' in person_fields:
+ self.message += ' Person enabled'
+
+ return 1
--- /dev/null
+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 Auth
+
+related_fields = Site.related_fields.keys()
+can_update = lambda (field, value): field in \
+ ['name', 'abbreviated_name', 'login_base',
+ 'is_public', 'latitude', 'longitude', 'url',
+ 'max_slices', 'max_slivers', 'enabled', 'ext_consortium_id'] + \
+ related_fields
+
+class UpdateSite(Method):
+ """
+ Updates a site. Only the fields specified in update_fields are
+ updated, all other fields are left untouched.
+
+ PIs can only update sites they are a member of. Only admins can
+ update max_slices, max_slivers, and login_base.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi']
+
+ site_fields = dict(filter(can_update, Site.fields.items() + Site.related_fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Site.fields['site_id'],
+ Site.fields['login_base']),
+ site_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, site_id_or_login_base, site_fields):
+ site_fields = dict(filter(can_update, site_fields.items()))
+
+ # Get site information
+ sites = Sites(self.api, [site_id_or_login_base])
+ if not sites:
+ raise PLCInvalidArgument, "No such site"
+ site = sites[0]
+
+ if site['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local site"
+
+ # 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"
+
+ # Remove admin only fields
+ for key in 'max_slices', 'max_slivers', 'login_base':
+ if key in site_fields:
+ del site_fields[key]
+
+ # Make requested associations
+ for field in related_fields:
+ if field in site_fields:
+ site.associate(auth, field, site_fields[field])
+ site_fields.pop(field)
+
+ site.update(site_fields)
+ site.update_last_updated(False)
+ site.sync()
+
+ # Logging variables
+ self.event_objects = {'Site': [site['site_id']]}
+ self.message = 'Site %d updated: %s' % \
+ (site['site_id'], ", ".join(site_fields.keys()))
+
+ return 1
--- /dev/null
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+
+related_fields = Slice.related_fields.keys()
+can_update = lambda (field, value): field in \
+ ['instantiation', 'url', 'description', 'max_nodes', 'expires'] + \
+ related_fields
+
+
+class UpdateSlice(Method):
+ """
+ Updates the parameters of an existing slice with the values in
+ slice_fields.
+
+ Users may only update slices of which they are members. PIs may
+ update any of the slices at their sites, or any slices of which
+ they are members. Admins may update any slice.
+
+ Only PIs and admins may update max_nodes. Slices cannot be renewed
+ (by updating the expires parameter) more than 8 weeks into the
+ future.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ slice_fields = dict(filter(can_update, Slice.fields.items() + Slice.related_fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(Slice.fields['slice_id'],
+ Slice.fields['name']),
+ slice_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_id_or_name, slice_fields):
+ slice_fields = dict(filter(can_update, slice_fields.items()))
+
+ slices = Slices(self.api, [slice_id_or_name])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ if slice['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local slice"
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ # Renewing
+ if 'expires' in slice_fields and slice_fields['expires'] > slice['expires']:
+ sites = Sites(self.api, [slice['site_id']])
+ assert sites
+ site = sites[0]
+
+ if site['max_slices'] < 0:
+ raise PLCInvalidArgument, "Slice creation and renewal have been disabled for the site"
+
+ # Maximum expiration date is 8 weeks from now
+ # XXX Make this configurable
+ max_expires = time.time() + (8 * 7 * 24 * 60 * 60)
+
+ if 'admin' not in self.caller['roles'] and slice_fields['expires'] > max_expires:
+ raise PLCInvalidArgument, "Cannot renew a slice beyond 8 weeks from now"
+
+ # XXX Make this a configurable policy
+ if slice['description'] is None or not slice['description'].strip():
+ if 'description' not in slice_fields or slice_fields['description'] is None or \
+ not slice_fields['description'].strip():
+ raise PLCInvalidArgument, "Cannot renew a slice with an empty description or URL"
+
+ if slice['url'] is None or not slice['url'].strip():
+ if 'url' not in slice_fields or slice_fields['url'] is None or \
+ not slice_fields['url'].strip():
+ raise PLCInvalidArgument, "Cannot renew a slice with an empty description or URL"
+
+ if 'max_nodes' in slice_fields and slice_fields['max_nodes'] != slice['max_nodes']:
+ if 'admin' not in self.caller['roles'] and \
+ 'pi' not in self.caller['roles']:
+ raise PLCInvalidArgument, "Only admins and PIs may update max_nodes"
+
+ # Make requested associations
+ for field in related_fields:
+ if field in slice_fields:
+ slice.associate(auth, field, slice_fields[field])
+ slice_fields.pop(field)
+
+ slice.update(slice_fields)
+ slice.sync()
+
+ self.event_objects = {'Slice': [slice['slice_id']]}
+
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Slices import Slice, Slices
+from PLC.InitScripts import InitScript, InitScripts
+from PLC.Auth import Auth
+
+class UpdateSliceAttribute(Method):
+ """
+ Updates the value of an existing slice or sliver attribute.
+
+ Users may only update attributes of slices or slivers of which
+ they are members. PIs may only update attributes of slices or
+ slivers at their sites, or of which they are members. Admins may
+ update attributes of any slice or sliver.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin', 'pi', 'user']
+
+ accepts = [
+ Auth(),
+ SliceAttribute.fields['slice_attribute_id'],
+ Mixed(SliceAttribute.fields['value'],
+ InitScript.fields['name'])
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, slice_attribute_id, value):
+ slice_attributes = SliceAttributes(self.api, [slice_attribute_id])
+ if not slice_attributes:
+ raise PLCInvalidArgument, "No such slice attribute"
+ slice_attribute = slice_attributes[0]
+
+ slices = Slices(self.api, [slice_attribute['slice_id']])
+ if not slices:
+ raise PLCInvalidArgument, "No such slice"
+ slice = slices[0]
+
+ assert slice_attribute['slice_attribute_id'] in slice['slice_attribute_ids']
+
+ if 'admin' not in self.caller['roles']:
+ if self.caller['person_id'] in slice['person_ids']:
+ pass
+ elif 'pi' not in self.caller['roles']:
+ raise PLCPermissionDenied, "Not a member of the specified slice"
+ elif slice['site_id'] not in self.caller['site_ids']:
+ raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+ if slice_attribute['min_role_id'] is not None and \
+ min(self.caller['role_ids']) > slice_attribute['min_role_id']:
+ raise PLCPermissionDenied, "Not allowed to update the specified attribute"
+
+ if slice_attribute['name'] in ['initscript']:
+ initscripts = InitScripts(self.api, {'enabled': True, 'name': value})
+ if not initscripts:
+ raise PLCInvalidArgument, "No such plc initscript"
+
+ slice_attribute['value'] = unicode(value)
+ slice_attribute.sync()
+ self.event_objects = {'SliceAttribute': [slice_attribute['slice_attribute_id']]}
+ return 1
--- /dev/null
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+ ['name', 'description', 'min_role_id']
+
+class UpdateSliceAttributeType(Method):
+ """
+ Updates the parameters of an existing attribute with the values in
+ attribute_type_fields.
+
+ Returns 1 if successful, faults otherwise.
+ """
+
+ roles = ['admin']
+
+ attribute_type_fields = dict(filter(can_update, SliceAttributeType.fields.items()))
+
+ accepts = [
+ Auth(),
+ Mixed(SliceAttributeType.fields['attribute_type_id'],
+ SliceAttributeType.fields['name']),
+ attribute_type_fields
+ ]
+
+ returns = Parameter(int, '1 if successful')
+
+ def call(self, auth, attribute_type_id_or_name, attribute_type_fields):
+ attribute_type_fields = dict(filter(can_update, attribute_type_fields.items()))
+
+ attribute_types = SliceAttributeTypes(self.api, [attribute_type_id_or_name])
+ if not attribute_types:
+ raise PLCInvalidArgument, "No such attribute"
+ attribute_type = attribute_types[0]
+
+ attribute_type.update(attribute_type_fields)
+ attribute_type.sync()
+ self.event_objects = {'AttributeType': [attribute_type['attribute_type_id']]}
+
+ return 1
--- /dev/null
+import random
+import base64
+import time
+import urllib
+
+from PLC.Debug import log
+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.Messages import Message, Messages
+from PLC.Auth import Auth
+from PLC.sendmail import sendmail
+
+class VerifyPerson(Method):
+ """
+ Verify a new (must be disabled) user's e-mail address and registration.
+
+ If verification_key is not specified, then a new verification_key
+ will be generated and stored with the user's account. The key will
+ be e-mailed to the user in the form of a link to a web page.
+
+ The web page should verify the key by calling this function again
+ and specifying verification_key. If the key matches what has been
+ stored in the user's account, then an e-mail will be sent to the
+ user's PI (and support if the user is requesting a PI role),
+ asking the PI (or support) to enable the account.
+
+ Returns 1 if the verification key if valid.
+ """
+
+ roles = ['admin']
+
+ accepts = [
+ Auth(),
+ Mixed(Person.fields['person_id'],
+ Person.fields['email']),
+ Person.fields['verification_key'],
+ Person.fields['verification_expires']
+ ]
+
+ returns = Parameter(int, '1 if verification_key is valid')
+
+ def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None):
+ # Get account information
+ persons = Persons(self.api, [person_id_or_email])
+ if not persons:
+ raise PLCInvalidArgument, "No such account %r"%person_id_or_email
+ person = persons[0]
+
+ if person['peer_id'] is not None:
+ raise PLCInvalidArgument, "Not a local account %r"%person_id_or_email
+
+ if person['enabled']:
+ raise PLCInvalidArgument, "Account %r must be new (disabled)"%person_id_or_email
+
+ # Get the primary site name
+ person_sites = Sites(self.api, person['site_ids'])
+ if person_sites:
+ site_name = person_sites[0]['name']
+ else:
+ site_name = "No Site"
+
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ random_key = base64.b64encode("".join(map(chr, bytes)))
+
+ if verification_key is None or \
+ (verification_key is not None and person['verification_expires'] and \
+ person['verification_expires'] < time.time()):
+ # Only allow one verification at a time
+ if person['verification_expires'] is not None and \
+ person['verification_expires'] > time.time():
+ raise PLCPermissionDenied, "Verification request already pending"
+
+ if verification_expires is None:
+ verification_expires = int(time.time() + (24 * 60 * 60))
+
+ person['verification_key'] = random_key
+ person['verification_expires'] = verification_expires
+ person.sync()
+
+ # Send e-mail to user
+ To = ("%s %s" % (person['first_name'], person['last_name']), person['email'])
+ Cc = None
+
+ message_id = 'Verify account'
+
+
+ elif verification_key is not None:
+ if person['verification_key'] is None or \
+ person['verification_expires'] is None:
+ raise PLCPermissionDenied, "Invalid Verification key"
+ elif person['verification_key'] != verification_key:
+ raise PLCPermissionDenied, "Verification key incorrect"
+ else:
+ person['verification_key'] = None
+ person['verification_expires'] = None
+ person.sync()
+
+ # Get the PI(s) of each site that the user is registering with
+ person_ids = set()
+ for site in person_sites:
+ person_ids.update(site['person_ids'])
+ persons = Persons(self.api, person_ids)
+ pis = filter(lambda person: 'pi' in person['roles'] and person['enabled'], persons)
+
+ # Send e-mail to PI(s) and copy the user
+ To = [("%s %s" % (pi['first_name'], pi['last_name']), pi['email']) for pi in pis]
+ Cc = ("%s %s" % (person['first_name'], person['last_name']), person['email'])
+
+ if 'pi' in person['roles']:
+ # And support if user is requesting a PI role
+ To.append(("%s Support" % self.api.config.PLC_NAME,
+ self.api.config.PLC_MAIL_SUPPORT_ADDRESS))
+ message_id = 'New PI account'
+ else:
+ message_id = 'New account'
+
+ messages = Messages(self.api, [message_id])
+ if messages:
+ # Send message to user
+ message = messages[0]
+
+ params = {'PLC_NAME': self.api.config.PLC_NAME,
+ 'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
+ 'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
+ 'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
+ 'person_id': person['person_id'],
+ # Will be used in a URL, so must quote appropriately
+ 'verification_key': urllib.quote_plus(random_key),
+ 'site_name': site_name,
+ 'first_name': person['first_name'],
+ 'last_name': person['last_name'],
+ 'email': person['email'],
+ 'roles': ", ".join(person['roles'])}
+
+ sendmail(self.api,
+ To = To,
+ Cc = Cc,
+ Subject = message['subject'] % params,
+ Body = message['template'] % params)
+ else:
+ print >> log, "Warning: No message template '%s'" % message_id
+
+ # Logging variables
+ self.event_objects = {'Person': [person['person_id']]}
+ self.message = message_id
+
+ if verification_key is not None and person['verification_expires'] and \
+ person['verification_expires'] < time.time():
+ raise PLCPermissionDenied, "Verification key has expired. Another email has been sent."
+
+ return 1
--- /dev/null
+methods = """
+AddAddressType
+AddAddressTypeToAddress
+AddBootState
+AddConfFile
+AddConfFileToNodeGroup
+AddConfFileToNode
+AddInitScript
+AddKeyType
+AddMessage
+AddNetworkMethod
+AddNetworkType
+AddNodeGroup
+AddNodeNetwork
+AddNodeNetworkSetting
+AddNodeNetworkSettingType
+AddNode
+AddNodeToNodeGroup
+AddNodeToPCU
+AddPCUProtocolType
+AddPCU
+AddPCUType
+AddPeer
+AddPersonKey
+AddPerson
+AddPersonToSite
+AddPersonToSlice
+AddRole
+AddRoleToPerson
+AddSession
+AddSiteAddress
+AddSite
+AddSliceAttribute
+AddSliceAttributeType
+AddSliceInstantiation
+AddSlice
+AddSliceToNodes
+AddSliceToNodesWhitelist
+AdmAddAddressType
+AdmAddNodeGroup
+AdmAddNodeNetwork
+AdmAddNode
+AdmAddNodeToNodeGroup
+AdmAddPersonKey
+AdmAddPerson
+AdmAddPersonToSite
+AdmAddSitePowerControlUnit
+AdmAddSite
+AdmAssociateNodeToPowerControlUnitPort
+AdmAuthCheck
+AdmDeleteAddressType
+AdmDeleteAllPersonKeys
+AdmDeleteNodeGroup
+AdmDeleteNodeNetwork
+AdmDeleteNode
+AdmDeletePersonKeys
+AdmDeletePerson
+AdmDeleteSitePowerControlUnit
+AdmDeleteSite
+AdmDisassociatePowerControlUnitPort
+AdmGenerateNodeConfFile
+AdmGetAllAddressTypes
+AdmGetAllKeyTypes
+AdmGetAllNodeNetworks
+AdmGetAllRoles
+AdmGetNodeGroupNodes
+AdmGetNodeGroups
+AdmGetNodes
+AdmGetPersonKeys
+AdmGetPersonRoles
+AdmGetPersonSites
+AdmGetPersons
+AdmGetPowerControlUnitNodes
+AdmGetPowerControlUnits
+AdmGetSiteNodes
+AdmGetSitePersons
+AdmGetSitePIs
+AdmGetSitePowerControlUnits
+AdmGetSites
+AdmGetSiteTechContacts
+AdmGrantRoleToPerson
+AdmIsPersonInRole
+AdmQueryConfFile
+AdmQueryNode
+AdmQueryPerson
+AdmQueryPowerControlUnit
+AdmQuerySite
+AdmRebootNode
+AdmRemoveNodeFromNodeGroup
+AdmRemovePersonFromSite
+AdmRevokeRoleFromPerson
+AdmSetPersonEnabled
+AdmSetPersonPrimarySite
+AdmUpdateNodeGroup
+AdmUpdateNodeNetwork
+AdmUpdateNode
+AdmUpdatePerson
+AdmUpdateSitePowerControlUnit
+AdmUpdateSite
+AnonAdmGetNodeGroups
+AuthCheck
+BlacklistKey
+BootCheckAuthentication
+BootGetNodeDetails
+BootNotifyOwners
+BootUpdateNode
+DeleteAddress
+DeleteAddressTypeFromAddress
+DeleteAddressType
+DeleteBootState
+DeleteConfFileFromNodeGroup
+DeleteConfFileFromNode
+DeleteConfFile
+DeleteInitScript
+DeleteKey
+DeleteKeyType
+DeleteMessage
+DeleteNetworkMethod
+DeleteNetworkType
+DeleteNodeFromNodeGroup
+DeleteNodeFromPCU
+DeleteNodeGroup
+DeleteNodeNetwork
+DeleteNodeNetworkSetting
+DeleteNodeNetworkSettingType
+DeleteNode
+DeletePCUProtocolType
+DeletePCU
+DeletePCUType
+DeletePeer
+DeletePersonFromSite
+DeletePersonFromSlice
+DeletePerson
+DeleteRoleFromPerson
+DeleteRole
+DeleteSession
+DeleteSite
+DeleteSliceAttribute
+DeleteSliceAttributeType
+DeleteSliceFromNodes
+DeleteSliceFromNodesWhitelist
+DeleteSliceInstantiation
+DeleteSlice
+GenerateNodeConfFile
+GetAddresses
+GetAddressTypes
+GetBootMedium
+GetBootStates
+GetConfFiles
+GetEventObjects
+GetEvents
+GetInitScripts
+GetKeys
+GetKeyTypes
+GetMessages
+GetNetworkMethods
+GetNetworkTypes
+GetNodeGroups
+GetNodeNetworkSettings
+GetNodeNetworkSettingTypes
+GetNodeNetworks
+GetNodes
+GetPCUProtocolTypes
+GetPCUs
+GetPCUTypes
+GetPeerData
+GetPeerName
+GetPeers
+GetPersons
+GetPlcRelease
+GetRoles
+GetSession
+GetSessions
+GetSites
+GetSliceAttributes
+GetSliceAttributeTypes
+GetSliceInstantiations
+GetSliceKeys
+GetSlicesMD5
+GetSlices
+GetSliceTicket
+GetSlivers
+GetWhitelist
+NotifyPersons
+NotifySupport
+RebootNode
+RefreshPeer
+ResetPassword
+SetPersonPrimarySite
+SliceCreate
+SliceDelete
+SliceExtendedInfo
+SliceGetTicket
+SliceInfo
+SliceListNames
+SliceListUserSlices
+SliceNodesAdd
+SliceNodesDel
+SliceNodesList
+SliceRenew
+SliceTicketGet
+SliceUpdate
+SliceUserAdd
+SliceUserDel
+SliceUsersList
+system.listMethods
+system.methodHelp
+system.methodSignature
+system.multicall
+UpdateAddress
+UpdateAddressType
+UpdateConfFile
+UpdateInitScript
+UpdateKey
+UpdateMessage
+UpdateNodeGroup
+UpdateNodeNetwork
+UpdateNodeNetworkSetting
+UpdateNodeNetworkSettingType
+UpdateNode
+UpdatePCUProtocolType
+UpdatePCU
+UpdatePCUType
+UpdatePeer
+UpdatePerson
+UpdateSite
+UpdateSliceAttribute
+UpdateSliceAttributeType
+UpdateSlice
+VerifyPerson
+""".split()
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter
+import PLC.Methods
+
+class listMethods(Method):
+ """
+ This method lists all the methods that the XML-RPC server knows
+ how to dispatch.
+ """
+
+ roles = []
+ accepts = []
+ returns = Parameter(list, 'List of methods')
+
+ def __init__(self, api):
+ Method.__init__(self, api)
+ self.name = "system.listMethods"
+
+ def call(self):
+ return self.api.methods
--- /dev/null
+from PLC.Method import Method
+from PLC.Parameter import Parameter
+
+class methodHelp(Method):
+ """
+ Returns help text if defined for the method passed, otherwise
+ returns an empty string.
+ """
+
+ roles = []
+ accepts = [Parameter(str, 'Method name')]
+ returns = Parameter(str, 'Method help')
+
+ def __init__(self, api):
+ Method.__init__(self, api)
+ self.name = "system.methodHelp"
+
+ def call(self, method):
+ function = self.api.callable(method)
+ return function.help()
--- /dev/null
+from PLC.Parameter import Parameter, Mixed
+from PLC.Method import Method, xmlrpc_type
+
+class methodSignature(Method):
+ """
+ Returns an array of known signatures (an array of arrays) for the
+ method name passed. If no signatures are known, returns a
+ none-array (test for type != array to detect missing signature).
+ """
+
+ roles = []
+ accepts = [Parameter(str, "Method name")]
+ returns = [Parameter([str], "Method signature")]
+
+ def __init__(self, api):
+ Method.__init__(self, api)
+ self.name = "system.methodSignature"
+
+ def possible_signatures(self, signature, arg):
+ """
+ Return a list of the possible new signatures given a current
+ signature and the next argument.
+ """
+
+ if isinstance(arg, Mixed):
+ arg_types = [xmlrpc_type(mixed_arg) for mixed_arg in arg]
+ else:
+ arg_types = [xmlrpc_type(arg)]
+
+ return [signature + [arg_type] for arg_type in arg_types]
+
+ def signatures(self, returns, args):
+ """
+ Returns a list of possible signatures given a return value and
+ a set of arguments.
+ """
+
+ signatures = [[xmlrpc_type(returns)]]
+
+ for arg in args:
+ # Create lists of possible new signatures for each current
+ # signature. Reduce the list of lists back down to a
+ # single list.
+ signatures = reduce(lambda a, b: a + b,
+ [self.possible_signatures(signature, arg) \
+ for signature in signatures])
+
+ return signatures
+
+ def call(self, method):
+ function = self.api.callable(method)
+ (min_args, max_args, defaults) = function.args()
+
+ signatures = []
+
+ assert len(max_args) >= len(min_args)
+ for num_args in range(len(min_args), len(max_args) + 1):
+ signatures += self.signatures(function.returns, function.accepts[:num_args])
+
+ return signatures
--- /dev/null
+import sys
+import xmlrpclib
+
+from PLC.Parameter import Parameter, Mixed
+from PLC.Method import Method
+
+class multicall(Method):
+ """
+ Process an array of calls, and return an array of results. Calls
+ should be structs of the form
+
+ {'methodName': string, 'params': array}
+
+ Each result will either be a single-item array containg the result
+ value, or a struct of the form
+
+ {'faultCode': int, 'faultString': string}
+
+ This is useful when you need to make lots of small calls without
+ lots of round trips.
+ """
+
+ roles = []
+ accepts = [[{'methodName': Parameter(str, "Method name"),
+ 'params': Parameter(list, "Method arguments")}]]
+ returns = Mixed([Mixed()],
+ {'faultCode': Parameter(int, "XML-RPC fault code"),
+ 'faultString': Parameter(int, "XML-RPC fault detail")})
+
+ def __init__(self, api):
+ Method.__init__(self, api)
+ self.name = "system.multicall"
+
+ def call(self, calls):
+ # Some error codes, borrowed from xmlrpc-c.
+ REQUEST_REFUSED_ERROR = -507
+
+ results = []
+ for call in calls:
+ try:
+ name = call['methodName']
+ params = call['params']
+ if name == 'system.multicall':
+ errmsg = "Recursive system.multicall forbidden"
+ raise xmlrpclib.Fault(REQUEST_REFUSED_ERROR, errmsg)
+ result = [self.api.call(self.source, name, *params)]
+ except xmlrpclib.Fault, fault:
+ result = {'faultCode': fault.faultCode,
+ 'faultString': fault.faultString}
+ except:
+ errmsg = "%s:%s" % (sys.exc_type, sys.exc_value)
+ result = {'faultCode': 1, 'faultString': errmsg}
+ results.append(result)
+ return results
--- /dev/null
+#
+# Functions for interacting with the network_methods table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: NetworkMethods.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class NetworkMethod(Row):
+ """
+ Representation of a row in the network_methods table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'network_methods'
+ primary_key = 'method'
+ join_tables = ['nodenetworks']
+ fields = {
+ 'method': Parameter(str, "Network method", max = 20),
+ }
+
+ def validate_method(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Network method must be specified"
+
+ # Make sure network method does not alredy exist
+ conflicts = NetworkMethods(self.api, [name])
+ if conflicts:
+ raise PLCInvalidArgument, "Network method name already in use"
+
+ return name
+
+class NetworkMethods(Table):
+ """
+ Representation of the network_methods table in the database.
+ """
+
+ def __init__(self, api, methods = None):
+ Table.__init__(self, api, NetworkMethod)
+
+ sql = "SELECT %s FROM network_methods" % \
+ ", ".join(NetworkMethod.fields)
+
+ if methods:
+ sql += " WHERE method IN (%s)" % ", ".join(map(api.db.quote, methods))
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the network_types table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: NetworkTypes.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class NetworkType(Row):
+ """
+ Representation of a row in the network_types table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'network_types'
+ primary_key = 'type'
+ join_tables = ['nodenetworks']
+ fields = {
+ 'type': Parameter(str, "Network type", max = 20),
+ }
+
+ def validate_type(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Network type must be specified"
+
+ # Make sure network type does not alredy exist
+ conflicts = NetworkTypes(self.api, [name])
+ if conflicts:
+ raise PLCInvalidArgument, "Network type name already in use"
+
+ return name
+
+class NetworkTypes(Table):
+ """
+ Representation of the network_types table in the database.
+ """
+
+ def __init__(self, api, types = None):
+ Table.__init__(self, api, NetworkType)
+
+ sql = "SELECT %s FROM network_types" % \
+ ", ".join(NetworkType.fields)
+
+ if types:
+ sql += " WHERE type IN (%s)" % ", ".join(map(api.db.quote, types))
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the nodegroups table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: NodeGroups.py 5666 2007-11-06 21:52:21Z tmack $
+#
+
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.Nodes import Node, Nodes
+
+class NodeGroup(Row):
+ """
+ Representation of a row in the nodegroups table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ table_name = 'nodegroups'
+ primary_key = 'nodegroup_id'
+ join_tables = ['nodegroup_node', 'conf_file_nodegroup']
+ fields = {
+ 'nodegroup_id': Parameter(int, "Node group identifier"),
+ 'name': Parameter(str, "Node group name", max = 50),
+ 'description': Parameter(str, "Node group description", max = 200, nullok = True),
+ 'node_ids': Parameter([int], "List of nodes in this node group"),
+ 'conf_file_ids': Parameter([int], "List of configuration files specific to this node group"),
+ }
+ related_fields = {
+ 'conf_files': [Parameter(int, "ConfFile identifier")],
+ 'nodes': [Mixed(Parameter(int, "Node identifier"),
+ Parameter(str, "Fully qualified hostname"))]
+ }
+
+ def validate_name(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Invalid node group name"
+
+ # Make sure node group does not alredy exist
+ conflicts = NodeGroups(self.api, [name])
+ for nodegroup in conflicts:
+ if 'nodegroup_id' not in self or self['nodegroup_id'] != nodegroup['nodegroup_id']:
+ raise PLCInvalidArgument, "Node group name already in use"
+
+ return name
+
+ def add_node(self, node, commit = True):
+ """
+ Add node to existing nodegroup.
+ """
+
+ assert 'nodegroup_id' in self
+ assert isinstance(node, Node)
+ assert 'node_id' in node
+
+ node_id = node['node_id']
+ nodegroup_id = self['nodegroup_id']
+
+ if node_id not in self['node_ids']:
+ assert nodegroup_id not in node['nodegroup_ids']
+
+ self.api.db.do("INSERT INTO nodegroup_node (nodegroup_id, node_id)" \
+ " VALUES(%(nodegroup_id)d, %(node_id)d)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].append(node_id)
+ node['nodegroup_ids'].append(nodegroup_id)
+
+ def remove_node(self, node, commit = True):
+ """
+ Remove node from existing nodegroup.
+ """
+
+ assert 'nodegroup_id' in self
+ assert isinstance(node, Node)
+ assert 'node_id' in node
+
+ node_id = node['node_id']
+ nodegroup_id = self['nodegroup_id']
+
+ if node_id in self['node_ids']:
+ assert nodegroup_id in node['nodegroup_ids']
+
+ self.api.db.do("DELETE FROM nodegroup_node" \
+ " WHERE nodegroup_id = %(nodegroup_id)d" \
+ " AND node_id = %(node_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].remove(node_id)
+ node['nodegroup_ids'].remove(nodegroup_id)
+
+ def associate_nodes(self, auth, field, value):
+ """
+ Adds nodes found in value list to this nodegroup (using AddNodeToNodeGroup).
+ Deletes nodes not found in value list from this slice (using DeleteNodeFromNodeGroup).
+ """
+
+ assert 'node_ids' in self
+ assert 'nodegroup_id' in self
+ assert isinstance(value, list)
+
+ (node_ids, hostnames) = self.separate_types(value)[0:2]
+
+ # Translate hostnames into node_ids
+ if hostnames:
+ nodes = Nodes(self.api, hostnames, ['node_id']).dict('node_id')
+ node_ids += nodes.keys()
+
+ # Add new ids, remove stale ids
+ if self['node_ids'] != node_ids:
+ from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
+ from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
+ new_nodes = set(node_ids).difference(self['node_ids'])
+ stale_nodes = set(self['node_ids']).difference(node_ids)
+
+ for new_node in new_nodes:
+ AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, new_node, self['nodegroup_id'])
+ for stale_node in stale_nodes:
+ DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, stale_node, self['nodegroup_id'])
+
+ def associate_conf_files(self, auth, field, value):
+ """
+ Add conf_files found in value list (AddConfFileToNodeGroup)
+ Delets conf_files not found in value list (DeleteConfFileFromNodeGroup)
+ """
+
+ assert 'conf_file_ids' in self
+ assert 'nodegroup_id' in self
+ assert isinstance(value, list)
+
+ conf_file_ids = self.separate_types(value)[0]
+
+ if self['conf_file_ids'] != conf_file_ids:
+ from PLC.Methods.AddConfFileToNodeGroup import AddConfFileToNodeGroup
+ from PLC.Methods.DeleteConfFileFromNodeGroup import DeleteConfFileFromNodeGroup
+ new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
+ stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
+
+ for new_conf_file in new_conf_files:
+ AddConfFileToNodeGroup.__call__(AddConfFileToNodeGroup(self.api), auth, new_conf_file, self['nodegroup_id'])
+ for stale_conf_file in stale_conf_files:
+ DeleteConfFileFromNodeGroup.__call__(DeleteConfFileFromNodeGroup(self.api), auth, stale_conf_file, self['nodegroup_id'])
+
+
+class NodeGroups(Table):
+ """
+ Representation of row(s) from the nodegroups table in the
+ database.
+ """
+
+ def __init__(self, api, nodegroup_filter = None, columns = None):
+ Table.__init__(self, api, NodeGroup, columns)
+
+ sql = "SELECT %s FROM view_nodegroups WHERE True" % \
+ ", ".join(self.columns)
+
+ if nodegroup_filter is not None:
+ if isinstance(nodegroup_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), nodegroup_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), nodegroup_filter)
+ nodegroup_filter = Filter(NodeGroup.fields, {'nodegroup_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % nodegroup_filter.sql(api, "OR")
+ elif isinstance(nodegroup_filter, dict):
+ nodegroup_filter = Filter(NodeGroup.fields, nodegroup_filter)
+ sql += " AND (%s) %s" % nodegroup_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Roles import Role, Roles
+
+class NodeNetworkSettingType (Row):
+
+ """
+ Representation of a row in the nodenetwork_setting_types table.
+ """
+
+ table_name = 'nodenetwork_setting_types'
+ primary_key = 'nodenetwork_setting_type_id'
+ join_tables = ['nodenetwork_setting']
+ fields = {
+ 'nodenetwork_setting_type_id': Parameter(int, "Nodenetwork setting type identifier"),
+ 'name': Parameter(str, "Nodenetwork setting type name", max = 100),
+ 'description': Parameter(str, "Nodenetwork setting type description", max = 254),
+ 'category' : Parameter (str, "Nodenetwork setting category", max=64),
+ 'min_role_id': Parameter(int, "Minimum (least powerful) role that can set or change this attribute"),
+ }
+
+ # for Cache
+ class_key = 'name'
+ foreign_fields = ['category','description','min_role_id']
+ foreign_xrefs = []
+
+ def validate_name(self, name):
+ if not len(name):
+ raise PLCInvalidArgument, "nodenetwork setting type name must be set"
+
+ conflicts = NodeNetworkSettingTypes(self.api, [name])
+ for setting_type in conflicts:
+ if 'nodenetwork_setting_type_id' not in self or \
+ self['nodenetwork_setting_type_id'] != setting_type['nodenetwork_setting_type_id']:
+ raise PLCInvalidArgument, "nodenetwork setting type name already in use"
+
+ return name
+
+ def validate_min_role_id(self, role_id):
+ roles = [row['role_id'] for row in Roles(self.api)]
+ if role_id not in roles:
+ raise PLCInvalidArgument, "Invalid role"
+
+ return role_id
+
+class NodeNetworkSettingTypes(Table):
+ """
+ Representation of row(s) from the nodenetwork_setting_types table
+ in the database.
+ """
+
+ def __init__(self, api, nodenetwork_setting_type_filter = None, columns = None):
+ Table.__init__(self, api, NodeNetworkSettingType, columns)
+
+ sql = "SELECT %s FROM nodenetwork_setting_types WHERE True" % \
+ ", ".join(self.columns)
+
+ if nodenetwork_setting_type_filter is not None:
+ if isinstance(nodenetwork_setting_type_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), nodenetwork_setting_type_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), nodenetwork_setting_type_filter)
+ nodenetwork_setting_type_filter = Filter(NodeNetworkSettingType.fields, {'nodenetwork_setting_type_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % nodenetwork_setting_type_filter.sql(api, "OR")
+ elif isinstance(nodenetwork_setting_type_filter, dict):
+ nodenetwork_setting_type_filter = Filter(NodeNetworkSettingType.fields, nodenetwork_setting_type_filter)
+ sql += " AND (%s) %s" % nodenetwork_setting_type_filter.sql(api, "AND")
+ elif isinstance (nodenetwork_setting_type_filter, StringTypes):
+ nodenetwork_setting_type_filter = Filter(NodeNetworkSettingType.fields, {'name':[nodenetwork_setting_type_filter]})
+ sql += " AND (%s) %s" % nodenetwork_setting_type_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong nodenetwork setting type filter %r"%nodenetwork_setting_type_filter
+
+ self.selectall(sql)
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 5574 $
+#
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.NodeNetworkSettingTypes import NodeNetworkSettingType, NodeNetworkSettingTypes
+
+class NodeNetworkSetting(Row):
+ """
+ Representation of a row in the nodenetwork_setting.
+ To use, instantiate with a dict of values.
+ """
+
+ table_name = 'nodenetwork_setting'
+ primary_key = 'nodenetwork_setting_id'
+ fields = {
+ 'nodenetwork_setting_id': Parameter(int, "Nodenetwork setting identifier"),
+ 'nodenetwork_id': Parameter(int, "NodeNetwork identifier"),
+ 'nodenetwork_setting_type_id': NodeNetworkSettingType.fields['nodenetwork_setting_type_id'],
+ 'name': NodeNetworkSettingType.fields['name'],
+ 'description': NodeNetworkSettingType.fields['description'],
+ 'category': NodeNetworkSettingType.fields['category'],
+ 'min_role_id': NodeNetworkSettingType.fields['min_role_id'],
+ 'value': Parameter(str, "Nodenetwork setting value"),
+ ### relations
+
+ }
+
+class NodeNetworkSettings(Table):
+ """
+ Representation of row(s) from the nodenetwork_setting table in the
+ database.
+ """
+
+ def __init__(self, api, nodenetwork_setting_filter = None, columns = None):
+ Table.__init__(self, api, NodeNetworkSetting, columns)
+
+ sql = "SELECT %s FROM view_nodenetwork_settings WHERE True" % \
+ ", ".join(self.columns)
+
+ if nodenetwork_setting_filter is not None:
+ if isinstance(nodenetwork_setting_filter, (list, tuple, set)):
+ nodenetwork_setting_filter = Filter(NodeNetworkSetting.fields, {'nodenetwork_setting_id': nodenetwork_setting_filter})
+ elif isinstance(nodenetwork_setting_filter, dict):
+ nodenetwork_setting_filter = Filter(NodeNetworkSetting.fields, nodenetwork_setting_filter)
+ elif isinstance(nodenetwork_setting_filter, int):
+ nodenetwork_setting_filter = Filter(NodeNetworkSetting.fields, {'nodenetwork_setting_id': [nodenetwork_setting_filter]})
+ else:
+ raise PLCInvalidArgument, "Wrong nodenetwork setting filter %r"%nodenetwork_setting_filter
+ sql += " AND (%s) %s" % nodenetwork_setting_filter.sql(api)
+
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the nodenetworks table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: NodeNetworks.py 7159 2007-11-27 22:05:24Z dhozac $
+#
+
+from types import StringTypes
+import socket
+import struct
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.NetworkTypes import NetworkType, NetworkTypes
+from PLC.NetworkMethods import NetworkMethod, NetworkMethods
+import PLC.Nodes
+
+def valid_ip(ip):
+ try:
+ ip = socket.inet_ntoa(socket.inet_aton(ip))
+ return True
+ except socket.error:
+ return False
+
+def in_same_network(address1, address2, netmask):
+ """
+ Returns True if two IPv4 addresses are in the same network. Faults
+ if an address is invalid.
+ """
+
+ address1 = struct.unpack('>L', socket.inet_aton(address1))[0]
+ address2 = struct.unpack('>L', socket.inet_aton(address2))[0]
+ netmask = struct.unpack('>L', socket.inet_aton(netmask))[0]
+
+ return (address1 & netmask) == (address2 & netmask)
+
+class NodeNetwork(Row):
+ """
+ Representation of a row in the nodenetworks table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ table_name = 'nodenetworks'
+ primary_key = 'nodenetwork_id'
+ join_tables = ['nodenetwork_setting']
+ fields = {
+ 'nodenetwork_id': Parameter(int, "Node interface identifier"),
+ 'method': Parameter(str, "Addressing method (e.g., 'static' or 'dhcp')"),
+ 'type': Parameter(str, "Address type (e.g., 'ipv4')"),
+ 'ip': Parameter(str, "IP address", nullok = True),
+ 'mac': Parameter(str, "MAC address", nullok = True),
+ 'gateway': Parameter(str, "IP address of primary gateway", nullok = True),
+ 'network': Parameter(str, "Subnet address", nullok = True),
+ 'broadcast': Parameter(str, "Network broadcast address", nullok = True),
+ 'netmask': Parameter(str, "Subnet mask", nullok = True),
+ 'dns1': Parameter(str, "IP address of primary DNS server", nullok = True),
+ 'dns2': Parameter(str, "IP address of secondary DNS server", nullok = True),
+ 'bwlimit': Parameter(int, "Bandwidth limit", min = 0, nullok = True),
+ 'hostname': Parameter(str, "(Optional) Hostname", nullok = True),
+ 'node_id': Parameter(int, "Node associated with this interface"),
+ 'is_primary': Parameter(bool, "Is the primary interface for this node"),
+ 'nodenetwork_setting_ids' : Parameter([int], "List of nodenetwork settings"),
+ }
+
+ def validate_method(self, method):
+ network_methods = [row['method'] for row in NetworkMethods(self.api)]
+ if method not in network_methods:
+ raise PLCInvalidArgument, "Invalid addressing method %s"%method
+ return method
+
+ def validate_type(self, type):
+ network_types = [row['type'] for row in NetworkTypes(self.api)]
+ if type not in network_types:
+ raise PLCInvalidArgument, "Invalid address type %s"%type
+ return type
+
+ def validate_ip(self, ip):
+ if ip and not valid_ip(ip):
+ raise PLCInvalidArgument, "Invalid IP address %s"%ip
+ return ip
+
+ def validate_mac(self, mac):
+ if not mac:
+ return mac
+
+ try:
+ bytes = mac.split(":")
+ if len(bytes) < 6:
+ raise Exception
+ for i, byte in enumerate(bytes):
+ byte = int(byte, 16)
+ if byte < 0 or byte > 255:
+ raise Exception
+ bytes[i] = "%02x" % byte
+ mac = ":".join(bytes)
+ except:
+ raise PLCInvalidArgument, "Invalid MAC address %s"%mac
+
+ return mac
+
+ validate_gateway = validate_ip
+ validate_network = validate_ip
+ validate_broadcast = validate_ip
+ validate_netmask = validate_ip
+ validate_dns1 = validate_ip
+ validate_dns2 = validate_ip
+
+ def validate_bwlimit(self, bwlimit):
+ if not bwlimit:
+ return bwlimit
+
+ if bwlimit < 500000:
+ raise PLCInvalidArgument, 'Minimum bw is 500 kbs'
+
+ return bwlimit
+
+ def validate_hostname(self, hostname):
+ # Optional
+ if not hostname:
+ return hostname
+
+ if not PLC.Nodes.valid_hostname(hostname):
+ raise PLCInvalidArgument, "Invalid hostname %s"%hostname
+
+ return hostname
+
+ def validate_node_id(self, node_id):
+ nodes = PLC.Nodes.Nodes(self.api, [node_id])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node %d"%node_id
+
+ return node_id
+
+ def validate_is_primary(self, is_primary):
+ """
+ Set this interface to be the primary one.
+ """
+
+ if is_primary:
+ nodes = PLC.Nodes.Nodes(self.api, [self['node_id']])
+ if not nodes:
+ raise PLCInvalidArgument, "No such node %d"%node_id
+ node = nodes[0]
+
+ if node['nodenetwork_ids']:
+ conflicts = NodeNetworks(self.api, node['nodenetwork_ids'])
+ for nodenetwork in conflicts:
+ if ('nodenetwork_id' not in self or \
+ self['nodenetwork_id'] != nodenetwork['nodenetwork_id']) and \
+ nodenetwork['is_primary']:
+ raise PLCInvalidArgument, "Can only set one primary interface per node"
+
+ return is_primary
+
+ def validate(self):
+ """
+ Flush changes back to the database.
+ """
+
+ # Basic validation
+ Row.validate(self)
+
+ assert 'method' in self
+ method = self['method']
+
+ if method == "proxy" or method == "tap":
+ if 'mac' in self and self['mac']:
+ raise PLCInvalidArgument, "For %s method, mac should not be specified" % method
+ if 'ip' not in self or not self['ip']:
+ raise PLCInvalidArgument, "For %s method, ip is required" % method
+ if method == "tap" and ('gateway' not in self or not self['gateway']):
+ raise PLCInvalidArgument, "For tap method, gateway is required and should be " \
+ "the IP address of the node that proxies for this address"
+ # Should check that the proxy address is reachable, but
+ # there's no way to tell if the only primary interface is
+ # DHCP!
+
+ elif method == "static":
+ if 'is_primary' in self and self['is_primary'] is True:
+ for key in ['gateway', 'dns1']:
+ if key not in self or not self[key]:
+ raise PLCInvalidArgument, "For static method primary network, %s is required" % key
+ globals()[key] = self[key]
+ for key in ['ip', 'network', 'broadcast', 'netmask']:
+ if key not in self or not self[key]:
+ raise PLCInvalidArgument, "For static method, %s is required" % key
+ globals()[key] = self[key]
+ if not in_same_network(ip, network, netmask):
+ raise PLCInvalidArgument, "IP address %s is inconsistent with network %s/%s" % \
+ (ip, network, netmask)
+ if not in_same_network(broadcast, network, netmask):
+ raise PLCInvalidArgument, "Broadcast address %s is inconsistent with network %s/%s" % \
+ (broadcast, network, netmask)
+ if 'gateway' in globals() and not in_same_network(ip, gateway, netmask):
+ raise PLCInvalidArgument, "Gateway %s is not reachable from %s/%s" % \
+ (gateway, ip, netmask)
+
+ elif method == "ipmi":
+ if 'ip' not in self or not self['ip']:
+ raise PLCInvalidArgument, "For ipmi method, ip is required"
+
+class NodeNetworks(Table):
+ """
+ Representation of row(s) from the nodenetworks table in the
+ database.
+ """
+
+ def __init__(self, api, nodenetwork_filter = None, columns = None):
+ Table.__init__(self, api, NodeNetwork, columns)
+
+ sql = "SELECT %s FROM view_nodenetworks WHERE True" % \
+ ", ".join(self.columns)
+
+ if nodenetwork_filter is not None:
+ if isinstance(nodenetwork_filter, (list, tuple, set)):
+ nodenetwork_filter = Filter(NodeNetwork.fields, {'nodenetwork_id': nodenetwork_filter})
+ elif isinstance(nodenetwork_filter, dict):
+ nodenetwork_filter = Filter(NodeNetwork.fields, nodenetwork_filter)
+ elif isinstance(nodenetwork_filter, int):
+ nodenetwork_filter = Filter(NodeNetwork.fields, {'nodenetwork_id': [nodenetwork_filter]})
+ else:
+ raise PLCInvalidArgument, "Wrong node network filter %r"%nodenetwork_filter
+ sql += " AND (%s) %s" % nodenetwork_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the nodes table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Nodes.py 5654 2007-11-06 03:43:55Z tmack $
+#
+
+from types import StringTypes
+import re
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.NodeNetworks import NodeNetwork, NodeNetworks
+from PLC.BootStates import BootStates
+
+def valid_hostname(hostname):
+ # 1. Each part begins and ends with a letter or number.
+ # 2. Each part except the last can contain letters, numbers, or hyphens.
+ # 3. Each part is between 1 and 64 characters, including the trailing dot.
+ # 4. At least two parts.
+ # 5. Last part can only contain between 2 and 6 letters.
+ good_hostname = r'^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+' \
+ r'[a-z]{2,6}$'
+ return hostname and \
+ re.match(good_hostname, hostname, re.IGNORECASE)
+
+class Node(Row):
+ """
+ Representation of a row in the nodes table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ table_name = 'nodes'
+ primary_key = 'node_id'
+ # Thierry -- we use delete on nodenetworks so the related NodeNetworkSettings get deleted too
+ join_tables = ['nodegroup_node', 'conf_file_node', 'pcu_node', 'slice_node', 'slice_attribute', 'node_session', 'peer_node','node_slice_whitelist']
+ fields = {
+ 'node_id': Parameter(int, "Node identifier"),
+ 'hostname': Parameter(str, "Fully qualified hostname", max = 255),
+ 'site_id': Parameter(int, "Site at which this node is located"),
+ 'boot_state': Parameter(str, "Boot state", max = 20),
+ 'model': Parameter(str, "Make and model of the actual machine", max = 255, nullok = True),
+ 'boot_nonce': Parameter(str, "(Admin only) Random value generated by the node at last boot", max = 128),
+ 'version': Parameter(str, "Apparent Boot CD version", max = 64),
+ 'ssh_rsa_key': Parameter(str, "Last known SSH host key", max = 1024),
+ 'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
+ 'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
+ 'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
+ 'key': Parameter(str, "(Admin only) Node key", max = 256),
+ 'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
+ 'nodenetwork_ids': Parameter([int], "List of network interfaces that this node has"),
+ 'nodegroup_ids': Parameter([int], "List of node groups that this node is in"),
+ 'conf_file_ids': Parameter([int], "List of configuration files specific to this node"),
+ # 'root_person_ids': Parameter([int], "(Admin only) List of people who have root access to this node"),
+ 'slice_ids': Parameter([int], "List of slices on this node"),
+ 'slice_ids_whitelist': Parameter([int], "List of slices allowed on this node"),
+ 'pcu_ids': Parameter([int], "List of PCUs that control this node"),
+ 'ports': Parameter([int], "List of PCU ports that this node is connected to"),
+ 'peer_id': Parameter(int, "Peer to which this node belongs", nullok = True),
+ 'peer_node_id': Parameter(int, "Foreign node identifier at peer", nullok = True),
+ }
+ related_fields = {
+ 'nodenetworks': [Mixed(Parameter(int, "NodeNetwork identifier"),
+ Filter(NodeNetwork.fields))],
+ 'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
+ Parameter(str, "NodeGroup name"))],
+ 'conf_files': [Parameter(int, "ConfFile identifier")],
+ 'slices': [Mixed(Parameter(int, "Slice identifier"),
+ Parameter(str, "Slice name"))],
+ 'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
+ Parameter(str, "Slice name"))]
+ }
+ # for Cache
+ class_key = 'hostname'
+ foreign_fields = ['boot_state','model','version']
+ # forget about these ones, they are read-only anyway
+ # handling them causes Cache to re-sync all over again
+ # 'date_created','last_updated'
+ foreign_xrefs = [
+ # in this case, we dont need the 'table' but Cache will look it up, so...
+ {'field' : 'site_id' , 'class' : 'Site' , 'table' : 'unused-on-direct-refs' } ,
+ ]
+
+ def validate_hostname(self, hostname):
+ if not valid_hostname(hostname):
+ raise PLCInvalidArgument, "Invalid hostname"
+
+ conflicts = Nodes(self.api, [hostname])
+ for node in conflicts:
+ if 'node_id' not in self or self['node_id'] != node['node_id']:
+ raise PLCInvalidArgument, "Hostname already in use"
+
+ return hostname
+
+ def validate_boot_state(self, boot_state):
+ boot_states = [row['boot_state'] for row in BootStates(self.api)]
+ if boot_state not in boot_states:
+ raise PLCInvalidArgument, "Invalid boot state"
+
+ return boot_state
+
+ validate_date_created = Row.validate_timestamp
+ validate_last_updated = Row.validate_timestamp
+ validate_last_contact = Row.validate_timestamp
+
+ def update_last_contact(self, commit = True):
+ """
+ Update last_contact field with current time
+ """
+
+ assert 'node_id' in self
+ assert self.table_name
+
+ self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
+ " where node_id = %d" % ( self['node_id']) )
+ self.sync(commit)
+
+
+ def update_last_updated(self, commit = True):
+ """
+ Update last_updated field with current time
+ """
+
+ assert 'node_id' in self
+ assert self.table_name
+
+ self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+ " where node_id = %d" % (self['node_id']) )
+ self.sync(commit)
+
+ def associate_nodenetworks(self, auth, field, value):
+ """
+ Delete nodenetworks not found in value list (using DeleteNodeNetwor)k
+ Add nodenetworks found in value list (using AddNodeNetwork)
+ Updates nodenetworks found w/ nodenetwork_id in value list (using UpdateNodeNetwork)
+ """
+
+ assert 'nodenetworkp_ids' in self
+ assert 'node_id' in self
+ assert isinstance(value, list)
+
+ (nodenetwork_ids, blank, nodenetworks) = self.separate_types(value)
+
+ if self['nodenetwork_ids'] != nodenetwork_ids:
+ from PLC.Methods.DeleteNodeNetwork import DeleteNodeNetwork
+
+ stale_nodenetworks = set(self['nodenetwork_ids']).difference(nodenetwork_ids)
+
+ for stale_nodenetwork in stale_nodenetworks:
+ DeleteNodeNetwork.__call__(DeleteNodeNetwork(self.api), auth, stale_nodenetwork['nodenetwork_id'])
+
+ def associate_nodegroups(self, auth, field, value):
+ """
+ Add node to nodegroups found in value list (AddNodeToNodegroup)
+ Delete node from nodegroup not found in value list (DeleteNodeFromNodegroup)
+ """
+
+ from PLC.NodeGroups import NodeGroups
+
+ assert 'nodegroup_ids' in self
+ assert 'node_id' in self
+ assert isinstance(value, list)
+
+ (nodegroup_ids, nodegroup_names) = self.separate_types(value)[0:2]
+
+ if nodegroup_names:
+ nodegroups = NodeGroups(self.api, nodegroup_names, ['nodegroup_id']).dict('nodegroup_id')
+ nodegroup_ids += nodegroups.keys()
+
+ if self['nodegroup_ids'] != nodegroup_ids:
+ from PLC.Methods.AddNodeToNodeGroup import AddNodeToNodeGroup
+ from PLC.Methods.DeleteNodeFromNodeGroup import DeleteNodeFromNodeGroup
+
+ new_nodegroups = set(nodegroup_ids).difference(self['nodegroup_ids'])
+ stale_nodegroups = set(self['nodegroup_ids']).difference(nodegroup_ids)
+
+ for new_nodegroup in new_nodegroups:
+ AddNodeToNodeGroup.__call__(AddNodeToNodeGroup(self.api), auth, self['node_id'], new_nodegroup)
+ for stale_nodegroup in stale_nodegroups:
+ DeleteNodeFromNodeGroup.__call__(DeleteNodeFromNodeGroup(self.api), auth, self['node_id'], stale_nodegroup)
+
+
+
+ def associate_conf_files(self, auth, field, value):
+ """
+ Add conf_files found in value list (AddConfFileToNode)
+ Delets conf_files not found in value list (DeleteConfFileFromNode)
+ """
+
+ assert 'conf_file_ids' in self
+ assert 'node_id' in self
+ assert isinstance(value, list)
+
+ conf_file_ids = self.separate_types(value)[0]
+
+ if self['conf_file_ids'] != conf_file_ids:
+ from PLC.Methods.AddConfFileToNode import AddConfFileToNode
+ from PLC.Methods.DeleteConfFileFromNode import DeleteConfFileFromNode
+ new_conf_files = set(conf_file_ids).difference(self['conf_file_ids'])
+ stale_conf_files = set(self['conf_file_ids']).difference(conf_file_ids)
+
+ for new_conf_file in new_conf_files:
+ AddConfFileToNode.__call__(AddConfFileToNode(self.api), auth, new_conf_file, self['node_id'])
+ for stale_conf_file in stale_conf_files:
+ DeleteConfFileFromNode.__call__(DeleteConfFileFromNode(self.api), auth, stale_conf_file, self['node_id'])
+
+
+ def associate_slices(self, auth, field, value):
+ """
+ Add slices found in value list to (AddSliceToNode)
+ Delete slices not found in value list (DeleteSliceFromNode)
+ """
+
+ from PLC.Slices import Slices
+
+ assert 'slice_ids' in self
+ assert 'node_id' in self
+ assert isinstance(value, list)
+
+ (slice_ids, slice_names) = self.separate_types(value)[0:2]
+
+ if slice_names:
+ slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
+ slice_ids += slices.keys()
+
+ if self['slice_ids'] != slice_ids:
+ from PLC.Methods.AddSliceToNodes import AddSliceToNodes
+ from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
+ new_slices = set(slice_ids).difference(self['slice_ids'])
+ stale_slices = set(self['slice_ids']).difference(slice_ids)
+
+ for new_slice in new_slices:
+ AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, new_slice, [self['node_id']])
+ for stale_slice in stale_slices:
+ DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, stale_slice, [self['node_id']])
+
+ def associate_slices_whitelist(self, auth, field, value):
+ """
+ Add slices found in value list to whitelist (AddSliceToNodesWhitelist)
+ Delete slices not found in value list from whitelist (DeleteSliceFromNodesWhitelist)
+ """
+
+ from PLC.Slices import Slices
+
+ assert 'slice_ids_whitelist' in self
+ assert 'node_id' in self
+ assert isinstance(value, list)
+
+ (slice_ids, slice_names) = self.separate_types(value)[0:2]
+
+ if slice_names:
+ slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
+ slice_ids += slices.keys()
+
+ if self['slice_ids_whitelist'] != slice_ids:
+ from PLC.Methods.AddSliceToNodesWhitelist import AddSliceToNodesWhitelist
+ from PLC.Methods.DeleteSliceFromNodesWhitelist import DeleteSliceFromNodesWhitelist
+ new_slices = set(slice_ids).difference(self['slice_ids_whitelist'])
+ stale_slices = set(self['slice_ids_whitelist']).difference(slice_ids)
+
+ for new_slice in new_slices:
+ AddSliceToNodesWhitelist.__call__(AddSliceToNodesWhitelist(self.api), auth, new_slice, [self['node_id']])
+ for stale_slice in stale_slices:
+ DeleteSliceFromNodesWhitelist.__call__(DeleteSliceFromNodesWhitelist(self.api), auth, stale_slice, [self['node_id']])
+
+
+ def delete(self, commit = True):
+ """
+ Delete existing node.
+ """
+
+ assert 'node_id' in self
+ assert 'nodenetwork_ids' in self
+
+ # we need to clean up NodeNetworkSettings, so handling nodenetworks as part of join_tables does not work
+ for nodenetwork in NodeNetworks(self.api,self['nodenetwork_ids']):
+ nodenetwork.delete()
+
+ # Clean up miscellaneous join tables
+ for table in self.join_tables:
+ self.api.db.do("DELETE FROM %s WHERE node_id = %d" % \
+ (table, self['node_id']))
+
+ # Mark as deleted
+ self['deleted'] = True
+ self.sync(commit)
+
+
+class Nodes(Table):
+ """
+ Representation of row(s) from the nodes table in the
+ database.
+ """
+
+ def __init__(self, api, node_filter = None, columns = None):
+ Table.__init__(self, api, Node, columns)
+
+ sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
+ ", ".join(self.columns)
+
+ if node_filter is not None:
+ if isinstance(node_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), node_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), node_filter)
+ node_filter = Filter(Node.fields, {'node_id': ints, 'hostname': strs})
+ sql += " AND (%s) %s" % node_filter.sql(api, "OR")
+ elif isinstance(node_filter, dict):
+ node_filter = Filter(Node.fields, node_filter)
+ sql += " AND (%s) %s" % node_filter.sql(api, "AND")
+ elif isinstance (node_filter, StringTypes):
+ node_filter = Filter(Node.fields, {'hostname':[node_filter]})
+ sql += " AND (%s) %s" % node_filter.sql(api, "AND")
+ elif isinstance (node_filter, int):
+ node_filter = Filter(Node.fields, {'node_id':[node_filter]})
+ sql += " AND (%s) %s" % node_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong node filter %r"%node_filter
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the pcu_type_port table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id:
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+from PLC.Filter import Filter
+
+class PCUProtocolType(Row):
+ """
+ Representation of a row in the pcu_protocol_type table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'pcu_protocol_type'
+ primary_key = 'pcu_protocol_type_id'
+ join_tables = []
+ fields = {
+ 'pcu_protocol_type_id': Parameter(int, "PCU protocol type identifier"),
+ 'pcu_type_id': Parameter(int, "PCU type identifier"),
+ 'port': Parameter(int, "PCU port"),
+ 'protocol': Parameter(str, "Protocol"),
+ 'supported': Parameter(bool, "Is the port/protocol supported by PLC")
+ }
+
+ def validate_port(self, port):
+ # make sure port is not blank
+
+ if not port:
+ raise PLCInvalidArgument, "Port must be specified"
+
+ return port
+
+ def validate_protocol(self, protocol):
+ # make sure port is not blank
+ if not len(protocol):
+ raise PLCInvalidArgument, "protocol must be specified"
+
+ return protocol
+
+class PCUProtocolTypes(Table):
+ """
+ Representation of the pcu_protocol_types table in the database.
+ """
+
+ def __init__(self, api, protocol_type_filter = None, columns = None):
+ Table.__init__(self, api, PCUProtocolType, columns)
+
+ sql = "SELECT %s FROM pcu_protocol_type WHERE True" % \
+ ", ".join(self.columns)
+
+ if protocol_type_filter is not None:
+ if isinstance(protocol_type_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), protocol_type_filter)
+ protocol_type_filter = Filter(PCUProtocolType.fields, {'pcu_protocol_type_id': ints})
+ sql += " AND (%s) %s" % protocol_type_filter.sql(api, "OR")
+ elif isinstance(protocol_type_filter, dict):
+ protocol_type_filter = Filter(PCUProtocolType.fields, protocol_type_filter)
+ sql += " AND (%s) %s" % protocol_type_filter.sql(api, "AND")
+ elif isinstance (protocol_type_filter, int):
+ protocol_type_filter = Filter(PCUProtocolType.fields, {'pcu_protocol_type_id':[protocol_type_filter]})
+
+ sql += " AND (%s) %s" % protocol_type_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong pcu_protocol_type filter %r"%protocol_type_filter
+
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the pcu_types table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id:
+#
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+from PLC.Filter import Filter
+
+class PCUType(Row):
+ """
+ Representation of a row in the pcu_types table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'pcu_types'
+ primary_key = 'pcu_type_id'
+ join_tables = ['pcu_protocol_type']
+ fields = {
+ 'pcu_type_id': Parameter(int, "PCU Type Identifier"),
+ 'model': Parameter(str, "PCU model", max = 254),
+ 'name': Parameter(str, "PCU full name", max = 254),
+ 'pcu_protocol_type_ids': Parameter([int], "PCU Protocol Type Identifiers"),
+ 'pcu_protocol_types': Parameter([dict], "PCU Protocol Type List")
+ }
+
+ def validate_model(self, model):
+ # Make sure name is not blank
+ if not len(model):
+ raise PLCInvalidArgument, "Model must be specified"
+
+ # Make sure boot state does not alredy exist
+ conflicts = PCUTypes(self.api, [model])
+ for pcu_type in conflicts:
+ if 'pcu_type_id' not in self or self['pcu_type_id'] != pcu_type['pcu_type_id']:
+ raise PLCInvalidArgument, "Model already in use"
+
+ return model
+
+class PCUTypes(Table):
+ """
+ Representation of the pcu_types table in the database.
+ """
+
+ def __init__(self, api, pcu_type_filter = None, columns = None):
+
+ # Remove pcu_protocol_types from query since its not really a field
+ # in the db. We will add it later
+ if columns == None:
+ columns = PCUType.fields.keys()
+ if 'pcu_protocol_types' in columns:
+ removed_fields = ['pcu_protocol_types']
+ columns.remove('pcu_protocol_types')
+ else:
+ removed_fields = []
+
+ Table.__init__(self, api, PCUType, columns)
+
+ sql = "SELECT %s FROM view_pcu_types WHERE True" % \
+ ", ".join(self.columns)
+
+ if pcu_type_filter is not None:
+ if isinstance(pcu_type_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), pcu_type_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), pcu_type_filter)
+ pcu_type_filter = Filter(PCUType.fields, {'pcu_type_id': ints, 'model': strs})
+ sql += " AND (%s) %s" % pcu_type_filter.sql(api, "OR")
+ elif isinstance(pcu_type_filter, dict):
+ pcu_type_filter = Filter(PCUType.fields, pcu_type_filter)
+ sql += " AND (%s) %s" % pcu_type_filter.sql(api, "AND")
+ elif isinstance (pcu_type_filter, StringTypes):
+ pcu_type_filter = Filter(PCUType.fields, {'model':[pcu_type_filter]})
+ sql += " AND (%s) %s" % pcu_type_filter.sql(api, "AND")
+ elif isinstance (pcu_type_filter, int):
+ pcu_type_filter = Filter(PCUType.fields, {'pcu_type_id':[pcu_type_filter]})
+ sql += " AND (%s) %s" % pcu_type_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong pcu_type filter %r"%pcu_type_filter
+
+
+ self.selectall(sql)
+
+ # return a list of protocol type objects for each port type
+ if 'pcu_protocol_types' in removed_fields:
+ from PLC.PCUProtocolTypes import PCUProtocolTypes
+ protocol_type_ids = set()
+ for pcu_type in self:
+ protocol_type_ids.update(pcu_type['pcu_protocol_type_ids'])
+
+ protocol_return_fields = ['pcu_protocol_type_id', 'port', 'protocol', 'supported']
+ all_protocol_types = PCUProtocolTypes(self.api, list(protocol_type_ids), \
+ protocol_return_fields).dict('pcu_protocol_type_id')
+
+ for pcu_type in self:
+ pcu_type['pcu_protocol_types'] = []
+ for protocol_type_id in pcu_type['pcu_protocol_type_ids']:
+ pcu_type['pcu_protocol_types'].append(all_protocol_types[protocol_type_id])
--- /dev/null
+#
+# Functions for interacting with the pcus table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: PCUs.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.NodeNetworks import valid_ip, NodeNetwork, NodeNetworks
+from PLC.Nodes import Node, Nodes
+
+class PCU(Row):
+ """
+ Representation of a row in the pcus table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'pcus'
+ primary_key = 'pcu_id'
+ join_tables = ['pcu_node']
+ fields = {
+ 'pcu_id': Parameter(int, "PCU identifier"),
+ 'site_id': Parameter(int, "Identifier of site where PCU is located"),
+ 'hostname': Parameter(str, "PCU hostname", max = 254),
+ 'ip': Parameter(str, "PCU IP address", max = 254),
+ 'protocol': Parameter(str, "PCU protocol, e.g. ssh, https, telnet", max = 16, nullok = True),
+ 'username': Parameter(str, "PCU username", max = 254, nullok = True),
+ 'password': Parameter(str, "PCU username", max = 254, nullok = True),
+ 'notes': Parameter(str, "Miscellaneous notes", max = 254, nullok = True),
+ 'model': Parameter(str, "PCU model string", max = 32, nullok = True),
+ 'node_ids': Parameter([int], "List of nodes that this PCU controls"),
+ 'ports': Parameter([int], "List of the port numbers that each node is connected to"),
+ }
+
+ def validate_ip(self, ip):
+ if not valid_ip(ip):
+ raise PLCInvalidArgument, "Invalid IP address " + ip
+ return ip
+
+ def add_node(self, node, port, commit = True):
+ """
+ Add node to existing PCU.
+ """
+
+ assert 'pcu_id' in self
+ assert isinstance(node, Node)
+ assert isinstance(port, (int, long))
+ assert 'node_id' in node
+
+ pcu_id = self['pcu_id']
+ node_id = node['node_id']
+
+ if node_id not in self['node_ids'] and port not in self['ports']:
+ self.api.db.do("INSERT INTO pcu_node (pcu_id, node_id, port)" \
+ " VALUES(%(pcu_id)d, %(node_id)d, %(port)d)",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].append(node_id)
+ self['ports'].append(port)
+
+ def remove_node(self, node, commit = True):
+ """
+ Remove node from existing PCU.
+ """
+
+ assert 'pcu_id' in self
+ assert isinstance(node, Node)
+ assert 'node_id' in node
+
+ pcu_id = self['pcu_id']
+ node_id = node['node_id']
+
+ if node_id in self['node_ids']:
+ i = self['node_ids'].index(node_id)
+ port = self['ports'][i]
+
+ self.api.db.do("DELETE FROM pcu_node" \
+ " WHERE pcu_id = %(pcu_id)d" \
+ " AND node_id = %(node_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ self['node_ids'].remove(node_id)
+ self['ports'].remove(port)
+
+class PCUs(Table):
+ """
+ Representation of row(s) from the pcus table in the
+ database.
+ """
+
+ def __init__(self, api, pcu_filter = None, columns = None):
+ Table.__init__(self, api, PCU, columns)
+
+ sql = "SELECT %s FROM view_pcus WHERE True" % \
+ ", ".join(self.columns)
+
+ if pcu_filter is not None:
+ if isinstance(pcu_filter, (list, tuple, set)):
+ pcu_filter = Filter(PCU.fields, {'pcu_id': pcu_filter})
+ elif isinstance(pcu_filter, dict):
+ pcu_filter = Filter(PCU.fields, pcu_filter)
+ sql += " AND (%s) %s" % pcu_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+# Marc E. Fiuczynski <mef@cs.princeton.edu>
+# Copyright (C) 2004 The Trustees of Princeton University
+#
+# Client ping of death program for both udp & icmp
+#
+# modified for inclusion by api by Aaron K
+
+import struct
+import os
+import array
+import getopt
+from socket import *
+
+UPOD_PORT = 664
+
+def _in_cksum(packet):
+ """THE RFC792 states: 'The 16 bit one's complement of
+ the one's complement sum of all 16 bit words in the header.'
+ Generates a checksum of a (ICMP) packet. Based on in_chksum found
+ in ping.c on FreeBSD.
+ """
+
+ # add byte if not dividable by 2
+ if len(packet) & 1:
+ packet = packet + '\0'
+
+ # split into 16-bit word and insert into a binary array
+ words = array.array('h', packet)
+ sum = 0
+
+ # perform ones complement arithmetic on 16-bit words
+ for word in words:
+ sum += (word & 0xffff)
+
+ hi = sum >> 16
+ lo = sum & 0xffff
+ sum = hi + lo
+ sum = sum + (sum >> 16)
+
+ return (~sum) & 0xffff # return ones complement
+
+def _construct(id, data):
+ """Constructs a ICMP IPOD packet
+ """
+ ICMP_TYPE = 6 # ping of death code used by PLK
+ ICMP_CODE = 0
+ ICMP_CHECKSUM = 0
+ ICMP_ID = 0
+ ICMP_SEQ_NR = 0
+
+ header = struct.pack('bbHHh', ICMP_TYPE, ICMP_CODE, ICMP_CHECKSUM, \
+ ICMP_ID, ICMP_SEQ_NR+id)
+
+ packet = header + data # ping packet without checksum
+ checksum = _in_cksum(packet) # make checksum
+
+ # construct header with correct checksum
+ header = struct.pack('bbHHh', ICMP_TYPE, ICMP_CODE, checksum, ICMP_ID, \
+ ICMP_SEQ_NR+id)
+
+ # ping packet *with* checksum
+ packet = header + data
+
+ # a perfectly formatted ICMP echo packet
+ return packet
+
+def icmp_pod(host,key):
+ uid = os.getuid()
+ if uid <> 0:
+ print "must be root to send icmp pod"
+ return
+
+ s = socket(AF_INET, SOCK_RAW, getprotobyname("icmp"))
+ packet = _construct(0, key) # make a ping packet
+ addr = (host,1)
+ print 'pod sending icmp-based reboot request to %s' % host
+ for i in range(1,10):
+ s.sendto(packet, addr)
+
+def udp_pod(host,key,fromaddr=('', 0)):
+ addr = host, UPOD_PORT
+ s = socket(AF_INET, SOCK_DGRAM)
+ s.bind(fromaddr)
+ packet = key
+ print 'pod sending udp-based reboot request to %s' % host
+ for i in range(1,10):
+ s.sendto(packet, addr)
+
+def noop_pod(host,key):
+ pass
--- /dev/null
+#
+# Shared type definitions
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Parameter.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from types import *
+from PLC.Faults import *
+
+class Parameter:
+ """
+ Typed value wrapper. Use in accepts and returns to document method
+ parameters. Set the optional and default attributes for
+ sub-parameters (i.e., dict fields).
+ """
+
+ def __init__(self, type, doc = "",
+ min = None, max = None,
+ optional = None,
+ ro = False,
+ nullok = False):
+ # Basic type of the parameter. Must be a builtin type
+ # that can be marshalled by XML-RPC.
+ self.type = type
+
+ # Documentation string for the parameter
+ self.doc = doc
+
+ # Basic value checking. For numeric types, the minimum and
+ # maximum possible values, inclusive. For string types, the
+ # minimum and maximum possible UTF-8 encoded byte lengths.
+ self.min = min
+ self.max = max
+
+ # Whether the sub-parameter is optional or not. If None,
+ # unknown whether it is optional.
+ self.optional = optional
+
+ # Whether the DB field is read-only.
+ self.ro = ro
+
+ # Whether the DB field can be NULL.
+ self.nullok = nullok
+
+ def type(self):
+ return self.type
+
+ def __repr__(self):
+ return repr(self.type)
+
+class Mixed(tuple):
+ """
+ A list (technically, a tuple) of types. Use in accepts and returns
+ to document method parameters that may return mixed types.
+ """
+
+ def __new__(cls, *types):
+ return tuple.__new__(cls, types)
+
+
+def python_type(arg):
+ """
+ Returns the Python type of the specified argument, which may be a
+ Python type, a typed value, or a Parameter.
+ """
+
+ if isinstance(arg, Parameter):
+ arg = arg.type
+
+ if isinstance(arg, type):
+ return arg
+ else:
+ return type(arg)
+
+def xmlrpc_type(arg):
+ """
+ Returns the XML-RPC type of the specified argument, which may be a
+ Python type, a typed value, or a Parameter.
+ """
+
+ arg_type = python_type(arg)
+
+ if arg_type == NoneType:
+ return "nil"
+ elif arg_type == IntType or arg_type == LongType:
+ return "int"
+ elif arg_type == bool:
+ return "boolean"
+ elif arg_type == FloatType:
+ return "double"
+ elif arg_type in StringTypes:
+ return "string"
+ elif arg_type == ListType or arg_type == TupleType:
+ return "array"
+ elif arg_type == DictType:
+ return "struct"
+ elif arg_type == Mixed:
+ # Not really an XML-RPC type but return "mixed" for
+ # documentation purposes.
+ return "mixed"
+ else:
+ raise PLCAPIError, "XML-RPC cannot marshal %s objects" % arg_type
--- /dev/null
+#
+# Thierry Parmentelat - INRIA
+#
+
+import re
+from types import StringTypes
+from urlparse import urlparse
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+import PLC.Auth
+
+from PLC.Sites import Site, Sites
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Nodes import Node, Nodes
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Slices import Slice, Slices
+
+class Peer(Row):
+ """
+ Stores the list of peering PLCs in the peers table.
+ See the Row class for more details
+ """
+
+ table_name = 'peers'
+ primary_key = 'peer_id'
+ join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node',
+ 'peer_slice_attribute_type', 'peer_slice_attribute', 'peer_slice']
+ fields = {
+ 'peer_id': Parameter (int, "Peer identifier"),
+ 'peername': Parameter (str, "Peer name"),
+ 'peer_url': Parameter (str, "Peer API URL"),
+ 'key': Parameter(str, "Peer GPG public key"),
+ 'cacert': Parameter(str, "Peer SSL public certificate"),
+ ### cross refs
+ 'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
+ 'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
+ 'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
+ 'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
+ 'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
+ }
+
+ def validate_peername(self, peername):
+ if not len(peername):
+ raise PLCInvalidArgument, "Peer name must be specified"
+
+ conflicts = Peers(self.api, [peername])
+ for peer in conflicts:
+ if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
+ raise PLCInvalidArgument, "Peer name already in use"
+
+ return peername
+
+ def validate_peer_url(self, url):
+ """
+ Validate URL. Must be HTTPS.
+ """
+
+ (scheme, netloc, path, params, query, fragment) = urlparse(url)
+ if scheme != "https":
+ raise PLCInvalidArgument, "Peer URL scheme must be https"
+
+ return url
+
+ def delete(self, commit = True):
+ """
+ Deletes this peer and all related entities.
+ """
+
+ assert 'peer_id' in self
+
+ # Remove all related entities
+ for obj in \
+ Slices(self.api, self['slice_ids']) + \
+ Keys(self.api, self['key_ids']) + \
+ Persons(self.api, self['person_ids']) + \
+ Nodes(self.api, self['node_ids']) + \
+ Sites(self.api, self['site_ids']):
+ assert obj['peer_id'] == self['peer_id']
+ obj.delete(commit = False)
+
+ # Mark as deleted
+ self['deleted'] = True
+ self.sync(commit)
+
+ def add_site(self, site, peer_site_id, commit = True):
+ """
+ Associate a local site entry with this peer.
+ """
+
+ add = Row.add_object(Site, 'peer_site')
+ add(self, site,
+ {'peer_id': self['peer_id'],
+ 'site_id': site['site_id'],
+ 'peer_site_id': peer_site_id},
+ commit = commit)
+
+ def add_person(self, person, peer_person_id, commit = True):
+ """
+ Associate a local user entry with this peer.
+ """
+
+ add = Row.add_object(Person, 'peer_person')
+ add(self, person,
+ {'peer_id': self['peer_id'],
+ 'person_id': person['person_id'],
+ 'peer_person_id': peer_person_id},
+ commit = commit)
+
+ def add_key(self, key, peer_key_id, commit = True):
+ """
+ Associate a local key entry with this peer.
+ """
+
+ add = Row.add_object(Key, 'peer_key')
+ add(self, key,
+ {'peer_id': self['peer_id'],
+ 'key_id': key['key_id'],
+ 'peer_key_id': peer_key_id},
+ commit = commit)
+
+ def add_node(self, node, peer_node_id, commit = True):
+ """
+ Associate a local node entry with this peer.
+ """
+
+ add = Row.add_object(Node, 'peer_node')
+ add(self, node,
+ {'peer_id': self['peer_id'],
+ 'node_id': node['node_id'],
+ 'peer_node_id': peer_node_id},
+ commit = commit)
+
+ def add_slice(self, slice, peer_slice_id, commit = True):
+ """
+ Associate a local slice entry with this peer.
+ """
+
+ add = Row.add_object(Slice, 'peer_slice')
+ add(self, slice,
+ {'peer_id': self['peer_id'],
+ 'slice_id': slice['slice_id'],
+ 'peer_slice_id': peer_slice_id},
+ commit = commit)
+
+ def connect(self, **kwds):
+ """
+ Connect to this peer via XML-RPC.
+ """
+
+ import xmlrpclib
+ from PLC.PyCurl import PyCurlTransport
+ self.server = xmlrpclib.ServerProxy(self['peer_url'],
+ PyCurlTransport(self['peer_url'], self['cacert']),
+ allow_none = 1, **kwds)
+
+ def add_auth(self, function, methodname, **kwds):
+ """
+ Sign the specified XML-RPC call and add an auth struct as the
+ first argument of the call.
+ """
+
+ def wrapper(*args, **kwds):
+ from PLC.GPG import gpg_sign
+ signature = gpg_sign(args,
+ self.api.config.PLC_ROOT_GPG_KEY,
+ self.api.config.PLC_ROOT_GPG_KEY_PUB,
+ methodname)
+
+ auth = {'AuthMethod': "gpg",
+ 'name': self.api.config.PLC_NAME,
+ 'signature': signature}
+
+ # Automagically add auth struct to every call
+ args = (auth,) + args
+
+ return function(*args)
+
+ return wrapper
+
+ def __getattr__(self, attr):
+ """
+ Returns a callable API function if attr is the name of a
+ PLCAPI function; otherwise, returns the specified attribute.
+ """
+
+ try:
+ # Figure out if the specified attribute is the name of a
+ # PLCAPI function. If so and the function requires an
+ # authentication structure as its first argument, return a
+ # callable that automagically adds an auth struct to the
+ # call.
+ methodname = attr
+ api_function = self.api.callable(methodname)
+ if api_function.accepts and \
+ (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
+ (isinstance(api_function.accepts[0], Mixed) and \
+ filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
+ function = getattr(self.server, methodname)
+ return self.add_auth(function, methodname)
+ except Exception, err:
+ pass
+
+ if hasattr(self, attr):
+ return getattr(self, attr)
+ else:
+ raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
+
+class Peers (Table):
+ """
+ Maps to the peers table in the database
+ """
+
+ def __init__ (self, api, peer_filter = None, columns = None):
+ Table.__init__(self, api, Peer, columns)
+
+ sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
+ ", ".join(self.columns)
+
+ if peer_filter is not None:
+ if isinstance(peer_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
+ peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
+ sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
+ elif isinstance(peer_filter, dict):
+ peer_filter = Filter(Peer.fields, peer_filter)
+ sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the persons table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Persons.py 5652 2007-11-06 03:42:57Z tmack $
+#
+
+from types import StringTypes
+from datetime import datetime
+import md5
+import time
+from random import Random
+import re
+import crypt
+
+from PLC.Faults import *
+from PLC.Debug import log
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Roles import Role, Roles
+from PLC.Keys import Key, Keys
+from PLC.Messages import Message, Messages
+
+class Person(Row):
+ """
+ Representation of a row in the persons table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ table_name = 'persons'
+ primary_key = 'person_id'
+ join_tables = ['person_key', 'person_role', 'person_site', 'slice_person', 'person_session', 'peer_person']
+ fields = {
+ 'person_id': Parameter(int, "User identifier"),
+ 'first_name': Parameter(str, "Given name", max = 128),
+ 'last_name': Parameter(str, "Surname", max = 128),
+ 'title': Parameter(str, "Title", max = 128, nullok = True),
+ 'email': Parameter(str, "Primary e-mail address", max = 254),
+ 'phone': Parameter(str, "Telephone number", max = 64, nullok = True),
+ 'url': Parameter(str, "Home page", max = 254, nullok = True),
+ 'bio': Parameter(str, "Biography", max = 254, nullok = True),
+ 'enabled': Parameter(bool, "Has been enabled"),
+ 'password': Parameter(str, "Account password in crypt() form", max = 254),
+ 'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
+ 'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True),
+ 'last_updated': Parameter(int, "Date and time of last update", ro = True),
+ 'date_created': Parameter(int, "Date and time when account was created", ro = True),
+ 'role_ids': Parameter([int], "List of role identifiers"),
+ 'roles': Parameter([str], "List of roles"),
+ 'site_ids': Parameter([int], "List of site identifiers"),
+ 'key_ids': Parameter([int], "List of key identifiers"),
+ 'slice_ids': Parameter([int], "List of slice identifiers"),
+ 'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
+ 'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
+ }
+ related_fields = {
+ 'roles': [Mixed(Parameter(int, "Role identifier"),
+ Parameter(str, "Role name"))],
+ 'sites': [Mixed(Parameter(int, "Site identifier"),
+ Parameter(str, "Site name"))],
+ 'keys': [Mixed(Parameter(int, "Key identifier"),
+ Filter(Key.fields))],
+ 'slices': [Mixed(Parameter(int, "Slice identifier"),
+ Parameter(str, "Slice name"))]
+ }
+
+
+
+ # for Cache
+ class_key = 'email'
+ foreign_fields = ['first_name', 'last_name', 'title', 'email', 'phone', 'url',
+ 'bio', 'enabled', 'password', ]
+ # forget about these ones, they are read-only anyway
+ # handling them causes Cache to re-sync all over again
+ # 'last_updated', 'date_created'
+ foreign_xrefs = [
+ {'field' : 'key_ids', 'class': 'Key', 'table' : 'person_key' } ,
+ {'field' : 'site_ids', 'class': 'Site', 'table' : 'person_site'},
+# xxx this is not handled by Cache yet
+# 'role_ids': Parameter([int], "List of role identifiers"),
+]
+
+ def validate_email(self, email):
+ """
+ Validate email address. Stolen from Mailman.
+ """
+
+ invalid_email = PLCInvalidArgument("Invalid e-mail address")
+ email_badchars = r'[][()<>|;^,\200-\377]'
+
+ # Pretty minimal, cheesy check. We could do better...
+ if not email or email.count(' ') > 0:
+ raise invalid_email
+ if re.search(email_badchars, email) or email[0] == '-':
+ raise invalid_email
+
+ email = email.lower()
+ at_sign = email.find('@')
+ if at_sign < 1:
+ raise invalid_email
+ user = email[:at_sign]
+ rest = email[at_sign+1:]
+ domain = rest.split('.')
+
+ # This means local, unqualified addresses, are not allowed
+ if not domain:
+ raise invalid_email
+ if len(domain) < 2:
+ raise invalid_email
+
+ # check only against users on the same peer
+ if 'peer_id' in self:
+ namespace_peer_id = self['peer_id']
+ else:
+ namespace_peer_id = None
+
+ conflicts = Persons(self.api, {'email':email,'peer_id':namespace_peer_id})
+
+ for person in conflicts:
+ if 'person_id' not in self or self['person_id'] != person['person_id']:
+ raise PLCInvalidArgument, "E-mail address already in use"
+
+ return email
+
+ def validate_password(self, password):
+ """
+ Encrypt password if necessary before committing to the
+ database.
+ """
+
+ magic = "$1$"
+
+ if len(password) > len(magic) and \
+ password[0:len(magic)] == magic:
+ return password
+ else:
+ # Generate a somewhat unique 8 character salt string
+ salt = str(time.time()) + str(Random().random())
+ salt = md5.md5(salt).hexdigest()[:8]
+ return crypt.crypt(password.encode(self.api.encoding), magic + salt + "$")
+
+ validate_date_created = Row.validate_timestamp
+ validate_last_updated = Row.validate_timestamp
+ validate_verification_expires = Row.validate_timestamp
+
+ def can_update(self, person):
+ """
+ Returns true if we can update the specified person. We can
+ update a person if:
+
+ 1. We are the person.
+ 2. We are an admin.
+ 3. We are a PI and the person is a user or tech or at
+ one of our sites.
+ """
+
+ assert isinstance(person, Person)
+
+ if self['person_id'] == person['person_id']:
+ return True
+
+ if 'admin' in self['roles']:
+ return True
+
+ if 'pi' in self['roles']:
+ if set(self['site_ids']).intersection(person['site_ids']):
+ # Can update people with higher role IDs
+ return min(self['role_ids']) < min(person['role_ids'])
+
+ return False
+
+ def can_view(self, person):
+ """
+ Returns true if we can view the specified person. We can
+ view a person if:
+
+ 1. We are the person.
+ 2. We are an admin.
+ 3. We are a PI and the person is at one of our sites.
+ """
+
+ assert isinstance(person, Person)
+
+ if self.can_update(person):
+ return True
+
+ if 'pi' in self['roles']:
+ if set(self['site_ids']).intersection(person['site_ids']):
+ # Can view people with equal or higher role IDs
+ return min(self['role_ids']) <= min(person['role_ids'])
+
+ return False
+
+ add_role = Row.add_object(Role, 'person_role')
+ remove_role = Row.remove_object(Role, 'person_role')
+
+ add_key = Row.add_object(Key, 'person_key')
+ remove_key = Row.remove_object(Key, 'person_key')
+
+ def set_primary_site(self, site, commit = True):
+ """
+ Set the primary site for an existing user.
+ """
+
+ assert 'person_id' in self
+ assert 'site_id' in site
+
+ person_id = self['person_id']
+ site_id = site['site_id']
+ self.api.db.do("UPDATE person_site SET is_primary = False" \
+ " WHERE person_id = %(person_id)d",
+ locals())
+ self.api.db.do("UPDATE person_site SET is_primary = True" \
+ " WHERE person_id = %(person_id)d" \
+ " AND site_id = %(site_id)d",
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ assert 'site_ids' in self
+ assert site_id in self['site_ids']
+
+ # Make sure that the primary site is first in the list
+ self['site_ids'].remove(site_id)
+ self['site_ids'].insert(0, site_id)
+
+ def update_last_updated(self, commit = True):
+ """
+ Update last_updated field with current time
+ """
+
+ assert 'person_id' in self
+ assert self.table_name
+
+ self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+ " where person_id = %d" % (self['person_id']) )
+ self.sync(commit)
+
+ def associate_roles(self, auth, field, value):
+ """
+ Adds roles found in value list to this person (using AddRoleToPerson).
+ Deletes roles not found in value list from this person (using DeleteRoleFromPerson).
+ """
+
+ assert 'role_ids' in self
+ assert 'person_id' in self
+ assert isinstance(value, list)
+
+ (role_ids, roles_names) = self.separate_types(value)[0:2]
+
+ # Translate roles into role_ids
+ if roles_names:
+ roles = Roles(self.api, role_names, ['role_id']).dict('role_id')
+ role_ids += roles.keys()
+
+ # Add new ids, remove stale ids
+ if self['role_ids'] != role_ids:
+ from PLC.Methods.AddRoleToPerson import AddRoleToPerson
+ from PLC.Methods.DeleteRoleFromPerson import DeleteRoleFromPerson
+ new_roles = set(role_ids).difference(self['role_ids'])
+ stale_roles = set(self['role_ids']).difference(role_ids)
+
+ for new_role in new_roles:
+ AddRoleToPerson.__call__(AddRoleToPerson(self.api), auth, new_role, self['person_id'])
+ for stale_role in stale_roles:
+ DeleteRoleFromPerson.__call__(DeleteRoleFromPerson(self.api), auth, stale_role, self['person_id'])
+
+
+ def associate_sites(self, auth, field, value):
+ """
+ Adds person to sites found in value list (using AddPersonToSite).
+ Deletes person from site not found in value list (using DeletePersonFromSite).
+ """
+
+ from PLC.Sites import Sites
+
+ assert 'site_ids' in self
+ assert 'person_id' in self
+ assert isinstance(value, list)
+
+ (site_ids, site_names) = self.separate_types(value)[0:2]
+
+ # Translate roles into role_ids
+ if site_names:
+ sites = Sites(self.api, site_names, ['site_id']).dict('site_id')
+ site_ids += sites.keys()
+
+ # Add new ids, remove stale ids
+ if self['site_ids'] != site_ids:
+ from PLC.Methods.AddPersonToSite import AddPersonToSite
+ from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
+ new_sites = set(site_ids).difference(self['site_ids'])
+ stale_sites = set(self['site_ids']).difference(site_ids)
+
+ for new_site in new_sites:
+ AddPersonToSite.__call__(AddPersonToSite(self.api), auth, self['person_id'], new_site)
+ for stale_site in stale_sites:
+ DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, self['person_id'], stale_site)
+
+
+ def associate_keys(self, auth, field, value):
+ """
+ Deletes key_ids not found in value list (using DeleteKey).
+ Adds key if key_fields w/o key_id is found (using AddPersonKey).
+ Updates key if key_fields w/ key_id is found (using UpdateKey).
+ """
+ assert 'key_ids' in self
+ assert 'person_id' in self
+ assert isinstance(value, list)
+
+ (key_ids, blank, keys) = self.separate_types(value)
+
+ if self['key_ids'] != key_ids:
+ from PLC.Methods.DeleteKey import DeleteKey
+ stale_keys = set(self['key_ids']).difference(key_ids)
+
+ for stale_key in stale_keys:
+ DeleteKey.__call__(DeleteKey(self.api), auth, stale_key)
+
+ if keys:
+ from PLC.Methods.AddPersonKey import AddPersonKey
+ from PLC.Methods.UpdateKey import UpdateKey
+ updated_keys = filter(lambda key: 'key_id' in key, keys)
+ added_keys = filter(lambda key: 'key_id' not in key, keys)
+
+ for key in added_keys:
+ AddPersonKey.__call__(AddPersonKey(self.api), auth, self['person_id'], key)
+ for key in updated_keys:
+ key_id = key.pop('key_id')
+ UpdateKey.__call__(UpdateKey(self.api), auth, key_id, key)
+
+
+ def associate_slices(self, auth, field, value):
+ """
+ Adds person to slices found in value list (using AddPersonToSlice).
+ Deletes person from slices found in value list (using DeletePersonFromSlice).
+ """
+
+ from PLC.Slices import Slices
+
+ assert 'slice_ids' in self
+ assert 'person_id' in self
+ assert isinstance(value, list)
+
+ (slice_ids, slice_names) = self.separate_types(value)[0:2]
+
+ # Translate roles into role_ids
+ if slice_names:
+ slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
+ slice_ids += slices.keys()
+
+ # Add new ids, remove stale ids
+ if self['slice_ids'] != slice_ids:
+ from PLC.Methods.AddPersonToSlice import AddPersonToSlice
+ from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
+ new_slices = set(slice_ids).difference(self['slice_ids'])
+ stale_slices = set(self['slice_ids']).difference(slice_ids)
+
+ for new_slice in new_slices:
+ AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, self['person_id'], new_slice)
+ for stale_slice in stale_slices:
+ DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, self['person_id'], stale_slice)
+
+
+ def delete(self, commit = True):
+ """
+ Delete existing user.
+ """
+
+ # Delete all keys
+ keys = Keys(self.api, self['key_ids'])
+ for key in keys:
+ key.delete(commit = False)
+
+ # Clean up miscellaneous join tables
+ for table in self.join_tables:
+ self.api.db.do("DELETE FROM %s WHERE person_id = %d" % \
+ (table, self['person_id']))
+
+ # Mark as deleted
+ self['deleted'] = True
+ self.sync(commit)
+
+class Persons(Table):
+ """
+ Representation of row(s) from the persons table in the
+ database.
+ """
+
+ def __init__(self, api, person_filter = None, columns = None):
+ Table.__init__(self, api, Person, columns)
+ #sql = "SELECT %s FROM view_persons WHERE deleted IS False" % \
+ # ", ".join(self.columns)
+ foreign_fields = {'role_ids': ('role_id', 'person_role'),
+ 'roles': ('name', 'roles'),
+ 'site_ids': ('site_id', 'person_site'),
+ 'key_ids': ('key_id', 'person_key'),
+ 'slice_ids': ('slice_id', 'slice_person')
+ }
+ foreign_keys = {}
+ db_fields = filter(lambda field: field not in foreign_fields.keys(), Person.fields.keys())
+ all_fields = db_fields + [value[0] for value in foreign_fields.values()]
+ fields = []
+ _select = "SELECT "
+ _from = " FROM persons "
+ _join = " LEFT JOIN peer_person USING (person_id) "
+ _where = " WHERE deleted IS False "
+
+ if not columns:
+ # include all columns
+ fields = all_fields
+ tables = [value[1] for value in foreign_fields.values()]
+ tables.sort()
+ for key in foreign_fields.keys():
+ foreign_keys[foreign_fields[key][0]] = key
+ for table in tables:
+ if table in ['roles']:
+ _join += " LEFT JOIN roles USING(role_id) "
+ else:
+ _join += " LEFT JOIN %s USING (person_id) " % (table)
+ else:
+ tables = set()
+ columns = filter(lambda column: column in db_fields+foreign_fields.keys(), columns)
+ columns.sort()
+ for column in columns:
+ if column in foreign_fields.keys():
+ (field, table) = foreign_fields[column]
+ foreign_keys[field] = column
+ fields += [field]
+ tables.add(table)
+ if column in ['roles']:
+ _join += " LEFT JOIN roles USING(role_id) "
+ else:
+ _join += " LEFT JOIN %s USING (person_id)" % \
+ (foreign_fields[column][1])
+
+ else:
+ fields += [column]
+
+ # postgres will return timestamps as datetime objects.
+ # XMLPRC cannot marshal datetime so convert to int
+ timestamps = ['date_created', 'last_updated', 'verification_expires']
+ for field in fields:
+ if field in timestamps:
+ fields[fields.index(field)] = \
+ "CAST(date_part('epoch', %s) AS bigint) AS %s" % (field, field)
+
+ _select += ", ".join(fields)
+ sql = _select + _from + _join + _where
+
+ # deal with filter
+ if person_filter is not None:
+ if isinstance(person_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
+ person_filter = Filter(Person.fields, {'person_id': ints, 'email': strs})
+ sql += " AND (%s) %s" % person_filter.sql(api, "OR")
+ elif isinstance(person_filter, dict):
+ person_filter = Filter(Person.fields, person_filter)
+ sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+ elif isinstance (person_filter, StringTypes):
+ person_filter = Filter(Person.fields, {'email':[person_filter]})
+ sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+ elif isinstance (person_filter, int):
+ person_filter = Filter(Person.fields, {'person_id':[person_filter]})
+ sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
+
+ # aggregate data
+ all_persons = {}
+ for row in self.api.db.selectall(sql):
+ person_id = row['person_id']
+
+ if all_persons.has_key(person_id):
+ for (key, key_list) in foreign_keys.items():
+ data = row.pop(key)
+ row[key_list] = [data]
+ if data and data not in all_persons[person_id][key_list]:
+ all_persons[person_id][key_list].append(data)
+ else:
+ for key in foreign_keys.keys():
+ value = row.pop(key)
+ if value:
+ row[foreign_keys[key]] = [value]
+ else:
+ row[foreign_keys[key]] = []
+ if row:
+ all_persons[person_id] = row
+
+ # populate self
+ for row in all_persons.values():
+ obj = self.classobj(self.api, row)
+ self.append(obj)
+
--- /dev/null
+#
+# PostgreSQL database interface. Sort of like DBI(3) (Database
+# independent interface for Perl).
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: PostgreSQL.py 10071 2008-07-31 18:10:11Z tmack $
+#
+
+import psycopg2
+import psycopg2.extensions
+psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+# UNICODEARRAY not exported yet
+psycopg2.extensions.register_type(psycopg2._psycopg.UNICODEARRAY)
+
+import pgdb
+from types import StringTypes, NoneType
+import traceback
+import commands
+import re
+from pprint import pformat
+
+from PLC.Debug import profile, log
+from PLC.Faults import *
+
+if not psycopg2:
+ is8bit = re.compile("[\x80-\xff]").search
+
+ def unicast(typecast):
+ """
+ pgdb returns raw UTF-8 strings. This function casts strings that
+ appear to contain non-ASCII characters to unicode objects.
+ """
+
+ def wrapper(*args, **kwds):
+ value = typecast(*args, **kwds)
+
+ # pgdb always encodes unicode objects as UTF-8 regardless of
+ # the DB encoding (and gives you no option for overriding
+ # the encoding), so always decode 8-bit objects as UTF-8.
+ if isinstance(value, str) and is8bit(value):
+ value = unicode(value, "utf-8")
+
+ return value
+
+ return wrapper
+
+ pgdb.pgdbTypeCache.typecast = unicast(pgdb.pgdbTypeCache.typecast)
+
+class PostgreSQL:
+ def __init__(self, api):
+ self.api = api
+ self.debug = False
+ self.connection = None
+
+ def cursor(self):
+ if self.connection is None:
+ # (Re)initialize database connection
+ if psycopg2:
+ try:
+ # Try UNIX socket first
+ self.connection = psycopg2.connect(user = self.api.config.PLC_DB_USER,
+ password = self.api.config.PLC_DB_PASSWORD,
+ database = self.api.config.PLC_DB_NAME)
+ except psycopg2.OperationalError:
+ # Fall back on TCP
+ self.connection = psycopg2.connect(user = self.api.config.PLC_DB_USER,
+ password = self.api.config.PLC_DB_PASSWORD,
+ database = self.api.config.PLC_DB_NAME,
+ host = self.api.config.PLC_DB_HOST,
+ port = self.api.config.PLC_DB_PORT)
+ self.connection.set_client_encoding("UNICODE")
+ else:
+ self.connection = pgdb.connect(user = self.api.config.PLC_DB_USER,
+ password = self.api.config.PLC_DB_PASSWORD,
+ host = "%s:%d" % (api.config.PLC_DB_HOST, api.config.PLC_DB_PORT),
+ database = self.api.config.PLC_DB_NAME)
+
+ (self.rowcount, self.description, self.lastrowid) = \
+ (None, None, None)
+
+ return self.connection.cursor()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+ def quote(self, value):
+ """
+ Returns quoted version of the specified value.
+ """
+
+ # The pgdb._quote function is good enough for general SQL
+ # quoting, except for array types.
+ if isinstance(value, (list, tuple, set)):
+ return "ARRAY[%s]" % ", ".join(map, self.quote, value)
+ else:
+ return pgdb._quote(value)
+
+ quote = classmethod(quote)
+
+ def param(self, name, value):
+ # None is converted to the unquoted string NULL
+ if isinstance(value, NoneType):
+ conversion = "s"
+ # True and False are also converted to unquoted strings
+ elif isinstance(value, bool):
+ conversion = "s"
+ elif isinstance(value, float):
+ conversion = "f"
+ elif not isinstance(value, StringTypes):
+ conversion = "d"
+ else:
+ conversion = "s"
+
+ return '%(' + name + ')' + conversion
+
+ param = classmethod(param)
+
+ def begin_work(self):
+ # Implicit in pgdb.connect()
+ pass
+
+ def commit(self):
+ self.connection.commit()
+
+ def rollback(self):
+ self.connection.rollback()
+
+ def do(self, query, params = None):
+ cursor = self.execute(query, params)
+ cursor.close()
+ return self.rowcount
+
+ def next_id(self, table_name, primary_key):
+ sequence = "%(table_name)s_%(primary_key)s_seq" % locals()
+ sql = "SELECT nextval('%(sequence)s')" % locals()
+ rows = self.selectall(sql, hashref = False)
+ if rows:
+ return rows[0][0]
+
+ return None
+
+ def last_insert_id(self, table_name, primary_key):
+ if isinstance(self.lastrowid, int):
+ sql = "SELECT %s FROM %s WHERE oid = %d" % \
+ (primary_key, table_name, self.lastrowid)
+ rows = self.selectall(sql, hashref = False)
+ if rows:
+ return rows[0][0]
+
+ return None
+
+ # modified for psycopg2-2.0.7
+ # executemany is undefined for SELECT's
+ # see http://www.python.org/dev/peps/pep-0249/
+ # accepts either None, a single dict, a tuple of single dict - in which case it execute's
+ # or a tuple of several dicts, in which case it executemany's
+ def execute(self, query, params = None):
+
+ cursor = self.cursor()
+ try:
+
+ # psycopg2 requires %()s format for all parameters,
+ # regardless of type.
+ if psycopg2:
+ query = re.sub(r'(%\([^)]*\)|%)[df]', r'\1s', query)
+
+ if not params:
+ if self.debug:
+ print >> log,'execute0',query
+ cursor.execute(query)
+ elif isinstance(params,dict):
+ if self.debug:
+ print >> log,'execute-dict: params',params,'query',query%params
+ cursor.execute(query,params)
+ elif isinstance(params,tuple) and len(params)==1:
+ if self.debug:
+ print >> log,'execute-tuple',query%params[0]
+ cursor.execute(query,params[0])
+ else:
+ param_seq=(params,)
+ if self.debug:
+ for params in param_seq:
+ print >> log,'executemany',query%params
+ cursor.executemany(query, param_seq)
+ (self.rowcount, self.description, self.lastrowid) = \
+ (cursor.rowcount, cursor.description, cursor.lastrowid)
+ except Exception, e:
+ try:
+ self.rollback()
+ except:
+ pass
+ uuid = commands.getoutput("uuidgen")
+ print >> log, "Database error %s:" % uuid
+ print >> log, e
+ print >> log, "Query:"
+ print >> log, query
+ print >> log, "Params:"
+ print >> log, pformat(params)
+ raise PLCDBError("Please contact " + \
+ self.api.config.PLC_NAME + " Support " + \
+ "<" + self.api.config.PLC_MAIL_SUPPORT_ADDRESS + ">" + \
+ " and reference " + uuid)
+
+ return cursor
+
+ def selectall(self, query, params = None, hashref = True, key_field = None):
+ """
+ Return each row as a dictionary keyed on field name (like DBI
+ selectrow_hashref()). If key_field is specified, return rows
+ as a dictionary keyed on the specified field (like DBI
+ selectall_hashref()).
+
+ If params is specified, the specified parameters will be bound
+ to the query.
+ """
+
+ cursor = self.execute(query, params)
+ rows = cursor.fetchall()
+ cursor.close()
+
+ if hashref or key_field is not None:
+ # Return each row as a dictionary keyed on field name
+ # (like DBI selectrow_hashref()).
+ labels = [column[0] for column in self.description]
+ rows = [dict(zip(labels, row)) for row in rows]
+
+ if key_field is not None and key_field in labels:
+ # Return rows as a dictionary keyed on the specified field
+ # (like DBI selectall_hashref()).
+ return dict([(row[key_field], row) for row in rows])
+ else:
+ return rows
+
+ def fields(self, table, notnull = None, hasdef = None):
+ """
+ Return the names of the fields of the specified table.
+ """
+
+ if hasattr(self, 'fields_cache'):
+ if self.fields_cache.has_key((table, notnull, hasdef)):
+ return self.fields_cache[(table, notnull, hasdef)]
+ else:
+ self.fields_cache = {}
+
+ sql = "SELECT attname FROM pg_attribute, pg_class" \
+ " WHERE pg_class.oid = attrelid" \
+ " AND attnum > 0 AND relname = %(table)s"
+
+ if notnull is not None:
+ sql += " AND attnotnull is %(notnull)s"
+
+ if hasdef is not None:
+ sql += " AND atthasdef is %(hasdef)s"
+
+ rows = self.selectall(sql, locals(), hashref = False)
+
+ self.fields_cache[(table, notnull, hasdef)] = [row[0] for row in rows]
+
+ return self.fields_cache[(table, notnull, hasdef)]
--- /dev/null
+#
+# Replacement for xmlrpclib.SafeTransport, which does not validate
+# SSL certificates. Requires PyCurl.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: PyCurl.py 7535 2007-12-17 15:31:38Z thierry $
+#
+
+import os
+import xmlrpclib
+import pycurl
+from tempfile import NamedTemporaryFile
+
+class PyCurlTransport(xmlrpclib.Transport):
+ def __init__(self, uri, cert = None, timeout = 300):
+ xmlrpclib.Transport.__init__(self)
+ self.curl = pycurl.Curl()
+
+ # Suppress signals
+ self.curl.setopt(pycurl.NOSIGNAL, 1)
+
+ # Follow redirections
+ self.curl.setopt(pycurl.FOLLOWLOCATION, 1)
+
+ # Set URL
+ self.url = uri
+ self.curl.setopt(pycurl.URL, str(uri))
+
+ # Set certificate path
+ if cert is not None:
+ if os.path.exists(cert):
+ cert_path = str(cert)
+ else:
+ # Keep a reference so that it does not get deleted
+ self.cert = NamedTemporaryFile(prefix = "cert")
+ self.cert.write(cert)
+ self.cert.flush()
+ cert_path = self.cert.name
+ self.curl.setopt(pycurl.CAINFO, cert_path)
+ self.curl.setopt(pycurl.SSL_VERIFYPEER, 2)
+
+ # Set connection timeout
+ if timeout:
+ self.curl.setopt(pycurl.CONNECTTIMEOUT, timeout)
+ self.curl.setopt(pycurl.TIMEOUT, timeout)
+
+ # Set request callback
+ self.body = ""
+ def body(buf):
+ self.body += buf
+ self.curl.setopt(pycurl.WRITEFUNCTION, body)
+
+ def request(self, host, handler, request_body, verbose = 1):
+ # Set verbosity
+ self.curl.setopt(pycurl.VERBOSE, verbose)
+
+ # Post request
+ self.curl.setopt(pycurl.POST, 1)
+ self.curl.setopt(pycurl.POSTFIELDS, request_body)
+
+ try:
+ self.curl.perform()
+ errcode = self.curl.getinfo(pycurl.HTTP_CODE)
+ response = self.body
+ self.body = ""
+ errmsg="<no known errmsg>"
+ except pycurl.error, err:
+ (errcode, errmsg) = err
+
+ if errcode == 60:
+ raise Exception, "PyCurl: SSL certificate validation failed"
+ elif errcode != 200:
+ raise Exception, "PyCurl: HTTP error %d -- %r" % (errcode,errmsg)
+
+ # Parse response
+ p, u = self.getparser()
+ p.feed(response)
+ p.close()
+
+ return u.close()
--- /dev/null
+#
+# Functions for interacting with the roles table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Roles.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from types import StringTypes
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+
+class Role(Row):
+ """
+ Representation of a row in the roles table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'roles'
+ primary_key = 'role_id'
+ join_tables = ['person_role', ('slice_attribute_types', 'min_role_id')]
+ fields = {
+ 'role_id': Parameter(int, "Role identifier"),
+ 'name': Parameter(str, "Role", max = 100),
+ }
+
+ def validate_role_id(self, role_id):
+ # Make sure role does not already exist
+ conflicts = Roles(self.api, [role_id])
+ if conflicts:
+ raise PLCInvalidArgument, "Role ID already in use"
+
+ return role_id
+
+ def validate_name(self, name):
+ # Make sure name is not blank
+ if not len(name):
+ raise PLCInvalidArgument, "Role must be specified"
+
+ # Make sure role does not already exist
+ conflicts = Roles(self.api, [name])
+ if conflicts:
+ raise PLCInvalidArgument, "Role name already in use"
+
+ return name
+
+class Roles(Table):
+ """
+ Representation of the roles table in the database.
+ """
+
+ def __init__(self, api, role_filter = None):
+ Table.__init__(self, api, Role)
+
+ sql = "SELECT %s FROM roles WHERE True" % \
+ ", ".join(Role.fields)
+
+ if role_filter is not None:
+ if isinstance(role_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), role_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), role_filter)
+ role_filter = Filter(Role.fields, {'role_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % role_filter.sql(api, "OR")
+ elif isinstance(role_filter, dict):
+ role_filter = Filter(Role.fields, role_filter)
+ sql += " AND (%s) %s" % role_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+from types import StringTypes
+import random
+import base64
+import time
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.Persons import Person, Persons
+from PLC.Nodes import Node, Nodes
+
+class Session(Row):
+ """
+ Representation of a row in the sessions table. To use, instantiate
+ with a dict of values.
+ """
+
+ table_name = 'sessions'
+ primary_key = 'session_id'
+ join_tables = ['person_session', 'node_session']
+ fields = {
+ 'session_id': Parameter(str, "Session key"),
+ 'person_id': Parameter(int, "Account identifier, if applicable"),
+ 'node_id': Parameter(int, "Node identifier, if applicable"),
+ 'expires': Parameter(int, "Date and time when session expires, in seconds since UNIX epoch"),
+ }
+
+ def validate_expires(self, expires):
+ if expires < time.time():
+ raise PLCInvalidArgument, "Expiration date must be in the future"
+
+ return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(expires))
+
+ add_person = Row.add_object(Person, 'person_session')
+
+ def add_node(self, node, commit = True):
+ # Nodes can have only one session at a time
+ self.api.db.do("DELETE FROM node_session WHERE node_id = %d" % \
+ node['node_id'])
+
+ add = Row.add_object(Node, 'node_session')
+ add(self, node, commit = commit)
+
+ def sync(self, commit = True, insert = None):
+ if not self.has_key('session_id'):
+ # Before a new session is added, delete expired sessions
+ expired = Sessions(self.api, expires = -int(time.time()))
+ for session in expired:
+ session.delete(commit)
+
+ # Generate 32 random bytes
+ bytes = random.sample(xrange(0, 256), 32)
+ # Base64 encode their string representation
+ self['session_id'] = base64.b64encode("".join(map(chr, bytes)))
+ # Force insert
+ insert = True
+
+ Row.sync(self, commit, insert)
+
+class Sessions(Table):
+ """
+ Representation of row(s) from the session table in the database.
+ """
+
+ def __init__(self, api, session_filter = None, expires = int(time.time())):
+ Table.__init__(self, api, Session)
+
+ sql = "SELECT %s FROM view_sessions WHERE True" % \
+ ", ".join(Session.fields)
+
+ if session_filter is not None:
+ if isinstance(session_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), session_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), session_filter)
+ session_filter = Filter(Session.fields, {'person_id': ints, 'session_id': strs})
+ sql += " AND (%s) %s" % session_filter.sql(api, "OR")
+ elif isinstance(session_filter, dict):
+ session_filter = Filter(Session.fields, session_filter)
+ sql += " AND (%s) %s" % session_filter.sql(api, "AND")
+
+ if expires is not None:
+ if expires >= 0:
+ sql += " AND expires > %(expires)d"
+ else:
+ expires = -expires
+ sql += " AND expires < %(expires)d"
+
+ self.selectall(sql, locals())
--- /dev/null
+#!/usr/bin/python
+#
+# Interactive shell for testing PLCAPI
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2005 The Trustees of Princeton University
+#
+# $Id: Shell.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import os
+import pydoc
+import xmlrpclib
+
+from PLC.API import PLCAPI
+from PLC.Parameter import Mixed
+from PLC.Auth import Auth
+from PLC.Config import Config
+from PLC.Method import Method
+from PLC.PyCurl import PyCurlTransport
+import PLC.Methods
+
+class Callable:
+ """
+ Wrapper to call a method either directly or remotely and
+ automagically add the authentication structure if necessary.
+ """
+
+ def __init__(self, shell, name, func, auth = None):
+ self.shell = shell
+ self.name = name
+ self.func = func
+ self.auth = auth
+
+ def __call__(self, *args, **kwds):
+ """
+ Automagically add the authentication structure if the function
+ requires it and it has not been specified.
+ """
+
+ if self.auth and \
+ (not args or not isinstance(args[0], dict) or \
+ (not args[0].has_key('AuthMethod') and \
+ not args[0].has_key('session'))):
+ args = (self.auth,) + args
+
+ if self.shell.multi:
+ self.shell.calls.append({'methodName': self.name, 'params': list(args)})
+ return None
+ else:
+ return self.func(*args, **kwds)
+
+class Shell:
+ def __init__(self,
+ # Add API functions to global scope
+ globals = None,
+ # Configuration file
+ config = None,
+ # XML-RPC server
+ url = None, xmlrpc = False, cacert = None,
+ # API authentication method
+ method = None,
+ # Password authentication
+ role = None, user = None, password = None,
+ # Session authentication
+ session = None):
+ """
+ Initialize a new shell instance. Re-initializes globals.
+ """
+
+ try:
+ # If any XML-RPC options have been specified, do not try
+ # connecting directly to the DB.
+ if (url, method, user, password, role, cacert, xmlrpc) != \
+ (None, None, None, None, None, None, False):
+ raise Exception
+
+ # Otherwise, first try connecting directly to the DB. This
+ # absolutely requires a configuration file; the API
+ # instance looks for one in a default location if one is
+ # not specified. If this fails, try connecting to the API
+ # server via XML-RPC.
+ if config is None:
+ self.api = PLCAPI()
+ else:
+ self.api = PLCAPI(config)
+ self.config = self.api.config
+ self.url = None
+ self.server = None
+ except Exception, err:
+ # Try connecting to the API server via XML-RPC
+ self.api = PLCAPI(None)
+
+ try:
+ if config is None:
+ self.config = Config()
+ else:
+ self.config = Config(config)
+ except Exception, err:
+ # Try to continue if no configuration file is available
+ self.config = None
+
+ if url is None:
+ if self.config is None:
+ raise Exception, "Must specify API URL"
+
+ url = "https://" + self.config.PLC_API_HOST + \
+ ":" + str(self.config.PLC_API_PORT) + \
+ "/" + self.config.PLC_API_PATH + "/"
+
+ if cacert is None:
+ cacert = self.config.PLC_API_CA_SSL_CRT
+
+ self.url = url
+ if cacert is not None:
+ self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
+ else:
+ self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
+
+ # Set up authentication structure
+
+ # Default is to use session or capability authentication
+ if (method, user, password) == (None, None, None):
+ if session is not None or os.path.exists("/etc/planetlab/session"):
+ method = "session"
+ if session is None:
+ session = "/etc/planetlab/session"
+ else:
+ method = "capability"
+
+ if method == "capability":
+ # Load defaults from configuration file if using capability
+ # authentication.
+ if user is None and self.config is not None:
+ user = self.config.PLC_API_MAINTENANCE_USER
+ if password is None and self.config is not None:
+ password = self.config.PLC_API_MAINTENANCE_PASSWORD
+ if role is None:
+ role = "admin"
+ elif method is None:
+ # Otherwise, default to password authentication
+ method = "password"
+
+ if role == "anonymous" or method == "anonymous":
+ self.auth = {'AuthMethod': "anonymous"}
+ elif method == "session":
+ if session is None:
+ raise Exception, "Must specify session"
+
+ if os.path.exists(session):
+ session = file(session).read()
+
+ self.auth = {'AuthMethod': "session", 'session': session}
+ else:
+ if user is None:
+ raise Exception, "Must specify username"
+
+ if password is None:
+ raise Exception, "Must specify password"
+
+ self.auth = {'AuthMethod': method,
+ 'Username': user,
+ 'AuthString': password}
+
+ if role is not None:
+ self.auth['Role'] = role
+
+ for method in PLC.Methods.methods:
+ api_function = self.api.callable(method)
+
+ if self.server is None:
+ # Can just call it directly
+ func = api_function
+ else:
+ func = getattr(self.server, method)
+
+ # If the function requires an authentication structure as
+ # its first argument, automagically add an auth struct to
+ # the call.
+ if api_function.accepts and \
+ (isinstance(api_function.accepts[0], Auth) or \
+ (isinstance(api_function.accepts[0], Mixed) and \
+ filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
+ auth = self.auth
+ else:
+ auth = None
+
+ callable = Callable(self, method, func, auth)
+
+ # Add to ourself and the global environment. Add dummy
+ # subattributes to support tab completion of methods with
+ # dots in their names (e.g., system.listMethods).
+ class Dummy: pass
+ paths = method.split(".")
+ if len(paths) > 1:
+ first = paths.pop(0)
+
+ if not hasattr(self, first):
+ obj = Dummy()
+ setattr(self, first, obj)
+ # Also add to global environment if specified
+ if globals is not None:
+ globals[first] = obj
+
+ obj = getattr(self, first)
+
+ for path in paths:
+ if not hasattr(obj, path):
+ if path == paths[-1]:
+ setattr(obj, path, callable)
+ else:
+ setattr(obj, path, Dummy())
+ obj = getattr(obj, path)
+ else:
+ setattr(self, method, callable)
+ # Also add to global environment if specified
+ if globals is not None:
+ globals[method] = callable
+
+ # Override help(), begin(), and commit()
+ if globals is not None:
+ globals['help'] = self.help
+ globals['begin'] = self.begin
+ globals['commit'] = self.commit
+
+ # Multicall support
+ self.calls = []
+ self.multi = False
+
+ def help(self, topic = None):
+ if isinstance(topic, Callable):
+ pydoc.pager(self.system.methodHelp(topic.name))
+ else:
+ pydoc.help(topic)
+
+ def begin(self):
+ if self.calls:
+ raise Exception, "multicall already in progress"
+
+ self.multi = True
+
+ def commit(self):
+ if self.calls:
+ ret = []
+ self.multi = False
+ results = self.system.multicall(self.calls)
+ for result in results:
+ if type(result) == type({}):
+ raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
+ elif type(result) == type([]):
+ ret.append(result[0])
+ else:
+ raise ValueError, "unexpected type in multicall result"
+ else:
+ ret = None
+
+ self.calls = []
+ self.multi = False
+
+ return ret
--- /dev/null
+from types import StringTypes
+import string
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.Slices import Slice, Slices
+from PLC.PCUs import PCU, PCUs
+from PLC.Nodes import Node, Nodes
+from PLC.Addresses import Address, Addresses
+from PLC.Persons import Person, Persons
+
+class Site(Row):
+ """
+ Representation of a row in the sites table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ table_name = 'sites'
+ primary_key = 'site_id'
+ join_tables = ['person_site', 'site_address', 'peer_site']
+ fields = {
+ 'site_id': Parameter(int, "Site identifier"),
+ 'name': Parameter(str, "Full site name", max = 254),
+ 'abbreviated_name': Parameter(str, "Abbreviated site name", max = 50),
+ 'login_base': Parameter(str, "Site slice prefix", max = 20),
+ 'is_public': Parameter(bool, "Publicly viewable site"),
+ 'enabled': Parameter(bool, "Has been enabled"),
+ 'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
+ 'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
+ 'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
+ 'date_created': Parameter(int, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True),
+ 'last_updated': Parameter(int, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True),
+ 'max_slices': Parameter(int, "Maximum number of slices that the site is able to create"),
+ 'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create"),
+ 'person_ids': Parameter([int], "List of account identifiers"),
+ 'slice_ids': Parameter([int], "List of slice identifiers"),
+ 'address_ids': Parameter([int], "List of address identifiers"),
+ 'pcu_ids': Parameter([int], "List of PCU identifiers"),
+ 'node_ids': Parameter([int], "List of site node identifiers"),
+ 'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
+ 'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
+ 'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
+ }
+ related_fields = {
+ 'persons': [Mixed(Parameter(int, "Person identifier"),
+ Parameter(str, "Email address"))],
+ 'addresses': [Mixed(Parameter(int, "Address identifer"),
+ Filter(Address.fields))]
+ }
+ # for Cache
+ class_key = 'login_base'
+ foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
+ 'url', 'max_slices', 'max_slivers',
+ ]
+ # forget about these ones, they are read-only anyway
+ # handling them causes Cache to re-sync all over again
+ # 'last_updated', 'date_created'
+ foreign_xrefs = []
+
+ def validate_name(self, name):
+ if not len(name):
+ raise PLCInvalidArgument, "Name must be specified"
+
+ return name
+
+ validate_abbreviated_name = validate_name
+
+ def validate_login_base(self, login_base):
+ if not len(login_base):
+ raise PLCInvalidArgument, "Login base must be specified"
+
+ if not set(login_base).issubset(string.lowercase + string.digits):
+ raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
+
+ conflicts = Sites(self.api, [login_base])
+ for site in conflicts:
+ if 'site_id' not in self or self['site_id'] != site['site_id']:
+ raise PLCInvalidArgument, "login_base already in use"
+
+ return login_base
+
+ def validate_latitude(self, latitude):
+ if not self.has_key('longitude') or \
+ self['longitude'] is None:
+ raise PLCInvalidArgument, "Longitude must also be specified"
+
+ return latitude
+
+ def validate_longitude(self, longitude):
+ if not self.has_key('latitude') or \
+ self['latitude'] is None:
+ raise PLCInvalidArgument, "Latitude must also be specified"
+
+ return longitude
+
+ validate_date_created = Row.validate_timestamp
+ validate_last_updated = Row.validate_timestamp
+
+ add_person = Row.add_object(Person, 'person_site')
+ remove_person = Row.remove_object(Person, 'person_site')
+
+ add_address = Row.add_object(Address, 'site_address')
+ remove_address = Row.remove_object(Address, 'site_address')
+
+ def update_last_updated(self, commit = True):
+ """
+ Update last_updated field with current time
+ """
+
+ assert 'site_id' in self
+ assert self.table_name
+
+ self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+ " where site_id = %d" % (self['site_id']) )
+ self.sync(commit)
+
+
+ def associate_persons(self, auth, field, value):
+ """
+ Adds persons found in value list to this site (using AddPersonToSite).
+ Deletes persons not found in value list from this site (using DeletePersonFromSite).
+ """
+
+ assert 'person_ids' in self
+ assert 'site_id' in self
+ assert isinstance(value, list)
+
+ (person_ids, emails) = self.separate_types(value)[0:2]
+
+ # Translate emails into person_ids
+ if emails:
+ persons = Persons(self.api, emails, ['person_id']).dict('person_id')
+ person_ids += persons.keys()
+
+ # Add new ids, remove stale ids
+ if self['person_ids'] != person_ids:
+ from PLC.Methods.AddPersonToSite import AddPersonToSite
+ from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
+ new_persons = set(person_ids).difference(self['person_ids'])
+ stale_persons = set(self['person_ids']).difference(person_ids)
+
+ for new_person in new_persons:
+ AddPersonToSite.__call__(AddPersonToSite(self.api), auth, new_person, self['site_id'])
+ for stale_person in stale_persons:
+ DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, stale_person, self['site_id'])
+
+ def associate_addresses(self, auth, field, value):
+ """
+ Deletes addresses_ids not found in value list (using DeleteAddress).
+ Adds address if slice_fields w/o address_id found in value list (using AddSiteAddress).
+ Update address if slice_fields w/ address_id found in value list (using UpdateAddress).
+ """
+
+ assert 'address_ids' in self
+ assert 'site_id' in self
+ assert isinstance(value, list)
+
+ (address_ids, blank, addresses) = self.separate_types(value)
+
+ for address in addresses:
+ if 'address_id' in address:
+ address_ids.append(address['address_id'])
+
+ # Add new ids, remove stale ids
+ if self['address_ids'] != address_ids:
+ from PLC.Methods.DeleteAddress import DeleteAddress
+ stale_addresses = set(self['address_ids']).difference(address_ids)
+
+ for stale_address in stale_addresses:
+ DeleteAddress.__call__(DeleteAddress(self.api), auth, stale_address)
+
+ if addresses:
+ from PLC.Methods.AddSiteAddress import AddSiteAddress
+ from PLC.Methods.UpdateAddress import UpdateAddress
+
+ updated_addresses = filter(lambda address: 'address_id' in address, addresses)
+ added_addresses = filter(lambda address: 'address_id' not in address, addresses)
+
+ for address in added_addresses:
+ AddSiteAddress.__call__(AddSiteAddress(self.api), auth, self['site_id'], address)
+ for address in updated_addresses:
+ address_id = address.pop('address_id')
+ UpdateAddress.__call__(UpdateAddress(self.api), auth, address_id, address)
+
+ def delete(self, commit = True):
+ """
+ Delete existing site.
+ """
+
+ assert 'site_id' in self
+
+ # Delete accounts of all people at the site who are not
+ # members of at least one other non-deleted site.
+ persons = Persons(self.api, self['person_ids'])
+ for person in persons:
+ delete = True
+
+ person_sites = Sites(self.api, person['site_ids'])
+ for person_site in person_sites:
+ if person_site['site_id'] != self['site_id']:
+ delete = False
+ break
+
+ if delete:
+ person.delete(commit = False)
+
+ # Delete all site addresses
+ addresses = Addresses(self.api, self['address_ids'])
+ for address in addresses:
+ address.delete(commit = False)
+
+ # Delete all site slices
+ slices = Slices(self.api, self['slice_ids'])
+ for slice in slices:
+ slice.delete(commit = False)
+
+ # Delete all site PCUs
+ pcus = PCUs(self.api, self['pcu_ids'])
+ for pcu in pcus:
+ pcu.delete(commit = False)
+
+ # Delete all site nodes
+ nodes = Nodes(self.api, self['node_ids'])
+ for node in nodes:
+ node.delete(commit = False)
+
+ # Clean up miscellaneous join tables
+ for table in self.join_tables:
+ self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
+ (table, self['site_id']))
+
+ # Mark as deleted
+ self['deleted'] = True
+ self.sync(commit)
+
+class Sites(Table):
+ """
+ Representation of row(s) from the sites table in the
+ database.
+ """
+
+ def __init__(self, api, site_filter = None, columns = None):
+ Table.__init__(self, api, Site, columns)
+
+ sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
+ ", ".join(self.columns)
+
+ if site_filter is not None:
+ if isinstance(site_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
+ site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
+ sql += " AND (%s) %s" % site_filter.sql(api, "OR")
+ elif isinstance(site_filter, dict):
+ site_filter = Filter(Site.fields, site_filter)
+ sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+ elif isinstance (site_filter, StringTypes):
+ site_filter = Filter(Site.fields, {'login_base':[site_filter]})
+ sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+ elif isinstance (site_filter, int):
+ site_filter = Filter(Site.fields, {'site_id':[site_filter]})
+ sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong site filter %r"%site_filter
+
+ self.selectall(sql)
--- /dev/null
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Roles import Role, Roles
+
+class SliceAttributeType(Row):
+ """
+ Representation of a row in the slice_attribute_types table. To
+ use, instantiate with a dict of values.
+ """
+
+ table_name = 'slice_attribute_types'
+ primary_key = 'attribute_type_id'
+ join_tables = ['slice_attribute']
+ fields = {
+ 'attribute_type_id': Parameter(int, "Slice attribute type identifier"),
+ 'name': Parameter(str, "Slice attribute type name", max = 100),
+ 'description': Parameter(str, "Slice attribute type description", max = 254),
+ 'min_role_id': Parameter(int, "Minimum (least powerful) role that can set or change this attribute"),
+ }
+
+ # for Cache
+ class_key = 'name'
+ foreign_fields = ['description','min_role_id']
+ foreign_xrefs = []
+
+ def validate_name(self, name):
+ if not len(name):
+ raise PLCInvalidArgument, "Slice attribute type name must be set"
+
+ conflicts = SliceAttributeTypes(self.api, [name])
+ for attribute in conflicts:
+ if 'attribute_type_id' not in self or \
+ self['attribute_type_id'] != attribute['attribute_type_id']:
+ raise PLCInvalidArgument, "Slice attribute type name already in use"
+
+ return name
+
+ def validate_min_role_id(self, role_id):
+ roles = [row['role_id'] for row in Roles(self.api)]
+ if role_id not in roles:
+ raise PLCInvalidArgument, "Invalid role"
+
+ return role_id
+
+class SliceAttributeTypes(Table):
+ """
+ Representation of row(s) from the slice_attribute_types table in the
+ database.
+ """
+
+ def __init__(self, api, attribute_type_filter = None, columns = None):
+ Table.__init__(self, api, SliceAttributeType, columns)
+
+ sql = "SELECT %s FROM slice_attribute_types WHERE True" % \
+ ", ".join(self.columns)
+
+ if attribute_type_filter is not None:
+ if isinstance(attribute_type_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), attribute_type_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), attribute_type_filter)
+ attribute_type_filter = Filter(SliceAttributeType.fields, {'attribute_type_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % attribute_type_filter.sql(api, "OR")
+ elif isinstance(attribute_type_filter, dict):
+ attribute_type_filter = Filter(SliceAttributeType.fields, attribute_type_filter)
+ sql += " AND (%s) %s" % attribute_type_filter.sql(api, "AND")
+
+ self.selectall(sql)
--- /dev/null
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+
+class SliceAttribute(Row):
+ """
+ Representation of a row in the slice_attribute table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'slice_attribute'
+ primary_key = 'slice_attribute_id'
+ fields = {
+ 'slice_attribute_id': Parameter(int, "Slice attribute identifier"),
+ 'slice_id': Parameter(int, "Slice identifier"),
+ 'node_id': Parameter(int, "Node identifier, if a sliver attribute"),
+ 'nodegroup_id': Parameter(int, "Nodegroup identifier, if a sliver attribute"),
+ 'attribute_type_id': SliceAttributeType.fields['attribute_type_id'],
+ 'name': SliceAttributeType.fields['name'],
+ 'description': SliceAttributeType.fields['description'],
+ 'min_role_id': SliceAttributeType.fields['min_role_id'],
+ 'value': Parameter(str, "Slice attribute value"),
+ }
+
+class SliceAttributes(Table):
+ """
+ Representation of row(s) from the slice_attribute table in the
+ database.
+ """
+
+ def __init__(self, api, slice_attribute_filter = None, columns = None):
+ Table.__init__(self, api, SliceAttribute, columns)
+
+ sql = "SELECT %s FROM view_slice_attributes WHERE True" % \
+ ", ".join(self.columns)
+
+ if slice_attribute_filter is not None:
+ if isinstance(slice_attribute_filter, (list, tuple, set)):
+ slice_attribute_filter = Filter(SliceAttribute.fields, {'slice_attribute_id': slice_attribute_filter})
+ elif isinstance(slice_attribute_filter, dict):
+ slice_attribute_filter = Filter(SliceAttribute.fields, slice_attribute_filter)
+ sql += " AND (%s) %s" % slice_attribute_filter.sql(api)
+
+ self.selectall(sql)
--- /dev/null
+#
+# Functions for interacting with the slice_instantiations table in the database
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: SliceInstantiations.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Table import Row, Table
+
+class SliceInstantiation(Row):
+ """
+ Representation of a row in the slice_instantiations table. To use,
+ instantiate with a dict of values.
+ """
+
+ table_name = 'slice_instantiations'
+ primary_key = 'instantiation'
+ join_tables = ['slices']
+ fields = {
+ 'instantiation': Parameter(str, "Slice instantiation state", max = 100),
+ }
+
+ def validate_instantiation(self, instantiation):
+ # Make sure name is not blank
+ if not len(instantiation):
+ raise PLCInvalidArgument, "Slice instantiation state name must be specified"
+
+ # Make sure slice instantiation does not alredy exist
+ conflicts = SliceInstantiations(self.api, [instantiation])
+ if conflicts:
+ raise PLCInvalidArgument, "Slice instantiation state name already in use"
+
+ return instantiation
+
+class SliceInstantiations(Table):
+ """
+ Representation of the slice_instantiations table in the database.
+ """
+
+ def __init__(self, api, instantiations = None):
+ Table.__init__(self, api, SliceInstantiation)
+
+ sql = "SELECT %s FROM slice_instantiations" % \
+ ", ".join(SliceInstantiation.fields)
+
+ if instantiations:
+ sql += " WHERE instantiation IN (%s)" % ", ".join(map(api.db.quote, instantiations))
+
+ self.selectall(sql)
--- /dev/null
+from types import StringTypes
+import time
+import re
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Debug import profile
+from PLC.Table import Row, Table
+from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
+from PLC.Nodes import Node
+from PLC.Persons import Person, Persons
+from PLC.SliceAttributes import SliceAttribute
+
+class Slice(Row):
+ """
+ Representation of a row in the slices table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().To use, instantiate
+ with a dict of values.
+ """
+
+ table_name = 'slices'
+ primary_key = 'slice_id'
+ join_tables = ['slice_node', 'slice_person', 'slice_attribute', 'peer_slice', 'node_slice_whitelist']
+ fields = {
+ 'slice_id': Parameter(int, "Slice identifier"),
+ 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
+ 'name': Parameter(str, "Slice name", max = 32),
+ 'instantiation': Parameter(str, "Slice instantiation state"),
+ 'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
+ 'description': Parameter(str, "Slice description", max = 2048, nullok = True),
+ 'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice"),
+ 'creator_person_id': Parameter(int, "Identifier of the account that created this slice"),
+ 'created': Parameter(int, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
+ 'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"),
+ 'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
+ 'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
+ 'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
+ 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
+ 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
+ }
+ related_fields = {
+ 'persons': [Mixed(Parameter(int, "Person identifier"),
+ Parameter(str, "Email address"))],
+ 'nodes': [Mixed(Parameter(int, "Node identifier"),
+ Parameter(str, "Fully qualified hostname"))]
+ }
+ # for Cache
+ class_key = 'name'
+ foreign_fields = ['instantiation', 'url', 'description', 'max_nodes', 'expires']
+ foreign_xrefs = [
+ {'field': 'node_ids' , 'class': 'Node', 'table': 'slice_node' },
+ {'field': 'person_ids', 'class': 'Person', 'table': 'slice_person'},
+ {'field': 'creator_person_id', 'class': 'Person', 'table': 'unused-on-direct-refs'},
+ {'field': 'site_id', 'class': 'Site', 'table': 'unused-on-direct-refs'},
+ ]
+ # forget about this one, it is read-only anyway
+ # handling it causes Cache to re-sync all over again
+ # 'created'
+
+ def validate_name(self, name):
+ # N.B.: Responsibility of the caller to ensure that login_base
+ # portion of the slice name corresponds to a valid site, if
+ # desired.
+
+ # 1. Lowercase.
+ # 2. Begins with login_base (letters or numbers).
+ # 3. Then single underscore after login_base.
+ # 4. Then letters, numbers, or underscores.
+ good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
+ if not name or \
+ not re.match(good_name, name):
+ raise PLCInvalidArgument, "Invalid slice name"
+
+ conflicts = Slices(self.api, [name])
+ for slice in conflicts:
+ if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
+ raise PLCInvalidArgument, "Slice name already in use, %s"%name
+
+ return name
+
+ def validate_instantiation(self, instantiation):
+ instantiations = [row['instantiation'] for row in SliceInstantiations(self.api)]
+ if instantiation not in instantiations:
+ raise PLCInvalidArgument, "No such instantiation state"
+
+ return instantiation
+
+ validate_created = Row.validate_timestamp
+
+ def validate_expires(self, expires):
+ # N.B.: Responsibility of the caller to ensure that expires is
+ # not too far into the future.
+ check_future = not ('is_deleted' in self and self['is_deleted'])
+ return Row.validate_timestamp(self, expires, check_future = check_future)
+
+ add_person = Row.add_object(Person, 'slice_person')
+ remove_person = Row.remove_object(Person, 'slice_person')
+
+ add_node = Row.add_object(Node, 'slice_node')
+ remove_node = Row.remove_object(Node, 'slice_node')
+
+ add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
+ delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
+
+ def associate_persons(self, auth, field, value):
+ """
+ Adds persons found in value list to this slice (using AddPersonToSlice).
+ Deletes persons not found in value list from this slice (using DeletePersonFromSlice).
+ """
+
+ assert 'person_ids' in self
+ assert 'slice_id' in self
+ assert isinstance(value, list)
+
+ (person_ids, emails) = self.separate_types(value)[0:2]
+
+ # Translate emails into person_ids
+ if emails:
+ persons = Persons(self.api, emails, ['person_id']).dict('person_id')
+ person_ids += persons.keys()
+
+ # Add new ids, remove stale ids
+ if self['person_ids'] != person_ids:
+ from PLC.Methods.AddPersonToSlice import AddPersonToSlice
+ from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
+ new_persons = set(person_ids).difference(self['person_ids'])
+ stale_persons = set(self['person_ids']).difference(person_ids)
+
+ for new_person in new_persons:
+ AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, new_person, self['slice_id'])
+ for stale_person in stale_persons:
+ DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, stale_person, self['slice_id'])
+
+ def associate_nodes(self, auth, field, value):
+ """
+ Adds nodes found in value list to this slice (using AddSliceToNodes).
+ Deletes nodes not found in value list from this slice (using DeleteSliceFromNodes).
+ """
+
+ from PLC.Nodes import Nodes
+
+ assert 'node_ids' in self
+ assert 'slice_id' in self
+ assert isinstance(value, list)
+
+ (node_ids, hostnames) = self.separate_types(value)[0:2]
+
+ # Translate hostnames into node_ids
+ if hostnames:
+ nodes = Nodes(self.api, hostnames, ['node_id']).dict('node_id')
+ node_ids += nodes.keys()
+
+ # Add new ids, remove stale ids
+ if self['node_ids'] != node_ids:
+ from PLC.Methods.AddSliceToNodes import AddSliceToNodes
+ from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
+ new_nodes = set(node_ids).difference(self['node_ids'])
+ stale_nodes = set(self['node_ids']).difference(node_ids)
+
+ if new_nodes:
+ AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, self['slice_id'], list(new_nodes))
+ if stale_nodes:
+ DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, self['slice_id'], list(stale_nodes))
+ def associate_slice_attributes(self, auth, fields, value):
+ """
+ Deletes slice_attribute_ids not found in value list (using DeleteSliceAttribute).
+ Adds slice_attributes if slice_fields w/o slice_id is found (using AddSliceAttribute).
+ Updates slice_attribute if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).
+ """
+
+ assert 'slice_attribute_ids' in self
+ assert isinstance(value, list)
+
+ (attribute_ids, blank, attributes) = self.separate_types(value)
+
+ # There is no way to add attributes by id. They are
+ # associated with a slice when they are created.
+ # So we are only looking to delete here
+ if self['slice_attribute_ids'] != attribute_ids:
+ from PLC.Methods.DeleteSliceAttribute import DeleteSliceAttribute
+ stale_attributes = set(self['slice_attribute_ids']).difference(attribute_ids)
+
+ for stale_attribute in stale_attributes:
+ DeleteSliceAttribute.__call__(DeleteSliceAttribute(self.api), auth, stale_attribute['slice_attribute_id'])
+
+ # If dictionary exists, we are either adding new
+ # attributes or updating existing ones.
+ if attributes:
+ from PLC.Methods.AddSliceAttribute import AddSliceAttribute
+ from PLC.Methods.UpdateSliceAttribute import UpdateSliceAttribute
+
+ added_attributes = filter(lambda x: 'slice_attribute_id' not in x, attributes)
+ updated_attributes = filter(lambda x: 'slice_attribute_id' in x, attributes)
+
+ for added_attribute in added_attributes:
+ if 'attribute_type' in added_attribute:
+ type = added_attribute['attribute_type']
+ elif 'attribute_type_id' in added_attribute:
+ type = added_attribute['attribute_type_id']
+ else:
+ raise PLCInvalidArgument, "Must specify attribute_type or attribute_type_id"
+
+ if 'value' in added_attribute:
+ value = added_attribute['value']
+ else:
+ raise PLCInvalidArgument, "Must specify a value"
+
+ if 'node_id' in added_attribute:
+ node_id = added_attribute['node_id']
+ else:
+ node_id = None
+
+ if 'nodegroup_id' in added_attribute:
+ nodegroup_id = added_attribute['nodegroup_id']
+ else:
+ nodegroup_id = None
+
+ AddSliceAttribute.__call__(AddSliceAttribute(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
+ for updated_attribute in updated_attributes:
+ attribute_id = updated_attribute.pop('slice_attribute_id')
+ if attribute_id not in self['slice_attribute_ids']:
+ raise PLCInvalidArgument, "Attribute doesnt belong to this slice"
+ else:
+ UpdateSliceAttribute.__call__(UpdateSliceAttribute(self.api), auth, attribute_id, updated_attribute)
+
+ def sync(self, commit = True):
+ """
+ Add or update a slice.
+ """
+
+ # Before a new slice is added, delete expired slices
+ if 'slice_id' not in self:
+ expired = Slices(self.api, expires = -int(time.time()))
+ for slice in expired:
+ slice.delete(commit)
+
+ Row.sync(self, commit)
+
+ def delete(self, commit = True):
+ """
+ Delete existing slice.
+ """
+
+ assert 'slice_id' in self
+
+ # Clean up miscellaneous join tables
+ for table in self.join_tables:
+ self.api.db.do("DELETE FROM %s WHERE slice_id = %d" % \
+ (table, self['slice_id']))
+
+ # Mark as deleted
+ self['is_deleted'] = True
+ self.sync(commit)
+
+
+class Slices(Table):
+ """
+ Representation of row(s) from the slices table in the
+ database.
+ """
+
+ def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
+ Table.__init__(self, api, Slice, columns)
+
+ sql = "SELECT %s FROM view_slices WHERE is_deleted IS False" % \
+ ", ".join(self.columns)
+
+ if expires is not None:
+ if expires >= 0:
+ sql += " AND expires > %d" % expires
+ else:
+ expires = -expires
+ sql += " AND expires < %d" % expires
+
+ if slice_filter is not None:
+ if isinstance(slice_filter, (list, tuple, set)):
+ # Separate the list into integers and strings
+ ints = filter(lambda x: isinstance(x, (int, long)), slice_filter)
+ strs = filter(lambda x: isinstance(x, StringTypes), slice_filter)
+ slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
+ sql += " AND (%s) %s" % slice_filter.sql(api, "OR")
+ elif isinstance(slice_filter, dict):
+ slice_filter = Filter(Slice.fields, slice_filter)
+ sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+ elif isinstance (slice_filter, StringTypes):
+ slice_filter = Filter(Slice.fields, {'name':[slice_filter]})
+ sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+ elif isinstance (slice_filter, int):
+ slice_filter = Filter(Slice.fields, {'slice_id':[slice_filter]})
+ sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+ else:
+ raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
+
+ self.selectall(sql)
--- /dev/null
+from types import StringTypes, IntType, LongType
+import time
+import calendar
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+
+class Row(dict):
+ """
+ Representation of a row in a database table. To use, optionally
+ instantiate with a dict of values. Update as you would a
+ dict. Commit to the database with sync().
+ """
+
+ # Set this to the name of the table that stores the row.
+ table_name = None
+
+ # Set this to the name of the primary key of the table. It is
+ # assumed that the this key is a sequence if it is not set when
+ # sync() is called.
+ primary_key = None
+
+ # Set this to the names of tables that reference this table's
+ # primary key.
+ join_tables = []
+
+ # Set this to a dict of the valid fields of this object and their
+ # types. Not all fields (e.g., joined fields) may be updated via
+ # sync().
+ fields = {}
+
+ def __init__(self, api, fields = {}):
+ dict.__init__(self, fields)
+ self.api = api
+
+ def validate(self):
+ """
+ Validates values. Will validate a value with a custom function
+ if a function named 'validate_[key]' exists.
+ """
+
+ # Warn about mandatory fields
+ mandatory_fields = self.api.db.fields(self.table_name, notnull = True, hasdef = False)
+ for field in mandatory_fields:
+ if not self.has_key(field) or self[field] is None:
+ raise PLCInvalidArgument, field + " must be specified and cannot be unset in class %s"%self.__class__.__name__
+
+ # Validate values before committing
+ for key, value in self.iteritems():
+ if value is not None and hasattr(self, 'validate_' + key):
+ validate = getattr(self, 'validate_' + key)
+ self[key] = validate(value)
+
+ def separate_types(self, items):
+ """
+ Separate a list of different typed objects.
+ Return a list for each type (ints, strs and dicts)
+ """
+
+ if isinstance(items, (list, tuple, set)):
+ ints = filter(lambda x: isinstance(x, (int, long)), items)
+ strs = filter(lambda x: isinstance(x, StringTypes), items)
+ dicts = filter(lambda x: isinstance(x, dict), items)
+ return (ints, strs, dicts)
+ else:
+ raise PLCInvalidArgument, "Can only separate list types"
+
+
+ def associate(self, *args):
+ """
+ Provides a means for high lvl api calls to associate objects
+ using low lvl calls.
+ """
+
+ if len(args) < 3:
+ raise PLCInvalidArgumentCount, "auth, field, value must be specified"
+ elif hasattr(self, 'associate_' + args[1]):
+ associate = getattr(self, 'associate_'+args[1])
+ associate(*args)
+ else:
+ raise PLCInvalidArguemnt, "No such associate function associate_%s" % args[1]
+
+ def validate_timestamp(self, timestamp, check_future = False):
+ """
+ Validates the specified GMT timestamp string (must be in
+ %Y-%m-%d %H:%M:%S format) or number (seconds since UNIX epoch,
+ i.e., 1970-01-01 00:00:00 GMT). If check_future is True,
+ raises an exception if timestamp is not in the future. Returns
+ a GMT timestamp string.
+ """
+
+ time_format = "%Y-%m-%d %H:%M:%S"
+
+ if isinstance(timestamp, StringTypes):
+ # calendar.timegm() is the inverse of time.gmtime()
+ timestamp = calendar.timegm(time.strptime(timestamp, time_format))
+
+ # Human readable timestamp string
+ human = time.strftime(time_format, time.gmtime(timestamp))
+
+ if check_future and timestamp < time.time():
+ raise PLCInvalidArgument, "'%s' not in the future" % human
+
+ return human
+
+ def add_object(self, classobj, join_table, columns = None):
+ """
+ Returns a function that can be used to associate this object
+ with another.
+ """
+
+ def add(self, obj, columns = None, commit = True):
+ """
+ Associate with the specified object.
+ """
+
+ # Various sanity checks
+ assert isinstance(self, Row)
+ assert self.primary_key in self
+ assert join_table in self.join_tables
+ assert isinstance(obj, classobj)
+ assert isinstance(obj, Row)
+ assert obj.primary_key in obj
+ assert join_table in obj.join_tables
+
+ # By default, just insert the primary keys of each object
+ # into the join table.
+ if columns is None:
+ columns = {self.primary_key: self[self.primary_key],
+ obj.primary_key: obj[obj.primary_key]}
+
+ params = []
+ for name, value in columns.iteritems():
+ params.append(self.api.db.param(name, value))
+
+ self.api.db.do("INSERT INTO %s (%s) VALUES(%s)" % \
+ (join_table, ", ".join(columns), ", ".join(params)),
+ columns)
+
+ if commit:
+ self.api.db.commit()
+
+ return add
+
+ add_object = classmethod(add_object)
+
+ def remove_object(self, classobj, join_table):
+ """
+ Returns a function that can be used to disassociate this
+ object with another.
+ """
+
+ def remove(self, obj, commit = True):
+ """
+ Disassociate from the specified object.
+ """
+
+ assert isinstance(self, Row)
+ assert self.primary_key in self
+ assert join_table in self.join_tables
+ assert isinstance(obj, classobj)
+ assert isinstance(obj, Row)
+ assert obj.primary_key in obj
+ assert join_table in obj.join_tables
+
+ self_id = self[self.primary_key]
+ obj_id = obj[obj.primary_key]
+
+ self.api.db.do("DELETE FROM %s WHERE %s = %s AND %s = %s" % \
+ (join_table,
+ self.primary_key, self.api.db.param('self_id', self_id),
+ obj.primary_key, self.api.db.param('obj_id', obj_id)),
+ locals())
+
+ if commit:
+ self.api.db.commit()
+
+ return remove
+
+ remove_object = classmethod(remove_object)
+
+ def db_fields(self, obj = None):
+ """
+ Return only those fields that can be set or updated directly
+ (i.e., those fields that are in the primary table (table_name)
+ for this object, and are not marked as a read-only Parameter.
+ """
+
+ if obj is None:
+ obj = self
+
+ db_fields = self.api.db.fields(self.table_name)
+ return dict(filter(lambda (key, value): \
+ key in db_fields and \
+ (key not in self.fields or \
+ not isinstance(self.fields[key], Parameter) or \
+ not self.fields[key].ro),
+ obj.items()))
+
+ def __eq__(self, y):
+ """
+ Compare two objects.
+ """
+
+ # Filter out fields that cannot be set or updated directly
+ # (and thus would not affect equality for the purposes of
+ # deciding if we should sync() or not).
+ x = self.db_fields()
+ y = self.db_fields(y)
+ return dict.__eq__(x, y)
+
+ def sync(self, commit = True, insert = None):
+ """
+ Flush changes back to the database.
+ """
+
+ # Validate all specified fields
+ self.validate()
+
+ # Filter out fields that cannot be set or updated directly
+ db_fields = self.db_fields()
+
+ # Parameterize for safety
+ keys = db_fields.keys()
+ values = [self.api.db.param(key, value) for (key, value) in db_fields.items()]
+
+ # If the primary key (usually an auto-incrementing serial
+ # identifier) has not been specified, or the primary key is the
+ # only field in the table, or insert has been forced.
+ if not self.has_key(self.primary_key) or \
+ keys == [self.primary_key] or \
+ insert is True:
+
+ # If primary key id is a serial int and it isnt included, get next id
+ if self.fields[self.primary_key].type in (IntType, LongType) and \
+ self.primary_key not in self:
+ pk_id = self.api.db.next_id(self.table_name, self.primary_key)
+ self[self.primary_key] = pk_id
+ db_fields[self.primary_key] = pk_id
+ keys = db_fields.keys()
+ values = [self.api.db.param(key, value) for (key, value) in db_fields.items()]
+ # Insert new row
+ sql = "INSERT INTO %s (%s) VALUES (%s)" % \
+ (self.table_name, ", ".join(keys), ", ".join(values))
+ else:
+ # Update existing row
+ columns = ["%s = %s" % (key, value) for (key, value) in zip(keys, values)]
+ sql = "UPDATE %s SET " % self.table_name + \
+ ", ".join(columns) + \
+ " WHERE %s = %s" % \
+ (self.primary_key,
+ self.api.db.param(self.primary_key, self[self.primary_key]))
+
+ self.api.db.do(sql, db_fields)
+
+ if commit:
+ self.api.db.commit()
+
+ def delete(self, commit = True):
+ """
+ Delete row from its primary table, and from any tables that
+ reference it.
+ """
+
+ assert self.primary_key in self
+
+ for table in self.join_tables + [self.table_name]:
+ if isinstance(table, tuple):
+ key = table[1]
+ table = table[0]
+ else:
+ key = self.primary_key
+
+ sql = "DELETE FROM %s WHERE %s = %s" % \
+ (table, key,
+ self.api.db.param(self.primary_key, self[self.primary_key]))
+
+ self.api.db.do(sql, self)
+
+ if commit:
+ self.api.db.commit()
+
+class Table(list):
+ """
+ Representation of row(s) in a database table.
+ """
+
+ def __init__(self, api, classobj, columns = None):
+ self.api = api
+ self.classobj = classobj
+ self.rows = {}
+
+ if columns is None:
+ columns = classobj.fields
+ else:
+ columns = filter(lambda x: x in classobj.fields, columns)
+ if not columns:
+ raise PLCInvalidArgument, "No valid return fields specified"
+
+ self.columns = columns
+
+ def sync(self, commit = True):
+ """
+ Flush changes back to the database.
+ """
+
+ for row in self:
+ row.sync(commit)
+
+ def selectall(self, sql, params = None):
+ """
+ Given a list of rows from the database, fill ourselves with
+ Row objects.
+ """
+
+ for row in self.api.db.selectall(sql, params):
+ obj = self.classobj(self.api, row)
+ self.append(obj)
+
+ def dict(self, key_field = None):
+ """
+ Return ourself as a dict keyed on key_field.
+ """
+
+ if key_field is None:
+ key_field = self.classobj.primary_key
+
+ return dict([(obj[key_field], obj) for obj in self])
--- /dev/null
+#!/usr/bin/python
+#
+# Test script utility class
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Test.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from pprint import pprint
+from string import letters, digits, punctuation
+from traceback import print_exc
+from optparse import OptionParser
+import socket
+import base64
+import struct
+import os
+import xmlrpclib
+
+from PLC.Shell import Shell
+
+from random import Random
+random = Random()
+
+def randfloat(min = 0.0, max = 1.0):
+ return float(min) + (random.random() * (float(max) - float(min)))
+
+def randint(min = 0, max = 1):
+ return int(randfloat(min, max + 1))
+
+# See "2.2 Characters" in the XML specification:
+#
+# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# avoiding
+# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
+#
+
+ascii_xml_chars = map(unichr, [0x9, 0xA])
+# xmlrpclib uses xml.parsers.expat, which always converts either '\r'
+# (#xD) or '\n' (#xA) to '\n'. So avoid using '\r', too, if this is
+# still the case.
+if xmlrpclib.loads(xmlrpclib.dumps(('\r',)))[0][0] == '\r':
+ ascii_xml_chars.append('\r')
+ascii_xml_chars += map(unichr, xrange(0x20, 0x7F - 1))
+low_xml_chars = list(ascii_xml_chars)
+low_xml_chars += map(unichr, xrange(0x84 + 1, 0x86 - 1))
+low_xml_chars += map(unichr, xrange(0x9F + 1, 0xFF))
+valid_xml_chars = list(low_xml_chars)
+valid_xml_chars += map(unichr, xrange(0xFF + 1, 0xD7FF))
+valid_xml_chars += map(unichr, xrange(0xE000, 0xFDD0 - 1))
+valid_xml_chars += map(unichr, xrange(0xFDDF + 1, 0xFFFD))
+
+def randstr(length, pool = valid_xml_chars, encoding = "utf-8"):
+ sample = random.sample(pool, min(length, len(pool)))
+ while True:
+ s = u''.join(sample)
+ bytes = len(s.encode(encoding))
+ if bytes > length:
+ sample.pop()
+ elif bytes < length:
+ sample += random.sample(pool, min(length - bytes, len(pool)))
+ random.shuffle(sample)
+ else:
+ break
+ return s
+
+def randhostname():
+ # 1. Each part begins and ends with a letter or number.
+ # 2. Each part except the last can contain letters, numbers, or hyphens.
+ # 3. Each part is between 1 and 64 characters, including the trailing dot.
+ # 4. At least two parts.
+ # 5. Last part can only contain between 2 and 6 letters.
+ hostname = 'a' + randstr(61, letters + digits + '-') + '1.' + \
+ 'b' + randstr(61, letters + digits + '-') + '2.' + \
+ 'c' + randstr(5, letters)
+ return hostname
+
+def randpath(length):
+ parts = []
+ for i in range(randint(1, 10)):
+ parts.append(randstr(randint(1, 30), ascii_xml_chars))
+ return u'/'.join(parts)[0:length]
+
+def randemail():
+ return (randstr(100, letters + digits) + "@" + randhostname()).lower()
+
+def randkey(bits = 2048):
+ ssh_key_types = ["ssh-dss", "ssh-rsa"]
+ key_type = random.sample(ssh_key_types, 1)[0]
+ return ' '.join([key_type,
+ base64.b64encode(''.join(randstr(bits / 8).encode("utf-8"))),
+ randemail()])
+
+def random_site():
+ return {
+ 'name': randstr(254),
+ 'abbreviated_name': randstr(50),
+ 'login_base': randstr(20, letters).lower(),
+ 'latitude': int(randfloat(-90.0, 90.0) * 1000) / 1000.0,
+ 'longitude': int(randfloat(-180.0, 180.0) * 1000) / 1000.0,
+ }
+
+def random_address_type():
+ return {
+ 'name': randstr(20),
+ 'description': randstr(254),
+ }
+
+def random_address():
+ return {
+ 'line1': randstr(254),
+ 'line2': randstr(254),
+ 'line3': randstr(254),
+ 'city': randstr(254),
+ 'state': randstr(254),
+ 'postalcode': randstr(64),
+ 'country': randstr(128),
+ }
+
+def random_person():
+ return {
+ 'first_name': randstr(128),
+ 'last_name': randstr(128),
+ 'email': randemail(),
+ 'bio': randstr(254),
+ # Accounts are disabled by default
+ 'enabled': False,
+ 'password': randstr(254),
+ }
+
+def random_key(key_types):
+ return {
+ 'key_type': random.sample(key_types, 1)[0],
+ 'key': randkey()
+ }
+
+def random_nodegroup():
+ return {
+ 'name': randstr(50),
+ 'description': randstr(200),
+ }
+
+def random_node(boot_states):
+ return {
+ 'hostname': randhostname(),
+ 'boot_state': random.sample(boot_states, 1)[0],
+ 'model': randstr(255),
+ 'version': randstr(64),
+ }
+
+def random_nodenetwork(method, type):
+ nodenetwork_fields = {
+ 'method': method,
+ 'type': type,
+ 'bwlimit': randint(500000, 10000000),
+ }
+
+ if method != 'dhcp':
+ ip = randint(0, 0xffffffff)
+ netmask = (0xffffffff << randint(2, 31)) & 0xffffffff
+ network = ip & netmask
+ broadcast = ((ip & netmask) | ~netmask) & 0xffffffff
+ gateway = randint(network + 1, broadcast - 1)
+ dns1 = randint(0, 0xffffffff)
+
+ for field in 'ip', 'netmask', 'network', 'broadcast', 'gateway', 'dns1':
+ nodenetwork_fields[field] = socket.inet_ntoa(struct.pack('>L', locals()[field]))
+
+ return nodenetwork_fields
+
+def random_pcu():
+ return {
+ 'hostname': randhostname(),
+ 'ip': socket.inet_ntoa(struct.pack('>L', randint(0, 0xffffffff))),
+ 'protocol': randstr(16),
+ 'username': randstr(254),
+ 'password': randstr(254),
+ 'notes': randstr(254),
+ 'model': randstr(32),
+ }
+
+def random_conf_file():
+ return {
+ 'enabled': bool(randint()),
+ 'source': randpath(255),
+ 'dest': randpath(255),
+ 'file_permissions': "%#o" % randint(0, 512),
+ 'file_owner': randstr(32, letters + '_' + digits),
+ 'file_group': randstr(32, letters + '_' + digits),
+ 'preinstall_cmd': randpath(100),
+ 'postinstall_cmd': randpath(100),
+ 'error_cmd': randpath(100),
+ 'ignore_cmd_errors': bool(randint()),
+ 'always_update': bool(randint()),
+ }
+
+def random_attribute_type(role_ids):
+ return {
+ 'name': randstr(100),
+ 'description': randstr(254),
+ 'min_role_id': random.sample(role_ids, 1)[0],
+ }
+
+def random_slice(login_base):
+ return {
+ 'name': login_base + "_" + randstr(11, letters).lower(),
+ 'url': "http://" + randhostname() + "/",
+ 'description': randstr(2048),
+ }
+
+class Test:
+ tiny = {
+ 'sites': 1,
+ 'address_types': 1,
+ 'addresses_per_site': 1,
+ 'persons_per_site': 1,
+ 'keys_per_person': 1,
+ 'nodegroups': 1,
+ 'nodes_per_site': 1,
+ 'nodenetworks_per_node': 1,
+ 'pcus_per_site': 1,
+ 'conf_files': 1,
+ 'attribute_types': 1,
+ 'slices_per_site': 1,
+ 'attributes_per_slice': 1,
+ }
+
+ default = {
+ 'sites': 10,
+ 'address_types': 2,
+ 'addresses_per_site': 2,
+ 'persons_per_site': 10,
+ 'keys_per_person': 2,
+ 'nodegroups': 10,
+ 'nodes_per_site': 2,
+ 'nodenetworks_per_node': 1,
+ 'pcus_per_site': 1,
+ 'conf_files': 10,
+ 'attribute_types': 10,
+ 'slices_per_site': 10,
+ 'attributes_per_slice': 2,
+ }
+
+ def __init__(self, api, check = True, verbose = True):
+ self.api = api
+ self.check = check
+ self.verbose = verbose
+
+ self.site_ids = []
+ self.address_type_ids = []
+ self.address_ids = []
+ self.person_ids = []
+ self.key_ids = []
+ self.nodegroup_ids = []
+ self.node_ids = []
+ self.nodenetwork_ids = []
+ self.pcu_ids = []
+ self.conf_file_ids = []
+ self.attribute_type_ids = []
+ self.slice_ids = []
+ self.slice_attribute_ids = []
+
+ def Run(self, **kwds):
+ """
+ Run a complete database and API consistency test. Populates
+ the database with a set of random entities, updates them, then
+ deletes them. Examples:
+
+ test.Run() # Defaults
+ test.Run(**Test.default) # Defaults
+ test.Run(**Test.tiny) # Tiny set
+ test.Run(sites = 123, slices_per_site = 4) # Defaults with overrides
+ """
+
+ try:
+ self.Add(**kwds)
+ self.Update()
+ finally:
+ self.Delete()
+
+ def Add(self, **kwds):
+ """
+ Populate the database with a set of random entities. Examples:
+
+ test.populate() # Defaults
+ test.populate(Test.tiny) # Tiny set
+ test.populate(sites = 123, slices_per_site = 4) # Defaults with overrides
+ """
+
+ params = self.default.copy()
+ params.update(kwds)
+
+ self.AddSites(params['sites'])
+ self.AddAddressTypes(params['address_types'])
+ self.AddAddresses(params['addresses_per_site'])
+ self.AddPersons(params['persons_per_site'])
+ self.AddKeys(params['keys_per_person'])
+ self.AddNodeGroups(params['nodegroups'])
+ self.AddNodes(params['nodes_per_site'])
+ self.AddNodeNetworks(params['nodenetworks_per_node'])
+ self.AddPCUs(params['pcus_per_site'])
+ self.AddConfFiles(params['conf_files'])
+ self.AddSliceAttributeTypes(params['attribute_types'])
+ self.AddSlices(params['slices_per_site'])
+ self.AddSliceAttributes(params['attributes_per_slice'])
+
+ def Update(self):
+ self.UpdateSites()
+ self.UpdateAddressTypes()
+ self.UpdateAddresses()
+ self.UpdatePersons()
+ self.UpdateKeys()
+ self.UpdateNodeGroups()
+ self.UpdateNodes()
+ self.UpdateNodeNetworks()
+ self.UpdatePCUs()
+ self.UpdateConfFiles()
+ self.UpdateSliceAttributeTypes()
+ self.UpdateSlices()
+ self.UpdateSliceAttributes()
+
+ def Delete(self):
+ self.DeleteSliceAttributes()
+ self.DeleteSlices()
+ self.DeleteSliceAttributeTypes()
+ self.DeleteKeys()
+ self.DeleteConfFiles()
+ self.DeletePCUs()
+ self.DeleteNodeNetworks()
+ self.DeleteNodes()
+ self.DeletePersons()
+ self.DeleteNodeGroups()
+ self.DeleteAddresses()
+ self.DeleteAddressTypes()
+ self.DeleteSites()
+
+ def AddSites(self, n = 10):
+ """
+ Add a number of random sites.
+ """
+
+ for i in range(n):
+ # Add site
+ site_fields = random_site()
+ site_id = self.api.AddSite(site_fields)
+
+ # Should return a unique site_id
+ assert site_id not in self.site_ids
+ self.site_ids.append(site_id)
+
+ # Enable slice creation
+ site_fields['max_slices'] = randint(1, 10)
+ self.api.UpdateSite(site_id, site_fields)
+
+ if self.check:
+ # Check site
+ site = self.api.GetSites([site_id])[0]
+ for field in site_fields:
+ assert site[field] == site_fields[field]
+
+ if self.verbose:
+ print "Added site", site_id
+
+ def UpdateSites(self):
+ """
+ Make random changes to any sites we may have added.
+ """
+
+ for site_id in self.site_ids:
+ # Update site
+ site_fields = random_site()
+ # Do not change login_base
+ if 'login_base' in site_fields:
+ del site_fields['login_base']
+ self.api.UpdateSite(site_id, site_fields)
+
+ if self.check:
+ # Check site
+ site = self.api.GetSites([site_id])[0]
+ for field in site_fields:
+ assert site[field] == site_fields[field]
+
+ if self.verbose:
+ print "Updated site", site_id
+
+ def DeleteSites(self):
+ """
+ Delete any random sites we may have added.
+ """
+
+ for site_id in self.site_ids:
+ self.api.DeleteSite(site_id)
+
+ if self.check:
+ assert not self.api.GetSites([site_id])
+
+ if self.verbose:
+ print "Deleted site", site_id
+
+ if self.check:
+ assert not self.api.GetSites(self.site_ids)
+
+ self.site_ids = []
+
+ def AddAddressTypes(self, n = 2):
+ """
+ Add a number of random address types.
+ """
+
+ for i in range(n):
+ address_type_fields = random_address_type()
+ address_type_id = self.api.AddAddressType(address_type_fields)
+
+ # Should return a unique address_type_id
+ assert address_type_id not in self.address_type_ids
+ self.address_type_ids.append(address_type_id)
+
+ if self.check:
+ # Check address type
+ address_type = self.api.GetAddressTypes([address_type_id])[0]
+ for field in address_type_fields:
+ assert address_type[field] == address_type_fields[field]
+
+ if self.verbose:
+ print "Added address type", address_type_id
+
+ def UpdateAddressTypes(self):
+ """
+ Make random changes to any address types we may have added.
+ """
+
+ for address_type_id in self.address_type_ids:
+ # Update address_type
+ address_type_fields = random_address_type()
+ self.api.UpdateAddressType(address_type_id, address_type_fields)
+
+ if self.check:
+ # Check address type
+ address_type = self.api.GetAddressTypes([address_type_id])[0]
+ for field in address_type_fields:
+ assert address_type[field] == address_type_fields[field]
+
+ if self.verbose:
+ print "Updated address_type", address_type_id
+
+ def DeleteAddressTypes(self):
+ """
+ Delete any random address types we may have added.
+ """
+
+ for address_type_id in self.address_type_ids:
+ self.api.DeleteAddressType(address_type_id)
+
+ if self.check:
+ assert not self.api.GetAddressTypes([address_type_id])
+
+ if self.verbose:
+ print "Deleted address type", address_type_id
+
+ if self.check:
+ assert not self.api.GetAddressTypes(self.address_type_ids)
+
+ self.address_type_ids = []
+
+ def AddAddresses(self, per_site = 2):
+ """
+ Add a number of random addresses to each site.
+ """
+
+ for site_id in self.site_ids:
+ for i in range(per_site):
+ address_fields = random_address()
+ address_id = self.api.AddSiteAddress(site_id, address_fields)
+
+ # Should return a unique address_id
+ assert address_id not in self.address_ids
+ self.address_ids.append(address_id)
+
+ # Add random address type
+ if self.address_type_ids:
+ for address_type_id in random.sample(self.address_type_ids, 1):
+ self.api.AddAddressTypeToAddress(address_type_id, address_id)
+
+ if self.check:
+ # Check address
+ address = self.api.GetAddresses([address_id])[0]
+ for field in address_fields:
+ assert address[field] == address_fields[field]
+
+ if self.verbose:
+ print "Added address", address_id, "to site", site_id
+
+ def UpdateAddresses(self):
+ """
+ Make random changes to any addresses we may have added.
+ """
+
+ for address_id in self.address_ids:
+ # Update address
+ address_fields = random_address()
+ self.api.UpdateAddress(address_id, address_fields)
+
+ if self.check:
+ # Check address
+ address = self.api.GetAddresses([address_id])[0]
+ for field in address_fields:
+ assert address[field] == address_fields[field]
+
+ if self.verbose:
+ print "Updated address", address_id
+
+ def DeleteAddresses(self):
+ """
+ Delete any random addresses we may have added.
+ """
+
+ for address_id in self.address_ids:
+ # Remove address types
+ address = self.api.GetAddresses([address_id])[0]
+ for address_type_id in address['address_type_ids']:
+ self.api.DeleteAddressTypeFromAddress(address_type_id, address_id)
+
+ if self.check:
+ address = self.api.GetAddresses([address_id])[0]
+ assert not address['address_type_ids']
+
+ self.api.DeleteAddress(address_id)
+
+ if self.check:
+ assert not self.api.GetAddresses([address_id])
+
+ if self.verbose:
+ print "Deleted address", address_id
+
+ if self.check:
+ assert not self.api.GetAddresses(self.address_ids)
+
+ self.address_ids = []
+
+ def AddPersons(self, per_site = 10):
+ """
+ Add a number of random users to each site.
+ """
+
+ for site_id in self.site_ids:
+ for i in range(per_site):
+ # Add user
+ person_fields = random_person()
+ person_id = self.api.AddPerson(person_fields)
+
+ # Should return a unique person_id
+ assert person_id not in self.person_ids
+ self.person_ids.append(person_id)
+
+ if self.check:
+ # Check user
+ person = self.api.GetPersons([person_id])[0]
+ for field in person_fields:
+ if field != 'password':
+ assert person[field] == person_fields[field]
+
+ auth = {'AuthMethod': "password",
+ 'Username': person_fields['email'],
+ 'AuthString': person_fields['password']}
+
+ if self.check:
+ # Check that user is disabled
+ try:
+ assert not self.api.AuthCheck(auth)
+ except:
+ pass
+
+ # Add random set of roles
+ role_ids = random.sample([20, 30, 40], randint(1, 3))
+ for role_id in role_ids:
+ self.api.AddRoleToPerson(role_id, person_id)
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert set(role_ids) == set(person['role_ids'])
+
+ # Enable user
+ self.api.UpdatePerson(person_id, {'enabled': True})
+
+ if self.check:
+ # Check that user is enabled
+ assert self.api.AuthCheck(auth)
+
+ # Associate user with site
+ self.api.AddPersonToSite(person_id, site_id)
+ self.api.SetPersonPrimarySite(person_id, site_id)
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert person['site_ids'][0] == site_id
+
+ if self.verbose:
+ print "Added user", person_id, "to site", site_id
+
+ def UpdatePersons(self):
+ """
+ Make random changes to any users we may have added.
+ """
+
+ for person_id in self.person_ids:
+ # Update user
+ person_fields = random_person()
+ # Keep them enabled
+ person_fields['enabled'] = True
+ self.api.UpdatePerson(person_id, person_fields)
+
+ if self.check:
+ # Check user
+ person = self.api.GetPersons([person_id])[0]
+ for field in person_fields:
+ if field != 'password':
+ assert person[field] == person_fields[field]
+
+ if self.verbose:
+ print "Updated person", person_id
+
+ person = self.api.GetPersons([person_id])[0]
+
+ # Associate user with a random set of sites
+ site_ids = random.sample(self.site_ids, randint(0, len(self.site_ids)))
+ for site_id in (set(site_ids) - set(person['site_ids'])):
+ self.api.AddPersonToSite(person_id, site_id)
+ for site_id in (set(person['site_ids']) - set(site_ids)):
+ self.api.DeletePersonFromSite(person_id, site_id)
+
+ if site_ids:
+ self.api.SetPersonPrimarySite(person_id, site_ids[0])
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert set(site_ids) == set(person['site_ids'])
+
+ if self.verbose:
+ print "Updated person", person_id, "to sites", site_ids
+
+ def DeletePersons(self):
+ """
+ Delete any random users we may have added.
+ """
+
+ for person_id in self.person_ids:
+ # Remove from site
+ person = self.api.GetPersons([person_id])[0]
+ for site_id in person['site_ids']:
+ self.api.DeletePersonFromSite(person_id, site_id)
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert not person['site_ids']
+
+ # Revoke roles
+ for role_id in person['role_ids']:
+ self.api.DeleteRoleFromPerson(role_id, person_id)
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert not person['role_ids']
+
+ # Disable account
+ self.api.UpdatePerson(person_id, {'enabled': False})
+
+ if self.check:
+ person = self.api.GetPersons([person_id])[0]
+ assert not person['enabled']
+
+ # Delete account
+ self.api.DeletePerson(person_id)
+
+ if self.check:
+ assert not self.api.GetPersons([person_id])
+
+ if self.verbose:
+ print "Deleted user", person_id
+
+ if self.check:
+ assert not self.api.GetPersons(self.person_ids)
+
+ self.person_ids = []
+
+ def AddKeys(self, per_person = 2):
+ """
+ Add a number of random keys to each user.
+ """
+
+ key_types = self.api.GetKeyTypes()
+ if not key_types:
+ raise Exception, "No key types"
+
+ for person_id in self.person_ids:
+ for i in range(per_person):
+ # Add key
+ key_fields = random_key(key_types)
+ key_id = self.api.AddPersonKey(person_id, key_fields)
+
+ # Should return a unique key_id
+ assert key_id not in self.key_ids
+ self.key_ids.append(key_id)
+
+ if self.check:
+ # Check key
+ key = self.api.GetKeys([key_id])[0]
+ for field in key_fields:
+ assert key[field] == key_fields[field]
+
+ # Add and immediately blacklist a key
+ key_fields = random_key(key_types)
+ key_id = self.api.AddPersonKey(person_id, key_fields)
+
+ self.api.BlacklistKey(key_id)
+
+ # Is effectively deleted
+ assert not self.api.GetKeys([key_id])
+
+ # Cannot be added again
+ try:
+ key_id = self.api.AddPersonKey(person_id, key_fields)
+ assert False
+ except Exception, e:
+ pass
+
+ if self.verbose:
+ print "Added key", key_id, "to user", person_id
+
+ def UpdateKeys(self):
+ """
+ Make random changes to any keys we may have added.
+ """
+
+ key_types = self.api.GetKeyTypes()
+ if not key_types:
+ raise Exception, "No key types"
+
+ for key_id in self.key_ids:
+ # Update key
+ key_fields = random_key(key_types)
+ self.api.UpdateKey(key_id, key_fields)
+
+ if self.check:
+ # Check key
+ key = self.api.GetKeys([key_id])[0]
+ for field in key_fields:
+ assert key[field] == key_fields[field]
+
+ if self.verbose:
+ print "Updated key", key_id
+
+ def DeleteKeys(self):
+ """
+ Delete any random keys we may have added.
+ """
+
+ for key_id in self.key_ids:
+ self.api.DeleteKey(key_id)
+
+ if self.check:
+ assert not self.api.GetKeys([key_id])
+
+ if self.verbose:
+ print "Deleted key", key_id
+
+ if self.check:
+ assert not self.api.GetKeys(self.key_ids)
+
+ self.key_ids = []
+
+ def AddNodeGroups(self, n = 10):
+ """
+ Add a number of random node groups.
+ """
+
+ for i in range(n):
+ # Add node group
+ nodegroup_fields = random_nodegroup()
+ nodegroup_id = self.api.AddNodeGroup(nodegroup_fields)
+
+ # Should return a unique nodegroup_id
+ assert nodegroup_id not in self.nodegroup_ids
+ self.nodegroup_ids.append(nodegroup_id)
+
+ if self.check:
+ # Check node group
+ nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
+ for field in nodegroup_fields:
+ assert nodegroup[field] == nodegroup_fields[field]
+
+ if self.verbose:
+ print "Added node group", nodegroup_id
+
+ def UpdateNodeGroups(self):
+ """
+ Make random changes to any node groups we may have added.
+ """
+
+ for nodegroup_id in self.nodegroup_ids:
+ # Update nodegroup
+ nodegroup_fields = random_nodegroup()
+ self.api.UpdateNodeGroup(nodegroup_id, nodegroup_fields)
+
+ if self.check:
+ # Check nodegroup
+ nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
+ for field in nodegroup_fields:
+ assert nodegroup[field] == nodegroup_fields[field]
+
+ if self.verbose:
+ print "Updated node group", nodegroup_id
+
+ def DeleteNodeGroups(self):
+ """
+ Delete any random node groups we may have added.
+ """
+
+ for nodegroup_id in self.nodegroup_ids:
+ self.api.DeleteNodeGroup(nodegroup_id)
+
+ if self.check:
+ assert not self.api.GetNodeGroups([nodegroup_id])
+
+ if self.verbose:
+ print "Deleted node group", nodegroup_id
+
+ if self.check:
+ assert not self.api.GetNodeGroups(self.nodegroup_ids)
+
+ self.nodegroup_ids = []
+
+ def AddNodes(self, per_site = 2):
+ """
+ Add a number of random nodes to each site. Each node will also
+ be added to a random node group if AddNodeGroups() was
+ previously run.
+ """
+
+ boot_states = self.api.GetBootStates()
+ if not boot_states:
+ raise Exception, "No boot states"
+
+ for site_id in self.site_ids:
+ for i in range(per_site):
+ # Add node
+ node_fields = random_node(boot_states)
+ node_id = self.api.AddNode(site_id, node_fields)
+
+ # Should return a unique node_id
+ assert node_id not in self.node_ids
+ self.node_ids.append(node_id)
+
+ # Add to a random set of node groups
+ nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
+ for nodegroup_id in nodegroup_ids:
+ self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
+
+ if self.check:
+ # Check node
+ node = self.api.GetNodes([node_id])[0]
+ for field in node_fields:
+ assert node[field] == node_fields[field]
+
+ if self.verbose:
+ print "Added node", node_id
+
+ def UpdateNodes(self):
+ """
+ Make random changes to any nodes we may have added.
+ """
+
+ boot_states = self.api.GetBootStates()
+ if not boot_states:
+ raise Exception, "No boot states"
+
+ for node_id in self.node_ids:
+ # Update node
+ node_fields = random_node(boot_states)
+ self.api.UpdateNode(node_id, node_fields)
+
+ node = self.api.GetNodes([node_id])[0]
+
+ # Add to a random set of node groups
+ nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
+ for nodegroup_id in (set(nodegroup_ids) - set(node['nodegroup_ids'])):
+ self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
+ for nodegroup_id in (set(node['nodegroup_ids']) - set(nodegroup_ids)):
+ self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
+
+ if self.check:
+ # Check node
+ node = self.api.GetNodes([node_id])[0]
+ for field in node_fields:
+ assert node[field] == node_fields[field]
+ assert set(nodegroup_ids) == set(node['nodegroup_ids'])
+
+ if self.verbose:
+ print "Updated node", node_id
+ print "Added node", node_id, "to node groups", nodegroup_ids
+
+ def DeleteNodes(self):
+ """
+ Delete any random nodes we may have added.
+ """
+
+ for node_id in self.node_ids:
+ # Remove from node groups
+ node = self.api.GetNodes([node_id])[0]
+ for nodegroup_id in node['nodegroup_ids']:
+ self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
+
+ if self.check:
+ node = self.api.GetNodes([node_id])[0]
+ assert not node['nodegroup_ids']
+
+ self.api.DeleteNode(node_id)
+
+ if self.check:
+ assert not self.api.GetNodes([node_id])
+
+ if self.verbose:
+ print "Deleted node", node_id
+
+ if self.check:
+ assert not self.api.GetNodes(self.node_ids)
+
+ self.node_ids = []
+
+ def AddNodeNetworks(self, per_node = 1):
+ """
+ Add a number of random network interfaces to each node.
+ """
+
+ network_methods = self.api.GetNetworkMethods()
+ if not network_methods:
+ raise Exception, "No network methods"
+
+ network_types = self.api.GetNetworkTypes()
+ if not network_types:
+ raise Exception, "No network types"
+
+ for node_id in self.node_ids:
+ for i in range(per_node):
+ method = random.sample(network_methods, 1)[0]
+ type = random.sample(network_types, 1)[0]
+
+ # Add node network
+ nodenetwork_fields = random_nodenetwork(method, type)
+ nodenetwork_id = self.api.AddNodeNetwork(node_id, nodenetwork_fields)
+
+ # Should return a unique nodenetwork_id
+ assert nodenetwork_id not in self.nodenetwork_ids
+ self.nodenetwork_ids.append(nodenetwork_id)
+
+ if self.check:
+ # Check node network
+ nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
+ for field in nodenetwork_fields:
+ assert nodenetwork[field] == nodenetwork_fields[field]
+
+ if self.verbose:
+ print "Added node network", nodenetwork_id, "to node", node_id
+
+ def UpdateNodeNetworks(self):
+ """
+ Make random changes to any network interfaces we may have added.
+ """
+
+ network_methods = self.api.GetNetworkMethods()
+ if not network_methods:
+ raise Exception, "No network methods"
+
+ network_types = self.api.GetNetworkTypes()
+ if not network_types:
+ raise Exception, "No network types"
+
+ for nodenetwork_id in self.nodenetwork_ids:
+ method = random.sample(network_methods, 1)[0]
+ type = random.sample(network_types, 1)[0]
+
+ # Update nodenetwork
+ nodenetwork_fields = random_nodenetwork(method, type)
+ self.api.UpdateNodeNetwork(nodenetwork_id, nodenetwork_fields)
+
+ if self.check:
+ # Check nodenetwork
+ nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
+ for field in nodenetwork_fields:
+ assert nodenetwork[field] == nodenetwork_fields[field]
+
+ if self.verbose:
+ print "Updated node network", nodenetwork_id
+
+ def DeleteNodeNetworks(self):
+ """
+ Delete any random network interfaces we may have added.
+ """
+
+ for nodenetwork_id in self.nodenetwork_ids:
+ self.api.DeleteNodeNetwork(nodenetwork_id)
+
+ if self.check:
+ assert not self.api.GetNodeNetworks([nodenetwork_id])
+
+ if self.verbose:
+ print "Deleted node network", nodenetwork_id
+
+ if self.check:
+ assert not self.api.GetNodeNetworks(self.nodenetwork_ids)
+
+ self.nodenetwork_ids = []
+
+ def AddPCUs(self, per_site = 1):
+ """
+ Add a number of random PCUs to each site. Each node at the
+ site will be added to a port on the PCU if AddNodes() was
+ previously run.
+ """
+
+ for site_id in self.site_ids:
+ for i in range(per_site):
+ # Add PCU
+ pcu_fields = random_pcu()
+ pcu_id = self.api.AddPCU(site_id, pcu_fields)
+
+ # Should return a unique pcu_id
+ assert pcu_id not in self.pcu_ids
+ self.pcu_ids.append(pcu_id)
+
+ # Add each node at this site to a different port on this PCU
+ site = self.api.GetSites([site_id])[0]
+ port = randint(1, 10)
+ for node_id in site['node_ids']:
+ self.api.AddNodeToPCU(node_id, pcu_id, port)
+ port += 1
+
+ if self.check:
+ # Check PCU
+ pcu = self.api.GetPCUs([pcu_id])[0]
+ for field in pcu_fields:
+ assert pcu[field] == pcu_fields[field]
+
+ if self.verbose:
+ print "Added PCU", pcu_id, "to site", site_id
+
+ def UpdatePCUs(self):
+ """
+ Make random changes to any PCUs we may have added.
+ """
+
+ for pcu_id in self.pcu_ids:
+ # Update PCU
+ pcu_fields = random_pcu()
+ self.api.UpdatePCU(pcu_id, pcu_fields)
+
+ if self.check:
+ # Check PCU
+ pcu = self.api.GetPCUs([pcu_id])[0]
+ for field in pcu_fields:
+ assert pcu[field] == pcu_fields[field]
+
+ if self.verbose:
+ print "Updated PCU", pcu_id
+
+ def DeletePCUs(self):
+ """
+ Delete any random nodes we may have added.
+ """
+
+ for pcu_id in self.pcu_ids:
+ # Remove nodes from PCU
+ pcu = self.api.GetPCUs([pcu_id])[0]
+ for node_id in pcu['node_ids']:
+ self.api.DeleteNodeFromPCU(node_id, pcu_id)
+
+ if self.check:
+ pcu = self.api.GetPCUs([pcu_id])[0]
+ assert not pcu['node_ids']
+
+ self.api.DeletePCU(pcu_id)
+
+ if self.check:
+ assert not self.api.GetPCUs([pcu_id])
+
+ if self.verbose:
+ print "Deleted PCU", pcu_id
+
+ if self.check:
+ assert not self.api.GetPCUs(self.pcu_ids)
+
+ self.pcu_ids = []
+
+ def AddConfFiles(self, n = 10):
+ """
+ Add a number of random global configuration files.
+ """
+
+ conf_files = []
+
+ for i in range(n):
+ # Add a random configuration file
+ conf_files.append(random_conf_file())
+
+ if n:
+ # Add a nodegroup override file
+ nodegroup_conf_file = conf_files[0].copy()
+ nodegroup_conf_file['source'] = randpath(255)
+ conf_files.append(nodegroup_conf_file)
+
+ # Add a node override file
+ node_conf_file = conf_files[0].copy()
+ node_conf_file['source'] = randpath(255)
+ conf_files.append(node_conf_file)
+
+ for conf_file_fields in conf_files:
+ conf_file_id = self.api.AddConfFile(conf_file_fields)
+
+ # Should return a unique conf_file_id
+ assert conf_file_id not in self.conf_file_ids
+ self.conf_file_ids.append(conf_file_id)
+
+ # Add to nodegroup
+ if conf_file_fields == nodegroup_conf_file and self.nodegroup_ids:
+ nodegroup_id = random.sample(self.nodegroup_ids, 1)[0]
+ self.api.AddConfFileToNodeGroup(conf_file_id, nodegroup_id)
+ else:
+ nodegroup_id = None
+
+ # Add to node
+ if conf_file_fields == node_conf_file and self.node_ids:
+ node_id = random.sample(self.node_ids, 1)[0]
+ self.api.AddConfFileToNode(conf_file_id, node_id)
+ else:
+ node_id = None
+
+ if self.check:
+ # Check configuration file
+ conf_file = self.api.GetConfFiles([conf_file_id])[0]
+ for field in conf_file_fields:
+ assert conf_file[field] == conf_file_fields[field]
+
+ if self.verbose:
+ print "Added configuration file", conf_file_id,
+ if nodegroup_id is not None:
+ print "to node group", nodegroup_id,
+ elif node_id is not None:
+ print "to node", node_id,
+ print
+
+ def UpdateConfFiles(self):
+ """
+ Make random changes to any configuration files we may have added.
+ """
+
+ for conf_file_id in self.conf_file_ids:
+ # Update configuration file
+ conf_file_fields = random_conf_file()
+ # Do not update dest so that it remains an override if set
+ if 'dest' in conf_file_fields:
+ del conf_file_fields['dest']
+ self.api.UpdateConfFile(conf_file_id, conf_file_fields)
+
+ if self.check:
+ # Check configuration file
+ conf_file = self.api.GetConfFiles([conf_file_id])[0]
+ for field in conf_file_fields:
+ assert conf_file[field] == conf_file_fields[field]
+
+ if self.verbose:
+ print "Updated configuration file", conf_file_id
+
+ def DeleteConfFiles(self):
+ """
+ Delete any random configuration files we may have added.
+ """
+
+ for conf_file_id in self.conf_file_ids:
+ self.api.DeleteConfFile(conf_file_id)
+
+ if self.check:
+ assert not self.api.GetConfFiles([conf_file_id])
+
+ if self.verbose:
+ print "Deleted configuration file", conf_file_id
+
+ if self.check:
+ assert not self.api.GetConfFiles(self.conf_file_ids)
+
+ self.conf_file_ids = []
+
+ def AddSliceAttributeTypes(self, n = 10):
+ """
+ Add a number of random slice attribute types.
+ """
+
+ roles = self.api.GetRoles()
+ if not roles:
+ raise Exception, "No roles"
+ role_ids = [role['role_id'] for role in roles]
+
+ for i in range(n):
+ attribute_type_fields = random_attribute_type(role_ids)
+ attribute_type_id = self.api.AddSliceAttributeType(attribute_type_fields)
+
+ # Should return a unique attribute_type_id
+ assert attribute_type_id not in self.attribute_type_ids
+ self.attribute_type_ids.append(attribute_type_id)
+
+ if self.check:
+ # Check slice attribute type
+ attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
+ for field in attribute_type_fields:
+ assert attribute_type[field] == attribute_type_fields[field]
+
+ if self.verbose:
+ print "Added slice attribute type", attribute_type_id
+
+ def UpdateSliceAttributeTypes(self):
+ """
+ Make random changes to any slice attribute types we may have added.
+ """
+
+ roles = self.api.GetRoles()
+ if not roles:
+ raise Exception, "No roles"
+ role_ids = [role['role_id'] for role in roles]
+
+ for attribute_type_id in self.attribute_type_ids:
+ # Update slice attribute type
+ attribute_type_fields = random_attribute_type(role_ids)
+ self.api.UpdateSliceAttributeType(attribute_type_id, attribute_type_fields)
+
+ if self.check:
+ # Check slice attribute type
+ attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
+ for field in attribute_type_fields:
+ assert attribute_type[field] == attribute_type_fields[field]
+
+ if self.verbose:
+ print "Updated slice attribute type", attribute_type_id
+
+ def DeleteSliceAttributeTypes(self):
+ """
+ Delete any random slice attribute types we may have added.
+ """
+
+ for attribute_type_id in self.attribute_type_ids:
+ self.api.DeleteSliceAttributeType(attribute_type_id)
+
+ if self.check:
+ assert not self.api.GetSliceAttributeTypes([attribute_type_id])
+
+ if self.verbose:
+ print "Deleted slice attribute type", attribute_type_id
+
+ if self.check:
+ assert not self.api.GetSliceAttributeTypes(self.attribute_type_ids)
+
+ self.attribute_type_ids = []
+
+ def AddSlices(self, per_site = 10):
+ """
+ Add a number of random slices per site.
+ """
+
+ for site in self.api.GetSites(self.site_ids):
+ for i in range(min(per_site, site['max_slices'])):
+ # Add slice
+ slice_fields = random_slice(site['login_base'])
+ slice_id = self.api.AddSlice(slice_fields)
+
+ # Should return a unique slice_id
+ assert slice_id not in self.slice_ids
+ self.slice_ids.append(slice_id)
+
+ # Add slice to a random set of nodes
+ node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
+ if node_ids:
+ self.api.AddSliceToNodes(slice_id, node_ids)
+
+ # Add random set of site users to slice
+ person_ids = random.sample(site['person_ids'], randint(0, len(site['person_ids'])))
+ for person_id in person_ids:
+ self.api.AddPersonToSlice(person_id, slice_id)
+
+ if self.check:
+ # Check slice
+ slice = self.api.GetSlices([slice_id])[0]
+ for field in slice_fields:
+ assert slice[field] == slice_fields[field]
+
+ assert set(node_ids) == set(slice['node_ids'])
+ assert set(person_ids) == set(slice['person_ids'])
+
+ if self.verbose:
+ print "Added slice", slice_id, "to site", site['site_id'],
+ if node_ids:
+ print "and nodes", node_ids,
+ print
+ if person_ids:
+ print "Added users", site['person_ids'], "to slice", slice_id
+
+ def UpdateSlices(self):
+ """
+ Make random changes to any slices we may have added.
+ """
+
+ for slice_id in self.slice_ids:
+ # Update slice
+ slice_fields = random_slice("unused")
+ # Cannot change slice name
+ if 'name' in slice_fields:
+ del slice_fields['name']
+ self.api.UpdateSlice(slice_id, slice_fields)
+
+ slice = self.api.GetSlices([slice_id])[0]
+
+ # Add slice to a random set of nodes
+ node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
+ self.api.AddSliceToNodes(slice_id, list(set(node_ids) - set(slice['node_ids'])))
+ self.api.DeleteSliceFromNodes(slice_id, list(set(slice['node_ids']) - set(node_ids)))
+
+ # Add random set of users to slice
+ person_ids = random.sample(self.person_ids, randint(0, len(self.person_ids)))
+ for person_id in (set(person_ids) - set(slice['person_ids'])):
+ self.api.AddPersonToSlice(person_id, slice_id)
+ for person_id in (set(slice['person_ids']) - set(person_ids)):
+ self.api.DeletePersonFromSlice(person_id, slice_id)
+
+ if self.check:
+ slice = self.api.GetSlices([slice_id])[0]
+ for field in slice_fields:
+ assert slice[field] == slice_fields[field]
+ assert set(node_ids) == set(slice['node_ids'])
+ assert set(person_ids) == set(slice['person_ids'])
+
+ if self.verbose:
+ print "Updated slice", slice_id
+ print "Added nodes", node_ids, "to slice", slice_id
+ print "Added persons", person_ids, "to slice", slice_id
+
+ def DeleteSlices(self):
+ """
+ Delete any random slices we may have added.
+ """
+
+ for slice_id in self.slice_ids:
+ self.api.DeleteSlice(slice_id)
+
+ if self.check:
+ assert not self.api.GetSlices([slice_id])
+
+ if self.verbose:
+ print "Deleted slice", slice_id
+
+ if self.check:
+ assert not self.api.GetSlices(self.slice_ids)
+
+ self.slice_ids = []
+
+ def AddSliceAttributes(self, per_slice = 2):
+ """
+ Add a number of random slices per site.
+ """
+
+ if not self.attribute_type_ids:
+ return
+
+ for slice_id in self.slice_ids:
+ slice = self.api.GetSlices([slice_id])[0]
+
+ for i in range(per_slice):
+ # Set a random slice/sliver attribute
+ for attribute_type_id in random.sample(self.attribute_type_ids, 1):
+ value = randstr(16, letters + '_' + digits)
+ # Make it a sliver attribute with 50% probability
+ if slice['node_ids']:
+ node_id = random.sample(slice['node_ids'] + [None] * len(slice['node_ids']), 1)[0]
+ else:
+ node_id = None
+
+ # Add slice attribute
+ if node_id is None:
+ slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value)
+ else:
+ slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value, node_id)
+
+ # Should return a unique slice_attribute_id
+ assert slice_attribute_id not in self.slice_attribute_ids
+ self.slice_attribute_ids.append(slice_attribute_id)
+
+ if self.check:
+ # Check slice attribute
+ slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
+ for field in 'attribute_type_id', 'slice_id', 'node_id', 'slice_attribute_id', 'value':
+ assert slice_attribute[field] == locals()[field]
+
+ if self.verbose:
+ print "Added slice attribute", slice_attribute_id, "of type", attribute_type_id,
+ if node_id is not None:
+ print "to node", node_id,
+ print
+
+ def UpdateSliceAttributes(self):
+ """
+ Make random changes to any slice attributes we may have added.
+ """
+
+ for slice_attribute_id in self.slice_attribute_ids:
+ # Update slice attribute
+ value = randstr(16, letters + '_' + digits)
+ self.api.UpdateSliceAttribute(slice_attribute_id, value)
+
+ # Check slice attribute again
+ slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
+ assert slice_attribute['value'] == value
+
+ if self.verbose:
+ print "Updated slice attribute", slice_attribute_id
+
+ def DeleteSliceAttributes(self):
+ """
+ Delete any random slice attributes we may have added.
+ """
+
+ for slice_attribute_id in self.slice_attribute_ids:
+ self.api.DeleteSliceAttribute(slice_attribute_id)
+
+ if self.check:
+ assert not self.api.GetSliceAttributes([slice_attribute_id])
+
+ if self.verbose:
+ print "Deleted slice attribute", slice_attribute_id
+
+ if self.check:
+ assert not self.api.GetSliceAttributes(self.slice_attribute_ids)
+
+ self.slice_attribute_ids = []
+
+def main():
+ parser = OptionParser()
+ parser.add_option("-c", "--check", action = "store_true", default = False, help = "Check most actions (default: %default)")
+ parser.add_option("-q", "--quiet", action = "store_true", default = False, help = "Be quiet (default: %default)")
+ parser.add_option("-t", "--tiny", action = "store_true", default = False, help = "Run a tiny test (default: %default)")
+ (options, args) = parser.parse_args()
+
+ test = Test(api = Shell(),
+ check = options.check,
+ verbose = not options.quiet)
+
+ if options.tiny:
+ params = Test.tiny
+ else:
+ params = Test.default
+
+ test.Run(**params)
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+all = """
+Addresses
+AddressTypes
+API
+Auth
+Boot
+BootStates
+ConfFiles
+Config
+Debug
+EventObjects
+Events
+Faults
+Filter
+GPG
+InitScripts
+Keys
+KeyTypes
+Messages
+Method
+NetworkMethods
+NetworkTypes
+NodeGroups
+NodeNetworkSettings
+NodeNetworkSettingTypes
+NodeNetworks
+Nodes
+Parameter
+PCUProtocolTypes
+PCUs
+PCUTypes
+Peers
+Persons
+POD
+PostgreSQL
+PyCurl
+Roles
+sendmail
+Sessions
+Shell
+Sites
+SliceAttributes
+SliceAttributeTypes
+SliceInstantiations
+Slices
+Table
+Test
+""".split()
--- /dev/null
+import os
+import sys
+import pprint
+from types import StringTypes
+from email.MIMEText import MIMEText
+from email.Header import Header
+from smtplib import SMTP
+
+from PLC.Debug import log
+from PLC.Faults import *
+
+def sendmail(api, To, Subject, Body, From = None, Cc = None, Bcc = None):
+ """
+ Uses sendmail (must be installed and running locally) to send a
+ message to the specified recipients. If the API is running under
+ mod_python, the apache user must be listed in e.g.,
+ /etc/mail/trusted-users.
+
+ To, Cc, and Bcc may be addresses or lists of addresses. Each
+ address may be either a plain text address or a tuple of (name,
+ address).
+ """
+
+ # Fix up defaults
+ if not isinstance(To, list):
+ To = [To]
+ if Cc is not None and not isinstance(Cc, list):
+ Cc = [Cc]
+ if Bcc is not None and not isinstance(Bcc, list):
+ Bcc = [Bcc]
+ if From is None:
+ From = ("%s Support" % api.config.PLC_NAME,
+ api.config.PLC_MAIL_SUPPORT_ADDRESS)
+
+ # Create a MIME-encoded UTF-8 message
+ msg = MIMEText(Body.encode(api.encoding), _charset = api.encoding)
+
+ # Unicode subject headers are automatically encoded correctly
+ msg['Subject'] = Subject
+
+ def encode_addresses(addresses, header_name = None):
+ """
+ Unicode address headers are automatically encoded by
+ email.Header, but not correctly. The correct way is to put the
+ textual name inside quotes and the address inside brackets:
+
+ To: "=?utf-8?b?encoded" <recipient@domain>
+
+ Each address in addrs may be a tuple of (name, address) or
+ just an address. Returns a tuple of (header, addrlist)
+ representing the encoded header text and the list of plain
+ text addresses.
+ """
+
+ header = []
+ addrs = []
+
+ for addr in addresses:
+ if isinstance(addr, tuple):
+ (name, addr) = addr
+ try:
+ name = name.encode('ascii')
+ header.append('%s <%s>' % (name, addr))
+ except:
+ h = Header(name, charset = api.encoding, header_name = header_name)
+ header.append('"%s" <%s>' % (h.encode(), addr))
+ else:
+ header.append(addr)
+ addrs.append(addr)
+
+ return (", ".join(header), addrs)
+
+ (msg['From'], from_addrs) = encode_addresses([From], 'From')
+ (msg['To'], to_addrs) = encode_addresses(To, 'To')
+
+ if Cc is not None:
+ (msg['Cc'], cc_addrs) = encode_addresses(Cc, 'Cc')
+ to_addrs += cc_addrs
+
+ if Bcc is not None:
+ (unused, bcc_addrs) = encode_addresses(Bcc, 'Bcc')
+ to_addrs += bcc_addrs
+
+ # Needed to pass some spam filters
+ msg['Reply-To'] = msg['From']
+ msg['X-Mailer'] = "Python/" + sys.version.split(" ")[0]
+
+ if not api.config.PLC_MAIL_ENABLED:
+ print >> log, "From: %(From)s, To: %(To)s, Subject: %(Subject)s" % msg
+ return
+
+ s = SMTP()
+ s.connect()
+ rejected = s.sendmail(from_addrs[0], to_addrs, msg.as_string(), rcpt_options = ["NOTIFY=NEVER"])
+ s.close()
+
+ if rejected:
+ raise PLCAPIError, "Error sending message to " + ", ".join(rejected.keys())