import sys
sys.path.append('.')
+
import os, os.path
import tempfile
import socket
from lxml import etree
from StringIO import StringIO
from optparse import OptionParser
-from sfa.client.client_helper import pg_users_arg, sfa_users_arg
-from sfa.util.sfalogging import sfi_logger
+
from sfa.trust.certificate import Keypair, Certificate
from sfa.trust.gid import GID
from sfa.trust.credential import Credential
from sfa.trust.sfaticket import SfaTicket
-from sfa.util.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
-from sfa.rspecs.rspec import RSpec
-from sfa.rspecs.rspec_converter import RSpecConverter
+
+from sfa.util.sfalogging import sfi_logger
from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
-import sfa.client.xmlrpcprotocol as xmlrpcprotocol
from sfa.util.config import Config
from sfa.util.version import version_core
from sfa.util.cache import Cache
+from sfa.util.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
+
+from sfa.rspecs.rspec import RSpec
+from sfa.rspecs.rspec_converter import RSpecConverter
from sfa.rspecs.version_manager import VersionManager
from sfa.client.return_value import ReturnValue
+import sfa.client.xmlrpcprotocol as xmlrpcprotocol
+from sfa.client.client_helper import pg_users_arg, sfa_users_arg
+
AGGREGATE_PORT=12346
CM_PORT=12346
# see pl.py for an example
# some descendant of SfaApi
def api_class (self) : pass
- # in practical terms these are modules for now
+ # the python classes to use to build up the context
def registry_class (self) : pass
def slicemgr_class (self) : pass
def aggregate_class (self) : pass
driver = self.make_driver (api.config, api.interface)
### arrange stuff together
# add a manager wrapper
- manager = ManagerWrapper(manager,api.interface)
- api.manager=manager
+ manager_wrap = ManagerWrapper(manager,api.interface)
+ api.manager=manager_wrap
# insert driver in manager
+ logger.info("Setting manager.driver, manager=%s"%manager)
manager.driver=driver
# add it in api as well for convenience
api.driver=driver
# when running GetCredential
# This is to reflect the 'enabled' user field in planetlab testbeds
# expected retcod boolean
- def is_enabled_entity (self, record, aggregates) : return True
-
+ def is_enabled (self, record) :
+ return True
+
+ # the following is used in Resolve (registry) when run in full mode
+ # after looking up the sfa db, we wish to be able to display
+ # testbed-specific info as well
+ # this at minima should fill in the 'researcher' field for slice records
+ def augment_records_with_testbed_info (self, sfa_records):
+ return sfa_records
+
# incoming record, as provided by the client to the Register API call
# expected retcod 'pointer'
# 'pointer' is typically an int db id, that makes sense in the testbed environment
# -1 if this feature is not relevant
# here type will be 'authority'
- def register (self, hrn, sfa_record, pub_key) : return -1
+ def register (self, sfa_record, hrn, pub_key) :
+ return -1
+
+ # incoming record is the existing sfa_record
+ # expected retcod boolean, error message logged if result is False
+ def remove (self, sfa_record):
+ return True
+
+ # incoming are the sfa_record:
+ # (*) old_sfa_record is what we have in the db for that hrn
+ # (*) new_sfa_record is what was passed in the Update call
+ # expected retcod boolean, error message logged if result is False
+ # NOTE 1. about keys
+ # this is confusing because a user may have several ssh keys in
+ # the planetlab database, but we need to pick one to generate its cert
+ # so as much as in principle we should be able to use new_sfa_record['keys']
+ # the manager code actually picks one (the first one), and it seems safer
+ # to pass it along rather than depending on the driver code to do the same
+ #
+ # NOTE 2. about keys
+ # when changing the ssh key through this method the gid gets changed too
+ # should anything be passed back to the caller in this case ?
+ def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
+ return True
from sfa.util.xrn import Xrn, get_authority, hrn_to_urn, urn_to_hrn
from sfa.util.plxrn import hrn_to_pl_login_base
from sfa.util.version import version_core
+from sfa.util.sfalogging import logger
from sfa.trust.gid import GID
from sfa.trust.credential import Credential
# verify_cancreate_credential requires that the member lists
# (researchers, pis, etc) be filled in
- if not api.driver.is_enabled_entity (record, api.aggregates):
+ if not self.driver.is_enabled (record):
raise AccountNotEnabled(": PlanetLab account %s is not enabled. Please contact your site PI" %(record['email']))
# get the callers gid
#
table = SfaTable()
local_records = table.findObjects({'hrn': local_hrns})
- # xxx driver todo
+
if full:
- api.driver.fill_record_info(local_records, api.aggregates)
+ # in full mode we get as much info as we can, which involves contacting the
+ # testbed for getting implementation details about the record
+ self.driver.augment_records_with_testbed_info(local_records)
+ # also we fill the 'url' field for known authorities
+ # used to be in the driver code, sounds like a poorman thing though
+ def solve_neighbour_url (record):
+ if not record['type'].startswith('authority'): return
+ hrn=record['hrn']
+ for neighbour_dict in [ api.aggregates, api.registries ]:
+ if hrn in neighbour_dict:
+ record['url']=neighbour_dict[hrn].get_url()
+ return
+ [ solve_neighbour_url (record) for record in local_records ]
+
+
# convert local record objects to dicts
records.extend([dict(record) for record in local_records])
gid = api.auth.hierarchy.create_gid(xrn, create_uuid(), pkey)
return gid.save_to_string(save_parents=True)
+ # utility for handling relationships among the SFA objects
+ # given that the SFA db does not handle this sort of relationsships
+ # it will rely on side-effects in the testbed to keep this persistent
+ # field_key is the name of one field in the record, typically 'researcher' for a 'slice' record
+ # hrns is the list of hrns that should be linked to the subject from now on
+ # target_type would be e.g. 'user' in the 'slice' x 'researcher' example
+ def update_relation (self, sfa_record, field_key, hrns, target_type):
+ # locate the linked objects in our db
+ subject_type=sfa_record['type']
+ subject_id=sfa_record['pointer']
+ table = SfaTable()
+ link_sfa_records = table.find ({'type':target_type, 'hrn': hrns})
+ link_ids = [ rec.get('pointer') for rec in link_sfa_records ]
+ self.driver.update_relation (subject_type, target_type, subject_id, link_ids)
+
+
def Register(self, api, record):
hrn, type = record['hrn'], record['type']
if 'gid' not in record:
uuid = create_uuid()
pkey = Keypair(create=True)
- if 'key' in record and record['key']:
+ if 'keys' in record and record['keys']:
+ pub_key=record['keys']
# use only first key in record
- if isinstance(record['key'], types.ListType):
- pub_key = record['key'][0]
- else:
- pub_key = record['key']
+ if isinstance(record['keys'], types.ListType):
+ pub_key = record['keys'][0]
pkey = convert_public_key(pub_key)
gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
# get the GID from the newly created authority
gid = auth_info.get_gid_object()
record.set_gid(gid.save_to_string(save_parents=True))
- pointer = api.driver.register (hrn, record, pub_key)
-
- elif (type == "slice"):
- pointer = api.driver.register (hrn, record, pub_key)
-
- elif (type == "user"):
- pointer = api.driver.register (hrn, record, pub_key)
-
- elif (type == "node"):
- pointer = api.driver.register (hrn, record, pub_key)
+
+ # update testbed-specific data f needed
+ logger.info("Getting driver from manager=%s"%self)
+ pointer = self.driver.register (record, hrn, pub_key)
record.set_pointer(pointer)
record_id = table.insert(record)
record['record_id'] = record_id
# update membership for researchers, pis, owners, operators
- api.driver.update_membership(None, record)
+ self.update_relation(record, 'researcher', record.get('researcher'), 'user')
return record.get_gid_object().save_to_string(save_parents=True)
record = records[0]
record['last_updated'] = time.gmtime()
- # Update_membership needs the membership lists in the existing record
- # filled in, so it can see if members were added or removed
- api.driver.fill_record_info(record, api.aggregates)
-
+ # validate the type
+ if type not in ['authority', 'slice', 'node', 'user']:
+ raise UnknownSfaType(type)
+
# Use the pointer from the existing record, not the one that the user
# gave us. This prevents the user from inserting a forged pointer
pointer = record['pointer']
- # update the PLC information that was specified with the record
- if (type == "authority"):
- api.driver.UpdateSite(pointer, new_record)
-
- elif type == "slice":
- pl_record=api.driver.sfa_fields_to_pl_fields(type, hrn, new_record)
- if 'name' in pl_record:
- pl_record.pop('name')
- api.driver.UpdateSlice(pointer, pl_record)
-
- elif type == "user":
- # SMBAKER: UpdatePerson only allows a limited set of fields to be
- # updated. Ideally we should have a more generic way of doing
- # this. I copied the field names from UpdatePerson.py...
- update_fields = {}
- all_fields = new_record
- for key in all_fields.keys():
- if key in ['first_name', 'last_name', 'title', 'email',
- 'password', 'phone', 'url', 'bio', 'accepted_aup',
- 'enabled']:
- update_fields[key] = all_fields[key]
- api.driver.UpdatePerson(pointer, update_fields)
-
- if 'key' in new_record and new_record['key']:
- # must check this key against the previous one if it exists
- persons = api.driver.GetPersons([pointer], ['key_ids'])
- person = persons[0]
- keys = person['key_ids']
- keys = api.driver.GetKeys(person['key_ids'])
- key_exists = False
- if isinstance(new_record['key'], types.ListType):
- new_key = new_record['key'][0]
- else:
- new_key = new_record['key']
-
- # Delete all stale keys
- for key in keys:
- if new_record['key'] != key['key']:
- api.driver.DeleteKey(key['key_id'])
- else:
- key_exists = True
- if not key_exists:
- api.driver.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
-
- # update the openssl key and gid
- pkey = convert_public_key(new_key)
- uuid = create_uuid()
- gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
- gid = gid_object.save_to_string(save_parents=True)
- record['gid'] = gid
- record = SfaRecord(dict=record)
- table.update(record)
-
- elif type == "node":
- api.driver.UpdateNode(pointer, new_record)
-
- else:
- raise UnknownSfaType(type)
+ # is the a change in keys ?
+ new_key=None
+ if type=='user':
+ if 'keys' in new_record and new_record['keys']:
+ new_key=new_record['keys']
+ if isinstance (new_key,types.ListType):
+ new_key=new_key[0]
+
+ # update the PLC information that was specified with the record
+ if not self.driver.update (record, new_record, hrn, new_key):
+ logger.warning("driver.update failed")
+ # take new_key into account
+ if new_key:
+ # update the openssl key and gid
+ pkey = convert_public_key(new_key)
+ uuid = create_uuid()
+ gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
+ gid = gid_object.save_to_string(save_parents=True)
+ record['gid'] = gid
+ record = SfaRecord(dict=record)
+ table.update(record)
+
# update membership for researchers, pis, owners, operators
- api.driver.update_membership(record, new_record)
+ self.update_relation(record, 'researcher', new_record.get('researcher'), 'user')
return 1
if not records: raise RecordNotFound(hrn)
record = records[0]
type = record['type']
-
+
+ if type not in ['slice', 'user', 'node', 'authority'] :
+ raise UnknownSfaType(type)
+
credential = api.getCredential()
registries = api.registries
result=registries[registry].remove_peer_object(credential, record, origin_hrn)
except:
pass
- if type == "user":
- persons = api.driver.GetPersons(record['pointer'])
- # only delete this person if he has site ids. if he doesnt, it probably means
- # he was just removed from a site, not actually deleted
- if persons and persons[0]['site_ids']:
- api.driver.DeletePerson(record['pointer'])
- elif type == "slice":
- if api.driver.GetSlices(record['pointer']):
- api.driver.DeleteSlice(record['pointer'])
- elif type == "node":
- if api.driver.GetNodes(record['pointer']):
- api.driver.DeleteNode(record['pointer'])
- elif type == "authority":
- if api.driver.GetSites(record['pointer']):
- api.driver.DeleteSite(record['pointer'])
- else:
- raise UnknownSfaType(type)
-
+
+ # call testbed callback first
+ # IIUC this is done on the local testbed TOO because of the refreshpeer link
+ if not self.driver.remove(record):
+ logger.warning("driver.remove failed")
+
+ # delete from sfa db
table.remove(record)
return 1
+ # This is a PLC-specific thing...
def get_key_from_incoming_ip (self, api):
# verify that the callers's ip address exist in the db and is an interface
# for a node in the db
(ip, port) = api.remote_addr
- interfaces = api.driver.GetInterfaces({'ip': ip}, ['node_id'])
+ interfaces = self.driver.GetInterfaces({'ip': ip}, ['node_id'])
if not interfaces:
raise NonExistingRecord("no such ip %(ip)s" % locals())
- nodes = api.driver.GetNodes([interfaces[0]['node_id']], ['node_id', 'hostname'])
+ nodes = self.driver.GetNodes([interfaces[0]['node_id']], ['node_id', 'hostname'])
if not nodes:
raise NonExistingRecord("no such node using ip %(ip)s" % locals())
node = nodes[0]
#
-from sfa.util.faults import MissingSfaInfo
+from sfa.util.faults import MissingSfaInfo, UnknownSfaType
from sfa.util.sfalogging import logger
from sfa.util.table import SfaTable
from sfa.util.defaultdict import defaultdict
convert a list of dictionaries into a dictionary keyed on the
specified dictionary key
"""
- keys = [rec[key] for rec in recs]
- return dict(zip(keys, recs))
+ return dict ( [ (rec[key],rec) for rec in recs ] )
#
-# inheriting Driver is not very helpful in the PL case but
-# makes sense in the general case
-#
# PlShell is just an xmlrpc serverproxy where methods
# can be sent as-is; it takes care of authentication
# from the global config
#
-# so OTOH we inherit PlShell just so one can do driver.GetNodes
+# so we inherit PlShell just so one can do driver.GetNodes
# which would not make much sense in the context of other testbeds
-# so ultimately PlDriver might just as well drop the PlShell inheritance
+# so ultimately PlDriver should drop the PlShell inheritance
+# and would have a driver.shell reference to a PlShell instead
#
class PlDriver (Driver, PlShell):
rspec_type == 'eucalyptus' or rspec_type == 'max')
########## disabled users
- def is_enabled_entity (self, record, aggregates):
- self.fill_record_info(record, aggregates)
+ def is_enabled (self, record):
+ self.fill_record_info(record, deep=False)
if record['type'] == 'user':
return record['enabled']
# only users can be disabled
return True
+ def augment_records_with_testbed_info (self, sfa_records):
+ return self.fill_record_info (sfa_records, deep=True)
+
##########
- def register (self, hrn, sfa_record, pub_key):
+ def register (self, sfa_record, hrn, pub_key):
type = sfa_record['type']
pl_record = self.sfa_fields_to_pl_fields(type, hrn, sfa_record)
return pointer
+ ##########
+ # xxx actually old_sfa_record comes filled with plc stuff as well in the original code
+ def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
+ pointer = old_sfa_record['pointer']
+ type = old_sfa_record['type']
+
+ # new_key implemented for users only
+ if new_key and type not in [ 'user' ]:
+ raise UnknownSfaType(type)
+
+ if (type == "authority"):
+ self.UpdateSite(pointer, new_sfa_record)
+
+ elif type == "slice":
+ pl_record=self.sfa_fields_to_pl_fields(type, hrn, new_sfa_record)
+ if 'name' in pl_record:
+ pl_record.pop('name')
+ self.UpdateSlice(pointer, pl_record)
+
+ elif type == "user":
+ # SMBAKER: UpdatePerson only allows a limited set of fields to be
+ # updated. Ideally we should have a more generic way of doing
+ # this. I copied the field names from UpdatePerson.py...
+ update_fields = {}
+ all_fields = new_sfa_record
+ for key in all_fields.keys():
+ if key in ['first_name', 'last_name', 'title', 'email',
+ 'password', 'phone', 'url', 'bio', 'accepted_aup',
+ 'enabled']:
+ update_fields[key] = all_fields[key]
+ self.UpdatePerson(pointer, update_fields)
+
+ if new_key:
+ # must check this key against the previous one if it exists
+ persons = self.GetPersons([pointer], ['key_ids'])
+ person = persons[0]
+ keys = person['key_ids']
+ keys = self.GetKeys(person['key_ids'])
+
+ # Delete all stale keys
+ key_exists = False
+ for key in keys:
+ if new_key != key['key']:
+ self.DeleteKey(key['key_id'])
+ else:
+ key_exists = True
+ if not key_exists:
+ self.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
+
+ elif type == "node":
+ self.UpdateNode(pointer, new_sfa_record)
+
+ return True
+
+
+ ##########
+ def remove (self, sfa_record):
+ type=sfa_record['type']
+ pointer=sfa_record['pointer']
+ if type == 'user':
+ persons = self.GetPersons(pointer)
+ # only delete this person if he has site ids. if he doesnt, it probably means
+ # he was just removed from a site, not actually deleted
+ if persons and persons[0]['site_ids']:
+ self.DeletePerson(pointer)
+ elif type == 'slice':
+ if self.GetSlices(pointer):
+ self.DeleteSlice(pointer)
+ elif type == 'node':
+ if self.GetNodes(pointer):
+ self.DeleteNode(pointer)
+ elif type == 'authority':
+ if self.GetSites(pointer):
+ self.DeleteSite(pointer)
+
+ return True
+
+
+
+
##
# Convert SFA fields to PLC fields for use when registering up updating
return pl_record
####################
- def fill_record_info(self, records, aggregates):
+ def fill_record_info(self, records, deep=False):
"""
Given a (list of) SFA record, fill in the PLC specific
and SFA specific fields in the record.
records = [records]
self.fill_record_pl_info(records)
- self.fill_record_sfa_info(records, aggregates)
+ if deep:
+ self.fill_record_hrns(records)
+ self.fill_record_sfa_info(records)
+ return records
def fill_record_pl_info(self, records):
"""
Fill in the planetlab specific fields of a SFA record. This
involves calling the appropriate PLC method to retrieve the
database record for the object.
-
- PLC data is filled into the pl_info field of the record.
-
+
@param record: record to fill in field (in/out param)
"""
# get ids by type
pubkeys = [keys[key_id]['key'] for key_id in record['key_ids'] if key_id in keys]
record['keys'] = pubkeys
- # fill in record hrns
- records = self.fill_record_hrns(records)
-
return records
def fill_record_hrns(self, records):
return records
# aggregates is basically api.aggregates
- def fill_record_sfa_info(self, records, aggregates):
+ def fill_record_sfa_info(self, records):
def startswith(prefix, values):
return [value for value in values if value.startswith(prefix)]
elif (type.startswith("authority")):
record['url'] = None
- if record['hrn'] in aggregates:
-
- record['url'] = aggregates[record['hrn']].get_url()
-
if record['pointer'] != -1:
record['PI'] = []
record['operator'] = []
# xxx TODO: PostalAddress, Phone
record.update(sfa_info)
+
####################
- def update_membership(self, oldRecord, record):
- if record.type == "slice":
- self.update_membership_list(oldRecord, record, 'researcher',
- self.AddPersonToSlice,
- self.DeletePersonFromSlice)
- elif record.type == "authority":
- # xxx TODO
- pass
-
- def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
- # get a list of the HRNs that are members of the old and new records
- if oldRecord:
- oldList = oldRecord.get(listName, [])
+ # plcapi works by changes, compute what needs to be added/deleted
+ def update_relation (self, subject_type, target_type, subject_id, target_ids):
+ # hard-wire the code for slice/user for now
+ if subject_type =='slice' and target_type == 'user':
+ subject=self.GetSlices (subject_id)[0]
+ current_target_ids = subject['person_ids']
+ add_target_ids = list ( set (target_ids).difference(current_target_ids))
+ del_target_ids = list ( set (current_target_ids).difference(target_ids))
+ logger.info ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
+ for target_id in add_target_ids:
+ self.AddPersonToSlice (target_id,subject_id)
+ logger.info ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
+ for target_id in del_target_ids:
+ logger.info ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
+ self.DeletePersonFromSlice (target_id, subject_id)
else:
- oldList = []
- newList = record.get(listName, [])
-
- # if the lists are the same, then we don't have to update anything
- if (oldList == newList):
- return
+ logger.info('unexpected relation to maintain, %s -> %s'%(subject_type,target_type))
- # build a list of the new person ids, by looking up each person to get
- # their pointer
- newIdList = []
- # xxx thgen fixme - use SfaTable hardwired for now
- #table = self.SfaTable()
- table = SfaTable()
- records = table.find({'type': 'user', 'hrn': newList})
- for rec in records:
- newIdList.append(rec['pointer'])
-
- # build a list of the old person ids from the person_ids field
- if oldRecord:
- oldIdList = oldRecord.get("person_ids", [])
- containerId = oldRecord.get_pointer()
- else:
- # if oldRecord==None, then we are doing a Register, instead of an
- # update.
- oldIdList = []
- containerId = record.get_pointer()
-
- # add people who are in the new list, but not the oldList
- for personId in newIdList:
- if not (personId in oldIdList):
- addFunc(personId, containerId)
-
- # remove people who are in the old list, but not the new list
- for personId in oldIdList:
- if not (personId in newIdList):
- delFunc(personId, containerId)
+
# path to configuration data
self.config_path = os.path.dirname(config_file)
+ ### xxx todo implement defaults in default_config.xml
# path to server data
if not hasattr(self, 'SFA_DATA_DIR'):
# default to /var/lib/sfa not specified in config
# path to config data
if not hasattr(self, 'SFA_CONFIG_DIR'):
- # default to /var/lib/sfa not specified in config
+ # default to /etc/sfa not specified in config
self.SFA_CONFIG_DIR="/etc/sfa"
if not hasattr(self, 'SFA_REGISTRY_LEVEL1_AUTH'):
self.SFA_REGISTRY_LEVEL1_AUTH=None
- # define interface types
- # this will determine which manager to use
- if not hasattr(self, 'SFA_REGISTRY_TYPE'):
- self.SFA_REGISTRY_TYPE='pl'
-
- if not hasattr(self, 'SFA_AGGREGATE_TYPE'):
- self.SFA_AGGREGATE_TYPE='pl'
-
- if not hasattr(self, 'SFA_SM_TYPE'):
- self.SFA_SM_TYPE='pl'
-
- if not hasattr(self, 'SFA_CM_TYPE'):
- self.SFA_COMPONENT_TYPE='pl'
-
- if not hasattr(self, 'SFA_MAX_SLICE_RENEW'):
- self.SFA_MAX_SLICE_RENEW=60
-
- if not hasattr(self, 'SFA_AGGREGATE_API_VERSION'):
- self.SFA_AGGREGATE_API_VERSION=1
-
# create the data directory if it doesnt exist
if not os.path.isdir(self.SFA_DATA_DIR):
try: