setting the sfa-0.9-15 tag
authorTony Mack <tmack@cs.princeton.edu>
Tue, 24 Aug 2010 17:53:50 +0000 (17:53 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Tue, 24 Aug 2010 17:53:50 +0000 (17:53 +0000)
322 files changed:
CHANGES-0.2-to-0.8.txt [new file with mode: 0644]
INSTALL.txt [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.txt [new file with mode: 0644]
TODO [new file with mode: 0644]
config/aggregates.xml [new file with mode: 0644]
config/default_config.xml [new file with mode: 0644]
config/gen-sfa-cm-config.py [new file with mode: 0644]
config/geni_aggregates.xml [new file with mode: 0644]
config/registries.xml [new file with mode: 0644]
config/sfa-config-tty [new file with mode: 0755]
config/sfa_component_config [new file with mode: 0644]
config/sfi_config [new file with mode: 0644]
docs/Geniwrapper_Design_Docu.html [new file with mode: 0644]
docs/Makefile [new file with mode: 0644]
docs/README [new file with mode: 0644]
docs/geniwrapper.doc [new file with mode: 0644]
docs/geniwrapper.pdf [new file with mode: 0644]
docs/pythondoc-cert.html [new file with mode: 0644]
docs/pythondoc-component.html [new file with mode: 0644]
docs/pythondoc-config.html [new file with mode: 0644]
docs/pythondoc-credential.html [new file with mode: 0644]
docs/pythondoc-geniclient.html [new file with mode: 0644]
docs/pythondoc-geniserver.html [new file with mode: 0644]
docs/pythondoc-gid.html [new file with mode: 0644]
docs/pythondoc-hierarchy.html [new file with mode: 0644]
docs/pythondoc-import.html [new file with mode: 0644]
docs/pythondoc-record.html [new file with mode: 0644]
docs/pythondoc-registry.html [new file with mode: 0644]
docs/pythondoc-rights.html [new file with mode: 0644]
docs/sfa-2008-08-08.pdf [new file with mode: 0644]
docs/sfa-2008-11-03.pdf [new file with mode: 0644]
docs/sfa-impl-2009-04-07.pdf [new file with mode: 0644]
docs/sfa-impl.doc [new file with mode: 0644]
docs/sfa-impl.pdf [new file with mode: 0644]
docs/sfa.doc [new file with mode: 0644]
docs/sfa.pdf [new file with mode: 0644]
keyconvert/keyconvert.py [new file with mode: 0755]
keyconvert/test.sh [new file with mode: 0755]
keyconvert/test.txt [new file with mode: 0644]
keyconvert/test/openssh_dsa_1024 [new file with mode: 0644]
keyconvert/test/openssh_dsa_1024.pub [new file with mode: 0644]
keyconvert/test/openssh_dsa_2048 [new file with mode: 0644]
keyconvert/test/openssh_dsa_2048.pub [new file with mode: 0644]
keyconvert/test/openssh_dsa_512 [new file with mode: 0644]
keyconvert/test/openssh_dsa_512.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa1_1024 [new file with mode: 0644]
keyconvert/test/openssh_rsa1_1024.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa1_2048 [new file with mode: 0644]
keyconvert/test/openssh_rsa1_2048.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa1_512 [new file with mode: 0644]
keyconvert/test/openssh_rsa1_512.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa_1024 [new file with mode: 0644]
keyconvert/test/openssh_rsa_1024.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa_2048 [new file with mode: 0644]
keyconvert/test/openssh_rsa_2048.pub [new file with mode: 0644]
keyconvert/test/openssh_rsa_512 [new file with mode: 0644]
keyconvert/test/openssh_rsa_512.pub [new file with mode: 0644]
keyconvert/testkey.sh [new file with mode: 0755]
rspec/model/Registry.ecore [new file with mode: 0644]
rspec/model/planetlab.ecore [new file with mode: 0644]
rspec/model/planetlab.ecore_diagram [new file with mode: 0644]
rspec/model/planetlab.genmodel [new file with mode: 0644]
rspec/model/planetlab.xsd [new file with mode: 0644]
rspec/sample_rspec.xml [new file with mode: 0644]
setup.py [new file with mode: 0755]
sfa.spec [new file with mode: 0644]
sfa/Makefile [new file with mode: 0644]
sfa/__init__.py [new file with mode: 0644]
sfa/client/__init__.py [new file with mode: 0644]
sfa/client/getNodes.py [new file with mode: 0644]
sfa/client/getRecord.py [new file with mode: 0755]
sfa/client/setRecord.py [new file with mode: 0755]
sfa/client/sfadump.py [new file with mode: 0644]
sfa/client/sfi.py [new file with mode: 0755]
sfa/client/sfiAddAttribute.py [new file with mode: 0755]
sfa/client/sfiAddSliver.py [new file with mode: 0755]
sfa/client/sfiDeleteAttribute.py [new file with mode: 0755]
sfa/client/sfiDeleteSliver.py [new file with mode: 0755]
sfa/client/sfiListNodes.py [new file with mode: 0755]
sfa/client/sfiListSlivers.py [new file with mode: 0755]
sfa/cron.d/sfa.cron [new file with mode: 0644]
sfa/init.d/sfa [new file with mode: 0755]
sfa/init.d/sfa-cm [new file with mode: 0755]
sfa/managers/__init__.py [new file with mode: 0644]
sfa/managers/aggregate_manager_eucalyptus.py [new file with mode: 0644]
sfa/managers/aggregate_manager_max.py [new file with mode: 0644]
sfa/managers/aggregate_manager_openflow.py [new file with mode: 0755]
sfa/managers/aggregate_manager_pl.py [new file with mode: 0644]
sfa/managers/aggregate_manager_vini.py [new file with mode: 0644]
sfa/managers/component_manager_default.py [new file with mode: 0644]
sfa/managers/component_manager_pl.py [new file with mode: 0644]
sfa/managers/eucalyptus/eucalyptus.rnc [new file with mode: 0644]
sfa/managers/eucalyptus/eucalyptus.rng [new file with mode: 0644]
sfa/managers/eucalyptus/eucalyptus.xml [new file with mode: 0644]
sfa/managers/max/max.xml [new file with mode: 0644]
sfa/managers/pl/pl.rnc [new file with mode: 0644]
sfa/managers/pl/pl.rng [new file with mode: 0644]
sfa/managers/pl/pl.xml [new file with mode: 0644]
sfa/managers/registry_manager_pl.py [new file with mode: 0644]
sfa/managers/slice_manager_pl.py [new file with mode: 0644]
sfa/managers/vini/__init__.py [new file with mode: 0644]
sfa/managers/vini/request.xml [new file with mode: 0644]
sfa/managers/vini/topology.py [new file with mode: 0755]
sfa/managers/vini/utils.py [new file with mode: 0644]
sfa/managers/vini/vini.rnc [new file with mode: 0644]
sfa/managers/vini/vini.rng [new file with mode: 0644]
sfa/managers/vini/vini.xml [new file with mode: 0644]
sfa/managers/vini/vini_network.py [new file with mode: 0644]
sfa/methods/CreateSliver.py [new file with mode: 0644]
sfa/methods/DeleteSliver.py [new file with mode: 0644]
sfa/methods/GetCredential.py [new file with mode: 0644]
sfa/methods/GetGids.py [new file with mode: 0644]
sfa/methods/GetSelfCredential.py [new file with mode: 0644]
sfa/methods/GetTicket.py [new file with mode: 0644]
sfa/methods/GetVersion.py [new file with mode: 0644]
sfa/methods/List.py [new file with mode: 0644]
sfa/methods/ListResources.py [new file with mode: 0644]
sfa/methods/ListSlices.py [new file with mode: 0644]
sfa/methods/RedeemTicket.py [new file with mode: 0644]
sfa/methods/Register.py [new file with mode: 0644]
sfa/methods/RegisterPeerObject.py [new file with mode: 0644]
sfa/methods/Remove.py [new file with mode: 0644]
sfa/methods/RemovePeerObject.py [new file with mode: 0644]
sfa/methods/RenewSliver.py [new file with mode: 0644]
sfa/methods/Resolve.py [new file with mode: 0644]
sfa/methods/ResolveGENI.py [new file with mode: 0644]
sfa/methods/Shutdown.py [new file with mode: 0644]
sfa/methods/SliverStatus.py [new file with mode: 0644]
sfa/methods/Start.py [new file with mode: 0644]
sfa/methods/Stop.py [new file with mode: 0644]
sfa/methods/Update.py [new file with mode: 0644]
sfa/methods/UpdateSliver.py [new file with mode: 0644]
sfa/methods/__init__.py [new file with mode: 0644]
sfa/methods/create_slice.py [new file with mode: 0644]
sfa/methods/delete_slice.py [new file with mode: 0644]
sfa/methods/get_aggregates.py [new file with mode: 0644]
sfa/methods/get_credential.py [new file with mode: 0644]
sfa/methods/get_gids.py [new file with mode: 0644]
sfa/methods/get_key.py [new file with mode: 0644]
sfa/methods/get_registries.py [new file with mode: 0644]
sfa/methods/get_resources.py [new file with mode: 0644]
sfa/methods/get_self_credential.py [new file with mode: 0644]
sfa/methods/get_slices.py [new file with mode: 0644]
sfa/methods/get_ticket.py [new file with mode: 0644]
sfa/methods/get_trusted_certs.py [new file with mode: 0644]
sfa/methods/list.py [new file with mode: 0644]
sfa/methods/redeem_ticket.py [new file with mode: 0644]
sfa/methods/register.py [new file with mode: 0644]
sfa/methods/register_peer_object.py [new file with mode: 0644]
sfa/methods/remove.py [new file with mode: 0644]
sfa/methods/remove_peer_object.py [new file with mode: 0644]
sfa/methods/reset_slice.py [new file with mode: 0644]
sfa/methods/resolve.py [new file with mode: 0644]
sfa/methods/start_slice.py [new file with mode: 0644]
sfa/methods/stop_slice.py [new file with mode: 0644]
sfa/methods/update.py [new file with mode: 0644]
sfa/plc/__init__.py [new file with mode: 0644]
sfa/plc/api-dev.py [new file with mode: 0644]
sfa/plc/api.py [new file with mode: 0644]
sfa/plc/network.py [new file with mode: 0644]
sfa/plc/peers.py [new file with mode: 0644]
sfa/plc/remoteshell.py [new file with mode: 0644]
sfa/plc/sfa-import-plc.py [new file with mode: 0755]
sfa/plc/sfa-nuke-plc.py [new file with mode: 0755]
sfa/plc/sfaImport.py [new file with mode: 0644]
sfa/plc/slices.py [new file with mode: 0644]
sfa/rspecs/__init__.py [new file with mode: 0644]
sfa/rspecs/aggregates/__init__.py [new file with mode: 0644]
sfa/rspecs/aggregates/max.xml [new file with mode: 0644]
sfa/rspecs/aggregates/openflow.xml [new file with mode: 0755]
sfa/rspecs/aggregates/rspec_manager_max.py [new file with mode: 0644]
sfa/rspecs/aggregates/rspec_manager_openflow.py [new file with mode: 0755]
sfa/rspecs/aggregates/rspec_manager_pl.py [new file with mode: 0644]
sfa/server/__init__.py [new file with mode: 0644]
sfa/server/aggregate.py [new file with mode: 0644]
sfa/server/component.py [new file with mode: 0644]
sfa/server/interface.py [new file with mode: 0644]
sfa/server/modpython/SfaAggregateModPython.py [new file with mode: 0755]
sfa/server/modpython/SfaRegistryModPython.py [new file with mode: 0755]
sfa/server/modpython/SfaSliceMgrModPython.py [new file with mode: 0755]
sfa/server/modpython/sfa.aggregate.httpd.conf [new file with mode: 0644]
sfa/server/modpython/sfa.registry.httpd.conf [new file with mode: 0644]
sfa/server/modpython/sfa.slicemgr.httpd.conf [new file with mode: 0644]
sfa/server/modpythonapi/ApiExceptionCodes.py [new file with mode: 0644]
sfa/server/modpythonapi/ApiExceptions.py [new file with mode: 0644]
sfa/server/modpythonapi/AuthenticatedApi.py [new file with mode: 0755]
sfa/server/modpythonapi/AuthenticatedClient.py [new file with mode: 0755]
sfa/server/modpythonapi/BaseApi.py [new file with mode: 0755]
sfa/server/modpythonapi/BaseClient.py [new file with mode: 0755]
sfa/server/modpythonapi/ModPython.py [new file with mode: 0755]
sfa/server/modpythonapi/TestApi.py [new file with mode: 0755]
sfa/server/modpythonapi/installTest.sh [new file with mode: 0755]
sfa/server/modpythonapi/test.py [new file with mode: 0755]
sfa/server/modpythonapi/test.sh [new file with mode: 0755]
sfa/server/modpythonapi/testapi.conf [new file with mode: 0644]
sfa/server/registry.py [new file with mode: 0644]
sfa/server/sfa-ca.py [new file with mode: 0755]
sfa/server/sfa-clean-peer-records.py [new file with mode: 0644]
sfa/server/sfa-server.py [new file with mode: 0755]
sfa/server/sfa_component_setup.py [new file with mode: 0755]
sfa/server/slicemgr.py [new file with mode: 0644]
sfa/trust/__init__.py [new file with mode: 0644]
sfa/trust/auth.py [new file with mode: 0644]
sfa/trust/certificate.py [new file with mode: 0644]
sfa/trust/credential.py [new file with mode: 0644]
sfa/trust/credential_legacy.py [new file with mode: 0644]
sfa/trust/gid.py [new file with mode: 0644]
sfa/trust/hierarchy.py [new file with mode: 0644]
sfa/trust/rights.py [new file with mode: 0644]
sfa/trust/trusted_certs/plc_ca.crt [new file with mode: 0644]
sfa/trust/trusted_certs/ple_ca.crt [new file with mode: 0644]
sfa/trust/trustedroot.py [new file with mode: 0644]
sfa/util/PostgreSQL.py [new file with mode: 0644]
sfa/util/__init__.py [new file with mode: 0644]
sfa/util/api.py [new file with mode: 0644]
sfa/util/cache.py [new file with mode: 0644]
sfa/util/client.py [new file with mode: 0644]
sfa/util/componentserver.py [new file with mode: 0644]
sfa/util/config.py [new file with mode: 0644]
sfa/util/debug.py [new file with mode: 0644]
sfa/util/faults.py [new file with mode: 0644]
sfa/util/filter.py [new file with mode: 0644]
sfa/util/method.py [new file with mode: 0644]
sfa/util/misc.py [new file with mode: 0644]
sfa/util/namespace.py [new file with mode: 0644]
sfa/util/nodemanager.py [new file with mode: 0644]
sfa/util/parameter.py [new file with mode: 0644]
sfa/util/policy.py [new file with mode: 0644]
sfa/util/prefixTree.py [new file with mode: 0755]
sfa/util/record.py [new file with mode: 0644]
sfa/util/report.py [new file with mode: 0644]
sfa/util/row.py [new file with mode: 0644]
sfa/util/rspec.py [new file with mode: 0644]
sfa/util/rspecHelper.py [new file with mode: 0755]
sfa/util/server.py [new file with mode: 0644]
sfa/util/sfalogging.py [new file with mode: 0644]
sfa/util/sfatablesRuntime.py [new file with mode: 0644]
sfa/util/sfaticket.py [new file with mode: 0644]
sfa/util/soapprotocol.py [new file with mode: 0644]
sfa/util/specdict.py [new file with mode: 0644]
sfa/util/storage.py [new file with mode: 0644]
sfa/util/table.py [new file with mode: 0644]
sfa/util/threadmanager.py [new file with mode: 0755]
sfa/util/xmlrpcprotocol.py [new file with mode: 0644]
sfatables/README [new file with mode: 0644]
sfatables/TO_CLEANUP [new file with mode: 0644]
sfatables/__init__.py [new file with mode: 0644]
sfatables/command.py [new file with mode: 0644]
sfatables/commands/Add.py [new file with mode: 0644]
sfatables/commands/Delete.py [new file with mode: 0644]
sfatables/commands/Insert.py [new file with mode: 0644]
sfatables/commands/List.py [new file with mode: 0644]
sfatables/commands/SetDefault.py [new file with mode: 0644]
sfatables/commands/__init__.py [new file with mode: 0644]
sfatables/commands/moo.py [new file with mode: 0644]
sfatables/globals.py [new file with mode: 0644]
sfatables/matches/all.xml [new file with mode: 0644]
sfatables/matches/hrn.xml [new file with mode: 0644]
sfatables/matches/slice-whitelist.xml [new file with mode: 0644]
sfatables/matches/slice.xml [new file with mode: 0644]
sfatables/pretty.py [new file with mode: 0644]
sfatables/processors/__sfatables_rule_wrap_up__.xsl [new file with mode: 0644]
sfatables/processors/__sfatables_wrap_up__.xsl [new file with mode: 0644]
sfatables/processors/accept.xsl [new file with mode: 0644]
sfatables/processors/all.xsl [new file with mode: 0644]
sfatables/processors/hrn.xsl [new file with mode: 0644]
sfatables/processors/legacy-restrict-to-nodes.xsl [new file with mode: 0644]
sfatables/processors/link/restrict_slice.xml [new file with mode: 0644]
sfatables/processors/max_link_kbps.xsl [new file with mode: 0644]
sfatables/processors/max_node_kbps.xsl [new file with mode: 0644]
sfatables/processors/reject.xsl [new file with mode: 0644]
sfatables/processors/restrict_to_flowspec.xsl [new file with mode: 0644]
sfatables/processors/restrict_to_nodes.xsl [new file with mode: 0644]
sfatables/processors/slice-hrn.xsl [new file with mode: 0644]
sfatables/processors/slice-whitelist.xsl [new file with mode: 0644]
sfatables/processors/test.xml [new file with mode: 0644]
sfatables/runtime.py [new file with mode: 0644]
sfatables/sfatables [new file with mode: 0755]
sfatables/targets/ACCEPT.xml [new file with mode: 0644]
sfatables/targets/REJECT.xml [new file with mode: 0644]
sfatables/targets/RESTRICT_SLICE_DOMAIN.xml [new file with mode: 0644]
sfatables/targets/RESTRICT_SLICE_PROP.xml [new file with mode: 0644]
sfatables/test.rspec [new file with mode: 0644]
sfatables/test.sfarspec [new file with mode: 0644]
sfatables/vini.rspec [new file with mode: 0644]
sfatables/xmlextension.py [new file with mode: 0644]
sfatables/xmlrule.py [new file with mode: 0644]
tests/client/README [new file with mode: 0644]
tests/client/demoAggregate.sh [new file with mode: 0644]
tests/client/testAggregate.py [new file with mode: 0644]
tests/client/testSfi.sh [new file with mode: 0644]
tests/client/testSfiDelegate.sh [new file with mode: 0644]
tests/client/testSfiSliceRegister.sh [new file with mode: 0644]
tests/testAll.py [new file with mode: 0755]
tests/testCert.py [new file with mode: 0755]
tests/testCred.py [new file with mode: 0755]
tests/testGid.py [new file with mode: 0755]
tests/testHierarchy.py [new file with mode: 0755]
tests/testInterfaces.py [new file with mode: 0755]
tests/testKeypair.py [new file with mode: 0755]
tests/testMisc.py [new file with mode: 0755]
tests/testRecord.py [new file with mode: 0755]
tests/testRights.py [new file with mode: 0755]
wsdl/Makefile [new file with mode: 0644]
wsdl/apistub.py [new file with mode: 0644]
wsdl/globals.py [new file with mode: 0644]
wsdl/sfa2wsdl.py [new file with mode: 0755]
xmlbuilder-0.9/LICENSE [new file with mode: 0644]
xmlbuilder-0.9/MANIFEST.in [new file with mode: 0644]
xmlbuilder-0.9/PKG-INFO [new file with mode: 0644]
xmlbuilder-0.9/README.txt [new file with mode: 0644]
xmlbuilder-0.9/setup.cfg [new file with mode: 0644]
xmlbuilder-0.9/setup.py [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder/__init__.py [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst [new file with mode: 0644]
xmlbuilder-0.9/xmlbuilder/tests/__init__.py [new file with mode: 0644]

diff --git a/CHANGES-0.2-to-0.8.txt b/CHANGES-0.2-to-0.8.txt
new file mode 100644 (file)
index 0000000..0064632
--- /dev/null
@@ -0,0 +1,56 @@
+====================
+changes between 0.2 and 0.8 from a user's perspective
+====================
+
+--------------------
+- 3 packages named
+-- sfa (libraries)
+-- sfa-plc (server side)
+-- sfa-client (client-side)
+
+-------------------- new names for commands
+gimport.py          ->   sfa-import-plc.py
+plc.py              ->   sfa-server.py
+geni-config-tty      ->          sfa-config-tty
+
+-------------------- new names for config files & miscell
+/etc/geni                      -> /etc/sfa
+/etc/geni/configSfi.sh         ->  /etc/sfa/sfa_config
+/etc/init.d/geniwrapper                -> /etc/init.d/sfa
+
+/etc/sfa/config_sfa, 
+/etc/sfa/config_sfi, 
+/etc/sfa/aggregates.xml, 
+/etc/sfa/registries.xml
+       are now preserved across rpm updates
+
+
+====================
+changes between 0.2 and 0.8 from a devel's perspective
+====================
+
+cmdline/ has moved to 
+           sfa/client
+first-class objects involved in the server side 
+           sfa/server
+plc-dependent code has moved to
+           sfa/plc
+basic identification/authentication objects (up to credentials) have moved to
+           sfa/trust
+           (this also holds in trusted_roots the CA certs of PLC-PLE)
+code for the methods have remained in
+            sfa/methods
+
+init.d instscript is now in
+            sfa/init.d
+
+config files and utilities are in
+            config/ 
+           (should this move under sfa ?)
+
+-------------------- deprecated
+gacks/
+gui/
+dummy/
+
+some apparently test-related stuff formerly in cmdline hace moved to tests/client
diff --git a/INSTALL.txt b/INSTALL.txt
new file mode 100644 (file)
index 0000000..d748883
--- /dev/null
@@ -0,0 +1,146 @@
+This installation note assumes that you have installed and configured MyPLC in the usual manner, and you have set up your yum repo like for a MyPLC install.
+
+Once you have completed the server configuration, a brief introduction to the SFA user tools is here:
+    http://svn.planet-lab.org/wiki/SFAGuide
+
+-----
+1) Install the SFA packages:
+
+# yum install sfa-plc sfa-client
+
+Note that the above command installs both sfa server (sfa-plc) and sfa client packages along with necessary dependency packages. Depending on the requirements, you may choose to install the appropriate one (server, client or both) for you. For e.g. to set up your own SFA server on top of your MyPLC, you need sfa-plc. On the other hand, if you plan to use an existing SFA server, you would typically need the sfa-client only.
+-------
+2) Note down the PLC_ROOT_USER, PLC_ROOT_PASSWORD, PLC_DB_USER and PLC_DB_PASSWORD of your MyPLC installation:
+
+# plc-config-tty 
+Enter command (u for usual changes, w to save, ? for help) s PLC_ROOT_USER
+PLC_ROOT_USER = root@test.onelab.eu
+Enter command (u for usual changes, w to save, ? for help) s PLC_ROOT_PASSWORD
+PLC_ROOT_PASSWORD = test++
+Enter command (u for usual changes, w to save, ? for help) s PLC_DB_USER
+PLC_DB_USER = pgsqluser
+Enter command (u for usual changes, w to save, ? for help) s PLC_DB_PASSWORD
+PLC_DB_PASSWORD = 4a333aba-a322-41b1-9c05-90b4f34d1332
+
+These four configuration parameters are required for SFA server configuration
+-------
+3) Configure and start SFA servers: 
+
+(a) # sfa-config-tty
+
+ set SFA_PLC_USER              to     PLC_ROOT_USER 
+ set SFA_PLC_PASSWORD          to     PLC_ROOT_PASSWORD
+ set SFA_PLC_DB_USER                   to     PLC_DB_USER
+ set SFA_PLC_DB_PASSWORD       to     PLC_DB_PASSWORD
+write and quit
+
+*NOTE* at this point you get a warning b/c the authorities hierarchy has not been created yet; ignore
+
+(b) # sfa-import-plc.py 
+
+(o/p will look like this)
+Import: creating top level authorities
+Hierarchy: creating authority: plc
+plc : 
+Import: creating table for authority plc
+NOTICE:  table "sfa$plc" does not exist, skipping
+  inserting authority record for plc
+Import: adding plc to trusted list
+Import_Site: importing site plc.internet2
+Hierarchy: creating authority: plc.internet2
+  inserting authority record for plc.internet2
+Import_Site: importing site plc.pl
+Hierarchy: creating authority: plc.pl
+  inserting authority record for plc.pl
+Import: importing person plc.pl.root
+Import: creating table for authority plc.pl
+NOTICE:  table "sfa$plc$pl" does not exist, skipping
+
+(c)  run sfa-config-tty again and select q to come out of the config command
+This will initialize /etc/sfa/authorities/server.key from /etc/sfa/authorities/plc/plc.pkey 
+
+(d) # service sfa start
+This will start Registry, Slice Manager and Aggregate Manager. Your ps command output would look like:
+
+# ps -ef | grep python
+root     24944     1  0 May11 ?        00:00:00 /usr/bin/python /usr/bin/sfa-server.py -r -d
+root     24957     1  0 May11 ?        00:00:00 /usr/bin/python /usr/bin/sfa-server.py -a -d
+root     24970     1  0 May11 ?        00:00:00 /usr/bin/python /usr/bin/sfa-server.py -s -d
+-------
+4) Configure SFA client:
+
+ (a) # mkdir ~/.sfi 
+ (b)copy your private RSA key to ~/.sfi/username.pkey Replace username with your actual account name.  NOTE: DSA KEYS WILL NOT WORK
+ (c) # cp  /etc/sfa/sfi_config ~/.sfi/
+ (d) edit ~/.sfi/sfi_config. A sample configuration looks like:
+
+       SFI_AUTH='plc.pl'
+       SFI_USER='plc.pl.root'
+       SFI_REGISTRY='http://vplc25.inria.fr:12345/'
+       SFI_SM='http://vplc25.inria.fr:12347/' 
+
+------
+5) Testing:
+
+At this stage you should be able to run sfi command. Some sample outputs are:
+
+ (a) # sfi.py list plc.pl
+     plc.pl.netflow (slice)
+     plc.pl.sirius (slice)
+     plc.pl.root (user)
+     plc.pl.pif (node)
+
+ (b) # sfi.py show plc.pl.pif
+     gid:
+          hrn: plc.pl.pif
+         uuid: 99878316891261700702442883738232624912
+     hrn: plc.pl.pif
+     type: node
+     node_type: regular
+     hostname: pif.inria.fr
+ (c) # sfi.py show plc.pl.root
+       gid:
+          hrn: plc.pl.root
+         uuid: 67306954103472941609600457537601239401
+     hrn: plc.pl.root
+     type: user
+     last_name: Administrator
+     phone: None
+     key: plc.pl.root#user
+     first_name: Default
+     email: root@vplc25.inria.fr
+
+ (d) # sfi.py slices
+     plc.pl.netflow
+     plc.pl.sirius
+
+ (e) # sfi.py resources
+<?xml version="1.0" ?>
+<RSpec duration="3600" start_time="1246736949"><networks><NetSpec duration="3600" name="plc" start_time="1246736950"><nodes><NodeSpec cpu_min="" cpu_pct="" cpu_share="" disk_max="" duration="" init_params="" name="pif.inria.fr" start_time="" type=""><net_if><IfSpec addr="138.96.250.224" init_params="" ip_spoof="" max_kbyte="" max_rate="" min_rate="" name="True" type="ipv4"/></net_if></NodeSpec></nodes></NetSpec></networks></RSpec>
+------
+
+6) Federation configuration:
+
+(a) Follow the same procedure to install and configure second MyPLC with SFA server.
+(b) On each PLC, edit the /etc/sfa/registries.xml file
+
+    set addr to ip or hostname of federated (remote) peer
+    set port to sfa service port on federated (remote) peer
+    set hrn to human readable name of federated (remote) peer interface
+Sample configuration:
+
+<registries>
+       <registry addr="vplc26.inria.fr" hrn="ple" port="12345"/>
+</registries>
+
+(c) Likewise, edit the /etc/sfa/aggregates.xml
+Sampel configuration
+
+<aggregates>
+       <aggregate addr="vplc26.inria.fr" hrn="ple" port="12346"/>
+</aggregates>
+
+(d) trade trusted root gid's (seen on /etc/sfa/trusted_roots)
+we have to copy the gid of first SFA server to the /etc/sfa/trusted_roots directory of second one and vice-versa. 
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..08f6d5d
--- /dev/null
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Board of Trustees, Princeton University
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and/or hardware specification (the “Work”) to
+deal in the Work without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Work, and to permit persons to whom the Work
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Work.
+
+THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
+IN THE WORK.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a1bd00a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,94 @@
+#
+## (Re)builds Python metafile (__init__.py) and documentation
+# 
+# overwritten by the specfile
+DESTDIR="/"
+
+##########
+all: python wsdl
+
+install: python-install wsdl-install xmlbuilder-install 
+
+clean: python-clean wsdl-clean
+
+uninstall: python-uninstall
+
+.PHONY: all install clean 
+
+##########
+python: 
+
+xmlbuilder-install:
+       cd xmlbuilder-0.9 && python setup.py install --root=$(DESTDIR) && cd -
+python-install:
+       python setup.py install --root=$(DESTDIR)       
+       chmod 444 $(DESTDIR)/etc/sfa/default_config.xml
+
+python-clean:
+       python setup.py clean
+       rm $(init)
+
+.PHONY: python python-install python-clean xmlbuilder-install
+##########
+wsdl: 
+       $(MAKE) -C wsdl 
+
+# propagate DESTDIR from the specfile
+wsdl-install:
+       $(MAKE) -C wsdl install 
+
+wsdl-clean:
+       $(MAKE) -C wsdl clean
+
+.PHONY: wsdl wsdl-install wsdl-clean
+##########
+
+# are the .java files used ?
+tags:  
+       find . -type f | egrep -v '/\.svn/|TAGS|\.py[co]$$|\.doc$$|\.html$$|\.pdf$$' | xargs etags
+.PHONY: tags
+
+
+########## sync
+# 2 forms are supported
+# (*) if your plc root context has direct ssh access:
+# make sync PLC=private.one-lab.org
+# (*) otherwise, entering through the root context
+# make sync PLCHOST=testbox1.inria.fr GUEST=vplc03.inria.fr
+
+ifdef GUEST
+ifdef PLCHOST
+SSHURL:=root@$(PLCHOST):/vservers/$(GUEST)
+SSHCOMMAND:=ssh root@$(PLCHOST) vserver $(GUEST)
+endif
+endif
+ifdef PLC
+SSHURL:=root@$(PLC):/
+SSHCOMMAND:=ssh root@$(PLC)
+endif
+
+LOCAL_RSYNC_EXCLUDES   := --exclude '*.pyc' 
+RSYNC_EXCLUDES         := --exclude .svn --exclude CVS --exclude '*~' --exclude TAGS $(LOCAL_RSYNC_EXCLUDES)
+RSYNC_COND_DRY_RUN     := $(if $(findstring n,$(MAKEFLAGS)),--dry-run,)
+RSYNC                  := rsync -a -v $(RSYNC_COND_DRY_RUN) $(RSYNC_EXCLUDES)
+
+BINS = ./config/sfa-config-tty ./config/gen-sfa-cm-config.py \
+       ./sfa/plc/sfa-import-plc.py ./sfa/plc/sfa-nuke-plc.py ./sfa/server/sfa-server.py \
+       ./sfa/client/sfi.py ./sfa/client/getNodes.py ./sfa/client/getRecord.py \
+       ./sfa/client/setRecord.py ./sfa/client/sfadump.py
+
+sync:
+ifeq (,$(SSHURL))
+       @echo "sync: You must define, either PLC, or PLCHOST & GUEST, on the command line"
+       @echo "  e.g. make sync PLC=private.one-lab.org"
+       @echo "  or   make sync PLCHOST=testbox1.inria.fr GUEST=vplc03.inria.fr"
+       @exit 1
+else
+       +$(RSYNC) ./sfa/ $(SSHURL)/usr/lib/python2.5/site-packages/sfa/
+       +$(RSYNC)  $(BINS) $(SSHURL)/usr/bin
+       $(SSHCOMMAND) exec service sfa restart
+endif
+
+.PHONY: sync
+##########
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..42255d4
--- /dev/null
@@ -0,0 +1,7 @@
+This module (sfa) is installed as part of MyPLC, available as
+part of the PlanetLab development effort. For more information on
+accessing, installing, developing and deploying PlanetLab software,
+visit http://svn.planet-lab.org.
+
+For a brief introduction to the SFA user tools, please visit:
+    http://svn.planet-lab.org/wiki/SFAGuide
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..b5628f4
--- /dev/null
+++ b/TODO
@@ -0,0 +1,41 @@
+- Build/Tags
+* test rpm build/install
+
+- Stop invalid users
+* a recently disabled/deleted user may still have a valid cred. Keep a list of valid/invalid users on the aggregate and check callers against this list
+
+- NM Plugin
+  * install the slice and node gid when the slice is created (create NM plugin to execute sfa_component_setup.py ?) 
+  * test
+- Component manager
+  * install trusted certs when interface starts (component_manager_pl.init_server())
+  * Redeem ticket - RedeemTicket/AdminTicket not working. Why?
+  ** This may be replaced by sfa + credentials  
+
+- Registry
+* fix legacy credential support
+* move db tables into db with less overhead (tokyocabinet?)
+
+- GUI/Auth Service
+  * develop a simple service where users auth using username/passord and 
+    receive their cred
+  * service manages users key/cert,creds
+  * gui requires user's cred (depends on Auth Service above)
+      
+- Protogeni
+* agree on standard set of privs
+* on permission error, return priv needed to make call
+* cache slice resource states (if aggregate goes down, how do we know what
+  slices were on it and recreate them? do we make some sort of transaction log)
+
+Questions
+=========
+- SM/Aggregate
+* should the rspec contain only the resources a slice is using or all resources availa and mark what the slice is using.
+
+-  Initscripts on sfa / geniwrapper
+* should sfa have native initscript support or should we piggyback off of myplc?
+* should this be in the rspec
+
+
diff --git a/config/aggregates.xml b/config/aggregates.xml
new file mode 100644 (file)
index 0000000..4630736
--- /dev/null
@@ -0,0 +1,3 @@
+<aggregates>
+       <aggregate addr="" hrn="" port=""/>
+</aggregates>
diff --git a/config/default_config.xml b/config/default_config.xml
new file mode 100644 (file)
index 0000000..cfe76c2
--- /dev/null
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Default SFA configuration file
+
+Thierry Parmentelat 
+
+$Id: default_config.xml 18190 2010-06-03 20:12:08Z tmack $
+$URL: http://svn.planet-lab.org/svn/sfa/trunk/config/default_config.xml $
+-->
+
+<!DOCTYPE configuration PUBLIC "-//PlanetLab Central//DTD PLC configuration//EN" "plc_config.dtd">
+
+<configuration>
+  <variables>
+
+    <!-- ======================================== -->
+    <category id="sfa">
+      <name>General</name>
+      <description>Basic system variables.</description>
+
+      <variablelist>
+       <variable id="interface_hrn" type="string">
+         <name>Human readable name</name>
+         <value>plc</value>
+         <description>The human readable name for this interface.</description>
+       </variable>
+
+       <variable id="api_debug" type="boolean">
+         <name>Debug</name>
+         <value>false</value>
+         <description>Flag to turn debug on.</description>
+       </variable>
+
+      </variablelist>
+    </category>
+
+    <!-- ======================================== -->
+    <category id="sfa_registry">
+      <name>Registry</name>
+      <description>The settings that affect the registry that will run
+      as part of this SFA instance.</description>
+
+      <variablelist>
+       <variable id="enabled" type="boolean">
+         <name>Enable Registry</name>
+         <value>true</value>
+         <description>Allows this local SFA instance to run as a
+         registry.</description>
+       </variable>
+
+       <variable id="type" type="string">
+         <name>Registry type</name>
+         <value>pl</value>
+         <description>The type of backend server for this
+         registry. Some registries may not be myplc.</description>
+       </variable>
+
+       <variable id="host" type="hostname">
+         <name>Hostname</name>
+         <value>localhost</value>
+         <description>The hostname where the registry is expected to
+         be found; using localhost when the local registry is enabled
+         seems reasonable.</description>
+       </variable>
+
+       <variable id="port" type="int">
+         <name>Port number</name>
+         <value>12345</value>
+         <description>The port where the registry is to be found.</description>
+       </variable>
+
+       <variable id="root_auth" type="string">
+         <name>Root Authority</name>
+         <value>plc</value>
+         <description>The hrn of the registry's root auth.</description>
+       </variable>
+
+    </variablelist>
+    </category>
+
+    <!-- ======================================== -->
+    <category id="sfa_aggregate">
+      <name>Aggregate</name>
+      <description>The settings that affect the aggregate manager that will run
+      as part of this SFA instance.</description>
+
+      <variablelist>
+       <variable id="enabled" type="boolean">
+         <name>Enable Aggregate</name>
+         <value>true</value>
+         <description>Allows this local SFA instance to run as an
+         aggregate manager.</description>
+       </variable>
+
+       <variable id="type" type="string">
+         <name>Aggregate type</name>
+         <value>pl</value>
+         <description>The type of backend server for this
+         aggregate. Some aggregates may not be myplc.</description>
+       </variable>
+    
+    <variable id="rspec_schema" type="string">
+      <name>RSpec Schema</name>
+      <value>/etc/sfa/pl.rng</value>
+      <description>The path to the default schema</description>
+    </variable>
+
+       <variable id="host" type="hostname">
+         <name>Hostname</name>
+         <value>localhost</value>
+         <description>The hostname where the aggregate is expected to
+         be found.</description>
+       </variable>
+
+       <variable id="port" type="int">
+         <name>Port number</name>
+         <value>12346</value>
+         <description>The port where the aggregate is to be found.</description>
+       </variable>
+      </variablelist>
+
+    </category>
+    <!-- ======================================== -->
+    <category id="sfa_geni_aggregate">
+      <name>GENI Aggregate</name>
+      <description>The settings that affect the geni aggregate manager that will run
+      as part of this SFA instance.</description>
+
+      <variablelist>
+       <variable id="enabled" type="boolean">
+         <name>Enable GENI Aggregate</name>
+         <value>true</value>
+         <description>Allows this local SFA instance to run as an
+         GENI aggregate manager.</description>
+       </variable>
+
+       <variable id="type" type="string">
+         <name>GENI Aggregate type</name>
+         <value>pl</value>
+         <description>The type of backend server for this
+         aggregate. Some aggregates may not be myplc.</description>
+       </variable>
+
+       <variable id="host" type="hostname">
+         <name>Hostname</name>
+         <value>localhost</value>
+         <description>The hostname where the aggregate is expected to
+         be found.</description>
+       </variable>
+
+       <variable id="port" type="int">
+         <name>Port number</name>
+         <value>12348</value>
+         <description>The port where the aggregate is to be found.</description>
+       </variable>
+      </variablelist>
+
+    </category>
+
+    <!-- ======================================== -->
+    <category id="sfa_sm">
+      <name>Slice Manager</name>
+      <description>The settings that affect the slice manager that will run
+      as part of this SFA instance.</description>
+
+      <variablelist>
+       <variable id="enabled" type="boolean">
+         <name>Enable Slice Manager</name>
+         <value>true</value>
+         <description>Allows this local SFA instance to run as a
+         slice manager.</description>
+       </variable>
+
+       <variable id="type" type="string">
+         <name>Slice Manager type</name>
+         <value>pl</value>
+         <description>The type of backend server for this
+         slice manager. Not all slice managers are myplc.</description>
+       </variable>
+
+       <variable id="host" type="hostname">
+         <name>Hostname</name>
+         <value>localhost</value>
+         <description>The hostname where the slice manager is expected to
+         be found.</description>
+       </variable>
+
+       <variable id="port" type="int">
+         <name>Port number</name>
+         <value>12347</value>
+         <description>The port where the slice manager is to be found.</description>
+       </variable>
+      </variablelist>
+    </category>
+
+    <!-- ======================================== -->
+    <category id="sfa_plc">
+      <name></name>
+      <description>The settings that tell this SFA instance how to interact with the underlying PLC. Refer to plc-config-tty on this installation for more information.</description>
+
+      <variablelist>
+       <variable id="user" type="string">
+         <name>PLC login name for an admin user; SFA will carry on operations under this account.</name>
+         <value>root@localhost.localdomain</value>
+         <description></description>
+       </variable>
+
+       <variable id="password" type="string">
+         <name>Password</name>
+         <value>root</value>
+         <description>The PLC password for SFA_PLC_USER.</description>
+       </variable>
+
+       <variable id="url" type="string">
+         <name>URL</name>
+         <value>https://localhost:443/PLCAPI/</value>
+         <description>Full URL of PLC interface.</description>
+       </variable>
+
+       <variable id="db_name" type="string">
+         <name>Database name</name>
+         <value>planetlab5</value>
+         <description>Planetlab database name.</description>
+       </variable>
+
+       <variable id="db_host" type="hostname">
+         <name>Datbase host</name>
+         <value>localhost</value>
+         <description>The host where the PLC database can be reached.</description>
+       </variable>
+
+       <variable id="db_port" type="int">
+         <name>Database port</name>
+         <value>5432</value>
+         <description>The port where the PLC database can be reached.</description>
+       </variable>
+
+       <variable id="db_user" type="string">
+         <name>Database user</name>
+         <value>pgsqluser</value>
+         <description></description>
+       </variable>
+
+       <variable id="db_password" type="string">
+         <name>Database password</name>
+         <value></value>
+         <description>The password for PLC_DB_USER.</description>
+       </variable>
+
+      </variablelist>
+    </category>
+
+  </variables>
+
+  <comps>
+    <!-- deprecated - not used anymore - use .lst files instead -->
+  </comps>
+
+</configuration>
diff --git a/config/gen-sfa-cm-config.py b/config/gen-sfa-cm-config.py
new file mode 100644 (file)
index 0000000..f8a0f70
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+import os
+import sys
+import socket
+sys.path.append('/usr/share/plc_api')
+from sfa.util.config import Config as SfaConfig
+from PLC.Config import Config as PlcConfig
+
+sfa_config = SfaConfig()
+plc_config = PlcConfig()
+default_host = socket.gethostbyname(socket.gethostname())
+all_vars = ['SFA_CONFIG_DIR', 'SFA_DATA_DIR', 'SFA_INTERFACE_HRN',
+            'SFA_CM_SLICE_PREFIX', 'SFA_REGISTRY_HOST', 'SFA_REGISTRY_PORT', 
+            'SFA_AGGREGATE_HOST', 'SFA_AGGREGATE_PORT', 
+            'SFA_SM_HOST', 'SFA_SM_PORT',
+            'SFA_CM_ENABLED', 'SFA_CM_HOST', 'SFA_CM_PORT', 'SFA_CM_TYPE', 'SFA_CM_SLICE_PREFIX',
+            'SFA_API_DEBUG']
+
+defaults = {
+    'SFA_CM_ENABLED': '1',
+    'SFA_CM_HOST': 'localhost',
+    'SFA_CM_PORT': '12346',
+    'SFA_CM_SLICE_PREFIX': plc_config.PLC_SLICE_PREFIX,
+    'SFA_CM_TYPE': 'pl',
+    'SFA_API_DEBUG': '0'
+    }
+
+host_defaults = {
+    'SFA_REGISTRY_HOST': default_host,
+    'SFA_AGGREGATE_HOST': default_host,
+    'SFA_SM_HOST': default_host,    
+    }
+     
+const_dict = {}
+for key in all_vars:
+    value = ""
+    
+           
+    if key in defaults:
+        value = defaults[key]
+    elif hasattr(sfa_config, key):
+        value = getattr(sfa_config, key)
+        # sfa_config may specify localhost instead of a resolvalbe host or ip
+        # if so replace this with the host's address
+        if key in host_defaults and value in ['localhost', '127.0.0.1']:
+            value = host_defaults[key] 
+    const_dict[key] = value
+
+filename = sfa_config.config_path + os.sep + 'sfa_component_config'
+conffile = open(filename, 'w')
+format='%s="%s"\n'
+
+for var in all_vars:
+    conffile.write(format % (var, const_dict[var]))
+
+conffile.close() 
+    
+
diff --git a/config/geni_aggregates.xml b/config/geni_aggregates.xml
new file mode 100644 (file)
index 0000000..e744134
--- /dev/null
@@ -0,0 +1,3 @@
+<aggregates>
+       <aggregate addr="" hrn="" port="" url=""/>
+</aggregates>
diff --git a/config/registries.xml b/config/registries.xml
new file mode 100644 (file)
index 0000000..7996781
--- /dev/null
@@ -0,0 +1,3 @@
+<registries>
+       <registry addr="" hrn="" port=""/>
+</registries>
diff --git a/config/sfa-config-tty b/config/sfa-config-tty
new file mode 100755 (executable)
index 0000000..9b2c439
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/env python
+
+import sys
+import readline
+import plc_config
+
+def validator(validated_variables):
+    pass
+#    maint_user = validated_variables["PLC_API_MAINTENANCE_USER"]
+#    root_user = validated_variables["PLC_ROOT_USER"]
+#    if maint_user == root_user:
+#        errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user,root_user)
+#        raise plc_config.ConfigurationException(errStr)
+
+usual_variables = [
+    "SFA_INTERFACE_HRN",
+    "SFA_REGISTRY_ROOT_AUTH",
+    "SFA_REGISTRY_HOST", 
+    "SFA_AGGREGATE_HOST",
+    "SFA_SM_HOST",
+    "SFA_PLC_USER",
+    "SFA_PLC_PASSWORD",
+    "SFA_PLC_DB_HOST",
+    "SFA_PLC_DB_USER",
+    "SFA_PLC_DB_PASSWORD",
+    "SFA_PLC_URL",
+    ]
+
+configuration={ \
+    'name':'sfa',
+    'service':"sfa",
+    'usual_variables':usual_variables,
+    'config_dir':"/etc/sfa",
+    'validate_variables':{},
+    'validator':validator,
+    }
+
+if __name__ == '__main__':
+    command=sys.argv[0]
+    argv = sys.argv[1:]
+    plc_config.main(command,argv,configuration)
diff --git a/config/sfa_component_config b/config/sfa_component_config
new file mode 100644 (file)
index 0000000..137405e
--- /dev/null
@@ -0,0 +1,116 @@
+# SFA Config file for the Component Manager
+#
+
+# HRN
+# Human readable name for this interface
+SFA_INTERFACE_HRN="plc"   
+
+# API Debug
+# Are we logging
+SFA_API_DEBUG=1
+
+## ============================================================
+# Registry Configuration
+#
+# Enabled
+# Enable the registry interface 
+SFA_REGISTRY_ENABLED=0
+
+#
+# The type of backend server for this registry
+# Some registries may not be myplc 
+SFA_REGISTRY_TYPE='pl'
+
+# Root Auth
+# The hrn of the registry's root auth
+SFA_REGISTRY_ROOT_AUTH="plc"
+
+# Level1 Auth
+# The hrn of the registry's level1 auth (sub authority)
+# The full name of this interface (only secify if this 
+# interface is a sub authority)
+# xxx could be determined from hrn above
+SFA_REGISTRY_LEVEL1_AUTH=""
+
+# Hostname
+# The fully qualified hostname of the registry server
+SFA_REGISTRY_HOST="localhost"
+
+# Port
+# SFA registry port
+SFA_REGISTRY_PORT=12345
+
+## ==============================================================
+## Aggregate Configuration
+##
+## Enable aggregate inteface
+## Enable the aggregate inteface.
+SFA_AGGREGATE_ENABLED=0
+
+# Aggregate Type
+#
+# The type of backend server for this aggregate
+# Some aggregates may not be myplc 
+SFA_AGGREGATE_TYPE='pl'
+
+#
+#
+## Hostname
+## The fully qualified hostname of the aggregate server
+SFA_AGGREGATE_HOST="localhost"
+#
+## Port
+## SFA aggregate server port
+SFA_AGGREGATE_PORT=12346
+
+## ==============================================================
+# Slice Manager Configuration
+#
+# Enabled
+# Enable the slice manager  
+SFA_SM_ENABLED=0
+
+# Slice Manager type
+#
+## The type of backend server for this slice manager
+## The slice manager doesnt rely on a specific backend server so 
+## you probably will never need to change this type unless you 
+## need to reimplement the slice manager
+SFA_SM_TYPE='pl' 
+
+# Host
+## The fully qualified hostname or IP address of the slice manager server
+SFA_SM_HOST="localhost"
+
+# Port
+# SFA slice manager server port  
+SFA_SM_PORT=12347
+
+## ===============================================================
+# Component Manager Configuration
+#
+# Enabled
+## Enable the slice manager
+SFA_CM_ENABLED=1
+
+# Component Manager type
+#
+# The type of backend server for this component manager
+SFA_CM_TYPE='pl'
+#
+# Host
+## The fully qualified hostname or IP address of the slice manager server
+SFA_CM_HOST="localhost"
+#
+# Port
+# SFA slice manager server port
+SFA_CM_PORT=12346
+
+
+# Directory internal data gets stored
+# using /etc/sfa until gec5 but /var/lib/sfa would be a more reasonable choice
+SFA_CONFIG_DIR="/etc/sfa"
+
+# Directory internal data gets stored
+SFA_DATA_DIR="/var/lib/sfa"
+
diff --git a/config/sfi_config b/config/sfi_config
new file mode 100644 (file)
index 0000000..3314790
--- /dev/null
@@ -0,0 +1,24 @@
+# user-level configuration for sfi
+
+### hrn of your authority
+# For instance, the hrn of a user at the Princeton site on PlanetLab (PLC)
+# would be plc.princeton.  Likewise if the user was part of the NYU site, the HRN
+# would be plc.nyu
+SFI_AUTH='plc.princeton'
+
+### your user hrn
+# This is your authority.<your user name on PlanetLab>
+SFI_USER='plc.princeton.faiyaza'
+#
+
+### where to find the registry
+SFI_REGISTRY='http://www.planet-lab.org:12345/'
+
+# where to find the slice manager
+SFI_SM='http://www.planet-lab.org:12347/'
+
+# where to find the geni aggregate manager
+# XX this should be handled by the slice manger
+# XX but we cant support it until the geni_am and sfa 
+# XX interfaces has been unified
+SFI_GENI_AM='http://www.planet-lab.org:12348'   
diff --git a/docs/Geniwrapper_Design_Docu.html b/docs/Geniwrapper_Design_Docu.html
new file mode 100644 (file)
index 0000000..8e8eefe
--- /dev/null
@@ -0,0 +1,1890 @@
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+<base target="_top">
+
+<style type="text/css">
+
+
+/* default css */
+
+table {
+  font-size: 1em;
+  line-height: inherit;
+}
+
+
+tr {
+  
+  text-align: left;
+  
+}
+
+
+div, address, ol, ul, li, option, select { 
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+p {
+  margin: 0px;
+}
+
+body {
+  margin: 6px;
+  padding: 0px;
+  font-family: Verdana, sans-serif;
+  font-size: 10pt;
+  background-color: #ffffff;
+}
+
+
+img {
+  -moz-force-broken-image-icon: 1;
+}
+
+@media screen {
+  html.pageview {
+    background-color: #f3f3f3 !important;
+  }
+
+  
+    
+  body { 
+    min-height: 1100px; 
+  }
+  * html body { 
+    height: 1100px; 
+  }
+  .pageview body {
+    border-top: 1px solid #ccc;
+    border-left: 1px solid #ccc;
+    border-right: 2px solid #bbb;
+    border-bottom: 2px solid #bbb;
+    width: 648px !important;
+    margin: 15px auto 25px;
+    padding: 40px 50px; 
+  }
+  /* IE6 */
+  * html {
+    overflow-y: scroll;
+  }
+  * html.pageview body {
+    overflow-x: auto;
+  }
+  /* Prevent repaint errors when scrolling in Safari. This "Star-7" css hack
+     targets Safari 3.1, but not WebKit nightlies and presumably Safari 4.
+     That's OK because this bug is fixed in WebKit nightlies/Safari 4 :-). */
+  html*#wys_frame::before {
+    content: '\A0';
+    position: fixed;
+    overflow: hidden;
+    width: 0;
+    height: 0;
+    top: 0;
+    left: 0;
+  }
+  
+  
+
+  
+    .writely-callout-data {
+      display: none;
+    }
+    .writely-footnote-marker {
+      background-image: url('MISSING');
+      background-color: transparent;
+      background-repeat: no-repeat;
+      width: 7px;
+      overflow: hidden;
+      height: 16px;
+      vertical-align: top;
+    }
+    .editor .writely-footnote-marker {
+      cursor: move;
+    }
+    .writely-footnote-marker-highlight {
+      background-position: -15px 0;
+    }
+    .writely-footnote-hide-selection ::-moz-selection, .writely-footnote-hide-selection::-moz-selection {
+      background: transparent;
+    }
+    .writely-footnote-hide-selection ::selection, .writely-footnote-hide-selection::selection {
+      background: transparent;
+    }
+    .writely-footnote-hide-selection {
+      cursor: move;
+    }
+
+    
+    .editor .writely-comment-yellow {
+      background-color: #FF9;
+      background-position: -240px 0;
+    }
+    .editor .writely-comment-yellow-hover {
+      background-color: #FF0;
+      background-position: -224px 0;
+    }
+    .editor .writely-comment-blue {
+      background-color: #C0D3FF;
+      background-position: -16px 0;
+    }
+    .editor .writely-comment-blue-hover {
+      background-color: #6292FE;
+      background-position: 0 0;
+    }
+    .editor .writely-comment-orange {
+      background-color: #FFDEAD;
+      background-position: -80px 0;
+    }
+    .editor .writely-comment-orange-hover {
+      background-color: #F90;
+      background-position: -64px 0;
+    }
+    .editor .writely-comment-green {
+      background-color: #99FBB3;
+      background-position: -48px 0;
+    }
+    .editor .writely-comment-green-hover {
+      background-color: #00F442;
+      background-position: -32px 0;
+    }
+    .editor .writely-comment-cyan {
+      background-color: #CFF;
+      background-position: -208px 0;
+    }
+    .editor .writely-comment-cyan-hover {
+      background-color: #0FF;
+      background-position: -192px 0;
+    }
+    .editor .writely-comment-purple {
+      background-color: #EBCCFF;
+      background-position: -144px 0;
+    }
+    .editor .writely-comment-purple-hover {
+      background-color: #90F;
+      background-position: -128px 0;
+    }
+    .editor .writely-comment-magenta {
+      background-color: #FCF;
+      background-position: -112px 0;
+    }
+    .editor .writely-comment-magenta-hover {
+      background-color: #F0F;
+      background-position: -96px 0;
+    }
+    .editor .writely-comment-red {
+      background-color: #FFCACA;
+      background-position: -176px 0;
+    }
+    .editor .writely-comment-red-hover {
+      background-color: #FF7A7A;
+      background-position: -160px 0;
+    }
+    
+    .editor .writely-comment-marker {
+      background-image: url('MISSING');
+      background-color: transparent;
+      padding-right: 11px;
+      background-repeat: no-repeat;
+      width: 16px;
+      height: 16px;
+      -moz-user-select: none;
+    }
+
+    .editor .writely-comment-hidden {
+      padding: 0;
+      background: none;
+    }
+    .editor .writely-comment-marker-hidden {
+      background: none;
+      padding: 0;
+      width: 0;
+    }
+    .editor .writely-comment-none {
+      opacity: .2;
+      filter:progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+      -moz-opacity: .2;
+    }
+    .editor .writely-comment-none-hover {
+      opacity: .2;
+      filter:progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+      -moz-opacity: .2;
+    }
+  
+
+
+  
+  .br_fix br:not(:-moz-last-node):not(:-moz-first-node) {
+    
+    position:relative;
+    
+    left: -1ex
+    
+  }
+  
+  .br_fix br+br {
+    position: static !important
+  }
+}
+
+h6 { font-size: 8pt }
+h5 { font-size: 8pt }
+h4 { font-size: 10pt }
+h3 { font-size: 12pt }
+h2 { font-size: 14pt }
+h1 { font-size: 18pt }
+
+blockquote {padding: 10px; border: 1px #DDD dashed }
+
+a img {border: 0}
+
+.pb {
+  border-width: 0;
+  page-break-after: always;
+  /* We don't want this to be resizeable, so enforce a width and height
+     using !important */
+  height: 1px !important;
+  width: 100% !important;
+}
+
+.editor .pb {
+  border-top: 1px dashed #C0C0C0;
+  border-bottom: 1px dashed #C0C0C0;
+}
+
+div.google_header, div.google_footer {
+  position: relative;
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+
+
+/* Table of contents */
+.editor div.writely-toc {
+  background-color: #f3f3f3;
+  border: 1px solid #ccc;
+}
+.writely-toc > ol {
+  padding-left: 3em;
+  font-weight: bold;
+}
+ol.writely-toc-subheading {
+  padding-left: 1em;
+  font-weight: normal;
+}
+/* IE6 only */
+* html writely-toc ol {
+  list-style-position: inside;
+}
+.writely-toc-none {
+  list-style-type: none;
+}
+.writely-toc-decimal {
+  list-style-type: decimal;
+}
+.writely-toc-upper-alpha {
+  list-style-type: upper-alpha;
+}
+.writely-toc-lower-alpha {
+  list-style-type: lower-alpha;
+}
+.writely-toc-upper-roman {
+  list-style-type: upper-roman;
+}
+.writely-toc-lower-roman {
+  list-style-type: lower-roman;
+}
+.writely-toc-disc {
+  list-style-type: disc;
+}
+
+/* end default css */
+
+
+  /* default print css */
+  
+  @media print {
+    body { 
+      padding: 0; 
+      margin: 0; 
+    }
+
+    div.google_header, div.google_footer {
+      display: block;
+      min-height: 0;
+      border: none;
+    }
+
+    div.google_header {
+      flow: static(header);
+    }
+
+    /* used to insert page numbers */
+    div.google_header::before, div.google_footer::before {
+      position: absolute;
+      top: 0;
+    }
+
+    div.google_footer {
+      flow: static(footer);
+    }
+
+    /* always consider this element at the start of the doc */
+    div#google_footer {
+      flow: static(footer, start);
+    }
+
+    span.google_pagenumber {
+      content: counter(page);
+    }
+
+    span.google_pagecount {
+      content: counter(pages);
+    }
+
+
+    callout.google_footnote {
+      
+      display: prince-footnote;
+      footnote-style-position: inside;
+      /* These styles keep the footnote from taking on the style of the text
+         surrounding the footnote marker. They can be overridden in the
+         document CSS. */
+      color: #000;
+      font-family: Verdana;
+      font-size: 10.0pt;
+      font-weight: normal;
+    }
+
+    /* Table of contents */
+    #WritelyTableOfContents a::after {
+      content: leader('.') target-counter(attr(href), page);
+    }
+
+    #WritelyTableOfContents a {
+      text-decoration: none;
+      color: black;
+    }
+  }
+
+  @page {
+    @top {
+      content: flow(header);
+    }
+    @bottom {
+      content: flow(footer);
+    }
+    @footnotes {
+      border-top: solid black thin;
+      padding-top: 8pt;
+    }
+  }
+  /* end default print css */
+
+/* custom css */
+
+
+/* end custom css */
+
+
+
+  /* ui edited css */
+  
+  body {
+    font-family: Verdana;
+    
+    font-size: 10.0pt;
+    line-height: normal;
+    background-color: #ffffff;
+  }
+  /* end ui edited css */
+
+
+
+/* editor CSS */
+.editor a:visited {color: #551A8B}
+.editor table.zeroBorder {border: 1px dotted gray}
+.editor table.zeroBorder td {border: 1px dotted gray}
+.editor table.zeroBorder th {border: 1px dotted gray}
+
+
+.editor div.google_header, .editor div.google_footer {
+  border: 2px #DDDDDD dashed;
+  position: static;
+  width: 100%;
+  min-height: 2em;
+}
+
+.editor .misspell {background-color: yellow}
+
+.editor .writely-comment {
+  font-size: 9pt; 
+  line-height: 1.4; 
+  padding: 1px; 
+  border: 1px dashed #C0C0C0
+}
+
+
+/* end editor CSS */
+</style>
+
+
+</head>
+
+<body onload="DoPageLoad();"
+    
+    revision="dhkdd78p_13kvrgbnfb:273">
+
+    
+    
+    
+<div>\r
+  <div>\r
+    <b>Geniwrapper Design Document</b>\r
+  </div>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>1.0 High Level Overview</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The purpose of Geniwrapper is to provide a Geni-like interface around the\r
+  existing planetlab infrastructure. The existing infrastructure consists of two\r
+  parts: planetlab central (PLC) and planetlab nodes. Geni defines three \r
+  interfaces: Registry, Slice, and Component Management.&nbsp; A part of the wrapper is co-located with PLC (it exports the Registry and Slice interfaces),\r
+  and a part of the wrapper is co-located with each node (it exports the Slice\r
+  and Management interfaces).
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniwrapper is comprised of the following logical modules: <i>utility\r
+  classes</i>,&nbsp;<i>plc wrapper</i>, <i>component wrapper</i>, and <i>command\r
+  line client</i>. Client-server communication uses a variant of XML-RPC called\r
+  the <i>Geni protocol</i>. Section 1 of this document presents a very brief\r
+  overview of each module. In-depth discussion occurs later.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Much of this design adheres to the SFA, and as such this document avoids\r
+  duplication of the information already presented in the SFA. For example, the\r
+  description of privileges, which operations are allowed by a specific\r
+  privileges, and how privileges are assigned to principals is described fully\r
+  in the SFA and is therefore not duplicated here.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  NOTE: <b>API documentation</b> is extracted from code comments automatically\r
+  and is maintained in separate files, one documentation file corresponding to\r
+  each python source file. An effort has been made to keep API documentation\r
+  separate from this document, so that the API documentation may be\r
+  self-maintaining as the code is updated.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniwrapper is checked into a subversion repository at\r
+  <a href=http://svn.planetlab.org/geniwrapper>http://svn.planetlab.org/geniwrapper</a>.\r
+  [TODO: verify link]\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <div>\r
+    <b>1.1 Utility classes</b>\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    Utility classes include python classes that implement certificates, GIDs,\r
+    credentials, and tickets. There are also utility classes for implementing\r
+    the server and client stubs and the security protocol. The utility modules\r
+    are designed to be generally re-usable. For example, the credential\r
+    management class may be used as part of the Geni Registry, Geni Components,\r
+    and the end-user tools that interact with Geni.\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    The&nbsp;utility classes&nbsp;are located in the\r
+    <i>util</i>&nbsp;subdirectory.\r
+  </div>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>1.2&nbsp;PLC wrapper</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The&nbsp;plc wrapper is intended to be colocated with PLC. All communication\r
+  between the&nbsp;plc wrapper and PLC uses the PLCAPI interface and as such,\r
+  the&nbsp;plc wrapper can be run on a separate machine for ease of development.\r
+  In addition to the Geni registry operations (register, update, ...),\r
+  the&nbsp;plc wrapper also implements slice operations, such as GetTicket.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The&nbsp;plc wrapper is located in the&nbsp;<i>plc</i> subdirectory.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  TODO: Most of the slice interface is yet to be implemented in&nbsp;plc wrapper.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>1.3&nbsp;The component wrapper</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The component wrapper is located on planetlab nodes. It implements\r
+  the&nbsp;slice interface, and the component management interface. Due to SFA\r
+  engineering decisions, some&nbsp;slice operations (i.e. GetTicket) are\r
+  not supported in the&nbsp;component wrapper.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The component wrapper is located in the <i>component</i> subdirectory.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>1.4 Command line client</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The command line client exports a client interface to Geni that may be used\r
+  for testing and demonstration purposes. It allows easy invocation of Geni api\r
+  functions and dumps the results in a human-readable format.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The command line client&nbsp;is located in the&nbsp;<i>cmdline</i>\r
+  subdirectory\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>1.5 Geni Protocol</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The Geni protocol is based on XML-RPC. It is implemented primarily in the\r
+  geniserver.py and geniclient.py files located with the utility classes.\r
+  Modifications to the XML-RPC protocol include the following:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<ol>\r
+  <li>\r
+    The transport mechanism uses HTTPS instead of HTTP.\r
+  </li>\r
+  <li>\r
+    HTTPS certificate verification is disabled so that custom Geni verification\r
+    based on GID can be done instead.\r
+  </li>\r
+  <li>\r
+    When an exception occurs on the server, verbose exception information is\r
+    sent to the client, to assist debugging efforts\r
+  </li>\r
+</ol>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Authentication:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Authentication of the client by the server is done by using Credentials/GIDs.\r
+  Generally, each operation contains a credential as the first argument. This\r
+  credential includes the GID of the caller, which in turn contains the public\r
+  key of the caller. The server ensures that this public key matches the public\r
+  key that is being used to decrypt the HTTPS connection, thus ensuring the\r
+  caller must posess the private key that corresponds to the GID.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Authentication of the server by the client is left as an exercise for the\r
+  client. It may be done easily by specifying the server's public key when the\r
+  client create the HTTPS connection. This presumes the client knows the public\r
+  key (or GID) of the server he is trying to connect to.\r
+</div>\r
+<div>\r
+  &nbsp;<br>\r
+  <b>1.6 Extending Geniwrapper to other environments</b><br>\r
+  <br>\r
+  The PLC Wrapper and Component Wrapper are implemented to work on top of the\r
+  Planetlab environment, but Geniwrapper was designed specifically to be easy to\r
+  port to other platforms or environments. The utility classes are generally\r
+  planetlab-independent, and the planetlab specific code is located in the\r
+  directories that house the PLC and Component wrappers. The utility classes\r
+  implement most of the complexity of the Geni wrapper, allowing the PLC and\r
+  Component wrappers to be relatively lightweight.<br>\r
+  <br>\r
+  For example, looking at the code for the stop_slice function implemented in\r
+  the component wrapper:<br>\r
+  <br>\r
+  &nbsp;&nbsp;&nbsp; def stop_slice(self, cred_str):<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\r
+  self.decode_authentication(cred_str, "stopslice")<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; slicename =\r
+  hrn_to_pl_slicename(self.object_gid.get_hrn())<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print "stopslice:", slicename<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; accounts.get(slicename).stop()<br>\r
+  <br>\r
+  The hrn_to_pl_slicename() function and accounts() class are planetlab-specific\r
+  and implement the actual stopping of the slice. The remaining code that\r
+  implements the Geni protocol, decodes credentials and authentication, etc., is\r
+  planetlab-independent, and could be easily used in a non-planetlab\r
+  environment.<br>\r
+  <br>\r
+</div>\r
+<div>\r
+  <b>2.0 Utility Classes</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.1 Certificates and Keys (cert.py)</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniwrapper uses two crypto libraries: pyOpenSSL and M2Crypto to implement the\r
+  necessary crypto functionality. Ideally just one of these libraries would be\r
+  used, but unfortunately each of these libraries is independently lacking. The\r
+  pyOpenSSL library is missing many necessary functions, and the M2Crypto\r
+  library has crashed inside of some of the functions. The design decision is to\r
+  use pyOpenSSL whenever possible as it seems more stable, and only use M2Crypto\r
+  for those functions that are not possible in pyOpenSSL.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.1.1 Keys</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Public-private key pairs are implemented by the <b>Keypair </b>class. A\r
+  Keypair object may represent both a public and private key pair, or it may\r
+  represent only a public key (this usage is consistent with OpenSSL).\r
+</div>\r
+<p>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;\r
+</p>\r
+<div>\r
+  <b>2.1.2 Certificates</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The certificate class implements a general purpose X509 certificate, making\r
+  use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds\r
+  several addition features, such as the ability to maintain a chain of parent\r
+  certificates, and storage of application-specific data.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Certificates include the ability to maintain a chain of parents. Each\r
+  certificate includes a pointer to it's parent certificate. When loaded from a\r
+  file or a string, the parent chain will be automatically loaded. When saving a\r
+  certificate to a file or a string, the caller can choose whether to save the\r
+  parent certificates as well.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Example creation of a certificate:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # create&nbsp;a key for an issuer<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; issuerKey = Keypair(create=True)<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; issuerSubject = "testissuer"\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # create a key for the certificate\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; userKey = KeyPair(create=True)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # create the certificate, set the issuer, and\r
+  sign it\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cert = Certificate(subject="test")<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cert.set_issuer(issuerKey, issuerSubject)\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cert.set_pubkey(userKey)<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cert.sign()\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.1.3 Certificate Verification</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <p>\r
+    Verification examines a chain of certificates to ensure that each parent\r
+    signs the child, and that some certificate in the chain is signed by a\r
+    trusted certificate. Verification is a basic recursion:\r
+  </p>\r
+  <pre>    if this_certificate was signed by trusted_certs:<br>        return<br>    else<br>        return verify_chain(parent, trusted_certs)</pre>\r
+</div>\r
+<div>\r
+  At each recursion, the parent is tested to ensure that it did sign the child.\r
+  If a parent did not sign a child, then an exception is thrown. If the bottom\r
+  of the recursion is reached and the certificate does not match a trusted root,\r
+  then an exception is thrown.\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\r
+  <b>2.2 GIDS (gid.py)</b>\r
+</div>\r
+<div>\r
+  <b></b>&nbsp;\r
+</div>\r
+<div>\r
+  GIDs are a derivative class of certificates and as such the&nbsp;GID class\r
+  inherits all the methods of the certificate class. A&nbsp;GID includes a tuple\r
+  of the following fields:&nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp; (uuid, hrn, public_key)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  UUID is a unique identifier and is created by the python uuid module (or the\r
+  utility function create_uuid() in gid.py).\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  HRN is a human readable name. It is a dotted form similar to a backward domain\r
+  name. For example, planetlab.us.arizona.bakers.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  PUBLIC_KEY is the public key of the principal identified by the UUID/HRN. It\r
+  is a Keypair object as defined in the cert.py module.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  It is expected that there is a one-to-one pairing between UUIDs and HRN, but\r
+  it is uncertain how this would be inforced or if it needs to be enforced.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.2.1 Encoding and Decoding</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The 5 fields of the&nbsp;GID tuple are stored in the subject-alt-name field of\r
+  the X509 certificate. Two routines are included to package and unpackage these\r
+  fields: Encode() and Decode(). Encode should be called prior to signing the\r
+  GID. Decode is automatically called on demand by the various get_*()\r
+  functions.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.2.2 Verification of GIDs</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+Verification first performs the checks of the certificate class (verifying that\r
+each parent signs the child, etc). In addition, GIDs also confirm that the\r
+parent's HRN is a prefix of the child's HRN. Verifying these prefixes prevents a\r
+rogue authority from signing a GID for a principal that is not a member of that\r
+authority. For example, planetlab.us.arizona cannot sign a GID for\r
+planetlab.us.princeton.foo.\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.3 Credentials (credential.py)</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Credentials are a derivative class of certificates and as such the credential\r
+  class inherits all the methods of the certificate class. A credential includes\r
+  a tuple of the following fields:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  GIDCaller identifies the holder of the credential. When a credential is\r
+  presented to a component, the security layer ensures that the client matches\r
+  the public key that is contained in GIDCaller.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  GIDObject identifies the object of the credential. This object depends upon\r
+  the type of the credential. For example, the credential for a user likely has\r
+  GIDObject == GIDCaller. Credentials for slices would include the GID of the\r
+  slice in the GIDObject field. Credentials for authorities include the GID of\r
+  the authority in the GIDObject field.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  LifeTime is the lifetime of the credential. Currently not implemented; expect\r
+  to implement it as an expiration date, and refuse credentials beyond that\r
+  date.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Privileges is a Rights object that describes the rights that are granted to\r
+  the holder of the credential.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Delegate is a True/False bit that indicates whether or not a credential can be\r
+  delegated to a different caller.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.3.1 Encoding and Decoding</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The 5 fields of the credential tuple are stored in the subject-alt-name field\r
+  of the X509 certificate. Two routines are included to package and unpackage\r
+  these fields: Encode() and Decode(). Encode should be called prior to signing\r
+  the credential. Decode is automatically called on demand by the various\r
+  get_*() functions.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.3.2 Verification of Credentials</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  In addition to the checks for ordinary certificates, verification of\r
+  credentials also ensures that the delegate bit was set by each parent in the\r
+  chain. If a delegate bit was not set, then an exception is thrown.&nbsp;Each\r
+  credential must also contain a subset of the rights of the parent credential\r
+  (i.e. a user credential cannot delegate authority rights).<br>\r
+  <br>\r
+  <b>2.4 Rights (rights.py)<br>\r
+  <br>\r
+  </b>Rights are implemented by two classes:<br>\r
+  <br>\r
+  Right - represents a single right<br>\r
+  RightList - represents a list of rights A right may allow several different\r
+  operations.<br>\r
+  <br>\r
+  For example, the "info" right allows "listslices", "listcomponentresources",\r
+  etc.<br>\r
+  <br>\r
+  <b>2.5 Records (record.py)</b><br>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The GeniRecord class implements a Geni Record. The GeniRecord class implements\r
+  an abstract interface for the record, so that a client may use records without\r
+  having to understant the underlying implementation details, such as whether\r
+  the record is realized in the registry database, a local cache, or has been\r
+  transmitted over the wire by an interface. A GeniRecord is a tuple (Name, GID,\r
+  Type, Info).\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp; Name specifies the HRN of the object GID is the GID of the object\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp; Type is user | sa | ma | slice | component Info is comprised of the\r
+  following sub-fields\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp; Pointer&nbsp;is a pointer to the record in the PL database\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp; pl_info&nbsp;is planetlab-specific info (when talking to client)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp; geni_info = geni-specific info (when talking to client)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The pointer is interpreted depending on the type of the record. For example,\r
+  if the type=="user", then pointer is assumed to be a person_id that indexes\r
+  into the persons table.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  A given HRN may have more than one record, provided that the records are of\r
+  different types. For example, planetlab.us.arizona may have both an SA and a\r
+  MA record, but cannot have two SA records.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.6 Tickets (geniticket.py)</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Similar to GIDs and Credentials, tickets also leverage the certificate object.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  A Ticket is tuple:<br>\r
+  &nbsp;&nbsp; (gidCaller, gidObject, attributes, rspec, delegate)<br>\r
+  <br>\r
+  &nbsp;&nbsp;&nbsp; gidCaller = GID of the caller performing the operation<br>\r
+  &nbsp;&nbsp;&nbsp; gidObject = GID of the slice<br>\r
+  &nbsp;&nbsp;&nbsp; attributes = slice attributes (keys, vref, instantiation,\r
+  etc)<br>\r
+  &nbsp;&nbsp;&nbsp; rspec = resources\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Tickets are created by invoking GetTicket() on the plc wrapper. The slice\r
+  attributes and rspec are taken from the planetlab slice database and represent\r
+  the current state of the slice. As of yet, tickets do not include any concept\r
+  of time -- a ticket represents the state of the slice at the current time\r
+  only.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Tickets are redeemed by invoking RedeemTicket() on the slice interface. The\r
+  attributes and spec are combined back into a planetlab slice record and handed\r
+  off to the node manager.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Tickets are signed by an authority and include parentage information that\r
+  traces the chain of authorities back to a trusted root.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.6.1 rspecs</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The rspec is currently a dictionary of {name: value} pairs. These pairs are\r
+  taken verbatim from the planetlab slice database.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The general rule that is used is that things in the slice record that do not\r
+  specifically imply a tangible resource (initscripts, keys, etc) are treated as\r
+  attributes and things that do specify a tangible resource (disk, network, etc)\r
+  are treated as the rspec.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  TODO: The definition of an rspec is evolving. It remains to reconcile the\r
+  eclipse schema with Geniwrapper. Gacks is also using another rspec format,\r
+  which may be need to be reconciled with the eclipse schema and/or geniwrapper.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <div>\r
+    <b>2.6.2 Encoding and Decoding</b>\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    The 5 fields of the credential tuple are stored in the subject-alt-name\r
+    field of the X509 certificate. Two routines are included to package and\r
+    unpackage these fields: Encode() and Decode(). Encode should be called prior\r
+    to signing the ticket. Decode is automatically called on demand by the\r
+    various get_*() functions.\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    <b>2.6.3 Verification of Tickets</b>\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    Verification uses the standard parentage verification provided by the\r
+    certificate class. Specifically, each certificate is signed by a parent, and\r
+    some certificate must resolve to the trusted root set that is specified on\r
+    the component.\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    Unlike credentials and GIDs, the parent of a ticket may be a degenerate\r
+    ticket that does not include the full 5-tuple (caller, object, attributes,\r
+    rspec, delegate). In such a case, the parent is just a placeholder in the\r
+    chain of authority used to convey the parentage information.\r
+  </div>\r
+  <div>\r
+    &nbsp;\r
+  </div>\r
+  <div>\r
+    Delegation of tickets is not something that is discussed in the SFA, but it\r
+    is supported in the ticket class and may be a useful feature. For example,\r
+    Alice may hold a ticket for a particular component, and delegate that ticket\r
+    to Bob. Bob could then instantiate a slice for Alice. This may be one way to\r
+    implement a slice manager.<br>\r
+    <br>\r
+    <b>2.7 Hierarchy of Authorities (hierarchy.py)</b><br>\r
+    <br>\r
+    This module implements a hierarchy of authorities and performs a similar\r
+    function as the "tree" module of the original geniwrapper prototype. An HRN\r
+    is assumed to be a string of authorities separated by dots. For example,\r
+    "planetlab.us.arizona.bakers". Each component of the HRN is a different\r
+    authority, with the last component being a leaf in the tree. Each authority\r
+    is stored in a subdirectory on the registry.<br>\r
+    <br>\r
+    Inside this subdirectory are several files:<br>\r
+    *.GID - GID file<br>\r
+    *.PKEY - private key file<br>\r
+    *.DBINFO - database info<br>\r
+    <br>\r
+    The hierarchy class can be used to create GIDs, Credentials, and Tickets for\r
+    a given authority.\r
+  </div>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The default behavior is that all authorities contained in the hierarchy will\r
+  be located together in a single physical registry. However, this is not\r
+  strictly necessary. The *.DBINFO files contain the database information for an\r
+  authority and can easily be configured to point to other machines. How an\r
+  authority would cause the DBINFO files to be installed in the correct places\r
+  is left as a separate exercise, possibly via an out-of-band management\r
+  interface or a web page.\r
+</div>\r
+<div>\r
+  <br>\r
+  <b>2.8 Configuration Information (config.py)</b><br>\r
+  <br>\r
+  This module holds configuration parameters for geniwrapper. There are two main\r
+  pieces of information that are used: the database connection and the PLCAPI\r
+  connection.<br>\r
+  <br>\r
+  Geniwrapper uses a MYSQL database to store records. This database may be\r
+  co-located with the PLC database, or it may be a separate database. The\r
+  following parameters define the connection to the database. Note that\r
+  Geniwrapper does not access any of the PLC databases directly via a mysql\r
+  connection; All PLC databases are accessed via PLCAPI.<br>\r
+</div>\r
+<p>\r
+  Geniwrapper uses a PLCAPI connection to perform operations on the registry,\r
+  such as creating and deleting slices. This connection requires an account on\r
+  the PLC server with full administrator access. The Url parameter controls\r
+  whether the connection uses PLCAPI directly (i.e. Geniwrapper is located on\r
+  the same machine as PLC), or uses a XMLRPC connection to the PLC machine. If\r
+  you wish to use the API directly, then remove the Url field from the\r
+  dictionary.\r
+</p>\r
+<div>\r
+  <br>\r
+  <b>2.8.1 Database Configuration</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Below is an example database configuration from config.py:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  def get_default_dbinfo():<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo={}<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo['dbname'] = 'planetlab4'<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo['address'] = 'localhost'<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo['port'] = 5432<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo['user'] = 'pgsqluser'<br>\r
+  &nbsp;&nbsp;&nbsp; dbinfo['password'] = '4c77b272-c892-4bdf-a833-dddeeee1a2ed'\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; return dbinfo\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  This identifies several important pieces of the database configuration. The\r
+  name specifies the database name as used by pgsql. The address is the hostname\r
+  (or ip-address) of the machine that is hosting the database. It is most likely\r
+  the local machine. Port specifies the socket port where the pgsql is\r
+  listening. The user and password authenticate Geniwrapper to the pgsql\r
+  database. In this example, an existing PLC database was used. This is not\r
+  strictly necessary as only Geni-specific information is stored in this\r
+  database. A separate database could be used, on a separate machine than PLC if\r
+  desired.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.8.2 PLCAPI Configuration</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Blow is an example PLCAPI configuration from config.py:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  def get_pl_auth():<br>\r
+  &nbsp;&nbsp;&nbsp; pl_auth = {'Username':\r
+  <a href=mailto:%27root@198.0.0.132%27>'root@198.0.0.132'</a>,<br>\r
+  &nbsp;&nbsp;&nbsp; 'AuthMethod': 'password',<br>\r
+  &nbsp;&nbsp;&nbsp; 'AuthString':&nbsp; 'root',<br>\r
+  &nbsp;&nbsp;&nbsp; "Url":\r
+  "<a href=https://localhost/PLCAPI/>https://localhost:443/PLCAPI/</a>"<br>\r
+  &nbsp;&nbsp;&nbsp; }\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; return pl_auth\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The PLCAPI configuration tells Geniwrapper how to connect to PLC. There are\r
+  two options: a local connection or a remote connection. If the Url field is\r
+  defined, then a remote connection is assumed, and Geniwrapper will attempt to\r
+  connect via XMLRPC to a remote PLCAPI server. If the Url field is not defined,\r
+  then Geniwrapper will assume that PYTHONPATH includes the relevant PLCAPI\r
+  classes to use PLCAPI directly.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Username specifies the name of the PLCAPI user. It is suggested that a user\r
+  with full administrative authority be allowed. Otherwise, Geniwrapper will be\r
+  unable to lookup public keys and other information that PLC does not make\r
+  available publicly. Administrative permission is also required to create PLC\r
+  sites, users, etc. Authmethod and AuthString specify the password require to\r
+  use this account.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>2.9 GeniServer and GeniClient</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Two files, geniserver.py and geniclient.py implement a basic Geni server and\r
+  client.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniserver forms the basis of any server that exports a Geni interface.\r
+  Examples include the PLC and Component wrappers. The Geniserver class itself\r
+  does not export any useful API functions other than a "noop" function that can\r
+  be used to test the server interface. Descendant classes register additional\r
+  API functions by overriding the register_function() member of the geniserver\r
+  object.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniserver provides a function, decode_authentication, that decodes\r
+  credentials. Credentials are supplied as the first parameter to many registry\r
+  and slice interface API functions. This function converts the credential\r
+  string supplied by the user into a credential object, checks to see that the\r
+  key the caller is using to encrypt the SSL connection matches the public key\r
+  in the caller GID of the credential, checks to see that the credential allows\r
+  the operation the caller is attempting to do, and finally verifies that the\r
+  parentage of the credential traces back to a trusted root.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Geniclient provides a variety of client-side stubs for invoking operations on\r
+  Geni interfaces. These stubs convert objects into strings that may be encoded\r
+  by XMLRPC, call the associated XMLRPC function, and convert the results back\r
+  into objects. Use of the Geniclient class is optional, but it makes a\r
+  convenient mechanism to execute API calls.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>3.0 The&nbsp;PLC Wrapper</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  This wrapper implements the Registry and Slice interfaces. According to the\r
+  SFA, the basic functionality of a registry is to map HRNs into records.\r
+  However, because of the interactions between Geniwrapper and PLC, the registry\r
+  does more than act as a simple database. The registry performs API calls on\r
+  PLC that create slices, sites, users, etc., and as such may indirectly cause\r
+  slices to be instantiated on components, because components are also linked to\r
+  PLC.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The mapping of Geni objects to planetlab objects is relatively\r
+  straightforward:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; slice&nbsp;= slice\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; user&nbsp;= person\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;component = node\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; sa = site\r
+</div>\r
+<div>\r
+  &nbsp;&nbsp;&nbsp; ma = site\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The one part that is slightly counterintuitive is SA and MA, which both map to\r
+  the planetlab site object. In a unified registry (a registry that serves as\r
+  both slice and component registry), these will map to the same site record in\r
+  the PLC database. However, there are two distinct Geni records, one&nbsp;for\r
+  the SA and one for the MA.&nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Registry and slice&nbsp;operations generally authenticate the&nbsp;caller by\r
+  credential. There are a few exceptions, and the registry API&nbsp;documents\r
+  should note those exceptions.&nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>3.1&nbsp;PLC&nbsp;Wrapper&nbsp;Tools</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The registry include several additional tools that are used to manage it.\r
+  These include:\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  import.py - imports existing PLC records into the registry\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  nuke.py - deletes all Geni records\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>3.1 Bootstrapping&nbsp;the PLC Wrapper</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  There are several items that need to be done before starting the plc wrapper.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  1) Update util/config.py to match the parameters of your PLC installation.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  2) Import the existing planetlab database, creating the appropriate geni\r
+  records. This is done by running the "import.py" tool.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  3) Create a "trusted_roots" directory and place the certificate of the root\r
+  authority in that directory. Given the defaults in import.py, this certificate\r
+  would be named "planetlab.gid". For example, mkdir trusted_roots; cp\r
+  authorities/planetlab.gid trusted_roots/\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>4.0 The Component Wrapper</b>\r
+</div>\r
+<div>\r
+  <br>\r
+  The Geni Component Wrapper implements the Geni Slice and Component Management\r
+  interfaces. It includes functions for redeeming tickets,\r
+  starting/stopping/resetting/deleting slices, and management such as rebooting\r
+  the component.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The design of the component differs from the plc wrapper in the respect that\r
+  the component wrapper must be run in the same domain (VM) as the NM for the\r
+  node it is responsible for. That is, the component wrapper directly calls\r
+  the local node manager.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>4.1 Component Authentication of Credentials</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The component authenticates credentials the same way that the plc wrapper\r
+  does. Specifically, there is a directory of trusted_root certificates (or\r
+  GIDs) on the component. Any credential presented to the component must include\r
+  in it's parentage some certificate in the set of trusted roots. Otherwise, and\r
+  exception is thrown.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>4.2 The Ticket interface</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Mainpulating tickets is split between the PLC wrapper and the Component wrapper.\r
+  Specifically,&nbsp;the authoritative copy of planetlab state is stored on PLC and only cached on\r
+  the components. Thus, GetTicket() is implemented by the plc wrapper, and\r
+  RedeemTicket() is implemented by the component wrapper. Attempting to call GetTicket()\r
+  on a component will fail.<br>\r
+  <br>\r
+  InstantiateSlice is not implemented, as that operation is a combination of\r
+  GetTicket/RedeemTicket and would therefore span the PLC and Component\r
+  wrappers.<br>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>4.3 Sliver Credentials</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  A recent Geni Architecture call mentioned the need for sliver credentials. A\r
+  sliver credential would be identical to a slice credential, but would 1) only\r
+  be redeemable on a particular component, and 2) would resolve to a\r
+  trusted_root unique to that component (likely the component's GID\r
+  certificate). Sliver credentials would be returned by the RedeemTicket call\r
+  and would give the caller the permission required to start and stop the\r
+  sliver, etc.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Sliver credentials are not yet implemented, but their implementation would be\r
+  straightforward.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>4.4 Bootstrapping the Component Wrapper</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The first step is to install some required libraries on the component. These\r
+  include the m2crypto and pyopenssl libraries. Installing the actual RPMs for\r
+  these libaries on a running component proved difficult due to additional\r
+  support packages that require installation (python-devel, etc). For\r
+  development purposes, it was sufficient to copy the installed/compiled version\r
+  of the libraries from the development machine to the component.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The&nbsp;second step is to copy the files required by the component wrapper to\r
+  the node manager. They are copied to the /usr/share/Nodemanager directory. A\r
+  list of the files is contained in the copynode.sh script in the component\r
+  subdirectory.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The third step is to copy the trusted root certificates to the component. They\r
+  are stored in /usr/share/Nodemanager/trusted_roots. This should include the\r
+  certificate for the registry.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The&nbsp;fourth step is to start the component manager. This is done by\r
+  connecting to the component via SSH and running\r
+  /usr/share/Nodemanager/component.py.\r
+</div>\r
+<div>\r
+  <br>\r
+  In a production environment, all of these steps would be integrated into the\r
+  DVD boot image for the planetlab node.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.0 Command-Line Interface</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  A command-line interface is provided that allows a user to interact with the\r
+  Geni Registry and Component. This command line interface is located in the\r
+  cmdline directory and can be invoked by running genicli.py. Specifying\r
+  "genicli.py help" will display a list of available commands.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.1 Examples</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Several examples of using the CLI are presented in the form of shell scripts\r
+  in the cmdline directory. These scripts demonstrate creating slices,\r
+  authorities, users, nodes, and getting tickets and redeeming tickets. Rather\r
+  than duplicating all of those examples here, a few short examples are\r
+  presented below.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.1.1 Getting a Credential</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  python ./genicli.py --username&nbsp;root --credfile None --outfile test.cred\r
+  getCredential user planetlab.us.pl.account_test\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The credential for planetlab.us.pl.account_test is retrieved and stored in the\r
+  local file test.cred. The private ket test.pkey is used when opening the\r
+  XMLRPC connection and authenticates the client. test.pkey must match the\r
+  public key that is in the GID for the user record for\r
+  planetlab.us.pl.account_test.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Sample output: (in human-readable summary)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  CREDENTIAL planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; privs: refresh,resolve,info<br>\r
+  &nbsp; gidCaller:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn:\r
+  planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid:\r
+  276262316202422735940395896620385479122<br>\r
+  &nbsp; gidObject:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn:\r
+  planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid:\r
+  276262316202422735940395896620385479122<br>\r
+  &nbsp;&nbsp; delegate: False\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.1.2 Resolving a record</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  python ./genicli.py --username test resolve planetlab.us.pl.account_test\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The record for planetlab.us.pl.account_test is retrieved and printed to\r
+  stdout. The credential used comes from the local file test.cred.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Sample output: (in human-readable summary)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  RECORD planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn: planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: user<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gid:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn:\r
+  planetlab.us.pl.account_test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid:\r
+  276262316202422735940395896620385479122<br>\r
+  &nbsp;&nbsp;&nbsp; pointer: 6<br>\r
+  &nbsp; geni_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; email :\r
+  <a href=mailto:test@test.com>test@test.com</a><br>\r
+  &nbsp;&nbsp;&nbsp; pl_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bio : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; first_name : test<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; last_name : account<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; last_updated : 1222497672<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; roles : ['user']<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; title : None<br>\r
+  &nbsp;&amp;nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; url : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key_ids : [1]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; enabled : True<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; slice_ids : [24]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; phone : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_person_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; role_ids : [30]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; person_id : 6<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; date_created : 1219083140<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; site_ids : [1]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; email :\r
+  <a href=mailto:test@test.com>test@test.com</a>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.1.3 Updating a record</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  python ./genicli.py --username test update user planetlab.us.pl.account_test\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  The record for planetlab.us.pl.account_test is updated. The credential used\r
+  comes from the local file test.cred. No changes are specified, so the only\r
+  thing that should be updated is the expiration time.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  <b>5.1.4 Resolving an authority</b>\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  An authority is an example of an HRN that might resolve to two different\r
+  records, an SA and a MA record.\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  python ./genicli.py --username test resolve planetlab.us.pl\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  Sample Output: (in human readable summary)\r
+</div>\r
+<div>\r
+  &nbsp;\r
+</div>\r
+<div>\r
+  RECORD planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn: planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: sa<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gid:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn: planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid:\r
+  294786197975089072547582920862317666209<br>\r
+  &nbsp;&nbsp;&nbsp; pointer: 1<br>\r
+  &nbsp; geni_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pi :\r
+  ['planetlab.us.pl.Administrator_Default']<br>\r
+  &nbsp;&nbsp;&nbsp; pl_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; last_updated : 1224136003<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; node_ids : [1]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; site_id : 1<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pcu_ids : []<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max_slices : 100<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ext_consortium_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_site_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; abbreviated_name : plctest<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid :\r
+  230749975723590978208303655640765327534<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; person_ids : [2, 4, 6]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; slice_ids : [24, 1, 2]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; latitude : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max_slivers : 1000<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is_public : False<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; address_ids : []<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name : plctest Central<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; url :\r
+  <a href=http://198.0.0.132/>http://198.0.0.132/</a><br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; enabled : True<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; longitude : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; login_base : pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; date_created : 1209428329<br>\r
+  RESULT:<br>\r
+  RECORD planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn: planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: ma<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gid:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hrn: planetlab.us.pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid:\r
+  294786197975089072547582920862317666209<br>\r
+  &nbsp;&nbsp;&nbsp; pointer: 1<br>\r
+  &nbsp; geni_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; operator : []<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; owner :\r
+  ['planetlab.us.pl.Administrator_Default']<br>\r
+  &nbsp;&nbsp;&nbsp; pl_info:<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; last_updated : 1224136003<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; node_ids : [1]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; site_id : 1<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pcu_ids : []<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max_slices : 100<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ext_consortium_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_site_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; abbreviated_name : plctest<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uuid :\r
+  230749975723590978208303655640765327534<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; person_ids : [2, 4, 6]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; slice_ids : [24, 1, 2]<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; latitude : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peer_id : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max_slivers : 1000<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is_public : False<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; address_ids : []<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name : plctest Central<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; url :\r
+  <a href=http://198.0.0.132/>http://198.0.0.132/</a><br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; enabled : True<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; longitude : None<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; login_base : pl<br>\r
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; date_created : 1209428329\r
+</div>\r
+<br>
\ No newline at end of file
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..463dbaf
--- /dev/null
@@ -0,0 +1,11 @@
+### this seems like totally out-of-date
+doc:
+       pythondoc.py ../sfa/util/certificate.py ../sfa/util/credential.py ../sfa/util/gid.py \
+                     ../sfa/util/rights.py ../sfa/util/config.py ../sfa/trust/hierarchy.py \
+                     ../sfa/util/record.py ../sfa/util/client.py \
+                     ../sfa/util/server.py 
+
+       pythondoc.py ../sfa/registry/registry.py ../sfa/registry/import.py \
+                     ../sfa/registry/nuke.py
+
+       pythondoc.py ../component/component.py
diff --git a/docs/README b/docs/README
new file mode 100644 (file)
index 0000000..3dad1aa
--- /dev/null
@@ -0,0 +1,5 @@
+This directory contains API documentation. See the developer's wiki for a
+general design overview.
+
+API documentation is generated by using the pytondoc tool.
+See http://effbot.org/zone/pythondoc.htm to obtain the tool.
diff --git a/docs/geniwrapper.doc b/docs/geniwrapper.doc
new file mode 100644 (file)
index 0000000..e42b9af
Binary files /dev/null and b/docs/geniwrapper.doc differ
diff --git a/docs/geniwrapper.pdf b/docs/geniwrapper.pdf
new file mode 100644 (file)
index 0000000..e0cca42
Binary files /dev/null and b/docs/geniwrapper.pdf differ
diff --git a/docs/pythondoc-cert.html b/docs/pythondoc-cert.html
new file mode 100644 (file)
index 0000000..efff287
--- /dev/null
@@ -0,0 +1,307 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The cert Module</title>
+</head>
+<body>
+<h1>The cert Module</h1>
+<p>Geniwrapper uses two crypto libraries: pyOpenSSL and M2Crypto to implement
+the necessary crypto functionality. Ideally just one of these libraries
+would be used, but unfortunately each of these libraries is independently
+lacking. The pyOpenSSL library is missing many necessary functions, and
+the M2Crypto library has crashed inside of some of the functions. The
+design decision is to use pyOpenSSL whenever possible as it seems more
+stable, and only use M2Crypto for those functions that are not possible
+in pyOpenSSL.
+
+This module exports two classes: Keypair and Certificate.</p>
+<dl>
+<dt><b>Certificate(create=False, subject=None, string=None, filename=None)</b> (class) [<a href='#cert.Certificate-class'>#</a>]</dt>
+<dd>
+<p>The certificate class implements a general purpose X509 certificate, making
+use of the appropriate pyOpenSSL or M2Crypto abstractions.</p>
+<p>For more information about this class, see <a href='#cert.Certificate-class'><i>The Certificate Class</i></a>.</p>
+</dd>
+<dt><b>Keypair(create=False, string=None, filename=None)</b> (class) [<a href='#cert.Keypair-class'>#</a>]</dt>
+<dd>
+<p>Public-private key pairs are implemented by the Keypair class.</p>
+<p>For more information about this class, see <a href='#cert.Keypair-class'><i>The Keypair Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='cert.Certificate-class' name='cert.Certificate-class'>The Certificate Class</a></h2>
+<dl>
+<dt><b>Certificate(create=False, subject=None, string=None, filename=None)</b> (class) [<a href='#cert.Certificate-class'>#</a>]</dt>
+<dd>
+<p>The certificate class implements a general purpose X509 certificate, making
+use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds
+several addition features, such as the ability to maintain a chain of
+parent certificates, and storage of application-specific data.
+
+Certificates include the ability to maintain a chain of parents. Each
+certificate includes a pointer to it's parent certificate. When loaded
+from a file or a string, the parent chain will be automatically loaded.
+When saving a certificate to a file or a string, the caller can choose
+whether to save the parent certificates as well.</p>
+</dd>
+<dt><a id='cert.Certificate.__init__-method' name='cert.Certificate.__init__-method'><b>__init__(create=False, subject=None, string=None, filename=None)</b></a> [<a href='#cert.Certificate.__init__-method'>#</a>]</dt>
+<dd>
+<p>Create a certificate object.</p>
+<dl>
+<dt><i>create</i></dt>
+<dd>
+If create==True, then also create a blank X509 certificate.</dd>
+<dt><i>subject</i></dt>
+<dd>
+If subject!=None, then create a blank certificate and set
+    it's subject name.</dd>
+<dt><i>string</i></dt>
+<dd>
+If string!=None, load the certficate from the string.</dd>
+<dt><i>filename</i></dt>
+<dd>
+If filename!=None, load the certficiate from the file.</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.add_extension-method' name='cert.Certificate.add_extension-method'><b>add_extension(name, critical, value)</b></a> [<a href='#cert.Certificate.add_extension-method'>#</a>]</dt>
+<dd>
+<p>Add an X509 extension to the certificate. Add_extension can only be called
+once for a particular extension name, due to limitations in the underlying
+library.</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+string containing name of extension</dd>
+<dt><i>value</i></dt>
+<dd>
+string containing value of the extension</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.create-method' name='cert.Certificate.create-method'><b>create()</b></a> [<a href='#cert.Certificate.create-method'>#</a>]</dt>
+<dd>
+<p>Create a blank X509 certificate and store it in this object.</p>
+</dd>
+<dt><a id='cert.Certificate.get_data-method' name='cert.Certificate.get_data-method'><b>get_data()</b></a> [<a href='#cert.Certificate.get_data-method'>#</a>]</dt>
+<dd>
+<p>Return the data string that was previously set with set_data</p>
+</dd>
+<dt><a id='cert.Certificate.get_extension-method' name='cert.Certificate.get_extension-method'><b>get_extension(name)</b></a> [<a href='#cert.Certificate.get_extension-method'>#</a>]</dt>
+<dd>
+<p>Get an X509 extension from the certificate</p>
+</dd>
+<dt><a id='cert.Certificate.get_issuer-method' name='cert.Certificate.get_issuer-method'><b>get_issuer(which=&quot;CN&quot;)</b></a> [<a href='#cert.Certificate.get_issuer-method'>#</a>]</dt>
+<dd>
+<p>Get the issuer name</p>
+</dd>
+<dt><a id='cert.Certificate.get_parent-method' name='cert.Certificate.get_parent-method'><b>get_parent()</b></a> [<a href='#cert.Certificate.get_parent-method'>#</a>]</dt>
+<dd>
+<p>Return the certificate object of the parent of this certificate.</p>
+</dd>
+<dt><a id='cert.Certificate.get_pubkey-method' name='cert.Certificate.get_pubkey-method'><b>get_pubkey()</b></a> [<a href='#cert.Certificate.get_pubkey-method'>#</a>]</dt>
+<dd>
+<p>Get the public key of the certificate.
+It is returned in the form of a Keypair object.</p>
+</dd>
+<dt><a id='cert.Certificate.get_subject-method' name='cert.Certificate.get_subject-method'><b>get_subject(which=&quot;CN&quot;)</b></a> [<a href='#cert.Certificate.get_subject-method'>#</a>]</dt>
+<dd>
+<p>Get the subject name of the certificate</p>
+</dd>
+<dt><a id='cert.Certificate.is_pubkey-method' name='cert.Certificate.is_pubkey-method'><b>is_pubkey(pkey)</b></a> [<a href='#cert.Certificate.is_pubkey-method'>#</a>]</dt>
+<dd>
+<p>Return True if pkey is identical to the public key that is contained in the certificate.</p>
+<dl>
+<dt><i>pkey</i></dt>
+<dd>
+Keypair object</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.is_signed_by_cert-method' name='cert.Certificate.is_signed_by_cert-method'><b>is_signed_by_cert(cert)</b></a> [<a href='#cert.Certificate.is_signed_by_cert-method'>#</a>]</dt>
+<dd>
+<p>Given a certificate cert, verify that this certificate was signed by the
+public key contained in cert. Throw an exception otherwise.</p>
+<dl>
+<dt><i>cert</i></dt>
+<dd>
+certificate object</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.load_from_file-method' name='cert.Certificate.load_from_file-method'><b>load_from_file(filename)</b></a> [<a href='#cert.Certificate.load_from_file-method'>#</a>]</dt>
+<dd>
+<p>Load the certificate from a file</p>
+</dd>
+<dt><a id='cert.Certificate.load_from_pyopenssl_x509-method' name='cert.Certificate.load_from_pyopenssl_x509-method'><b>load_from_pyopenssl_x509(x509)</b></a> [<a href='#cert.Certificate.load_from_pyopenssl_x509-method'>#</a>]</dt>
+<dd>
+<p>Given a pyOpenSSL X509 object, store that object inside of this
+certificate object.</p>
+</dd>
+<dt><a id='cert.Certificate.load_from_string-method' name='cert.Certificate.load_from_string-method'><b>load_from_string(string)</b></a> [<a href='#cert.Certificate.load_from_string-method'>#</a>]</dt>
+<dd>
+<p>Load the certificate from a string</p>
+</dd>
+<dt><a id='cert.Certificate.save_to_file-method' name='cert.Certificate.save_to_file-method'><b>save_to_file(filename, save_parents=False)</b></a> [<a href='#cert.Certificate.save_to_file-method'>#</a>]</dt>
+<dd>
+<p>Save the certificate to a file.</p>
+<dl>
+<dt><i>save_parents</i></dt>
+<dd>
+If save_parents==True, then also save the parent certificates.</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.save_to_string-method' name='cert.Certificate.save_to_string-method'><b>save_to_string(save_parents=False)</b></a> [<a href='#cert.Certificate.save_to_string-method'>#</a>]</dt>
+<dd>
+<p>Save the certificate to a string.</p>
+<dl>
+<dt><i>save_parents</i></dt>
+<dd>
+If save_parents==True, then also save the parent certificates.</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.set_data-method' name='cert.Certificate.set_data-method'><b>set_data(str)</b></a> [<a href='#cert.Certificate.set_data-method'>#</a>]</dt>
+<dd>
+<p>Set_data is a wrapper around add_extension. It stores the parameter str in
+the X509 subject_alt_name extension. Set_data can only be called once, due
+to limitations in the underlying library.</p>
+</dd>
+<dt><a id='cert.Certificate.set_issuer-method' name='cert.Certificate.set_issuer-method'><b>set_issuer(key, subject=None, cert=None)</b></a> [<a href='#cert.Certificate.set_issuer-method'>#</a>]</dt>
+<dd>
+<p>Sets the issuer private key and name</p>
+<dl>
+<dt><i>key</i></dt>
+<dd>
+Keypair object containing the private key of the issuer</dd>
+<dt><i>subject</i></dt>
+<dd>
+String containing the name of the issuer</dd>
+<dt><i>cert</i></dt>
+<dd>
+(optional) Certificate object containing the name of the issuer</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.set_parent-method' name='cert.Certificate.set_parent-method'><b>set_parent(p)</b></a> [<a href='#cert.Certificate.set_parent-method'>#</a>]</dt>
+<dd>
+<p>Set the parent certficiate.</p>
+<dl>
+<dt><i>p</i></dt>
+<dd>
+certificate object.</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.set_pubkey-method' name='cert.Certificate.set_pubkey-method'><b>set_pubkey(key)</b></a> [<a href='#cert.Certificate.set_pubkey-method'>#</a>]</dt>
+<dd>
+<p>Get the public key of the certificate.</p>
+<dl>
+<dt><i>key</i></dt>
+<dd>
+Keypair object containing the public key</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.set_subject-method' name='cert.Certificate.set_subject-method'><b>set_subject(name)</b></a> [<a href='#cert.Certificate.set_subject-method'>#</a>]</dt>
+<dd>
+<p>Set the subject name of the certificate</p>
+</dd>
+<dt><a id='cert.Certificate.sign-method' name='cert.Certificate.sign-method'><b>sign()</b></a> [<a href='#cert.Certificate.sign-method'>#</a>]</dt>
+<dd>
+<p>Sign the certificate using the issuer private key and issuer subject previous set with set_issuer().</p>
+</dd>
+<dt><a id='cert.Certificate.verify-method' name='cert.Certificate.verify-method'><b>verify(pkey)</b></a> [<a href='#cert.Certificate.verify-method'>#</a>]</dt>
+<dd>
+<p>Verify the authenticity of a certificate.</p>
+<dl>
+<dt><i>pkey</i></dt>
+<dd>
+is a Keypair object representing a public key. If Pkey
+    did not sign the certificate, then an exception will be thrown.</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Certificate.verify_chain-method' name='cert.Certificate.verify_chain-method'><b>verify_chain(trusted_certs=None)</b></a> [<a href='#cert.Certificate.verify_chain-method'>#</a>]</dt>
+<dd>
+<p>Verification examines a chain of certificates to ensure that each parent
+signs the child, and that some certificate in the chain is signed by a
+trusted certificate.
+
+Verification is a basic recursion: <pre>
+    if this_certificate was signed by trusted_certs:
+        return
+    else
+        return verify_chain(parent, trusted_certs)
+</pre>
+
+At each recursion, the parent is tested to ensure that it did sign the
+child. If a parent did not sign a child, then an exception is thrown. If
+the bottom of the recursion is reached and the certificate does not match
+a trusted root, then an exception is thrown.
+
+</p><dl>
+<dt><i>Trusted_certs</i></dt>
+<dd>
+is a list of certificates that are trusted.</dd>
+</dl><br />
+</dd>
+</dl>
+<h2><a id='cert.Keypair-class' name='cert.Keypair-class'>The Keypair Class</a></h2>
+<dl>
+<dt><b>Keypair(create=False, string=None, filename=None)</b> (class) [<a href='#cert.Keypair-class'>#</a>]</dt>
+<dd>
+<p>Public-private key pairs are implemented by the Keypair class.
+A Keypair object may represent both a public and private key pair, or it
+may represent only a public key (this usage is consistent with OpenSSL).</p>
+</dd>
+<dt><a id='cert.Keypair.__init__-method' name='cert.Keypair.__init__-method'><b>__init__(create=False, string=None, filename=None)</b></a> [<a href='#cert.Keypair.__init__-method'>#</a>]</dt>
+<dd>
+<p>Creates a Keypair object</p>
+<dl>
+<dt><i>create</i></dt>
+<dd>
+If create==True, creates a new public/private key and
+    stores it in the object</dd>
+<dt><i>string</i></dt>
+<dd>
+If string!=None, load the keypair from the string (PEM)</dd>
+<dt><i>filename</i></dt>
+<dd>
+If filename!=None, load the keypair from the file</dd>
+</dl><br />
+</dd>
+<dt><a id='cert.Keypair.as_pem-method' name='cert.Keypair.as_pem-method'><b>as_pem()</b></a> [<a href='#cert.Keypair.as_pem-method'>#</a>]</dt>
+<dd>
+<p>Return the private key in PEM format.</p>
+</dd>
+<dt><a id='cert.Keypair.create-method' name='cert.Keypair.create-method'><b>create()</b></a> [<a href='#cert.Keypair.create-method'>#</a>]</dt>
+<dd>
+<p>Create a RSA public/private key pair and store it inside the keypair object</p>
+</dd>
+<dt><a id='cert.Keypair.get_m2_pkey-method' name='cert.Keypair.get_m2_pkey-method'><b>get_m2_pkey()</b></a> [<a href='#cert.Keypair.get_m2_pkey-method'>#</a>]</dt>
+<dd>
+<p>Return an OpenSSL pkey object</p>
+</dd>
+<dt><a id='cert.Keypair.get_openssl_pkey-method' name='cert.Keypair.get_openssl_pkey-method'><b>get_openssl_pkey()</b></a> [<a href='#cert.Keypair.get_openssl_pkey-method'>#</a>]</dt>
+<dd>
+<p>Given another Keypair object, return TRUE if the two keys are the same.</p>
+</dd>
+<dt><a id='cert.Keypair.load_from_file-method' name='cert.Keypair.load_from_file-method'><b>load_from_file(filename)</b></a> [<a href='#cert.Keypair.load_from_file-method'>#</a>]</dt>
+<dd>
+<p>Load the private key from a file. Implicity the private key includes the public key.</p>
+</dd>
+<dt><a id='cert.Keypair.load_from_string-method' name='cert.Keypair.load_from_string-method'><b>load_from_string(string)</b></a> [<a href='#cert.Keypair.load_from_string-method'>#</a>]</dt>
+<dd>
+<p>Load the private key from a string. Implicitly the private key includes the public key.</p>
+</dd>
+<dt><a id='cert.Keypair.load_pubkey_from_file-method' name='cert.Keypair.load_pubkey_from_file-method'><b>load_pubkey_from_file(filename)</b></a> [<a href='#cert.Keypair.load_pubkey_from_file-method'>#</a>]</dt>
+<dd>
+<p>Load the public key from a string. No private key is loaded.</p>
+</dd>
+<dt><a id='cert.Keypair.load_pubkey_from_string-method' name='cert.Keypair.load_pubkey_from_string-method'><b>load_pubkey_from_string(string)</b></a> [<a href='#cert.Keypair.load_pubkey_from_string-method'>#</a>]</dt>
+<dd>
+<p>Load the public key from a string. No private key is loaded.</p>
+</dd>
+<dt><a id='cert.Keypair.save_to_file-method' name='cert.Keypair.save_to_file-method'><b>save_to_file(filename)</b></a> [<a href='#cert.Keypair.save_to_file-method'>#</a>]</dt>
+<dd>
+<p>Save the private key to a file</p>
+<dl>
+<dt><i>filename</i></dt>
+<dd>
+name of file to store the keypair in</dd>
+</dl><br />
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-component.html b/docs/pythondoc-component.html
new file mode 100644 (file)
index 0000000..655f286
--- /dev/null
@@ -0,0 +1,136 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The component Module</title>
+</head>
+<body>
+<h1>The component Module</h1>
+<p>Geni Component Wrapper
+
+This wrapper implements the Geni Component Interface</p>
+<dl>
+<dt><b>ComponentManager(ip, port, key_file, cert_file)</b> (class) [<a href='#component.ComponentManager-class'>#</a>]</dt>
+<dd>
+<p>ComponentManager is a GeniServer that serves component interface requests.</p>
+<p>For more information about this class, see <a href='#component.ComponentManager-class'><i>The ComponentManager Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='component.ComponentManager-class' name='component.ComponentManager-class'>The ComponentManager Class</a></h2>
+<dl>
+<dt><b>ComponentManager(ip, port, key_file, cert_file)</b> (class) [<a href='#component.ComponentManager-class'>#</a>]</dt>
+<dd>
+<p>ComponentManager is a GeniServer that serves component interface requests.</p>
+</dd>
+<dt><a id='component.ComponentManager.decode_ticket-method' name='component.ComponentManager.decode_ticket-method'><b>decode_ticket(ticket_string)</b></a> [<a href='#component.ComponentManager.decode_ticket-method'>#</a>]</dt>
+<dd>
+<p>Examine the ticket that was provided by the caller, check that it is
+signed and verified correctly. Throw an exception if something is
+wrong with the ticket.
+
+This is similar to geniserver.decode_authentication</p>
+<dl>
+<dt><i>ticket_string</i></dt>
+<dd>
+the string representation of the ticket</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.delete_slice-method' name='component.ComponentManager.delete_slice-method'><b>delete_slice(cred_str)</b></a> [<a href='#component.ComponentManager.delete_slice-method'>#</a>]</dt>
+<dd>
+<p>Delete a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.geni_ticket_to_plc_rec-method' name='component.ComponentManager.geni_ticket_to_plc_rec-method'><b>geni_ticket_to_plc_rec(ticket)</b></a> [<a href='#component.ComponentManager.geni_ticket_to_plc_rec-method'>#</a>]</dt>
+<dd>
+<p>Convert a geni ticket into a PLC record that can be submitted to the
+node manager. This involves some minor reformatting of the fields
+contained in the ticket.</p>
+<dl>
+<dt><i>ticket</i></dt>
+<dd>
+a ticket object</dd>
+<dt>Returns:</dt>
+<dd>
+a dictionary containing the PLC record info</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.list_slices-method' name='component.ComponentManager.list_slices-method'><b>list_slices(cred_str)</b></a> [<a href='#component.ComponentManager.list_slices-method'>#</a>]</dt>
+<dd>
+<p>List the slices on a component.</p>
+<dl>
+<dt><i>cred_str</i></dt>
+<dd>
+string representation of a credential object that
+    authorizes the caller</dd>
+<dt>Returns:</dt>
+<dd>
+a list of slice names</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.reboot-method' name='component.ComponentManager.reboot-method'><b>reboot(cred_str)</b></a> [<a href='#component.ComponentManager.reboot-method'>#</a>]</dt>
+<dd>
+<p>Reboot the component.</p>
+<dl>
+<dt><i>cred_str</i></dt>
+<dd>
+string representation of a credential object that
+    authorizes the caller</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.redeem_ticket-method' name='component.ComponentManager.redeem_ticket-method'><b>redeem_ticket(ticket_str)</b></a> [<a href='#component.ComponentManager.redeem_ticket-method'>#</a>]</dt>
+<dd>
+<p>Redeem a ticket.
+
+The ticket is submitted to the node manager, and the slice is instantiated
+or updated as appropriate.
+
+TODO: This operation should return a sliver credential and indicate
+whether or not the component will accept only sliver credentials, or
+will accept both sliver and slice credentials.</p>
+<dl>
+<dt><i>ticket_str</i></dt>
+<dd>
+the string representation of a ticket object</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.register_functions-method' name='component.ComponentManager.register_functions-method'><b>register_functions()</b></a> [<a href='#component.ComponentManager.register_functions-method'>#</a>]</dt>
+<dd>
+<p>Register the server RPCs for the component</p>
+</dd>
+<dt><a id='component.ComponentManager.reset_slice-method' name='component.ComponentManager.reset_slice-method'><b>reset_slice(cred_str)</b></a> [<a href='#component.ComponentManager.reset_slice-method'>#</a>]</dt>
+<dd>
+<p>Reset a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.start_slice-method' name='component.ComponentManager.start_slice-method'><b>start_slice(cred_str)</b></a> [<a href='#component.ComponentManager.start_slice-method'>#</a>]</dt>
+<dd>
+<p>Start a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='component.ComponentManager.stop_slice-method' name='component.ComponentManager.stop_slice-method'><b>stop_slice(cred_str)</b></a> [<a href='#component.ComponentManager.stop_slice-method'>#</a>]</dt>
+<dd>
+<p>Stop a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-config.html b/docs/pythondoc-config.html
new file mode 100644 (file)
index 0000000..a7240b2
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The config Module</title>
+</head>
+<body>
+<h1>The config Module</h1>
+<p>Geniwrapper Configuration Info
+
+This module holds configuration parameters for geniwrapper. There are two
+main pieces of information that are used: the database connection and
+the PLCAPI connection</p>
+<dl>
+<dt><a id='config.get_default_dbinfo-function' name='config.get_default_dbinfo-function'><b>get_default_dbinfo()</b></a> [<a href='#config.get_default_dbinfo-function'>#</a>]</dt>
+<dd>
+<p>Geniwrapper uses a MYSQL database to store records. This database may be
+co-located with the PLC database, or it may be a separate database. The
+following parameters define the connection to the database.
+
+Note that Geniwrapper does not access any of the PLC databases directly via
+a mysql connection; All PLC databases are accessed via PLCAPI.</p>
+</dd>
+<dt><a id='config.get_pl_auth-function' name='config.get_pl_auth-function'><b>get_pl_auth()</b></a> [<a href='#config.get_pl_auth-function'>#</a>]</dt>
+<dd>
+<p>Geniwrapper uses a PLCAPI connection to perform operations on the registry,
+such as creating and deleting slices. This connection requires an account
+on the PLC server with full administrator access.
+
+The Url parameter controls whether the connection uses PLCAPI directly (i.e.
+Geniwrapper is located on the same machine as PLC), or uses a XMLRPC connection
+to the PLC machine. If you wish to use the API directly, then remove the Url
+field from the dictionary.</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-credential.html b/docs/pythondoc-credential.html
new file mode 100644 (file)
index 0000000..7c88af3
--- /dev/null
@@ -0,0 +1,159 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The credential Module</title>
+</head>
+<body>
+<h1>The credential Module</h1>
+<p>Implements Geni Credentials
+
+Credentials are layered on top of certificates, and are essentially a
+certificate that stores a tuple of parameters.</p>
+<dl>
+<dt><b>Credential(create=False, subject=None, string=None, filename=None)</b> (class) [<a href='#credential.Credential-class'>#</a>]</dt>
+<dd>
+<p>Credential is a tuple:
+    (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)
+
+These fields are encoded using xmlrpc into the subjectAltName field of the
+x509 certificate.</p>
+<p>For more information about this class, see <a href='#credential.Credential-class'><i>The Credential Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='credential.Credential-class' name='credential.Credential-class'>The Credential Class</a></h2>
+<dl>
+<dt><b>Credential(create=False, subject=None, string=None, filename=None)</b> (class) [<a href='#credential.Credential-class'>#</a>]</dt>
+<dd>
+<p>Credential is a tuple:
+    (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)
+
+These fields are encoded using xmlrpc into the subjectAltName field of the
+x509 certificate. Note: Call encode() once the fields have been filled in
+to perform this encoding.</p>
+</dd>
+<dt><a id='credential.Credential.__init__-method' name='credential.Credential.__init__-method'><b>__init__(create=False, subject=None, string=None, filename=None)</b></a> [<a href='#credential.Credential.__init__-method'>#</a>]</dt>
+<dd>
+<p>Create a Credential object</p>
+<dl>
+<dt><i>create</i></dt>
+<dd>
+If true, create a blank x509 certificate</dd>
+<dt><i>subject</i></dt>
+<dd>
+If subject!=None, create an x509 cert with the subject name</dd>
+<dt><i>string</i></dt>
+<dd>
+If string!=None, load the credential from the string</dd>
+<dt><i>filename</i></dt>
+<dd>
+If filename!=None, load the credential from the file</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.can_perform-method' name='credential.Credential.can_perform-method'><b>can_perform(op_name)</b></a> [<a href='#credential.Credential.can_perform-method'>#</a>]</dt>
+<dd>
+<p>determine whether the credential allows a particular operation to be
+performed</p>
+<dl>
+<dt><i>op_name</i></dt>
+<dd>
+string specifying name of operation (&quot;lookup&quot;, &quot;update&quot;, etc)</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.decode-method' name='credential.Credential.decode-method'><b>decode()</b></a> [<a href='#credential.Credential.decode-method'>#</a>]</dt>
+<dd>
+<p>Retrieve the attributes of the credential from the alt-subject-name field
+of the X509 certificate. This is automatically done by the various
+get_* methods of this class and should not need to be called explicitly.</p>
+</dd>
+<dt><a id='credential.Credential.dump-method' name='credential.Credential.dump-method'><b>dump(dump_parents=False)</b></a> [<a href='#credential.Credential.dump-method'>#</a>]</dt>
+<dd>
+<p>Dump the contents of a credential to stdout in human-readable format</p>
+<dl>
+<dt><i>dump_parents</i></dt>
+<dd>
+If true, also dump the parent certificates</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.encode-method' name='credential.Credential.encode-method'><b>encode()</b></a> [<a href='#credential.Credential.encode-method'>#</a>]</dt>
+<dd>
+<p>Encode the attributes of the credential into a string and store that
+string in the alt-subject-name field of the X509 object. This should be
+done immediately before signing the credential.</p>
+</dd>
+<dt><a id='credential.Credential.get_delegate-method' name='credential.Credential.get_delegate-method'><b>get_delegate()</b></a> [<a href='#credential.Credential.get_delegate-method'>#</a>]</dt>
+<dd>
+<p>get the delegate bit</p>
+</dd>
+<dt><a id='credential.Credential.get_gid_caller-method' name='credential.Credential.get_gid_caller-method'><b>get_gid_caller()</b></a> [<a href='#credential.Credential.get_gid_caller-method'>#</a>]</dt>
+<dd>
+<p>get the GID of the object</p>
+</dd>
+<dt><a id='credential.Credential.get_gid_object-method' name='credential.Credential.get_gid_object-method'><b>get_gid_object()</b></a> [<a href='#credential.Credential.get_gid_object-method'>#</a>]</dt>
+<dd>
+<p>get the GID of the object</p>
+</dd>
+<dt><a id='credential.Credential.get_lifetime-method' name='credential.Credential.get_lifetime-method'><b>get_lifetime()</b></a> [<a href='#credential.Credential.get_lifetime-method'>#</a>]</dt>
+<dd>
+<p>get the lifetime of the credential</p>
+</dd>
+<dt><a id='credential.Credential.get_privileges-method' name='credential.Credential.get_privileges-method'><b>get_privileges()</b></a> [<a href='#credential.Credential.get_privileges-method'>#</a>]</dt>
+<dd>
+<p>return the privileges as a RightList object</p>
+</dd>
+<dt><a id='credential.Credential.set_delegate-method' name='credential.Credential.set_delegate-method'><b>set_delegate(delegate)</b></a> [<a href='#credential.Credential.set_delegate-method'>#</a>]</dt>
+<dd>
+<p>set the delegate bit</p>
+<dl>
+<dt><i>delegate</i></dt>
+<dd>
+boolean (True or False)</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.set_gid_caller-method' name='credential.Credential.set_gid_caller-method'><b>set_gid_caller(gid)</b></a> [<a href='#credential.Credential.set_gid_caller-method'>#</a>]</dt>
+<dd>
+<p>set the GID of the caller</p>
+<dl>
+<dt><i>gid</i></dt>
+<dd>
+GID object of the caller</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.set_gid_object-method' name='credential.Credential.set_gid_object-method'><b>set_gid_object(gid)</b></a> [<a href='#credential.Credential.set_gid_object-method'>#</a>]</dt>
+<dd>
+<p>set the GID of the object</p>
+<dl>
+<dt><i>gid</i></dt>
+<dd>
+GID object of the object</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.set_lifetime-method' name='credential.Credential.set_lifetime-method'><b>set_lifetime(lifeTime)</b></a> [<a href='#credential.Credential.set_lifetime-method'>#</a>]</dt>
+<dd>
+<p>set the lifetime of this credential</p>
+<dl>
+<dt><i>lifetime</i></dt>
+<dd>
+lifetime of credential</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.set_privileges-method' name='credential.Credential.set_privileges-method'><b>set_privileges(privs)</b></a> [<a href='#credential.Credential.set_privileges-method'>#</a>]</dt>
+<dd>
+<p>set the privileges</p>
+<dl>
+<dt><i>privs</i></dt>
+<dd>
+either a comma-separated list of privileges of a RightList object</dd>
+</dl><br />
+</dd>
+<dt><a id='credential.Credential.verify_chain-method' name='credential.Credential.verify_chain-method'><b>verify_chain(trusted_certs=None)</b></a> [<a href='#credential.Credential.verify_chain-method'>#</a>]</dt>
+<dd>
+<p>Verify that a chain of credentials is valid (see cert.py:verify). In
+addition to the checks for ordinary certificates, verification also
+ensures that the delegate bit was set by each parent in the chain. If
+a delegate bit was not set, then an exception is thrown.
+
+Each credential must be a subset of the rights of the parent.</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-geniclient.html b/docs/pythondoc-geniclient.html
new file mode 100644 (file)
index 0000000..6620407
--- /dev/null
@@ -0,0 +1,322 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The geniclient Module</title>
+</head>
+<body>
+<h1>The geniclient Module</h1>
+<p>This module implements the client-side of the Geni API. Stubs are provided
+that convert the supplied parameters to the necessary format and send them
+via XMLRPC to a Geni Server.
+
+TODO: Investigate ways to combine this with existing PLC API?</p>
+<dl>
+<dt><b>GeniClient(url, key_file, cert_file)</b> (class) [<a href='#geniclient.GeniClient-class'>#</a>]</dt>
+<dd>
+<p>The GeniClient class provides stubs for executing Geni operations.</p>
+<p>For more information about this class, see <a href='#geniclient.GeniClient-class'><i>The GeniClient Class</i></a>.</p>
+</dd>
+<dt><b>GeniTransport</b> (class)  [<a href='#geniclient.GeniTransport-class'>#</a>]</dt>
+<dd>
+<p>For more information about this class, see <a href='#geniclient.GeniTransport-class'><i>The GeniTransport Class</i></a>.</p>
+</dd>
+<dt><b>ServerException</b> (class)  [<a href='#geniclient.ServerException-class'>#</a>]</dt>
+<dd>
+<p>ServerException, ExceptionUnmarshaller
+
+Used to convert server exception strings back to an exception.</p>
+<p>For more information about this class, see <a href='#geniclient.ServerException-class'><i>The ServerException Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='geniclient.GeniClient-class' name='geniclient.GeniClient-class'>The GeniClient Class</a></h2>
+<dl>
+<dt><b>GeniClient(url, key_file, cert_file)</b> (class) [<a href='#geniclient.GeniClient-class'>#</a>]</dt>
+<dd>
+<p>The GeniClient class provides stubs for executing Geni operations. A given
+client object connects to one server. To connect to multiple servers, create
+multiple GeniClient objects.
+
+The Geni protocol uses an HTTPS connection, and the client's side of the
+connection uses his private key. Generally, this private key must match the
+public key that is containing in the GID that the client is providing for
+those functions that take a GID.</p>
+</dd>
+<dt><a id='geniclient.GeniClient.create_gid-method' name='geniclient.GeniClient.create_gid-method'><b>create_gid(cred, name, uuid, pkey_string)</b></a> [<a href='#geniclient.GeniClient.create_gid-method'>#</a>]</dt>
+<dd>
+<p>Create a new GID. For MAs and SAs that are physically located on the
+registry, this allows a owner/operator/PI to create a new GID and have it
+signed by his respective authority.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential of caller</dd>
+<dt><i>name</i></dt>
+<dd>
+hrn for new GID</dd>
+<dt><i>uuid</i></dt>
+<dd>
+unique identifier for new GID</dd>
+<dt><i>pkey_string</i></dt>
+<dd>
+public-key string (TODO: why is this a string and not a keypair object?)</dd>
+<dt>Returns:</dt>
+<dd>
+a GID object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.delete_slice-method' name='geniclient.GeniClient.delete_slice-method'><b>delete_slice(cred)</b></a> [<a href='#geniclient.GeniClient.delete_slice-method'>#</a>]</dt>
+<dd>
+<p>Delete a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.get_credential-method' name='geniclient.GeniClient.get_credential-method'><b>get_credential(cred, type, name)</b></a> [<a href='#geniclient.GeniClient.get_credential-method'>#</a>]</dt>
+<dd>
+<p>Retrieve a credential for an object.
+
+If cred==None, then the behavior reverts to get_self_credential()</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt><i>type</i></dt>
+<dd>
+type of object (user | slice | sa | ma | node)</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+<dt>Returns:</dt>
+<dd>
+a credental object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.get_gid-method' name='geniclient.GeniClient.get_gid-method'><b>get_gid(name)</b></a> [<a href='#geniclient.GeniClient.get_gid-method'>#</a>]</dt>
+<dd>
+<p>Retrieve the GID for an object. This function looks up a record in the
+registry and returns the GID of the record if it exists.
+TODO: Is this function needed? It's a shortcut for Resolve()</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+hrn to look up</dd>
+<dt>Returns:</dt>
+<dd>
+a GID object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.get_self_credential-method' name='geniclient.GeniClient.get_self_credential-method'><b>get_self_credential(type, name)</b></a> [<a href='#geniclient.GeniClient.get_self_credential-method'>#</a>]</dt>
+<dd>
+<p>Get_self_credential a degenerate version of get_credential used by a
+client to get his initial credential when he doesn't have one. This is
+the same as get_credential(..., cred=None,...).
+
+The registry ensures that the client is the principal that is named by
+(type, name) by comparing the public key in the record's GID to the
+private key used to encrypt the client-side of the HTTPS connection. Thus
+it is impossible for one principal to retrieve another principal's
+credential without having the appropriate private key.</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+type of object (user | slice | sa | ma | node</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+<dt>Returns:</dt>
+<dd>
+a credential object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.get_ticket-method' name='geniclient.GeniClient.get_ticket-method'><b>get_ticket(cred, name, rspec)</b></a> [<a href='#geniclient.GeniClient.get_ticket-method'>#</a>]</dt>
+<dd>
+<p>Retrieve a ticket. This operation is currently implemented on the
+registry (see SFA, engineering decisions), and is not implemented on
+components.
+
+The ticket is filled in with information from the PLC database. This
+information includes resources, and attributes such as user keys and
+initscripts.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object</dd>
+<dt><i>name</i></dt>
+<dd>
+name of the slice to retrieve a ticket for</dd>
+<dt><i>rspec</i></dt>
+<dd>
+resource specification dictionary</dd>
+<dt>Returns:</dt>
+<dd>
+a ticket object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.list-method' name='geniclient.GeniClient.list-method'><b>list(cred)</b></a> [<a href='#geniclient.GeniClient.list-method'>#</a>]</dt>
+<dd>
+<p>List the records in an authority. The objectGID in the supplied credential
+should name the authority that will be listed.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt>Returns:</dt>
+<dd>
+list of record objects</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.list_slices-method' name='geniclient.GeniClient.list_slices-method'><b>list_slices(cred)</b></a> [<a href='#geniclient.GeniClient.list_slices-method'>#</a>]</dt>
+<dd>
+<p>List the slices on a component.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object that authorizes the caller</dd>
+<dt>Returns:</dt>
+<dd>
+a list of slice names</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.redeem_ticket-method' name='geniclient.GeniClient.redeem_ticket-method'><b>redeem_ticket(ticket)</b></a> [<a href='#geniclient.GeniClient.redeem_ticket-method'>#</a>]</dt>
+<dd>
+<p>Redeem a ticket. This operation is currently implemented on the
+component.
+
+The ticket is submitted to the node manager, and the slice is instantiated
+or updated as appropriate.
+
+TODO: This operation should return a sliver credential and indicate
+whether or not the component will accept only sliver credentials, or
+will accept both sliver and slice credentials.</p>
+<dl>
+<dt><i>ticket</i></dt>
+<dd>
+a ticket object containing the ticket</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.register-method' name='geniclient.GeniClient.register-method'><b>register(cred, record)</b></a> [<a href='#geniclient.GeniClient.register-method'>#</a>]</dt>
+<dd>
+<p>Register an object with the registry. In addition to being stored in the
+Geni database, the appropriate records will also be created in the
+PLC databases.
+
+The geni_info and/or pl_info fields must in the record must be filled
+out correctly depending on the type of record that is being registered.
+
+TODO: The geni_info member of the record should be parsed and the pl_info
+adjusted as necessary (add/remove users from a slice, etc)</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt>Returns:</dt>
+<dd>
+record to register</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.remove-method' name='geniclient.GeniClient.remove-method'><b>remove(cred, record)</b></a> [<a href='#geniclient.GeniClient.remove-method'>#</a>]</dt>
+<dd>
+<p>Remove an object from the registry. If the object represents a PLC object,
+then the PLC records will also be removed.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt><i>record</i></dt>
+<dd>
+record to register. The only relevant
+    fields of the record are 'name' and 'type', which are used to lookup
+    the current copy of the record in the Geni database, to make sure
+    that the appopriate record is removed.</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.reset_slice-method' name='geniclient.GeniClient.reset_slice-method'><b>reset_slice(cred)</b></a> [<a href='#geniclient.GeniClient.reset_slice-method'>#</a>]</dt>
+<dd>
+<p>Reset a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.resolve-method' name='geniclient.GeniClient.resolve-method'><b>resolve(cred, name)</b></a> [<a href='#geniclient.GeniClient.resolve-method'>#</a>]</dt>
+<dd>
+<p>Resolve an object in the registry. A given HRN may have multiple records
+associated with it, and therefore multiple records may be returned. The
+caller should check the type fields of the records to find the one that
+he is interested in.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.start_slice-method' name='geniclient.GeniClient.start_slice-method'><b>start_slice(cred)</b></a> [<a href='#geniclient.GeniClient.start_slice-method'>#</a>]</dt>
+<dd>
+<p>Start a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.stop_slice-method' name='geniclient.GeniClient.stop_slice-method'><b>stop_slice(cred)</b></a> [<a href='#geniclient.GeniClient.stop_slice-method'>#</a>]</dt>
+<dd>
+<p>Stop a slice.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+a credential identifying the caller (callerGID) and the slice
+    (objectGID)</dd>
+</dl><br />
+</dd>
+<dt><a id='geniclient.GeniClient.update-method' name='geniclient.GeniClient.update-method'><b>update(cred, record)</b></a> [<a href='#geniclient.GeniClient.update-method'>#</a>]</dt>
+<dd>
+<p>Update an object in the registry. Currently, this only updates the
+PLC information associated with the record. The Geni fields (name, type,
+GID) are fixed.
+
+The record is expected to have the pl_info field filled in with the data
+that should be updated.
+
+TODO: The geni_info member of the record should be parsed and the pl_info
+adjusted as necessary (add/remove users from a slice, etc)</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt><i>record</i></dt>
+<dd>
+a record object to be updated</dd>
+</dl><br />
+</dd>
+</dl>
+<h2><a id='geniclient.GeniTransport-class' name='geniclient.GeniTransport-class'>The GeniTransport Class</a></h2>
+<dl>
+<dt><b>GeniTransport</b> (class)  [<a href='#geniclient.GeniTransport-class'>#</a>]</dt>
+<dd>
+<p>GeniTransport
+
+A transport for XMLRPC that works on top of HTTPS</p>
+</dd>
+</dl>
+<h2><a id='geniclient.ServerException-class' name='geniclient.ServerException-class'>The ServerException Class</a></h2>
+<dl>
+<dt><b>ServerException</b> (class)  [<a href='#geniclient.ServerException-class'>#</a>]</dt>
+<dd>
+<p>ServerException, ExceptionUnmarshaller
+
+Used to convert server exception strings back to an exception.
+   from usenet, Raghuram Devarakonda</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-geniserver.html b/docs/pythondoc-geniserver.html
new file mode 100644 (file)
index 0000000..9aa0996
--- /dev/null
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The geniserver Module</title>
+</head>
+<body>
+<h1>The geniserver Module</h1>
+<p>This module implements a general-purpose server layer for geni.
+The same basic server should be usable on the registry, component, or
+other interfaces.
+
+TODO: investigate ways to combine this with existing PLC server?</p>
+<dl>
+<dt><b>GeniServer(ip, port, key_file, cert_file)</b> (class) [<a href='#geniserver.GeniServer-class'>#</a>]</dt>
+<dd>
+<p>Implements an HTTPS XML-RPC server.</p>
+<p>For more information about this class, see <a href='#geniserver.GeniServer-class'><i>The GeniServer Class</i></a>.</p>
+</dd>
+<dt><b>SecureXMLRpcRequestHandler</b> (class)  [<a href='#geniserver.SecureXMLRpcRequestHandler-class'>#</a>]</dt>
+<dd>
+<p>taken from the web (XXX find reference).</p>
+<p>For more information about this class, see <a href='#geniserver.SecureXMLRpcRequestHandler-class'><i>The SecureXMLRpcRequestHandler Class</i></a>.</p>
+</dd>
+<dt><b>SecureXMLRPCServer(server_address, HandlerClass, key_file, cert_file, logRequests=True)</b> (class) [<a href='#geniserver.SecureXMLRPCServer-class'>#</a>]</dt>
+<dd>
+<p>Taken from the web (XXX find reference).</p>
+<p>For more information about this class, see <a href='#geniserver.SecureXMLRPCServer-class'><i>The SecureXMLRPCServer Class</i></a>.</p>
+</dd>
+<dt><a id='geniserver.verify_callback-function' name='geniserver.verify_callback-function'><b>verify_callback(conn, x509, err, depth, preverify)</b></a> [<a href='#geniserver.verify_callback-function'>#</a>]</dt>
+<dd>
+<p>Verification callback for pyOpenSSL. We do our own checking of keys because
+we have our own authentication spec. Thus we disable several of the normal
+prohibitions that OpenSSL places on certificates</p>
+</dd>
+</dl>
+<h2><a id='geniserver.GeniServer-class' name='geniserver.GeniServer-class'>The GeniServer Class</a></h2>
+<dl>
+<dt><b>GeniServer(ip, port, key_file, cert_file)</b> (class) [<a href='#geniserver.GeniServer-class'>#</a>]</dt>
+<dd>
+<p>Implements an HTTPS XML-RPC server. Generally it is expected that GENI
+functions will take a credential string, which is passed to
+decode_authentication. Decode_authentication() will verify the validity of
+the credential, and verify that the user is using the key that matches the
+GID supplied in the credential.</p>
+</dd>
+<dt><a id='geniserver.GeniServer.decode_authentication-method' name='geniserver.GeniServer.decode_authentication-method'><b>decode_authentication(cred_string, operation)</b></a> [<a href='#geniserver.GeniServer.decode_authentication-method'>#</a>]</dt>
+<dd>
+<p>Decode the credential string that was submitted by the caller. Several
+checks are performed to ensure that the credential is valid, and that the
+callerGID included in the credential matches the caller that is
+connected to the HTTPS connection.</p>
+</dd>
+<dt><a id='geniserver.GeniServer.noop-method' name='geniserver.GeniServer.noop-method'><b>noop(cred, anything)</b></a> [<a href='#geniserver.GeniServer.noop-method'>#</a>]</dt>
+<dd>
+<p>Sample no-op server function. The no-op function decodes the credential
+that was passed to it.</p>
+</dd>
+<dt><a id='geniserver.GeniServer.register_functions-method' name='geniserver.GeniServer.register_functions-method'><b>register_functions()</b></a> [<a href='#geniserver.GeniServer.register_functions-method'>#</a>]</dt>
+<dd>
+<p>Register functions that will be served by the XMLRPC server. This
+function should be overrided by each descendant class.</p>
+</dd>
+<dt><a id='geniserver.GeniServer.run-method' name='geniserver.GeniServer.run-method'><b>run()</b></a> [<a href='#geniserver.GeniServer.run-method'>#</a>]</dt>
+<dd>
+<p>Execute the server, serving requests forever.</p>
+</dd>
+</dl>
+<h2><a id='geniserver.SecureXMLRpcRequestHandler-class' name='geniserver.SecureXMLRpcRequestHandler-class'>The SecureXMLRpcRequestHandler Class</a></h2>
+<dl>
+<dt><b>SecureXMLRpcRequestHandler</b> (class)  [<a href='#geniserver.SecureXMLRpcRequestHandler-class'>#</a>]</dt>
+<dd>
+<p>taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler</p>
+</dd>
+</dl>
+<h2><a id='geniserver.SecureXMLRPCServer-class' name='geniserver.SecureXMLRPCServer-class'>The SecureXMLRPCServer Class</a></h2>
+<dl>
+<dt><b>SecureXMLRPCServer(server_address, HandlerClass, key_file, cert_file, logRequests=True)</b> (class) [<a href='#geniserver.SecureXMLRPCServer-class'>#</a>]</dt>
+<dd>
+<p>Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-gid.html b/docs/pythondoc-gid.html
new file mode 100644 (file)
index 0000000..c05dac4
--- /dev/null
@@ -0,0 +1,103 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The gid Module</title>
+</head>
+<body>
+<h1>The gid Module</h1>
+<p>Implements GENI GID. GIDs are based on certificates, and the GID class is a
+descendant of the certificate class.</p>
+<dl>
+<dt><a id='gid.create_uuid-function' name='gid.create_uuid-function'><b>create_uuid()</b></a> [<a href='#gid.create_uuid-function'>#</a>]</dt>
+<dd>
+<p>Create a new uuid. Returns the UUID as a string.</p>
+</dd>
+<dt><b>GID(create=False, subject=None, string=None, filename=None, uuid=None, hrn=None)</b> (class) [<a href='#gid.GID-class'>#</a>]</dt>
+<dd>
+<p>GID is a tuplie:
+   (uuid, hrn, public_key)
+
+UUID is a unique identifier and is created by the python uuid module
+   (or the utility function create_uuid() in gid.py).</p>
+<p>For more information about this class, see <a href='#gid.GID-class'><i>The GID Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='gid.GID-class' name='gid.GID-class'>The GID Class</a></h2>
+<dl>
+<dt><b>GID(create=False, subject=None, string=None, filename=None, uuid=None, hrn=None)</b> (class) [<a href='#gid.GID-class'>#</a>]</dt>
+<dd>
+<p>GID is a tuplie:
+   (uuid, hrn, public_key)
+
+UUID is a unique identifier and is created by the python uuid module
+   (or the utility function create_uuid() in gid.py).
+
+HRN is a human readable name. It is a dotted form similar to a backward domain
+   name. For example, planetlab.us.arizona.bakers.
+
+PUBLIC_KEY is the public key of the principal identified by the UUID/HRN.
+It is a Keypair object as defined in the cert.py module.
+
+It is expected that there is a one-to-one pairing between UUIDs and HRN,
+but it is uncertain how this would be inforced or if it needs to be enforced.
+
+These fields are encoded using xmlrpc into the subjectAltName field of the
+x509 certificate. Note: Call encode() once the fields have been filled in
+to perform this encoding.</p>
+</dd>
+<dt><a id='gid.GID.__init__-method' name='gid.GID.__init__-method'><b>__init__(create=False, subject=None, string=None, filename=None, uuid=None, hrn=None)</b></a> [<a href='#gid.GID.__init__-method'>#</a>]</dt>
+<dd>
+<p>Create a new GID object</p>
+<dl>
+<dt><i>create</i></dt>
+<dd>
+If true, create the X509 certificate</dd>
+<dt><i>subject</i></dt>
+<dd>
+If subject!=None, create the X509 cert and set the subject name</dd>
+<dt><i>string</i></dt>
+<dd>
+If string!=None, load the GID from a string</dd>
+<dt><i>filename</i></dt>
+<dd>
+If filename!=None, load the GID from a file</dd>
+</dl><br />
+</dd>
+<dt><a id='gid.GID.decode-method' name='gid.GID.decode-method'><b>decode()</b></a> [<a href='#gid.GID.decode-method'>#</a>]</dt>
+<dd>
+<p>Decode the subject-alt-name field of the X509 certificate into the
+fields of the GID. This is automatically called by the various get_*()
+functions in this class.</p>
+</dd>
+<dt><a id='gid.GID.dump-method' name='gid.GID.dump-method'><b>dump(indent=0, dump_parents=False)</b></a> [<a href='#gid.GID.dump-method'>#</a>]</dt>
+<dd>
+<p>Dump the credential to stdout.</p>
+<dl>
+<dt><i>indent</i></dt>
+<dd>
+specifies a number of spaces to indent the output</dd>
+<dt><i>dump_parents</i></dt>
+<dd>
+If true, also dump the parents of the GID</dd>
+</dl><br />
+</dd>
+<dt><a id='gid.GID.encode-method' name='gid.GID.encode-method'><b>encode()</b></a> [<a href='#gid.GID.encode-method'>#</a>]</dt>
+<dd>
+<p>Encode the GID fields and package them into the subject-alt-name field
+of the X509 certificate. This must be called prior to signing the
+certificate. It may only be called once per certificate.</p>
+</dd>
+<dt><a id='gid.GID.verify_chain-method' name='gid.GID.verify_chain-method'><b>verify_chain(trusted_certs=None)</b></a> [<a href='#gid.GID.verify_chain-method'>#</a>]</dt>
+<dd>
+<p>Verify the chain of authenticity of the GID. First perform the checks
+of the certificate class (verifying that each parent signs the child,
+etc). In addition, GIDs also confirm that the parent's HRN is a prefix
+of the child's HRN.
+
+Verifying these prefixes prevents a rogue authority from signing a GID
+for a principal that is not a member of that authority. For example,
+planetlab.us.arizona cannot sign a GID for planetlab.us.princeton.foo.</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-hierarchy.html b/docs/pythondoc-hierarchy.html
new file mode 100644 (file)
index 0000000..ec7ee80
--- /dev/null
@@ -0,0 +1,209 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The hierarchy Module</title>
+</head>
+<body>
+<h1>The hierarchy Module</h1>
+<p>This module implements a hierarchy of authorities and performs a similar
+function as the &quot;tree&quot; module of the original geniwrapper prototype. An HRN
+is assumed to be a string of authorities separated by dots. For example,
+&quot;planetlab.us.arizona.bakers&quot;. Each component of the HRN is a different
+authority, with the last component being a leaf in the tree.
+
+Each authority is stored in a subdirectory on the registry. Inside this
+subdirectory are several files:
+     *.GID - GID file
+     *.PKEY - private key file
+     *.DBINFO - database info</p>
+<dl>
+<dt><b>AuthInfo(hrn, gid_filename, privkey_filename, dbinfo_filename)</b> (class) [<a href='#hierarchy.AuthInfo-class'>#</a>]</dt>
+<dd>
+<p>The AuthInfo class contains the information for an authority.</p>
+<p>For more information about this class, see <a href='#hierarchy.AuthInfo-class'><i>The AuthInfo Class</i></a>.</p>
+</dd>
+<dt><b>Hierarchy(basedir=&quot;.&quot;)</b> (class) [<a href='#hierarchy.Hierarchy-class'>#</a>]</dt>
+<dd>
+<p>The Hierarchy class is responsible for managing the tree of authorities.</p>
+<p>For more information about this class, see <a href='#hierarchy.Hierarchy-class'><i>The Hierarchy Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='hierarchy.AuthInfo-class' name='hierarchy.AuthInfo-class'>The AuthInfo Class</a></h2>
+<dl>
+<dt><b>AuthInfo(hrn, gid_filename, privkey_filename, dbinfo_filename)</b> (class) [<a href='#hierarchy.AuthInfo-class'>#</a>]</dt>
+<dd>
+<p>The AuthInfo class contains the information for an authority. This information
+includes the GID, private key, and database connection information.</p>
+</dd>
+<dt><a id='hierarchy.AuthInfo.__init__-method' name='hierarchy.AuthInfo.__init__-method'><b>__init__(hrn, gid_filename, privkey_filename, dbinfo_filename)</b></a> [<a href='#hierarchy.AuthInfo.__init__-method'>#</a>]</dt>
+<dd>
+<p>Initialize and authority object.</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority</dd>
+<dt><i>gid_filename</i></dt>
+<dd>
+the filename containing the GID</dd>
+<dt><i>privkey_filename</i></dt>
+<dd>
+the filename containing the private key</dd>
+<dt><i>dbinfo_filename</i></dt>
+<dd>
+the filename containing the database info</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.AuthInfo.get_dbinfo-method' name='hierarchy.AuthInfo.get_dbinfo-method'><b>get_dbinfo()</b></a> [<a href='#hierarchy.AuthInfo.get_dbinfo-method'>#</a>]</dt>
+<dd>
+<p>Get the dbinfo in the form of a dictionary</p>
+</dd>
+<dt><a id='hierarchy.AuthInfo.get_gid_object-method' name='hierarchy.AuthInfo.get_gid_object-method'><b>get_gid_object()</b></a> [<a href='#hierarchy.AuthInfo.get_gid_object-method'>#</a>]</dt>
+<dd>
+<p>Get the GID in the form of a GID object</p>
+</dd>
+<dt><a id='hierarchy.AuthInfo.get_pkey_object-method' name='hierarchy.AuthInfo.get_pkey_object-method'><b>get_pkey_object()</b></a> [<a href='#hierarchy.AuthInfo.get_pkey_object-method'>#</a>]</dt>
+<dd>
+<p>Get the private key in the form of a Keypair object</p>
+</dd>
+<dt><a id='hierarchy.AuthInfo.set_gid_filename-method' name='hierarchy.AuthInfo.set_gid_filename-method'><b>set_gid_filename(fn)</b></a> [<a href='#hierarchy.AuthInfo.set_gid_filename-method'>#</a>]</dt>
+<dd>
+<p>Set the filename of the GID</p>
+<dl>
+<dt><i>fn</i></dt>
+<dd>
+filename of file containing GID</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.AuthInfo.update_gid_object-method' name='hierarchy.AuthInfo.update_gid_object-method'><b>update_gid_object(gid)</b></a> [<a href='#hierarchy.AuthInfo.update_gid_object-method'>#</a>]</dt>
+<dd>
+<p>Replace the GID with a new one. The file specified by gid_filename is
+overwritten with the new GID object</p>
+<dl>
+<dt><i>gid</i></dt>
+<dd>
+object containing new GID</dd>
+</dl><br />
+</dd>
+</dl>
+<h2><a id='hierarchy.Hierarchy-class' name='hierarchy.Hierarchy-class'>The Hierarchy Class</a></h2>
+<dl>
+<dt><b>Hierarchy(basedir=&quot;.&quot;)</b> (class) [<a href='#hierarchy.Hierarchy-class'>#</a>]</dt>
+<dd>
+<p>The Hierarchy class is responsible for managing the tree of authorities.
+Each authority is a node in the tree and exists as an AuthInfo object.
+
+The tree is stored on disk in a hierarchical manner than reflects the
+structure of the tree. Each authority is a subdirectory, and each subdirectory
+contains the GID, pkey, and dbinfo files for that authority (as well as
+subdirectories for each sub-authority)</p>
+</dd>
+<dt><a id='hierarchy.Hierarchy.auth_exists-method' name='hierarchy.Hierarchy.auth_exists-method'><b>auth_exists(hrn)</b></a> [<a href='#hierarchy.Hierarchy.auth_exists-method'>#</a>]</dt>
+<dd>
+<p>Check to see if an authority exists. An authority exists if it's disk
+files exist.</p>
+<dl>
+<dt><i>the</i></dt>
+<dd>
+human readable name of the authority to check</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.create_auth-method' name='hierarchy.Hierarchy.create_auth-method'><b>create_auth(hrn, create_parents=False)</b></a> [<a href='#hierarchy.Hierarchy.create_auth-method'>#</a>]</dt>
+<dd>
+<p>Create an authority. A private key for the authority and the associated
+GID are created and signed by the parent authority.</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority to create</dd>
+<dt><i>create_parents</i></dt>
+<dd>
+if true, also create the parents if they do not exist</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.create_gid-method' name='hierarchy.Hierarchy.create_gid-method'><b>create_gid(hrn, uuid, pkey)</b></a> [<a href='#hierarchy.Hierarchy.create_gid-method'>#</a>]</dt>
+<dd>
+<p>Create a new GID. The GID will be signed by the authority that is it's
+immediate parent in the hierarchy (and recursively, the parents' GID
+will be signed by its parent)</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name to store in the GID</dd>
+<dt><i>uuid</i></dt>
+<dd>
+the unique identifier to store in the GID</dd>
+<dt><i>pkey</i></dt>
+<dd>
+the public key to store in the GID</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.get_auth_cred-method' name='hierarchy.Hierarchy.get_auth_cred-method'><b>get_auth_cred(hrn)</b></a> [<a href='#hierarchy.Hierarchy.get_auth_cred-method'>#</a>]</dt>
+<dd>
+<p>Retrieve an authority credential for an authority. The authority
+credential will contain the authority privilege and will be signed by
+the authority's parent.</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.get_auth_filenames-method' name='hierarchy.Hierarchy.get_auth_filenames-method'><b>get_auth_filenames(hrn)</b></a> [<a href='#hierarchy.Hierarchy.get_auth_filenames-method'>#</a>]</dt>
+<dd>
+<p>Given a hrn, return the filenames of the GID, private key, and dbinfo
+files.</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.get_auth_info-method' name='hierarchy.Hierarchy.get_auth_info-method'><b>get_auth_info(hrn)</b></a> [<a href='#hierarchy.Hierarchy.get_auth_info-method'>#</a>]</dt>
+<dd>
+<p>Return the AuthInfo object for the specified authority. If the authority
+does not exist, then an exception is thrown. As a side effect, disk files
+and a subdirectory may be created to store the authority.</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority to create.</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.get_auth_ticket-method' name='hierarchy.Hierarchy.get_auth_ticket-method'><b>get_auth_ticket(hrn)</b></a> [<a href='#hierarchy.Hierarchy.get_auth_ticket-method'>#</a>]</dt>
+<dd>
+<p>Retrieve an authority ticket. An authority ticket is not actually a
+redeemable ticket, but only serves the purpose of being included as the
+parent of another ticket, in order to provide a chain of authentication
+for a ticket.
+
+This looks almost the same as get_auth_cred, but works for tickets
+XXX does similarity imply there should be more code re-use?</p>
+<dl>
+<dt><i>hrn</i></dt>
+<dd>
+the human readable name of the authority</dd>
+</dl><br />
+</dd>
+<dt><a id='hierarchy.Hierarchy.refresh_gid-method' name='hierarchy.Hierarchy.refresh_gid-method'><b>refresh_gid(gid, hrn=None, uuid=None, pubkey=None)</b></a> [<a href='#hierarchy.Hierarchy.refresh_gid-method'>#</a>]</dt>
+<dd>
+<p>Refresh a GID. The primary use of this function is to refresh the
+the expiration time of the GID. It may also be used to change the HRN,
+UUID, or Public key of the GID.</p>
+<dl>
+<dt><i>gid</i></dt>
+<dd>
+the GID to refresh</dd>
+<dt><i>hrn</i></dt>
+<dd>
+if !=None, change the hrn</dd>
+<dt><i>uuid</i></dt>
+<dd>
+if !=None, change the uuid</dd>
+<dt><i>pubkey</i></dt>
+<dd>
+if !=None, change the public key</dd>
+</dl><br />
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-import.html b/docs/pythondoc-import.html
new file mode 100644 (file)
index 0000000..0c66c15
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The import Module</title>
+</head>
+<body>
+<h1>The import Module</h1>
+<p>Import PLC records into the Geni database. It is indended that this tool be
+run once to create Geni records that reflect the current state of the
+planetlab database.
+
+The import tool assumes that the existing PLC hierarchy should all be part
+of &quot;planetlab.us&quot; (see the root_auth and level1_auth variables below).
+
+Public keys are extracted from the users' SSH keys automatically and used to
+create GIDs. This is relatively experimental as a custom tool had to be
+written to perform conversion from SSH to OpenSSL format. It only supports
+RSA keys at this time, not DSA keys.</p>
+<dl>
+<dt><a id='import.root_auth-variable' name='import.root_auth-variable'><b>root_auth</b></a> (variable) [<a href='#import.root_auth-variable'>#</a>]</dt>
+<dd>
+<p>Two authorities are specified: the root authority and the level1 authority.</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-record.html b/docs/pythondoc-record.html
new file mode 100644 (file)
index 0000000..97b5d27
--- /dev/null
@@ -0,0 +1,169 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The record Module</title>
+</head>
+<body>
+<h1>The record Module</h1>
+<p>Implements support for geni records
+
+TODO: Use existing PLC database methods? or keep this separate?</p>
+<dl>
+<dt><b>GeniRecord(name=None, gid=None, type=None, pointer=None, dict=None)</b> (class) [<a href='#record.GeniRecord-class'>#</a>]</dt>
+<dd>
+<p>The GeniRecord class implements a Geni Record.</p>
+<p>For more information about this class, see <a href='#record.GeniRecord-class'><i>The GeniRecord Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='record.GeniRecord-class' name='record.GeniRecord-class'>The GeniRecord Class</a></h2>
+<dl>
+<dt><b>GeniRecord(name=None, gid=None, type=None, pointer=None, dict=None)</b> (class) [<a href='#record.GeniRecord-class'>#</a>]</dt>
+<dd>
+<p>The GeniRecord class implements a Geni Record. A GeniRecord is a tuple
+(Name, GID, Type, Info).
+
+Name specifies the HRN of the object
+GID is the GID of the object
+Type is user | sa | ma | slice | component
+
+Info is comprised of the following sub-fields
+       pointer = a pointer to the record in the PL database
+       pl_info = planetlab-specific info (when talking to client)
+       geni_info = geni-specific info (when talking to client)
+
+The pointer is interpreted depending on the type of the record. For example,
+if the type==&quot;user&quot;, then pointer is assumed to be a person_id that indexes
+into the persons table.
+
+A given HRN may have more than one record, provided that the records are
+of different types. For example, planetlab.us.arizona may have both an SA
+and a MA record, but cannot have two SA records.</p>
+</dd>
+<dt><a id='record.GeniRecord.as_dict-method' name='record.GeniRecord.as_dict-method'><b>as_dict()</b></a> [<a href='#record.GeniRecord.as_dict-method'>#</a>]</dt>
+<dd>
+<p>Return the record in the form of a dictionary</p>
+</dd>
+<dt><a id='record.GeniRecord.dump-method' name='record.GeniRecord.dump-method'><b>dump(dump_parents=False)</b></a> [<a href='#record.GeniRecord.dump-method'>#</a>]</dt>
+<dd>
+<p>Dump the record to stdout</p>
+<dl>
+<dt><i>dump_parents</i></dt>
+<dd>
+if true, then the parents of the GID will be dumped</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.get_field_names-method' name='record.GeniRecord.get_field_names-method'><b>get_field_names()</b></a> [<a href='#record.GeniRecord.get_field_names-method'>#</a>]</dt>
+<dd>
+<p>Returns a list of field names in this record. pl_info, geni_info are not
+included because they are not part of the record that is stored in the
+database, but are rather computed values from other entities</p>
+</dd>
+<dt><a id='record.GeniRecord.get_field_value_string-method' name='record.GeniRecord.get_field_value_string-method'><b>get_field_value_string(fieldname)</b></a> [<a href='#record.GeniRecord.get_field_value_string-method'>#</a>]</dt>
+<dd>
+<p>Given a field name (&quot;name&quot;, &quot;gid&quot;, ...) return the value of that field.</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+is the name of field to be returned</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.get_field_value_strings-method' name='record.GeniRecord.get_field_value_strings-method'><b>get_field_value_strings(fieldnames)</b></a> [<a href='#record.GeniRecord.get_field_value_strings-method'>#</a>]</dt>
+<dd>
+<p>Given a list of field names, return a list of values for those fields.</p>
+<dl>
+<dt><i>fieldnames</i></dt>
+<dd>
+is a list of field names</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.get_geni_info-method' name='record.GeniRecord.get_geni_info-method'><b>get_geni_info()</b></a> [<a href='#record.GeniRecord.get_geni_info-method'>#</a>]</dt>
+<dd>
+<p>Return the geni_info of the record, or an empty dictionary if none exists</p>
+</dd>
+<dt><a id='record.GeniRecord.get_gid_object-method' name='record.GeniRecord.get_gid_object-method'><b>get_gid_object()</b></a> [<a href='#record.GeniRecord.get_gid_object-method'>#</a>]</dt>
+<dd>
+<p>Return the GID of the record, in the form of a GID object
+TODO: not the best name for the function, because we have things called
+gidObjects in the Cred</p>
+</dd>
+<dt><a id='record.GeniRecord.get_key-method' name='record.GeniRecord.get_key-method'><b>get_key()</b></a> [<a href='#record.GeniRecord.get_key-method'>#</a>]</dt>
+<dd>
+<p>Return a key that uniquely identifies this record among all records in
+Geni. This key is used to uniquely identify the record in the Geni
+database.</p>
+</dd>
+<dt><a id='record.GeniRecord.get_name-method' name='record.GeniRecord.get_name-method'><b>get_name()</b></a> [<a href='#record.GeniRecord.get_name-method'>#</a>]</dt>
+<dd>
+<p>Return the name (HRN) of the record</p>
+</dd>
+<dt><a id='record.GeniRecord.get_pl_info-method' name='record.GeniRecord.get_pl_info-method'><b>get_pl_info()</b></a> [<a href='#record.GeniRecord.get_pl_info-method'>#</a>]</dt>
+<dd>
+<p>Return the pl_info of the record, or an empty dictionary if none exists</p>
+</dd>
+<dt><a id='record.GeniRecord.get_pointer-method' name='record.GeniRecord.get_pointer-method'><b>get_pointer()</b></a> [<a href='#record.GeniRecord.get_pointer-method'>#</a>]</dt>
+<dd>
+<p>Return the pointer of the record. The pointer is an integer that may be
+used to look up the record in the PLC database. The evaluation of pointer
+depends on the type of the record</p>
+</dd>
+<dt><a id='record.GeniRecord.get_type-method' name='record.GeniRecord.get_type-method'><b>get_type()</b></a> [<a href='#record.GeniRecord.get_type-method'>#</a>]</dt>
+<dd>
+<p>Return the type of the record</p>
+</dd>
+<dt><a id='record.GeniRecord.set_geni_info-method' name='record.GeniRecord.set_geni_info-method'><b>set_geni_info(geni_info)</b></a> [<a href='#record.GeniRecord.set_geni_info-method'>#</a>]</dt>
+<dd>
+<p>Set the geni info the record</p>
+<dl>
+<dt><i>geni_info</i></dt>
+<dd>
+is a dictionary containing geni info</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.set_gid-method' name='record.GeniRecord.set_gid-method'><b>set_gid(gid)</b></a> [<a href='#record.GeniRecord.set_gid-method'>#</a>]</dt>
+<dd>
+<p>Set the GID of the record</p>
+<dl>
+<dt><i>gid</i></dt>
+<dd>
+is a GID object or the string representation of a GID object</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.set_name-method' name='record.GeniRecord.set_name-method'><b>set_name(name)</b></a> [<a href='#record.GeniRecord.set_name-method'>#</a>]</dt>
+<dd>
+<p>Set the name of the record</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+is a string containing the HRN</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.set_pl_info-method' name='record.GeniRecord.set_pl_info-method'><b>set_pl_info(pl_info)</b></a> [<a href='#record.GeniRecord.set_pl_info-method'>#</a>]</dt>
+<dd>
+<p>Set the PLC info of the record</p>
+<dl>
+<dt><i>pl_info</i></dt>
+<dd>
+is a dictionary containing planetlab info</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.set_pointer-method' name='record.GeniRecord.set_pointer-method'><b>set_pointer(pointer)</b></a> [<a href='#record.GeniRecord.set_pointer-method'>#</a>]</dt>
+<dd>
+<p>Set the pointer of the record</p>
+<dl>
+<dt><i>pointer</i></dt>
+<dd>
+is an integer containing the ID of a PLC record</dd>
+</dl><br />
+</dd>
+<dt><a id='record.GeniRecord.set_type-method' name='record.GeniRecord.set_type-method'><b>set_type(type)</b></a> [<a href='#record.GeniRecord.set_type-method'>#</a>]</dt>
+<dd>
+<p>Set the type of the record</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+is a string: user | sa | ma | slice | component</dd>
+</dl><br />
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-registry.html b/docs/pythondoc-registry.html
new file mode 100644 (file)
index 0000000..5bc665e
--- /dev/null
@@ -0,0 +1,433 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The registry Module</title>
+</head>
+<body>
+<h1>The registry Module</h1>
+<p>Geni Registry Wrapper
+
+This wrapper implements the Geni Registry.
+
+There are several items that need to be done before starting the registry.
+
+1) Update util/config.py to match the parameters of your PLC installation.
+
+2) Import the existing planetlab database, creating the
+appropriate geni records. This is done by running the &quot;import.py&quot; tool.
+
+3) Create a &quot;trusted_roots&quot; directory and place the certificate of the root
+authority in that directory. Given the defaults in import.py, this certificate
+would be named &quot;planetlab.gid&quot;. For example,
+
+   mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/</p>
+<dl>
+<dt><a id='registry.geni_fields_to_pl_fields-function' name='registry.geni_fields_to_pl_fields-function'><b>geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields)</b></a> [<a href='#registry.geni_fields_to_pl_fields-function'>#</a>]</dt>
+<dd>
+<p>Convert geni fields to PLC fields for use when registering up updating
+registry record in the PLC database</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+type of record (user, slice, ...)</dd>
+<dt><i>hrn</i></dt>
+<dd>
+human readable name</dd>
+<dt><i>geni_fields</i></dt>
+<dd>
+dictionary of geni fields</dd>
+<dt><i>pl_fields</i></dt>
+<dd>
+dictionary of PLC fields (output)</dd>
+</dl><br />
+</dd>
+<dt><b>Registry(ip, port, key_file, cert_file)</b> (class) [<a href='#registry.Registry-class'>#</a>]</dt>
+<dd>
+<p>Registry is a GeniServer that serves registry requests.</p>
+<p>For more information about this class, see <a href='#registry.Registry-class'><i>The Registry Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='registry.Registry-class' name='registry.Registry-class'>The Registry Class</a></h2>
+<dl>
+<dt><b>Registry(ip, port, key_file, cert_file)</b> (class) [<a href='#registry.Registry-class'>#</a>]</dt>
+<dd>
+<p>Registry is a GeniServer that serves registry requests. It also serves
+component and slice operations that are implemented on the registry
+due to SFA engineering decisions</p>
+</dd>
+<dt><a id='registry.Registry.connect_local_shell-method' name='registry.Registry.connect_local_shell-method'><b>connect_local_shell()</b></a> [<a href='#registry.Registry.connect_local_shell-method'>#</a>]</dt>
+<dd>
+<p>Connect to a local shell via local API functions</p>
+</dd>
+<dt><a id='registry.Registry.connect_remote_shell-method' name='registry.Registry.connect_remote_shell-method'><b>connect_remote_shell()</b></a> [<a href='#registry.Registry.connect_remote_shell-method'>#</a>]</dt>
+<dd>
+<p>Connect to a remote shell via XMLRPC</p>
+</dd>
+<dt><a id='registry.Registry.create_gid-method' name='registry.Registry.create_gid-method'><b>create_gid(cred, name, uuid, pubkey_str)</b></a> [<a href='#registry.Registry.create_gid-method'>#</a>]</dt>
+<dd>
+<p>GENI_API: Create_gid
+
+Create a new GID. For MAs and SAs that are physically located on the
+registry, this allows a owner/operator/PI to create a new GID and have it
+signed by his respective authority.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential of caller</dd>
+<dt><i>name</i></dt>
+<dd>
+hrn for new GID</dd>
+<dt><i>uuid</i></dt>
+<dd>
+unique identifier for new GID</dd>
+<dt><i>pkey_string</i></dt>
+<dd>
+public-key string (TODO: why is this a string and not a keypair object?)</dd>
+<dt>Returns:</dt>
+<dd>
+the string representation of a GID object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.determine_rights-method' name='registry.Registry.determine_rights-method'><b>determine_rights(type, name)</b></a> [<a href='#registry.Registry.determine_rights-method'>#</a>]</dt>
+<dd>
+<p>Determine tje rights that an object should have. The rights are entirely
+dependent on the type of the object. For example, users automatically
+get &quot;refresh&quot;, &quot;resolve&quot;, and &quot;info&quot;.</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+the type of the object (user | sa | ma | slice | node)</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of the object (not used at this time)</dd>
+<dt>Returns:</dt>
+<dd>
+RightList object containing rights</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.fill_record_geni_info-method' name='registry.Registry.fill_record_geni_info-method'><b>fill_record_geni_info(record)</b></a> [<a href='#registry.Registry.fill_record_geni_info-method'>#</a>]</dt>
+<dd>
+<p>Fill in the geni-specific fields of the record.
+
+Note: It is assumed the fill_record_pl_info() has already been performed
+on the record.</p>
+</dd>
+<dt><a id='registry.Registry.fill_record_info-method' name='registry.Registry.fill_record_info-method'><b>fill_record_info(record)</b></a> [<a href='#registry.Registry.fill_record_info-method'>#</a>]</dt>
+<dd>
+<p>Given a Geni record, fill in the PLC-specific and Geni-specific fields
+in the record.</p>
+</dd>
+<dt><a id='registry.Registry.fill_record_pl_info-method' name='registry.Registry.fill_record_pl_info-method'><b>fill_record_pl_info(record)</b></a> [<a href='#registry.Registry.fill_record_pl_info-method'>#</a>]</dt>
+<dd>
+<p>Fill in the planetlab-specific fields of a Geni record. This involves
+calling the appropriate PLC methods to retrieve the database record for
+the object.
+
+PLC data is filled into the pl_info field of the record.</p>
+<dl>
+<dt><i>record</i></dt>
+<dd>
+record to fill in fields (in/out param)</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_auth_info-method' name='registry.Registry.get_auth_info-method'><b>get_auth_info(auth_hrn)</b></a> [<a href='#registry.Registry.get_auth_info-method'>#</a>]</dt>
+<dd>
+<p>Given an authority name, return the information for that authority. This
+is basically a stub that calls the hierarchy module.</p>
+<dl>
+<dt><i>auth_hrn</i></dt>
+<dd>
+human readable name of authority</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_auth_table-method' name='registry.Registry.get_auth_table-method'><b>get_auth_table(auth_name)</b></a> [<a href='#registry.Registry.get_auth_table-method'>#</a>]</dt>
+<dd>
+<p>Given an authority name, return the database table for that authority. If
+the database table does not exist, then one will be automatically
+created.</p>
+<dl>
+<dt><i>auth_name</i></dt>
+<dd>
+human readable name of authority</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_credential-method' name='registry.Registry.get_credential-method'><b>get_credential(cred, type, name)</b></a> [<a href='#registry.Registry.get_credential-method'>#</a>]</dt>
+<dd>
+<p>GENI API: Get_credential
+
+Retrieve a credential for an object.
+
+If cred==None, then the behavior reverts to get_self_credential()</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential object specifying rights of the caller</dd>
+<dt><i>type</i></dt>
+<dd>
+type of object (user | slice | sa | ma | node)</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+<dt>Returns:</dt>
+<dd>
+the string representation of a credental object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_gid-method' name='registry.Registry.get_gid-method'><b>get_gid(name)</b></a> [<a href='#registry.Registry.get_gid-method'>#</a>]</dt>
+<dd>
+<p>GENI API: get_gid
+
+Retrieve the GID for an object. This function looks up a record in the
+registry and returns the GID of the record if it exists.
+TODO: Is this function needed? It's a shortcut for Resolve()</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+hrn to look up</dd>
+<dt>Returns:</dt>
+<dd>
+the string representation of a GID object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_self_credential-method' name='registry.Registry.get_self_credential-method'><b>get_self_credential(type, name)</b></a> [<a href='#registry.Registry.get_self_credential-method'>#</a>]</dt>
+<dd>
+<p>GENI API: Get_self_credential
+
+Get_self_credential a degenerate version of get_credential used by a
+client to get his initial credential when he doesn't have one. This is
+the same as get_credential(..., cred=None,...).
+
+The registry ensures that the client is the principal that is named by
+(type, name) by comparing the public key in the record's GID to the
+private key used to encrypt the client-side of the HTTPS connection. Thus
+it is impossible for one principal to retrieve another principal's
+credential without having the appropriate private key.</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+type of object (user | slice | sa | ma | node</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+<dt>Returns:</dt>
+<dd>
+the string representation of a credential object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.get_ticket-method' name='registry.Registry.get_ticket-method'><b>get_ticket(cred, name, rspec)</b></a> [<a href='#registry.Registry.get_ticket-method'>#</a>]</dt>
+<dd>
+<p>GENI API: get_ticket
+
+Retrieve a ticket. This operation is currently implemented on the
+registry (see SFA, engineering decisions), and is not implemented on
+components.
+
+The ticket is filled in with information from the PLC database. This
+information includes resources, and attributes such as user keys and
+initscripts.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string</dd>
+<dt><i>name</i></dt>
+<dd>
+name of the slice to retrieve a ticket for</dd>
+<dt><i>rspec</i></dt>
+<dd>
+resource specification dictionary</dd>
+<dt>Returns:</dt>
+<dd>
+the string representation of a ticket object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.list-method' name='registry.Registry.list-method'><b>list(cred)</b></a> [<a href='#registry.Registry.list-method'>#</a>]</dt>
+<dd>
+<p>List the records in an authority. The objectGID in the supplied credential
+should name the authority that will be listed.
+
+TODO: List doesn't take an hrn and uses the hrn contained in the
+   objectGid of the credential. Does this mean the only way to list an
+   authority is by having a credential for that authority?</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string specifying rights of the caller</dd>
+<dt>Returns:</dt>
+<dd>
+list of record dictionaries</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.lookup_users-method' name='registry.Registry.lookup_users-method'><b>lookup_users(auth_table, user_id_list, role=&quot;*&quot;)</b></a> [<a href='#registry.Registry.lookup_users-method'>#</a>]</dt>
+<dd>
+<p>Look up user records given PLC user-ids. This is used as part of the
+process for reverse-mapping PLC records into Geni records.</p>
+<dl>
+<dt><i>auth_table</i></dt>
+<dd>
+database table for the authority that holds the user records</dd>
+<dt><i>user_id_list</i></dt>
+<dd>
+list of user ids</dd>
+<dt><i>role</i></dt>
+<dd>
+either &quot;*&quot; or a string describing the role to look for (&quot;pi&quot;, &quot;user&quot;, ...)
+
+TODO: This function currently only searches one authority because it would
+be inefficient to brute-force search all authorities for a user id. The
+solution would likely be to implement a reverse mapping of user-id to
+(type, hrn) pairs.</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.record_to_slice_info-method' name='registry.Registry.record_to_slice_info-method'><b>record_to_slice_info(record)</b></a> [<a href='#registry.Registry.record_to_slice_info-method'>#</a>]</dt>
+<dd>
+<p>Convert a PLC record into the slice information that will be stored in
+a ticket. There are two parts to this information: attributes and
+rspec.
+
+Attributes are non-resource items, such as keys and the initscript
+RSpec is a set of resource specifications</p>
+<dl>
+<dt><i>record</i></dt>
+<dd>
+a record object</dd>
+<dt>Returns:</dt>
+<dd>
+a tuple (attrs, rspec) of dictionaries</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.register-method' name='registry.Registry.register-method'><b>register(cred, record_dict)</b></a> [<a href='#registry.Registry.register-method'>#</a>]</dt>
+<dd>
+<p>GENI API: register
+
+Register an object with the registry. In addition to being stored in the
+Geni database, the appropriate records will also be created in the
+PLC databases</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string</dd>
+<dt><i>record_dict</i></dt>
+<dd>
+dictionary containing record fields</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.register_functions-method' name='registry.Registry.register_functions-method'><b>register_functions()</b></a> [<a href='#registry.Registry.register_functions-method'>#</a>]</dt>
+<dd>
+<p>Register the server RPCs for the registry</p>
+</dd>
+<dt><a id='registry.Registry.remove-method' name='registry.Registry.remove-method'><b>remove(cred, record_dict)</b></a> [<a href='#registry.Registry.remove-method'>#</a>]</dt>
+<dd>
+<p>GENI API: remove
+
+Remove an object from the registry. If the object represents a PLC object,
+then the PLC records will also be removed.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string</dd>
+<dt><i>record_dict</i></dt>
+<dd>
+dictionary containing record fields. The only relevant
+    fields of the record are 'name' and 'type', which are used to lookup
+    the current copy of the record in the Geni database, to make sure
+    that the appopriate record is removed.</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.resolve-method' name='registry.Registry.resolve-method'><b>resolve(cred, name)</b></a> [<a href='#registry.Registry.resolve-method'>#</a>]</dt>
+<dd>
+<p>GENI API: Resolve
+
+This is a wrapper around resolve_raw that converts records objects into
+dictionaries before returning them to the user.</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string authorizing the caller</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name to resolve</dd>
+<dt>Returns:</dt>
+<dd>
+a list of record dictionaries, or an empty list</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.resolve_raw-method' name='registry.Registry.resolve_raw-method'><b>resolve_raw(type, name, must_exist=True)</b></a> [<a href='#registry.Registry.resolve_raw-method'>#</a>]</dt>
+<dd>
+<p>Resolve a record. This is an internal version of the Resolve API call
+and returns records in record object format rather than dictionaries
+that may be sent over XMLRPC.</p>
+<dl>
+<dt><i>type</i></dt>
+<dd>
+type of record to resolve (user | sa | ma | slice | node)</dd>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+<dt><i>must_exist</i></dt>
+<dd>
+if True, throw an exception if no records are found</dd>
+<dt>Returns:</dt>
+<dd>
+a list of record objects, or an empty list []</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.update-method' name='registry.Registry.update-method'><b>update(cred, record_dict)</b></a> [<a href='#registry.Registry.update-method'>#</a>]</dt>
+<dd>
+<p>GENI API: Register
+
+Update an object in the registry. Currently, this only updates the
+PLC information associated with the record. The Geni fields (name, type,
+GID) are fixed.
+
+The record is expected to have the pl_info field filled in with the data
+that should be updated.
+
+TODO: The geni_info member of the record should be parsed and the pl_info
+adjusted as necessary (add/remove users from a slice, etc)</p>
+<dl>
+<dt><i>cred</i></dt>
+<dd>
+credential string specifying rights of the caller</dd>
+<dt><i>record</i></dt>
+<dd>
+a record dictionary to be updated</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.verify_auth_belongs_to_me-method' name='registry.Registry.verify_auth_belongs_to_me-method'><b>verify_auth_belongs_to_me(name)</b></a> [<a href='#registry.Registry.verify_auth_belongs_to_me-method'>#</a>]</dt>
+<dd>
+<p>Verify that an authority belongs to this registry. This is basically left
+up to the implementation of the hierarchy module. If the specified name
+does not belong to this registry, an exception is thrown indicating the
+caller should contact someone else.</p>
+<dl>
+<dt><i>auth_name</i></dt>
+<dd>
+human readable name of authority</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.verify_object_belongs_to_me-method' name='registry.Registry.verify_object_belongs_to_me-method'><b>verify_object_belongs_to_me(name)</b></a> [<a href='#registry.Registry.verify_object_belongs_to_me-method'>#</a>]</dt>
+<dd>
+<p>Verify that an object belongs to this registry. By extension, this implies
+that the authority that owns the object belongs to this registry. If the
+object does not belong to this registry, then an exception is thrown.</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+human readable name of object</dd>
+</dl><br />
+</dd>
+<dt><a id='registry.Registry.verify_object_permission-method' name='registry.Registry.verify_object_permission-method'><b>verify_object_permission(name)</b></a> [<a href='#registry.Registry.verify_object_permission-method'>#</a>]</dt>
+<dd>
+<p>Verify that the object_gid that was specified in the credential allows
+permission to the object 'name'. This is done by a simple prefix test.
+For example, an object_gid for planetlab.us.arizona would match the
+objects planetlab.us.arizona.slice1 and planetlab.us.arizona.</p>
+<dl>
+<dt><i>name</i></dt>
+<dd>
+human readable name to test</dd>
+</dl><br />
+</dd>
+</dl>
+</body></html>
diff --git a/docs/pythondoc-rights.html b/docs/pythondoc-rights.html
new file mode 100644 (file)
index 0000000..cc32dee
--- /dev/null
@@ -0,0 +1,111 @@
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
+<html>
+<head>
+<meta http-equiv='Content-Type' content='text/html; charset=us-ascii' />
+<title>The rights Module</title>
+</head>
+<body>
+<h1>The rights Module</h1>
+<p>This Module implements rights and lists of rights for the Geni wrapper. Rights
+are implemented by two classes:
+
+Right - represents a single right
+
+RightList - represents a list of rights
+
+A right may allow several different operations. For example, the &quot;info&quot; right
+allows &quot;listslices&quot;, &quot;listcomponentresources&quot;, etc.</p>
+<dl>
+<dt><a id='rights.privilege_table-variable' name='rights.privilege_table-variable'><b>privilege_table</b></a> (variable) [<a href='#rights.privilege_table-variable'>#</a>]</dt>
+<dd>
+<p>privilege_table is a list of priviliges and what operations are allowed
+per privilege.</p>
+</dd>
+<dt><b>Right(kind)</b> (class) [<a href='#rights.Right-class'>#</a>]</dt>
+<dd>
+<p>The Right class represents a single privilege.</p>
+<p>For more information about this class, see <a href='#rights.Right-class'><i>The Right Class</i></a>.</p>
+</dd>
+<dt><b>RightList(string=None)</b> (class) [<a href='#rights.RightList-class'>#</a>]</dt>
+<dd>
+<p>A RightList object represents a list of privileges.</p>
+<p>For more information about this class, see <a href='#rights.RightList-class'><i>The RightList Class</i></a>.</p>
+</dd>
+</dl>
+<h2><a id='rights.Right-class' name='rights.Right-class'>The Right Class</a></h2>
+<dl>
+<dt><b>Right(kind)</b> (class) [<a href='#rights.Right-class'>#</a>]</dt>
+<dd>
+<p>The Right class represents a single privilege.</p>
+</dd>
+<dt><a id='rights.Right.can_perform-method' name='rights.Right.can_perform-method'><b>can_perform(op_name)</b></a> [<a href='#rights.Right.can_perform-method'>#</a>]</dt>
+<dd>
+<p>Test to see if this right object is allowed to perform an operation.
+Returns True if the operation is allowed, False otherwise.</p>
+<dl>
+<dt><i>op_name</i></dt>
+<dd>
+is a string naming the operation. For example &quot;listslices&quot;.</dd>
+</dl><br />
+</dd>
+<dt><a id='rights.Right.is_superset-method' name='rights.Right.is_superset-method'><b>is_superset(child)</b></a> [<a href='#rights.Right.is_superset-method'>#</a>]</dt>
+<dd>
+<p>Test to see if this right is a superset of a child right. A right is a
+superset if every operating that is allowed by the child is also allowed
+by this object.</p>
+<dl>
+<dt><i>child</i></dt>
+<dd>
+is a Right object describing the child right</dd>
+</dl><br />
+</dd>
+</dl>
+<h2><a id='rights.RightList-class' name='rights.RightList-class'>The RightList Class</a></h2>
+<dl>
+<dt><b>RightList(string=None)</b> (class) [<a href='#rights.RightList-class'>#</a>]</dt>
+<dd>
+<p>A RightList object represents a list of privileges.</p>
+</dd>
+<dt><a id='rights.RightList.add-method' name='rights.RightList.add-method'><b>add(right)</b></a> [<a href='#rights.RightList.add-method'>#</a>]</dt>
+<dd>
+<p>Add a right to this list</p>
+<dl>
+<dt><i>right</i></dt>
+<dd>
+is either a Right object or a string describing the right</dd>
+</dl><br />
+</dd>
+<dt><a id='rights.RightList.can_perform-method' name='rights.RightList.can_perform-method'><b>can_perform(op_name)</b></a> [<a href='#rights.RightList.can_perform-method'>#</a>]</dt>
+<dd>
+<p>Check to see if some right in this list allows an operation. This is
+done by evaluating the can_perform function of each operation in the
+list.</p>
+<dl>
+<dt><i>op_name</i></dt>
+<dd>
+is an operation to check, for example &quot;listslices&quot;</dd>
+</dl><br />
+</dd>
+<dt><a id='rights.RightList.is_superset-method' name='rights.RightList.is_superset-method'><b>is_superset(child)</b></a> [<a href='#rights.RightList.is_superset-method'>#</a>]</dt>
+<dd>
+<p>Check to see if all of the rights in this rightlist are a superset
+of all the rights in a child rightlist. A rightlist is a superset
+if there is no operation in the child rightlist that cannot be
+performed in the parent rightlist.</p>
+<dl>
+<dt><i>child</i></dt>
+<dd>
+is a rightlist object describing the child</dd>
+</dl><br />
+</dd>
+<dt><a id='rights.RightList.load_from_string-method' name='rights.RightList.load_from_string-method'><b>load_from_string(string)</b></a> [<a href='#rights.RightList.load_from_string-method'>#</a>]</dt>
+<dd>
+<p>Load the rightlist object from a string</p>
+</dd>
+<dt><a id='rights.RightList.save_to_string-method' name='rights.RightList.save_to_string-method'><b>save_to_string()</b></a> [<a href='#rights.RightList.save_to_string-method'>#</a>]</dt>
+<dd>
+<p>Save the rightlist object to a string. It is saved in the format of a
+comma-separated list.</p>
+</dd>
+</dl>
+</body></html>
diff --git a/docs/sfa-2008-08-08.pdf b/docs/sfa-2008-08-08.pdf
new file mode 100644 (file)
index 0000000..bf99df9
Binary files /dev/null and b/docs/sfa-2008-08-08.pdf differ
diff --git a/docs/sfa-2008-11-03.pdf b/docs/sfa-2008-11-03.pdf
new file mode 100644 (file)
index 0000000..6b51abd
Binary files /dev/null and b/docs/sfa-2008-11-03.pdf differ
diff --git a/docs/sfa-impl-2009-04-07.pdf b/docs/sfa-impl-2009-04-07.pdf
new file mode 100644 (file)
index 0000000..2eb49c2
Binary files /dev/null and b/docs/sfa-impl-2009-04-07.pdf differ
diff --git a/docs/sfa-impl.doc b/docs/sfa-impl.doc
new file mode 100644 (file)
index 0000000..5e1aa79
Binary files /dev/null and b/docs/sfa-impl.doc differ
diff --git a/docs/sfa-impl.pdf b/docs/sfa-impl.pdf
new file mode 100644 (file)
index 0000000..16eec40
Binary files /dev/null and b/docs/sfa-impl.pdf differ
diff --git a/docs/sfa.doc b/docs/sfa.doc
new file mode 100644 (file)
index 0000000..55f3c66
Binary files /dev/null and b/docs/sfa.doc differ
diff --git a/docs/sfa.pdf b/docs/sfa.pdf
new file mode 100644 (file)
index 0000000..873731b
Binary files /dev/null and b/docs/sfa.pdf differ
diff --git a/keyconvert/keyconvert.py b/keyconvert/keyconvert.py
new file mode 100755 (executable)
index 0000000..de904ee
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+import sys
+import base64
+import struct
+import binascii
+from M2Crypto import RSA, DSA, m2
+
+
+###### Workaround for bug in m2crypto-0.18 (on Fedora 8)
+class RSA_pub_fix(RSA.RSA_pub):
+    def save_key_bio(self, bio, *args, **kw):
+        return self.save_pub_key_bio(bio)
+
+def rsa_new_pub_key((e, n)):
+    rsa = m2.rsa_new()
+    m2.rsa_set_e(rsa, e)
+    m2.rsa_set_n(rsa, n)
+    return RSA_pub_fix(rsa, 1)
+######
+#rsa_new_pub_key = RSA.new_pub_key
+
+
+def decode_key(fname):
+    """Convert base64 encoded openssh key to binary"""
+    contents = open(fname).read()
+    fields = contents.split()
+
+    in_key = False
+    for f in fields:
+        f = f.strip()
+        if f.startswith("ssh-"):
+            in_key = True
+            continue
+        elif in_key:
+            return base64.b64decode(f)
+        
+    return None
+
+
+# openssh binary key format
+#
+# a section:
+# length = 4 bytes (32-bit big-endian integer)
+# data = length bytes of string 
+# 
+# sections of the key ( for RSA )
+# [key-type (in ASCII)] [public exponent (bignum)] [primes (bignum)]
+#
+# sections of the key ( for DSA )
+# [key-type (in ASCII)] [p (bignum)] [q (bignum)] [g (bignum)] [y (bignum)]
+#
+# - baris
+def read_key(key):
+    
+    def read_length(key):
+        length = key[0:4]
+        length = struct.unpack(">l", length)[0]
+        return length, key
+        
+    def read_values(key, count):
+        v = []
+        for i in range(count):
+            length, key = read_length(key)
+            size = 4 + length
+            v.append(key[:size])
+            key = key[size:]
+        return v
+
+    length, key = read_length(key)
+    key = key[4:]
+    key_type = key[:length]
+    key = key[length:]
+
+    if key_type == "ssh-rsa":
+        # prepare parameters for RSA.new_pub_key
+        v = read_values(key, 2)
+        e, n = v[0], v[1]
+        return key_type, e, n
+
+    elif key_type == "ssh-dss":
+        # prepare parameters for DSA.set_params
+        v = read_values(key, 4)
+        p, q, g, y = v[0], v[1], v[2], v[3]
+        return key_type, p, q, g, y
+
+
+def convert(fin, fout):
+    key = decode_key(fin)
+    ret = read_key(key)
+    key_type = ret[0]
+
+    if key_type == "ssh-rsa":
+        e, n = ret[1:]
+        rsa = rsa_new_pub_key((e, n))
+        rsa.save_pem(fout)
+
+    elif key_type == "ssh-dss":
+        p, q, g, y = ret[1:]
+        dsa = DSA.set_params(p, q, g)
+        dsa.gen_key()
+        dsa.save_pub_key(fout)
+        # FIXME: This is wrong.
+        # M2Crypto doesn't allow us to set the public key parameter
+        raise(Exception, "DSA keys are not supported yet: M2Crypto doesn't allow us to set the public key parameter")
+
+
+if __name__ == "__main__":
+    if len(sys.argv) != 3:
+        print "Usage: %s <input-file> <output-file>"
+        sys.exit(1)
+
+    fin = sys.argv[1]
+    fout = sys.argv[2]
+    convert(fin, fout)
diff --git a/keyconvert/test.sh b/keyconvert/test.sh
new file mode 100755 (executable)
index 0000000..5c5744a
--- /dev/null
@@ -0,0 +1,33 @@
+# this scripts tests the key conversion routines
+# it converts the _pub files in test/ from openssh to openssl
+# it then verifies signatures with openssl (see keytest.sh)
+
+rm -rf testout
+mkdir testout
+
+# rsa1 keys
+# these are in a different format
+#./keyconvert.py test/openssh_rsa1_512.pub testout/openssl_rsa1_512.pem
+#./keyconvert.py test/openssh_rsa1_1024.pub testout/openssl_rsa1_1024.pem
+#./keyconvert.py test/openssh_rsa1_2048.pub testout/openssl_rsa1_2048.pem
+
+# rsa2 keys
+./keyconvert.py test/openssh_rsa_512.pub testout/openssl_rsa_512.pem
+./keyconvert.py test/openssh_rsa_1024.pub testout/openssl_rsa_1024.pem
+./keyconvert.py test/openssh_rsa_2048.pub testout/openssl_rsa_2048.pem
+
+# dsa keys
+./keyconvert.py test/openssh_dsa_512.pub testout/openssl_dsa_512.pem
+./keyconvert.py test/openssh_dsa_1024.pub testout/openssl_dsa_1024.pem
+./keyconvert.py test/openssh_dsa_2048.pub testout/openssl_dsa_2048.pem
+
+# make a test file to encrypt
+echo "this is a test to see if the key conversion routines work" > test.txt
+
+# test the keys
+./testkey.sh -sha1 rsa_512
+./testkey.sh -sha1 rsa_1024
+./testkey.sh -sha1 rsa_2048
+./testkey.sh -dss1 dsa_512
+./testkey.sh -dss1 dsa_1024
+./testkey.sh -dss1 dsa_2048
diff --git a/keyconvert/test.txt b/keyconvert/test.txt
new file mode 100644 (file)
index 0000000..0bd7ba1
--- /dev/null
@@ -0,0 +1 @@
+this is a test to see if the key conversion routines work
diff --git a/keyconvert/test/openssh_dsa_1024 b/keyconvert/test/openssh_dsa_1024
new file mode 100644 (file)
index 0000000..f6c3f7d
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvQIBAAKBgQDx32kPfS+uiVq6pqgEGORLZx0EfSXvlluD/nKtLQoBxDY7JntP
+oVyI29KPVSnsjqEAVI7r9pEJ/aszrc4jLj5dbjqehLF5qnkrr/XAcTv4MsxKuyDy
+6R0AhU91YrkChO8JedxbUdf6lI3DLREaRIvLhHew05k34W0dGdEV/U/hvQIVANT3
+PYXSBKIShJovHfC4YQ65kOy3AoGBANs/BTSWWUEs3jppa65VZV/8WbV7Q3uV9PVu
+Ljf6E9DKFI5PPbJOMPbX7XF3zrCRtiTVl1dTc/xH7LYXvHTVU469mRpdNDwCMF5r
+y1bWbVVKhIrcrlVnEF3+up2jD7Xu+tfIn/YmJyrHhvKV842HLv+LAbUJbMQ/9POt
+5w9RufrNAoGBAJYsrPd4VhUQwTzWW/LQmIevjT/X5T2putWXttsZ1KzMP2qi9rf8
+GATnPJahYq1DXgNSPcqIHF5FUgVEITJ3R/CTCFiKF/+N5yBzEXmbo+HGG8W1hKnp
+QdzVKZq0esGwEKfkZ0lUPJCh5IYyRL8ceh8KRjEFSKuSEPZqwrngklw0AhUAg8tD
+T7VLBFkDCC7bhsKqFkJ4AAo=
+-----END DSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_dsa_1024.pub b/keyconvert/test/openssh_dsa_1024.pub
new file mode 100644 (file)
index 0000000..d81b006
--- /dev/null
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPHfaQ99L66JWrqmqAQY5EtnHQR9Je+WW4P+cq0tCgHENjsme0+hXIjb0o9VKeyOoQBUjuv2kQn9qzOtziMuPl1uOp6EsXmqeSuv9cBxO/gyzEq7IPLpHQCFT3ViuQKE7wl53FtR1/qUjcMtERpEi8uEd7DTmTfhbR0Z0RX9T+G9AAAAFQDU9z2F0gSiEoSaLx3wuGEOuZDstwAAAIEA2z8FNJZZQSzeOmlrrlVlX/xZtXtDe5X09W4uN/oT0MoUjk89sk4w9tftcXfOsJG2JNWXV1Nz/Efsthe8dNVTjr2ZGl00PAIwXmvLVtZtVUqEityuVWcQXf66naMPte7618if9iYnKseG8pXzjYcu/4sBtQlsxD/0863nD1G5+s0AAACBAJYsrPd4VhUQwTzWW/LQmIevjT/X5T2putWXttsZ1KzMP2qi9rf8GATnPJahYq1DXgNSPcqIHF5FUgVEITJ3R/CTCFiKF/+N5yBzEXmbo+HGG8W1hKnpQdzVKZq0esGwEKfkZ0lUPJCh5IYyRL8ceh8KRjEFSKuSEPZqwrngklw0 smbaker@test64
diff --git a/keyconvert/test/openssh_dsa_2048 b/keyconvert/test/openssh_dsa_2048
new file mode 100644 (file)
index 0000000..b43eb65
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIDPgIBAAKCAQEAhvJpPI7AbHEyXauGmXyCGsVCkTXD4i9b/eobbwG1Hhm8FvtM
+wq5oopWItMH0HMcKfBGmHlp46u5KJcYmrSsHSDX+6n9+Mw/Pn2Sldg26OTkyfsaV
+qelapH0Q3ILt8fTrHosy9aosBiZbu4wPdjiBs441ekhGpZfsdaa3g657VGSG1YqH
+S/eMw7fs068Vsp5ickYCapI+XICFnf2lQesC4gfElDRGOG3/6n0Pkhsw0TwXRgka
+GgXfxHR4CPIlH7fV1/QIYXLXRU8ftkrwakIltSrl50pQOXDuCOGR49ng2JaxFdWv
+sjUundZsmnWjGFFE2ORrTBFHagmwOsQXY4vOXwIVAJtUPdFxKVNC2t89reLuOGao
+FFxJAoIBAQCAZYog0D72F0TIAIMIahnDDNjN2T3Sb3Zqhd27oExdUpbNDbM8F+qY
+HpqJRtQRc59sSsroif+K0yEaeReOapEb6Cf1N4yGDppr1ClMBGHKk/Wsjf/ASqa8
+/mibtSF4P5I6e0w8FWrTrhu2vJ9W2ONRqVAUxgBMsEKXFXvkYtf31LKsQFUEcmtO
+uXalXiKAdr+APQya6lDiUxGELjuNzwsGxjdo15wa6NcKLfHUv3XYU6WxL2USGFBW
+5I1aHu/Uc7YGhl2I9fSulzotEc4PV40nlC54NVx65pQjeViJ4ZSSlrSm58xxfrdF
+TnWvEJjSu6CAFYfWEzx5RtbjuWMk6yAdAoIBAFx7TsT+G3sryKRs4DRWSSlU8PAO
+2I326UJps4CiiAS6x6XGnB9kLX3E/xfdmM9mrhQBNn0OPo5iCK2me1W2sXQ6QWD/
+dRhKwIcrHVexwu5iMtoMtMAeDI5P7/ahVSIxDAKkABD4hDJB3UcTDYXaz1/bNqW6
+FXYK2C9CrfhYF3NX28MU+eJhIFRpEaVU42bQ5OeDaJiz2tQeSrT5LqbV58RhARew
+9tK1FOCBTBsJ+bBr4Q5f9bFNpSqqn513H/cc6qkuRCluxeGTAtNt5qdHYH+rvxey
+FpBzy8MmXkHxsQWxZ1DAkaUgPaxXevNQRk/iNsk1W3zWqJ72KI5lNFgJ9yECFA/o
+gWjBm06JTkYWBOgkYRRVRzr7
+-----END DSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_dsa_2048.pub b/keyconvert/test/openssh_dsa_2048.pub
new file mode 100644 (file)
index 0000000..69d00b6
--- /dev/null
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAAEBAIbyaTyOwGxxMl2rhpl8ghrFQpE1w+IvW/3qG28BtR4ZvBb7TMKuaKKViLTB9BzHCnwRph5aeOruSiXGJq0rB0g1/up/fjMPz59kpXYNujk5Mn7GlanpWqR9ENyC7fH06x6LMvWqLAYmW7uMD3Y4gbOONXpIRqWX7HWmt4Oue1RkhtWKh0v3jMO37NOvFbKeYnJGAmqSPlyAhZ39pUHrAuIHxJQ0Rjht/+p9D5IbMNE8F0YJGhoF38R0eAjyJR+31df0CGFy10VPH7ZK8GpCJbUq5edKUDlw7gjhkePZ4NiWsRXVr7I1Lp3WbJp1oxhRRNjka0wRR2oJsDrEF2OLzl8AAAAVAJtUPdFxKVNC2t89reLuOGaoFFxJAAABAQCAZYog0D72F0TIAIMIahnDDNjN2T3Sb3Zqhd27oExdUpbNDbM8F+qYHpqJRtQRc59sSsroif+K0yEaeReOapEb6Cf1N4yGDppr1ClMBGHKk/Wsjf/ASqa8/mibtSF4P5I6e0w8FWrTrhu2vJ9W2ONRqVAUxgBMsEKXFXvkYtf31LKsQFUEcmtOuXalXiKAdr+APQya6lDiUxGELjuNzwsGxjdo15wa6NcKLfHUv3XYU6WxL2USGFBW5I1aHu/Uc7YGhl2I9fSulzotEc4PV40nlC54NVx65pQjeViJ4ZSSlrSm58xxfrdFTnWvEJjSu6CAFYfWEzx5RtbjuWMk6yAdAAABAFx7TsT+G3sryKRs4DRWSSlU8PAO2I326UJps4CiiAS6x6XGnB9kLX3E/xfdmM9mrhQBNn0OPo5iCK2me1W2sXQ6QWD/dRhKwIcrHVexwu5iMtoMtMAeDI5P7/ahVSIxDAKkABD4hDJB3UcTDYXaz1/bNqW6FXYK2C9CrfhYF3NX28MU+eJhIFRpEaVU42bQ5OeDaJiz2tQeSrT5LqbV58RhARew9tK1FOCBTBsJ+bBr4Q5f9bFNpSqqn513H/cc6qkuRCluxeGTAtNt5qdHYH+rvxeyFpBzy8MmXkHxsQWxZ1DAkaUgPaxXevNQRk/iNsk1W3zWqJ72KI5lNFgJ9yE= smbaker@test64
diff --git a/keyconvert/test/openssh_dsa_512 b/keyconvert/test/openssh_dsa_512
new file mode 100644 (file)
index 0000000..003f01c
--- /dev/null
@@ -0,0 +1,8 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIH4AgEAAkEAm3PK+DSgbavp8uFzwHkLBQkAQ+zYH7oQQfVf9qfj9McPwPSRjyE2
+euX37kG9V+8fMYGkrXiAnTF338/7l/BitQIVAPhdznYDxCFE+K7Fkmric3uRLhKl
+AkASi9+yBXkSh/PBIB2VTwUNJ2Je2rMT/KArLw86dQuMdV2QPnCI4jwvFeWJW8q1
+CSKyBcwoxaSwPN6MAYNRLmPtAkA/5M4TgOWQjoUDc1DwwAFzl8kXc8Jywyz3Ck1a
+uCgs+unOttuDT+ZYicDRSnq7G3paTGm5U9h3Pf4BOzqKymxmAhUAsvh3vpJX52p7
+WTaXKaBm7ysblAE=
+-----END DSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_dsa_512.pub b/keyconvert/test/openssh_dsa_512.pub
new file mode 100644 (file)
index 0000000..a4a4f6e
--- /dev/null
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAABBAJtzyvg0oG2r6fLhc8B5CwUJAEPs2B+6EEH1X/an4/THD8D0kY8hNnrl9+5BvVfvHzGBpK14gJ0xd9/P+5fwYrUAAAAVAPhdznYDxCFE+K7Fkmric3uRLhKlAAAAQBKL37IFeRKH88EgHZVPBQ0nYl7asxP8oCsvDzp1C4x1XZA+cIjiPC8V5YlbyrUJIrIFzCjFpLA83owBg1EuY+0AAABAP+TOE4DlkI6FA3NQ8MABc5fJF3PCcsMs9wpNWrgoLPrpzrbbg0/mWInA0Up6uxt6WkxpuVPYdz3+ATs6ispsZg== smbaker@test64
diff --git a/keyconvert/test/openssh_rsa1_1024 b/keyconvert/test/openssh_rsa1_1024
new file mode 100644 (file)
index 0000000..1d814be
Binary files /dev/null and b/keyconvert/test/openssh_rsa1_1024 differ
diff --git a/keyconvert/test/openssh_rsa1_1024.pub b/keyconvert/test/openssh_rsa1_1024.pub
new file mode 100644 (file)
index 0000000..8e8d308
--- /dev/null
@@ -0,0 +1 @@
+1024 35 129881281989970257849297143265405883841808716551531717161470388680233069834806491515662719447667414192316994820475038514678885956467447964923197868990351993958234384078203216494595575140725420329195654521080877033819243506183873392482029450927674981321293693168525102276485740189208563187461931348496284073193 smbaker@test64
diff --git a/keyconvert/test/openssh_rsa1_2048 b/keyconvert/test/openssh_rsa1_2048
new file mode 100644 (file)
index 0000000..adf13a2
Binary files /dev/null and b/keyconvert/test/openssh_rsa1_2048 differ
diff --git a/keyconvert/test/openssh_rsa1_2048.pub b/keyconvert/test/openssh_rsa1_2048.pub
new file mode 100644 (file)
index 0000000..8e44071
--- /dev/null
@@ -0,0 +1 @@
+2048 35 24962157904933386430289996182561670350750587808098241441684009673343331428018121642773893758528257864455887921889391600744909650863357446121763274135964860311094796038726741333109129308021024853945167223976961404644818452025130516204197410684388539496264836113893019917274320123849831069149349406430045852390329296737321686898170507200472856028425719837216148581236896907337787170590524504382167005872101057744617682247988500610477193140983913123507527384148704894663287519370503887384332696230450497547440681808464617977849888981033678132856675421465284453029930637905017868780839285125352782073857421780734978493573 smbaker@test64
diff --git a/keyconvert/test/openssh_rsa1_512 b/keyconvert/test/openssh_rsa1_512
new file mode 100644 (file)
index 0000000..4a353cb
Binary files /dev/null and b/keyconvert/test/openssh_rsa1_512 differ
diff --git a/keyconvert/test/openssh_rsa1_512.pub b/keyconvert/test/openssh_rsa1_512.pub
new file mode 100644 (file)
index 0000000..5bc93a4
--- /dev/null
@@ -0,0 +1 @@
+512 35 13061984420212984636582394800944353832030760610842910680821680254617574712157058435404440827653567296052614504017646755849229554697341079808835532254398769 smbaker@test64
diff --git a/keyconvert/test/openssh_rsa_1024 b/keyconvert/test/openssh_rsa_1024
new file mode 100644 (file)
index 0000000..0ec5529
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDcH11wYBhzzPX3cqz2Wc+tVks0RMoWBCW+aWgvW0I8/Qqf03xo
+a2ocNu8eFeRNTmcnp44SH/AnXmWXxFGigC76R4b//VZZ0L5bITI605JoctdLDndq
+cvZdV23neYv/xr6d8umtmsMn79EunFPoamv2ByjY1Ro4HqqrlpaA+ZoZWwIBIwKB
+gDiaWdsRZV+bGq1YAJceaJpJY8ucqP5Y1oFrj9GpwJNXArtiRI/hG0kVcK/3ADEi
+yhGC22O/Ed482EuY4cqzP0cxwdUxMF/QTSd9DWXPbmzSjRaMZcANUtCVizLWn88K
+IVIg2vRhZqpivyxTgPVCCLhUPirLAQld8qVQ3M3RZe5zAkEA+YlG16DO8QTAEIEu
+NhFQpcrTVrjj2JqbImg6TeiBedJ1qFyfwHEvT3eTb2pjdNq1pE5kdW85fLIznPvJ
+4vuAHwJBAOHTCkn5a5k4OmNEDuiaxe+SdVzdf0oZs32vdbAZkgkVPjyO5bTfKSBV
+tegs559vVxs+BthuExgsZtuVXcvZT0UCQQCj+x/syMKBIGD05yz+9W+C5F7+eX/I
+2qB1sjTxXkZ0oD6wd2GqWQHVIrFX2DK6gRhHZrcLV7gQHVUeBIv7j1QxAkEArjUW
+kNZaUaBnjmezusCKEJWNvKreiZ7MSwOyjymrK5QLcIt+AI7lNjN9sxtTmD/yvT56
+TzBX33K1v1X35mXPbwJAX/NeuLrH8m/EVPrskD3nFlEg69zaB5x4EjU6GtvRW7Gz
+Qo61Lj/KAbbKS7J0GFORm7gAuNWVAwiFxjbdBfnhAw==
+-----END RSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_rsa_1024.pub b/keyconvert/test/openssh_rsa_1024.pub
new file mode 100644 (file)
index 0000000..a1b8587
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA3B9dcGAYc8z193Ks9lnPrVZLNETKFgQlvmloL1tCPP0Kn9N8aGtqHDbvHhXkTU5nJ6eOEh/wJ15ll8RRooAu+keG//1WWdC+WyEyOtOSaHLXSw53anL2XVdt53mL/8a+nfLprZrDJ+/RLpxT6Gpr9gco2NUaOB6qq5aWgPmaGVs= smbaker@test64
diff --git a/keyconvert/test/openssh_rsa_2048 b/keyconvert/test/openssh_rsa_2048
new file mode 100644 (file)
index 0000000..4b32b5c
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAzKF8Ty+W800JzUEi74sw91ghB9ypEd00pFRluUJI0+6RC9EW
+TBfH1PT3N7RsICuO4L3M8UGJUAnNfqXT92wbwZ2ECHB/CyKqyuBigVOZii4tJXj4
+LIeszk5qDkVfgxFr8BaKiMn8dt0z7VLS4bEDVSrJil3B8pWDMeyv8weky0vziuFL
+1j2W5daJg72lh0CxSQWtsKD6/ZsZB/DqqdfSS3K28yRJk6Z8DfWpo1XfG6ZTR8u6
+C1h/Ebi35jA1JxrAYp2r42P8xJDrXJkOlo92aZa/Dk5lc961OCiycIuZ8zahIXDr
+muomuuQrP0nUoX+rmCFnxFgLgCia29DU0E1pvwIBIwKCAQEAuxdNFTLTJ5bkY+PI
+K3fyQTqh201nYMo+wiFHD8eTCui/IL8qVDL/2KV0UC/t1EUU6rwpFxdY+LiBXdlx
+Vz5FQ04vkq//IB+yGJKjNGmw811rGvJB/NPRMabHXYFBYeQK6iM81NXuI4hp/Y2N
+myV/Y89ZLgyUEP25xz7MwPELsozy10Jd07XNPvkwviaW10knAHAQY/RwwATCyrWk
+uhcMYyvrEpvK+jw93D9iSVxFtOrVn2JKnd0mXZTumye3DHUyR6uy80+dnrE+GKiR
+QlCIA51i1WBNA1/IerTpsUdURA+JM1L+i6s+XvT5mr5V31Mau7MQmayRySS8ImSQ
+XBVeiwKBgQDrWPUOITu/6btSdl9V+dgK2YEidrVcOfZA7K3JqssHuCk0pDcP/k0m
+eQ9I/t692YSMxqmcewzzeJ8p65ec67l4pdwdQJwxqfEyaUSmdzMhr9FXU1R+pIcI
+u9yypkG444kHFC7iu2Oh4+aCG265FF+LWh6aC4lVn+KjvLCEU/GIYwKBgQDelnun
+FXL+fzqpvWQakfCjxwn5TJhDcZ+jDYx004OVBtF5MpLTk6dx7AENJDo1BEDs2s6b
+86Gpor6pAOUoBcEYvl3C6Ki2tQ3dCFwJPtQ/9dFDoZiSk6bpNhYgMGPc5Tyb/R9C
+dsM4nyG4IprtoN07ALbzkLn2bEXJWYJSl6SB9QKBgBrlmFli4kHRkcBICuVBIAE9
+bdgNkRHavQ679pq0bvmRY8uAe1JI8t/TUjQ6YplasBAWtExl1ZgrCuA4LpWXSGWP
+TFsdU63niUeXAIgNn3GQb7I1aL4ELLCKf59jdTmzmqG5Ke4GyY7YN5nXP9qjPh6N
+9N5nt+yHTRoG75oYONxjAoGBAKu16lxSYAYnokhth7y5uaLqAGE7Fl/p8C1TmDzd
+rqYp1Ms1pHdUpb5IWJUb8mNprm2S2eYFFk+pboJfxrh5d7QAkXkZ4Tx9CrHTP69c
+Wpe9oW6v3BlNVN/Ko1qo/JR2U1PDRAASeVeQs5ys+yxXhheS1kbdXEHBPSZMXThm
+WlWfAoGAGZSqAK/6vz0vyUcPlxeQLKjibYO3AmQQ5BxxvzA+Da59f8IrvAa9ujAR
+ISpIIo4l+DeEDumPVHWjz2W7ho3tPZ3IyXFUUtnh+Rcor9rxw6/gXzaoTlodjvve
+0woZJu0OoYFjQMPm1jlR6ekwm2Ep97I05IaFMDPDhZJUw2sIPRM=
+-----END RSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_rsa_2048.pub b/keyconvert/test/openssh_rsa_2048.pub
new file mode 100644 (file)
index 0000000..47c6f4c
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzKF8Ty+W800JzUEi74sw91ghB9ypEd00pFRluUJI0+6RC9EWTBfH1PT3N7RsICuO4L3M8UGJUAnNfqXT92wbwZ2ECHB/CyKqyuBigVOZii4tJXj4LIeszk5qDkVfgxFr8BaKiMn8dt0z7VLS4bEDVSrJil3B8pWDMeyv8weky0vziuFL1j2W5daJg72lh0CxSQWtsKD6/ZsZB/DqqdfSS3K28yRJk6Z8DfWpo1XfG6ZTR8u6C1h/Ebi35jA1JxrAYp2r42P8xJDrXJkOlo92aZa/Dk5lc961OCiycIuZ8zahIXDrmuomuuQrP0nUoX+rmCFnxFgLgCia29DU0E1pvw== smbaker@test64
diff --git a/keyconvert/test/openssh_rsa_512 b/keyconvert/test/openssh_rsa_512
new file mode 100644 (file)
index 0000000..bd980ff
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOAIBAAJBAMRoeFn+NZYWZxbCMk1+kVCFHQrI9mpASrc0uDX/ZF45ymcrSjCM
+E+IeTq82wpoTcCY93vjSma6L6eO1PRePqvkCASMCQQC5L0znIuIRKxC9r8kHPNIn
+WO9/MoHvJqzYn2vbI/+MCPFrDFELJF4lV6CrVYi105n2KydKPN13NvdKzdbg0JoL
+AiEA9y1JKDVsxd38kcZLsmamBqsVT0nHj4kAF50pniD+EokCIQDLa0Gs/tHx7Aqe
+I69gLMNKETjaYE5dUcXjRMiURjB48QIgW8796lz8hAH+uc1PUORMS56hg9mTQ/EO
+qa9nOrvMFYMCIG5tbMugRhWdZNmBFg+jRXFZzmfrIzn5MOkWtgdZTYN7AiA9ckb9
+BnkGtXbl/dn7ZQ7ITS8/5dlBzM+9e16LXujjAg==
+-----END RSA PRIVATE KEY-----
diff --git a/keyconvert/test/openssh_rsa_512.pub b/keyconvert/test/openssh_rsa_512.pub
new file mode 100644 (file)
index 0000000..62e9dbf
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAEEAxGh4Wf41lhZnFsIyTX6RUIUdCsj2akBKtzS4Nf9kXjnKZytKMIwT4h5OrzbCmhNwJj3e+NKZrovp47U9F4+q+Q== smbaker@test64
diff --git a/keyconvert/testkey.sh b/keyconvert/testkey.sh
new file mode 100755 (executable)
index 0000000..f8bd69b
--- /dev/null
@@ -0,0 +1,6 @@
+# do not execute this script directly; it is run by test.sh
+
+rm -rf signature.bin
+openssl dgst $1 -sign test/openssh_$2 test.txt > signature.bin
+openssl dgst $1 -signature signature.bin -verify testout/openssl_$2.pem test.txt
+rm -rf signature.bin
diff --git a/rspec/model/Registry.ecore b/rspec/model/Registry.ecore
new file mode 100644 (file)
index 0000000..ae7ecac
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0"
+    xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="Registry"
+    nsURI="http://www.planet-lab.org/Registry" nsPrefix="Registry">
+  <eClassifiers xsi:type="ecore:EClass" name="UserRecord" eSuperTypes="#//RegistryRecord">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="email" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="first_name" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="last_name" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="phone" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="key" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="slice" upperBound="-1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" defaultValueLiteral=""/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="SliceRecord" eSuperTypes="#//RegistryRecord">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="url" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="expires" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="researcher" lowerBound="1"
+        upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="description" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        iD="true"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="NodeRecord" eSuperTypes="#//RegistryRecord">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="hostname" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" defaultValueLiteral=""/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="node_type" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" defaultValueLiteral="&quot;user&quot;"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="latitude" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="longitude" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
+        iD="true"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="Authority" eSuperTypes="#//RegistryRecord">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="last_updated" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="url" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="researcher" upperBound="-1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="operator" lowerBound="1"
+        upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="PI" lowerBound="1" upperBound="-1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="RegistryRecord">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="hrn" ordered="false" lowerBound="1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="gid" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="type" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        defaultValueLiteral=""/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="created" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"
+        iD="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="last_updated" ordered="false"
+        lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"
+        iD="true"/>
+  </eClassifiers>
+</ecore:EPackage>
diff --git a/rspec/model/planetlab.ecore b/rspec/model/planetlab.ecore
new file mode 100644 (file)
index 0000000..68dd1a7
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0"
+    xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="planetlab"
+    nsURI="http://www.planet-lab.org" nsPrefix="pl">
+  <eClassifiers xsi:type="ecore:EClass" name="RSpec">
+    <eStructuralFeatures xsi:type="ecore:EReference" name="networks" lowerBound="1"
+        upperBound="-1" eType="#//NetSpec"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="start_time" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="duration" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="NetSpec">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="nodes" lowerBound="1" upperBound="-1"
+        eType="#//NodeSpec"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="links" upperBound="-1"
+        eType="#//LinkSpec" containment="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="start_time" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="duration" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="NodeSpec">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" upperBound="1000"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="type" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        defaultValueLiteral="&quot;&quot;"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="init_params" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EByteArray"
+        defaultValueLiteral=""/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="cpu_min" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="cpu_share" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="cpu_pct" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="disk_max" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="net_if" lowerBound="1"
+        upperBound="-1" eType="#//IfSpec"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="start_time" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="duration" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="LinkSpec">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="type" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        changeable="false" defaultValueLiteral="&quot;&quot;"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="init_params" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EByteArray"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="bw" upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="min_alloc" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="max_alloc" upperBound="-1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EReference" name="endpoint" lowerBound="1"
+        upperBound="-1" eType="#//IfSpec" changeable="false"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="start_time" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="duration" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDate"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="IfSpec">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="addr" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="type" upperBound="-2" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
+        changeable="false" defaultValueLiteral=""/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="init_params" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EByteArray"
+        defaultValueLiteral=""/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="min_rate" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" defaultValueLiteral="0"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="max_rate" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"
+        defaultValueLiteral="12207"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="max_kbyte" upperBound="-1"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" defaultValueLiteral="5452595"
+        unsettable="true"/>
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="ip_spoof" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"
+        defaultValueLiteral="false"/>
+  </eClassifiers>
+  <eClassifiers xsi:type="ecore:EClass" name="SwitchSpec" eSuperTypes="#//NodeSpec">
+    <eStructuralFeatures xsi:type="ecore:EAttribute" name="fib_max" upperBound="-2"
+        eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" defaultValueLiteral="0"/>
+  </eClassifiers>
+</ecore:EPackage>
diff --git a/rspec/model/planetlab.ecore_diagram b/rspec/model/planetlab.ecore_diagram
new file mode 100644 (file)
index 0000000..df5642b
--- /dev/null
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<notation:Diagram xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:notation="http://www.eclipse.org/gmf/runtime/1.0.1/notation" xmi:id="_kVhkUCK_Ed2y9bl2PJcv2g" type="Ecore" measurementUnit="Pixel">
+  <children xmi:type="notation:Node" xmi:id="_kWAFcCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWAFcyK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWAFdCK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWAFdSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWAFdiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWAFdyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWAsgCK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWAsgSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWAsgiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWAsgyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWAshCK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWAshSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWAshiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWAshyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWAFcSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//Network"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWAFciK_Ed2y9bl2PJcv2g" x="114" y="55"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWAsiCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWAsiyK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWAsjCK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWAsjSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWAsjiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWAsjyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWAskCK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWAskSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWAskiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWAskyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWBTkCK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWBTkSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWBTkiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWBTkyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWAsiSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//PLNode"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWAsiiK_Ed2y9bl2PJcv2g" x="242" y="167"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWBTlCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWBTlyK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWBTmCK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWBTmSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWBTmiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWBTmyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWBTnCK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWBTnSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWBTniK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWBTnyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWBToCK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWBToSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWBToiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWBToyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWBTlSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//Link"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWBTliK_Ed2y9bl2PJcv2g" x="55" y="167"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWB6oCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWB6oyK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWB6pCK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWB6pSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWB6piK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWB6pyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWB6qCK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWB6qSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWB6qiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWB6qyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWB6rCK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWB6rSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWB6riK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWB6ryK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWB6oSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//Endpoint"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWB6oiK_Ed2y9bl2PJcv2g" x="60" y="350"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWChsCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWChsyK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWChtCK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWChtSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWChtiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWChtyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWChuCK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWChuSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWChuiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWChuyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWChvCK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWChvSK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWChviK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWChvyK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWChsSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//VININode"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWChsiK_Ed2y9bl2PJcv2g" x="410" y="55"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWChwCK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWDIwCK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWDIwSK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDIwiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWDIwyK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWDIxCK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWDIxSK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDIxiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWDIxyK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWDIyCK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWDIySK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDIyiK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWDIyyK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWDIzCK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWChwSK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//WirelessNode"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWChwiK_Ed2y9bl2PJcv2g" x="245" y="55"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWDIzSK_Ed2y9bl2PJcv2g" type="2005">
+    <children xmi:type="notation:Node" xmi:id="_kWDv0CK_Ed2y9bl2PJcv2g" type="5005"/>
+    <children xmi:type="notation:Node" xmi:id="_kWDv0SK_Ed2y9bl2PJcv2g" type="7011">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDv0iK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWDv0yK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWDv1CK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWDv1SK_Ed2y9bl2PJcv2g" type="7012">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDv1iK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWDv1yK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWDv2CK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWDIziK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EEnum" href="planetlab.ecore#//Address"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWDIzyK_Ed2y9bl2PJcv2g" x="546" y="55"/>
+  </children>
+  <children xmi:type="notation:Node" xmi:id="_kWDv2SK_Ed2y9bl2PJcv2g" type="2001">
+    <children xmi:type="notation:Node" xmi:id="_kWDv3CK_Ed2y9bl2PJcv2g" type="5001"/>
+    <children xmi:type="notation:Node" xmi:id="_kWDv3SK_Ed2y9bl2PJcv2g" type="7001">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWDv3iK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWEW4CK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWEW4SK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWEW4iK_Ed2y9bl2PJcv2g" type="7002">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWEW4yK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWEW5CK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWEW5SK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWEW5iK_Ed2y9bl2PJcv2g" type="7003">
+      <styles xmi:type="notation:DrawerStyle" xmi:id="_kWEW5yK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:SortingStyle" xmi:id="_kWEW6CK_Ed2y9bl2PJcv2g"/>
+      <styles xmi:type="notation:FilteringStyle" xmi:id="_kWEW6SK_Ed2y9bl2PJcv2g"/>
+    </children>
+    <styles xmi:type="notation:ShapeStyle" xmi:id="_kWDv2iK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EClass" href="planetlab.ecore#//Interface"/>
+    <layoutConstraint xmi:type="notation:Bounds" xmi:id="_kWDv2yK_Ed2y9bl2PJcv2g" x="412" y="167"/>
+  </children>
+  <styles xmi:type="notation:DiagramStyle" xmi:id="_kVhkUSK_Ed2y9bl2PJcv2g"/>
+  <element xmi:type="ecore:EPackage" href="planetlab.ecore#/"/>
+  <edges xmi:type="notation:Edge" xmi:id="_kWVcoCK_Ed2y9bl2PJcv2g" type="4002" source="_kWAFcCK_Ed2y9bl2PJcv2g" target="_kWAsiCK_Ed2y9bl2PJcv2g">
+    <children xmi:type="notation:Node" xmi:id="_kWWDsSK_Ed2y9bl2PJcv2g" type="6001">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWWDsiK_Ed2y9bl2PJcv2g" x="2" y="-19"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWWDsyK_Ed2y9bl2PJcv2g" type="6003">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWWDtCK_Ed2y9bl2PJcv2g" x="-18" y="5"/>
+    </children>
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kWVcoSK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kWVcoiK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EReference" href="planetlab.ecore#//Network/nodes"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kWWDsCK_Ed2y9bl2PJcv2g" points="[0, 26, -148, -121]$[148, 86, 0, -61]"/>
+  </edges>
+  <edges xmi:type="notation:Edge" xmi:id="_kWXR0CK_Ed2y9bl2PJcv2g" type="4003" source="_kWAFcCK_Ed2y9bl2PJcv2g" target="_kWBTlCK_Ed2y9bl2PJcv2g">
+    <children xmi:type="notation:Node" xmi:id="_kWX44CK_Ed2y9bl2PJcv2g" type="6002">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWX44SK_Ed2y9bl2PJcv2g" x="4" y="16"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWX44iK_Ed2y9bl2PJcv2g" type="6004">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWX44yK_Ed2y9bl2PJcv2g" x="2" y="28"/>
+    </children>
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kWXR0SK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kWXR0iK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EReference" href="planetlab.ecore#//Network/links"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kWXR0yK_Ed2y9bl2PJcv2g" points="[0, 26, 31, -121]$[-31, 86, 0, -61]"/>
+  </edges>
+  <edges xmi:type="notation:Edge" xmi:id="_kWZHACK_Ed2y9bl2PJcv2g" type="4002" source="_kWBTlCK_Ed2y9bl2PJcv2g" target="_kWB6oCK_Ed2y9bl2PJcv2g">
+    <children xmi:type="notation:Node" xmi:id="_kWZHBCK_Ed2y9bl2PJcv2g" type="6001">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWZHBSK_Ed2y9bl2PJcv2g" y="40"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kWZHBiK_Ed2y9bl2PJcv2g" type="6003">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWZHByK_Ed2y9bl2PJcv2g" x="-17" y="28"/>
+    </children>
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kWZHASK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kWZHAiK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EReference" href="planetlab.ecore#//Link/endpoints"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kWZHAyK_Ed2y9bl2PJcv2g" points="[0, 62, 0, -97]$[0, 122, 0, -37]"/>
+  </edges>
+  <edges xmi:type="notation:Edge" xmi:id="_kWf0sCK_Ed2y9bl2PJcv2g" type="4003" source="_kWChsCK_Ed2y9bl2PJcv2g" target="_kWDv2SK_Ed2y9bl2PJcv2g">
+    <children xmi:type="notation:Node" xmi:id="_kWhC0CK_Ed2y9bl2PJcv2g" type="6002">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kWhC0SK_Ed2y9bl2PJcv2g" x="-4" y="-41"/>
+    </children>
+    <children xmi:type="notation:Node" xmi:id="_kW2aACK_Ed2y9bl2PJcv2g" type="6004">
+      <layoutConstraint xmi:type="notation:Location" xmi:id="_kW2aASK_Ed2y9bl2PJcv2g" x="-3" y="-27"/>
+    </children>
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kWf0sSK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kWf0siK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xmi:type="ecore:EReference" href="planetlab.ecore#//VININode/interfaces"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kWf0syK_Ed2y9bl2PJcv2g" points="[0, 26, -35, -121]$[35, 86, 0, -61]"/>
+  </edges>
+  <edges xmi:type="notation:Edge" xmi:id="_kW8goCK_Ed2y9bl2PJcv2g" type="4004" source="_kWChsCK_Ed2y9bl2PJcv2g" target="_kWAsiCK_Ed2y9bl2PJcv2g">
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kW8goSK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kW8goiK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xsi:nil="true"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kW8goyK_Ed2y9bl2PJcv2g" points="[0, 26, 151, -121]$[-151, 86, 0, -61]"/>
+  </edges>
+  <edges xmi:type="notation:Edge" xmi:id="_kW9uwCK_Ed2y9bl2PJcv2g" type="4004" source="_kWChwCK_Ed2y9bl2PJcv2g" target="_kWAsiCK_Ed2y9bl2PJcv2g">
+    <styles xmi:type="notation:ConnectorStyle" xmi:id="_kW9uwSK_Ed2y9bl2PJcv2g"/>
+    <styles xmi:type="notation:FontStyle" xmi:id="_kW9uwiK_Ed2y9bl2PJcv2g" fontName="Sans"/>
+    <element xsi:nil="true"/>
+    <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_kW9uwyK_Ed2y9bl2PJcv2g" points="[0, 26, 0, -121]$[0, 86, 0, -61]"/>
+  </edges>
+</notation:Diagram>
diff --git a/rspec/model/planetlab.genmodel b/rspec/model/planetlab.genmodel
new file mode 100644 (file)
index 0000000..ce59a87
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<genmodel:GenModel xmi:version="2.0"
+    xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
+    xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" modelDirectory="/PlanetLab/src"
+    editDirectory="/PlanetLab.edit/src" editorDirectory="/PlanetLab.editor/src" modelPluginID="PlanetLab"
+    modelName="Planetlab" editPluginClass="planetlab.planetlab.provider.PlanetlabEditPlugin"
+    editorPluginClass="planetlab.planetlab.presentation.PlanetlabEditorPlugin" codeFormatting="true"
+    copyrightFields="false">
+  <foreignModel>planetlab.ecore</foreignModel>
+  <genPackages prefix="Planetlab" basePackage="planetlab" disposableProviderFactory="true"
+      ecorePackage="planetlab.ecore#/">
+    <genEnums typeSafeEnumCompatible="false" ecoreEnum="planetlab.ecore#//Address"/>
+    <genClasses ecoreClass="planetlab.ecore#//Network">
+      <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference planetlab.ecore#//Network/nodes"/>
+      <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference planetlab.ecore#//Network/links"/>
+    </genClasses>
+    <genClasses ecoreClass="planetlab.ecore#//PLNode">
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//PLNode/cpu_min"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//PLNode/cpu_share"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//PLNode/net_min_rate"/>
+    </genClasses>
+    <genClasses ecoreClass="planetlab.ecore#//Link">
+      <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference planetlab.ecore#//Link/endpoints"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Link/min_bandwidth"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Link/max_bandwidth"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Link/link_kind"/>
+    </genClasses>
+    <genClasses ecoreClass="planetlab.ecore#//Endpoint">
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Endpoint/addresses"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Endpoint/endpoint_kind"/>
+    </genClasses>
+    <genClasses ecoreClass="planetlab.ecore#//VININode">
+      <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference planetlab.ecore#//VININode/interfaces"/>
+    </genClasses>
+    <genClasses ecoreClass="planetlab.ecore#//WirelessNode"/>
+    <genClasses ecoreClass="planetlab.ecore#//Interface">
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Interface/min_bandwidth"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Interface/max_bandwidth"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Interface/interface_kind"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Interface/interface_address"/>
+      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute planetlab.ecore#//Interface/interface_name"/>
+    </genClasses>
+  </genPackages>
+</genmodel:GenModel>
diff --git a/rspec/model/planetlab.xsd b/rspec/model/planetlab.xsd
new file mode 100644 (file)
index 0000000..c607602
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xsd:schema xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:pl="http://www.planet-lab.org" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ecore:nsPrefix="pl" ecore:package="planetlab.planetlab" targetNamespace="http://www.planet-lab.org">
+  <xsd:import namespace="http://www.eclipse.org/emf/2002/Ecore" schemaLocation="platform:/plugin/org.eclipse.emf.ecore/model/Ecore.xsd"/>
+  <xsd:element ecore:ignore="true" name="RSpec" type="pl:RSpec"/>
+  <xsd:element ecore:ignore="true" name="NetSpec" type="pl:NetSpec"/>
+  <xsd:element ecore:ignore="true" name="NodeSpec" type="pl:NodeSpec"/>
+  <xsd:element ecore:ignore="true" name="LinkSpec" type="pl:LinkSpec"/>
+  <xsd:element ecore:ignore="true" name="IfSpec" type="pl:IfSpec"/>
+  <xsd:element ecore:ignore="true" name="SwitchSpec" type="pl:SwitchSpec"/>
+  <xsd:complexType name="RSpec">
+    <xsd:attribute ecore:reference="pl:NetSpec" name="networks" use="required">
+      <xsd:simpleType>
+        <xsd:list itemType="xsd:anyURI"/>
+      </xsd:simpleType>
+    </xsd:attribute>
+    <xsd:attribute ecore:name="start_time" name="start_time" type="ecore:EDate"/>
+    <xsd:attribute name="duration" type="ecore:EDate"/>
+  </xsd:complexType>
+  <xsd:complexType name="NetSpec">
+    <xsd:sequence>
+      <xsd:element ecore:resolveProxies="true" maxOccurs="unbounded" minOccurs="0" name="links" type="pl:LinkSpec"/>
+    </xsd:sequence>
+    <xsd:attribute name="name" type="ecore:EString"/>
+    <xsd:attribute ecore:reference="pl:NodeSpec" name="nodes" use="required">
+      <xsd:simpleType>
+        <xsd:list itemType="xsd:anyURI"/>
+      </xsd:simpleType>
+    </xsd:attribute>
+    <xsd:attribute ecore:name="start_time" name="start_time" type="ecore:EDate"/>
+    <xsd:attribute name="duration" type="ecore:EDate"/>
+  </xsd:complexType>
+  <xsd:complexType name="NodeSpec">
+    <xsd:sequence>
+      <xsd:element ecore:unique="true" maxOccurs="1000" name="name" nillable="true" type="ecore:EString"/>
+    </xsd:sequence>
+    <xsd:attribute default="&quot;&quot;" ecore:unsettable="false" name="type" type="ecore:EString"/>
+    <xsd:attribute default="" ecore:name="init_params" ecore:unsettable="false" name="init_params" type="ecore:EByteArray"/>
+    <xsd:attribute default="0" ecore:name="cpu_min" ecore:unsettable="false" name="cpu_min" type="ecore:EInt"/>
+    <xsd:attribute default="0" ecore:name="cpu_share" ecore:unsettable="false" name="cpu_share" type="ecore:EInt"/>
+    <xsd:attribute default="0" ecore:name="cpu_pct" ecore:unsettable="false" name="cpu_pct" type="ecore:EInt"/>
+    <xsd:attribute default="0" ecore:name="disk_max" ecore:unsettable="false" name="disk_max" type="ecore:EInt"/>
+    <xsd:attribute ecore:name="net_if" ecore:reference="pl:IfSpec" name="net_if" use="required">
+      <xsd:simpleType>
+        <xsd:list itemType="xsd:anyURI"/>
+      </xsd:simpleType>
+    </xsd:attribute>
+    <xsd:attribute ecore:name="start_time" name="start_time" type="ecore:EDate"/>
+    <xsd:attribute name="duration" type="ecore:EDate"/>
+  </xsd:complexType>
+  <xsd:complexType name="LinkSpec">
+    <xsd:sequence>
+      <xsd:element default="0" ecore:unique="true" maxOccurs="unbounded" minOccurs="0" name="bw" type="ecore:EInt"/>
+      <xsd:element default="0" ecore:name="max_alloc" ecore:unique="true" maxOccurs="unbounded" minOccurs="0" name="max_alloc" type="ecore:EInt"/>
+    </xsd:sequence>
+    <xsd:attribute default="&quot;&quot;" ecore:changeable="false" ecore:unsettable="false" name="type" type="ecore:EString"/>
+    <xsd:attribute ecore:name="init_params" name="init_params" type="ecore:EByteArray"/>
+    <xsd:attribute default="0" ecore:name="min_alloc" ecore:unsettable="false" name="min_alloc" type="ecore:EInt"/>
+    <xsd:attribute ecore:changeable="false" ecore:reference="pl:IfSpec" name="endpoint" use="required">
+      <xsd:simpleType>
+        <xsd:list itemType="xsd:anyURI"/>
+      </xsd:simpleType>
+    </xsd:attribute>
+    <xsd:attribute ecore:name="start_time" name="start_time" type="ecore:EDate"/>
+    <xsd:attribute name="duration" type="ecore:EDate"/>
+  </xsd:complexType>
+  <xsd:complexType name="IfSpec">
+    <xsd:sequence>
+      <xsd:element default="5452595" ecore:name="max_kbyte" ecore:unique="true" ecore:unsettable="true" maxOccurs="unbounded" minOccurs="0" name="max_kbyte" type="ecore:EInt"/>
+    </xsd:sequence>
+    <xsd:attribute name="name" type="ecore:EString"/>
+    <xsd:attribute name="addr" type="ecore:EString"/>
+    <xsd:attribute default="" ecore:changeable="false" ecore:unsettable="false" name="type" type="ecore:EString"/>
+    <xsd:attribute default="" ecore:name="init_params" ecore:unsettable="false" name="init_params" type="ecore:EByteArray"/>
+    <xsd:attribute default="0" ecore:name="min_rate" ecore:unsettable="false" name="min_rate" type="ecore:EInt"/>
+    <xsd:attribute default="12207" ecore:name="max_rate" ecore:unsettable="false" name="max_rate" type="ecore:EInt"/>
+    <xsd:attribute default="false" ecore:name="ip_spoof" ecore:unsettable="false" name="ip_spoof" type="ecore:EBoolean"/>
+  </xsd:complexType>
+  <xsd:complexType name="SwitchSpec">
+    <xsd:complexContent>
+      <xsd:extension base="pl:NodeSpec">
+        <xsd:attribute default="0" ecore:name="fib_max" ecore:unsettable="false" name="fib_max" type="ecore:EInt"/>
+      </xsd:extension>
+    </xsd:complexContent>
+  </xsd:complexType>
+</xsd:schema>
diff --git a/rspec/sample_rspec.xml b/rspec/sample_rspec.xml
new file mode 100644 (file)
index 0000000..886fc8a
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<RSpec start_time="1235696400" duration="2419200">
+       <networks>
+               <NetSpec name="planetlab.us" start_time="1235696400" duration="2419200">
+                       <nodes>
+                               <NodeSpec name="planetlab-1.cs.princeton.edu" type="" init_params="" cpu_min="" cpu_share="" cpu_pct="" disk_max="" start_time="" duration="">
+                                       <net_if>
+                                               <IfSpec name="128.112.139.71" addr="128.112.139.71" type="ipv4" init_params="" min_rate="0" max_rate="10000000" max_kbyte="" ip_spoof="" />
+                                       </net_if>
+                               </NodeSpec>
+                               <NodeSpec name="planetlab-2.cs.princeton.edu" type="" init_params="" cpu_min="" cpu_share="" cpu_pct="" disk_max="" start_time="" duration="">
+                                       <net_if>
+                                               <IfSpec name="128.112.139.72" addr="128.112.139.72" type="ipv4" init_params="" min_rate="0" max_rate="10000000" max_kbyte="" ip_spoof="" />
+                                               <IfSpec name="128.112.139.120" addr="128.112.139.120" type="proxy" init_params="" min_rate="0" max_rate="" max_kbyte="" ip_spoof="" />
+                                               <IfSpec name="128.112.139.119" addr="128.112.139.119" type="proxy" init_params="" min_rate="0" max_rate="" max_kbyte="" ip_spoof="" />
+                                       </net_if>
+                               </NodeSpec>
+                       </nodes>
+               </NetSpec>
+               <NetSpec name="planetlab.eu" start_time="" duration="">
+                       <nodes>
+                               <NodeSpec name="onelab03.onelab.eu" type="" init_params="" cpu_min="" cpu_share="" cpu_pct="" disk_max="" start_time="" duration="">
+                                       <net_if>
+                                               <IfSpec name="128.112.139.321" addr="128.112.139.321" type="ipv4" init_params="" min_rate="0" max_rate="10000000" max_kbyte="" ip_spoof="" />
+                                       </net_if>
+                               </NodeSpec>
+                       </nodes>
+               </NetSpec>
+       </networks>
+</RSpec>
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..7a858a8
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+
+"""
+Installation script for the sfa module
+"""
+
+import sys, os, os.path
+from glob import glob
+import shutil
+from distutils.core import setup
+
+bins = [ 
+    'config/sfa-config-tty',
+    'config/gen-sfa-cm-config.py',
+    'sfa/plc/sfa-import-plc.py', 
+    'sfa/plc/sfa-nuke-plc.py', 
+    'sfa/server/sfa-ca.py', 
+    'sfa/server/sfa-server.py', 
+    'sfa/server/sfa-clean-peer-records.py', 
+    'sfa/server/sfa_component_setup.py', 
+    'sfa/client/sfi.py', 
+    'sfa/client/getNodes.py',
+    'sfa/client/getRecord.py',
+    'sfa/client/setRecord.py',
+    'sfa/client/sfadump.py',
+    'sfa/client/sfiListNodes.py',
+    'sfa/client/sfiListSlivers.py',
+    'sfa/client/sfiAddSliver.py',
+    'sfa/client/sfiDeleteSliver.py',
+    'sfa/client/sfiAddAttribute.py',
+    'sfa/client/sfiDeleteAttribute.py',
+    'sfatables/sfatables',
+    'keyconvert/keyconvert.py'
+    ]
+
+package_dirs = [
+    'sfa', 
+    'sfa/client',
+    'sfa/methods',
+    'sfa/plc',
+    'sfa/server',
+    'sfa/trust',
+    'sfa/util', 
+    'sfa/managers',
+    'sfa/rspecs',
+    'sfa/rspecs/aggregates',
+    'sfatables',
+    'sfatables/commands',
+    'sfatables/processors',
+    ]
+
+
+data_files = [('/etc/sfa/', [ 'config/aggregates.xml',
+                              'config/registries.xml',
+                              'config/geni_aggregates.xml',
+                              'config/default_config.xml',
+                              'config/sfi_config',
+                              'sfa/managers/pl/pl.rng']),
+              ('/etc/sfatables/matches/', glob('sfatables/matches/*.xml')),
+              ('/etc/sfatables/targets/', glob('sfatables/targets/*.xml')),
+              ('/etc/init.d/', ['sfa/init.d/sfa', 'sfa/init.d/sfa-cm'])]
+
+# add sfatables processors as data_files
+processor_files = [f for f in glob('sfatables/processors/*') if os.path.isfile(f)]
+data_files.append(('/etc/sfatables/processors/', processor_files))
+processor_subdirs = [d for d in glob('sfatables/processors/*') if os.path.isdir(d)]
+for d in processor_subdirs:
+    etc_dir = os.path.join("/etc/sfatables/processors", os.path.basename(d))
+    d_files = [f for f in glob(d + '/*') if os.path.isfile(f)]
+    data_files.append((etc_dir, processor_files))
+
+initscripts = [ '/etc/init.d/sfa', '/etc/init.d/sfa-cm' ]
+
+if sys.argv[1] in ['uninstall', 'remove', 'delete', 'clean']:
+    python_path = sys.path
+    site_packages_path = [ os.path.join(p,'sfa') for p in python_path if p.endswith('site-packages')]
+    site_packages_path += [ os.path.join(p,'sfatables') for p in python_path if p.endswith('site-packages')]
+    remove_dirs = ['/etc/sfa/', '/etc/sfatables'] + site_packages_path
+    remove_bins = [ '/usr/bin/' + os.path.basename(bin) for bin in bins ]
+    remove_files = remove_bins + initscripts
+
+    # remove files   
+    for filepath in remove_files:
+        print "removing", filepath, "...",
+        try: 
+            os.remove(filepath)
+            print "success"
+        except: print "failed"
+    # remove directories 
+    for directory in remove_dirs: 
+        print "removing", directory, "...",
+        try: 
+            shutil.rmtree(directory)
+            print "success"
+        except: print "failed"
+else:
+    # avoid repeating what's in the specfile already
+    setup(name='sfa',
+          packages = package_dirs, 
+          data_files = data_files,
+          ext_modules = [],
+          py_modules = [],
+          scripts = bins)
+
diff --git a/sfa.spec b/sfa.spec
new file mode 100644 (file)
index 0000000..5a8f79d
--- /dev/null
+++ b/sfa.spec
@@ -0,0 +1,312 @@
+#
+# $Id: sfa.spec 18414 2010-06-29 20:34:20Z baris $
+#
+
+%define url $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa.spec $
+
+%define name sfa
+%define version 0.9
+%define taglevel 15
+
+%define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}}
+%global python_sitearch        %( python -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)" )
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+
+Name: %{name}
+Version: %{version}
+Release: %{release}
+Source0: %{name}-%{version}.tar.bz2
+License: GPL
+Group: Applications/System
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+
+Vendor: PlanetLab
+Packager: PlanetLab Central <support@planet-lab.org>
+Distribution: PlanetLab %{plrelease}
+URL: %(echo %{url} | cut -d ' ' -f 2)
+Summary: the SFA python libraries
+Group: Applications/System
+
+BuildRequires: make
+Requires: python >= 2.5
+Requires: m2crypto
+Requires: xmlsec1-openssl-devel
+Requires: libxslt-python
+Requires: python-ZSI
+# xmlbuilder depends on  lxml
+Requires: python-lxml
+Requires: python-setuptools
+Requires: python-dateutil
+# python 2.5 has uuid module added, for python 2.4 we still need it.
+# we can't really check for if we can load uuid as a python module,
+# it'll be installed by "devel.pkgs". we have the epel repository so
+# python-uuid will be provided. but we can test for the python
+# version.
+# %define has_py24 %( python -c "import sys;sys.exit(sys.version_info[0:2] == (2,4))" 2> /dev/null; echo $? )
+# %if %has_py24
+#
+# this also didn't work very well. I'll just check for distroname - baris
+%if %{distroname} == "centos5"
+Requires: python-uuid
+%endif
+
+%package cm
+Summary: the SFA wrapper around MyPLC's NodeManager
+Group: Applications/System
+Requires: sfa
+Requires: pyOpenSSL >= 0.6
+
+%package plc
+Summary: the SFA wrapper arounf MyPLC
+Group: Applications/System
+Requires: sfa
+Requires: python-psycopg2
+Requires: myplc-config
+Requires: pyOpenSSL >= 0.7
+
+%package client
+Summary: the SFA experimenter-side CLI
+Group: Applications/System
+Requires: sfa
+Requires: pyOpenSSL >= 0.7
+
+%package sfatables
+Summary: sfatables policy tool for SFA
+Group: Applications/System
+Requires: sfa
+
+%description
+This package provides the python libraries that the SFA implementation requires
+
+%description cm
+This package implements the SFA interface which serves as a layer
+between the existing PlanetLab NodeManager interfaces and the SFA API.
+%description plc
+This package implements the SFA interface which serves as a layer
+between the existing PlanetLab interfaces and the SFA API.
+
+%description client
+This package provides the client side of the SFA API, in particular
+sfi.py, together with other utilities.
+
+%description sfatables
+sfatables is a tool for defining access and admission control policies
+in an SFA network, in much the same way as iptables is for ip
+networks. This is the command line interface to manage sfatables
+
+%prep
+%setup -q
+
+%build
+make
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install DESTDIR="$RPM_BUILD_ROOT"
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+# sfa and sfatables depend each other.
+%{_bindir}/sfa-server.py*
+/etc/sfatables/*
+%{python_sitelib}/*
+%{_bindir}/keyconvert.py*
+/var/www/html/wsdl/*.wsdl
+
+%files cm
+/etc/init.d/sfa-cm
+%{_bindir}/sfa_component_setup.py*
+# cron jobs here 
+
+%files plc
+%defattr(-,root,root)
+%config /etc/sfa/default_config.xml
+%config (noreplace) /etc/sfa/aggregates.xml
+%config (noreplace) /etc/sfa/registries.xml
+/etc/init.d/sfa
+/etc/sfa/pl.rng
+%{_bindir}/sfa-config-tty
+%{_bindir}/sfa-import-plc.py*
+%{_bindir}/sfa-clean-peer-records.py*
+%{_bindir}/sfa-nuke-plc.py*
+%{_bindir}/gen-sfa-cm-config.py*
+%{_bindir}/sfa-ca.py*
+
+%files client
+%config (noreplace) /etc/sfa/sfi_config
+%{_bindir}/sfi*
+%{_bindir}/getNodes.py*
+%{_bindir}/getRecord.py*
+%{_bindir}/setRecord.py*
+%{_bindir}/sfadump.py*
+
+%files sfatables
+%{_bindir}/sfatables
+
+%pre plc
+[ -f %{_sysconfdir}/init.d/sfa ] && service sfa stop ||:
+
+%pre cm
+[ -f %{_sysconfdir}/init.d/sfa-cm ] && service sfa-cm stop ||:
+
+%post plc
+chkconfig --add sfa
+
+%post cm
+chkconfig --add sfa-cm
+
+%changelog
+* Tue Aug 24 2010 Tony Mack <tmack@cs.princeton.edu> - sfa-0.9-15
+- (Architecture) Credential format changed to match ProtoGENI xml format
+- (Architecture) All interfaces export a new set of methods that are compatible
+   with the ProtoGeni Aggregate spec. These new methods are considered a 
+   replacement  for the pervious methods exported by the interfaces. All 
+   previous methods are still exported and work as normal, but they are 
+   considered deprecated and will not be supported in future releases.  
+- (Architecture) SFI has been updated to use the new interface methods.
+- (Architecture) Changed keyconvet implementation from c to python.
+- (Architecture) Slice Manager now attempts looks for a delegated credential
+  provided by the client before using its own server credential.
+- (Archiceture) Slice Interface no longers stores cache of resources on disk. 
+  This cache now exists only in memory and is cleared when service is restarted
+  or cache lifetime is exceeded.  
+- (Performance) SliceManager sends request to Aggregates in parallel instead 
+  of sequentially.
+- (Bug fix) SFA tickets now support the new rspec format.
+- (Bug fix) SFI only uses cahced credential if they aren't expired.
+- (Bug fix) Cerdential delegation modified to work with new credential format.
+- (Enhancement) SFI -a --aggregatge option now sends requests directly to the
+  Aggregate instead of relaying through the Slice Manager.
+- (Enhancement) Simplified caching. Accociated a global cache instance with
+  the api handler on every new server request, making it easier to access the 
+  cache and use in more general ways.     
+
+%changelog
+* Thu May 11 2010 Tony Mack <tmack@cs.princeton.edu> - sfa-0.9-11
+- SfaServer now uses a pool of threads to handle requests concurrently
+- sfa.util.rspec no longer used to process/manage rspecs (deprecated). This is now handled by sfa.plc.network and is not backwards compatible
+- PIs can now get a slice credential for any slice at their site without having to be a member of the slice
+- Registry records for federated peers (defined in registries.xml, aggregates.xml) updated when sfa service is started
+- Interfaces will try to fetch and install gids from peers listed in registries.xml/aggregates.xml if gid is not found in /etc/sfa/trusted_roots dir   
+- Component manager does not install gid files if slice already has them  
+- Server automatically fetches and installs peer certificats (defined in registries/aggregates.xml) when service is restarted.
+- fix credential verification exploit (verify that the trusted signer is a parent of the object it it signed)
+- made it easier for root authorities to sign their sub's certifiacate using the sfa-ca.py (sfa/server/sfa-ca.py) tool
+     
+* Thu Jan 21 2010 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-10
+- This tag is quite same as the previous one (sfa-0.9-9) except that the vini and max aggregate managers are also updated for urn support.  Other features are:
+- - sfa-config-tty now has the same features like plc-config-tty
+- - Contains code to support both urn and hrn
+- - Cleaned up request_hash related stuff
+- - SM, AM and Registry code is organized under respective managers
+- - Site and Slice synchronization across federated aggregates
+- - Script to generate sfa_component_config
+
+* Fri Jan 15 2010 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-9
+- sfa-config-tty now has the same features like plc-config-tty
+- Contains code to support both urn and hrn
+- Cleaned up request_hash related stuff
+- SM, AM and Registry code is organized under respective managers
+- Slice synchronization across federated aggregates
+- some bugs are fixed
+
+* Wed Jan 06 2010 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-0.9-8
+- checkpoint with fewer mentions of geni
+
+* Tue Jan 05 2010 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-0.9-7
+- checkpointing
+- this is believed to pass the tests; among other things:
+- reworked configuration based on the myplc config with xml skeleton (no more sfa_config)
+
+* Mon Nov 16 2009 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-6
+- This tag includes:
+- - Sfatables
+- - Preliminary version of hash based authentication
+- - Initial code for Component Manager
+- - Authority structure is moved to /var/lib/sfa/
+- - some bug-fixes
+
+* Fri Oct 09 2009 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-5
+- Create_slice and get_resources methods are connected to sfatables.
+- Other features include compatibility with RP, handling remote objects created as part of federation, preliminary version of sfatables, call tracability and logging.
+
+* Wed Oct 07 2009 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-4
+- Bug fix on update and remove_peer_object methods
+- Compatibility with RP, preliminiary version of sfatables, call tracability and logging
+
+* Mon Oct 05 2009 anil vengalil <avengali@sophia.inria.fr> - sfa-0.9-3
+- Compatibility with RP, two additional methods to handle remote objects, call tracability and logging, PLCDB now has single table for sfa records, preliminary version of sfatables (still under development)
+
+* Fri Sep 18 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-0.9-2
+- compatibility with RefreshPeer
+- incremental mechanism for importing PLC records into SFA tables
+- unified single database (still inside the underlying PLC db postgresql server)
+- includes/improves call traceability and logging features
+- several bug fixes
+
+* Thu Sep 17 2009 Baris Metin <tmetin@sophia.inria.fr>
+- added libxslt-python dependency
+
+* Thu Sep 10 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-0.9-1
+- unified single SFA database in the PLC-DB
+- upcalls from  PLCAPI to SFA methods
+- SFA call traceability and logging features
+- many bug fixes
+- includes first/rough version of sfatables for policy implementation
+
+* Thu Jul 23 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-6
+- snapshot after the GEC5 demo
+- should be the last tag set in the geniwrapper module, are we are now moving to the sfa module
+
+* Wed Jul 15 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-5
+- snapshot july 15 - has gone through superficial manual testing
+- hopefully a good basis for gec5 demo
+- multi-dir sfi client tested as well
+
+* Wed Jul 08 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-4
+- rename geniwrapper.spec into sfa.spec
+
+* Wed Jul 08 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-3
+- clean up in xmlrpc/soap, --protocol option to chose between both
+- keyconvert packaged in /usr/bin, no /usr/share/keyconvert anymore
+- hopefully more helpful context in case of crashes when importing
+- bugfixes for using only /etc/sfa for site-dep files
+- bugfixes in wsdl generation
+
+* Mon Jul 06 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-2
+- cleanup of the config area; no dependency to a PLC config anymore as sfa can be run in standalone
+- config variables in sfa_config now start with SFA_ and not GENI_
+- config.py can be loaded even with no config present
+
+* Sun Jul 05 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.8-1
+- first step for cleanup and reorganization
+- mass-renaming from geni to sfa (some are still needed)
+- sfa/trust implements the security architecture
+
+* Wed Jul 01 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-7
+- snapshot for reproducible builds
+
+* Thu Jun 25 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-6
+- snapshot for the convenience of alpha users
+
+* Tue Jun 16 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-5
+- build fix - keyconvert was getting installed in /usr/share/keyconvert/keyconvert
+
+* Tue Jun 16 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-4
+- ongoing work - snapshot for 4.3-rc9
+
+* Wed Jun 03 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-3
+- various fixes
+
+* Sat May 30 2009 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - geniwrapper-0.2-2
+- bugfixes - still a work in progress
+
+* Fri May 18 2009 Baris Metin <tmetin@sophia.inria.fr>
+- initial package
+
+
+%define module_current_branch 0.2
diff --git a/sfa/Makefile b/sfa/Makefile
new file mode 100644 (file)
index 0000000..06d3355
--- /dev/null
@@ -0,0 +1,5 @@
+tags:  
+       find . -name '*.py' | grep -v '/\.svn/' | xargs etags
+.PHONY: tags
+
+
diff --git a/sfa/__init__.py b/sfa/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/client/__init__.py b/sfa/client/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/client/getNodes.py b/sfa/client/getNodes.py
new file mode 100644 (file)
index 0000000..b87fa40
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+import sys
+import os
+from optparse import OptionParser
+from pprint import pprint
+from types import StringTypes
+
+from sfa.util.rspec import RSpec
+
+def create_parser():
+    command = sys.argv[0]
+    argv = sys.argv[1:]
+    usage = "%(command)s [options]" % locals()
+    description = """getNodes will open a rspec file and print all key/values, or filter results based on a given key or set of keys."""
+    parser = OptionParser(usage=usage,description=description)
+    parser.add_option("-i", "--infile", dest="infile", default=None,  help = "input rspec file")
+    parser.add_option("-t", "--tag", dest="tag", default=None,  help = "filter rspec for this tag")
+    parser.add_option("-a", "--attribute", dest="attribute", default=None,  help = "comma separated list of attributes to display")
+    parser.add_option("-r", "--recursive", dest="print_children", default=False,  action="store_true", help = "print the tag's child nodes")
+
+    return parser    
+
+
+def print_dict(rdict, options, counter=1):
+    print_children = options.print_children
+    attributes = []
+    if options.attribute: 
+        attributes = options.attribute.split(',') 
+    lists = []
+    tab = "    "
+    
+    if not isinstance(rdict, dict):
+        raise "%s not a dict" % rdict 
+    for (key, value) in rdict.items():
+        if isinstance(value, StringTypes):
+            if (attributes and key in attributes) or not attributes:
+                print tab * counter + "%s: %s" % (key, value)
+        elif isinstance(value, list):
+            for listitem in value:
+                if isinstance(listitem, dict):
+                    lists.append((key, listitem))
+        elif isinstance(value, dict):
+            lists.append((key, value)) 
+    
+    if counter == 1 or print_children: 
+        for (key, listitem) in lists:
+            if isinstance(listitem, dict):
+                print tab * (counter - 1) + key
+                print_dict(listitem, options, counter+1)
+    elif not attributes or (attributes and 'children' in attributes):
+        keys = set([key for (key, listitem) in lists])
+        if keys: print tab * (counter) + "(children: %s)" % (",".join(keys))    
+        
+
+def main():
+    parser = create_parser(); 
+    (options, args) = parser.parse_args()
+    if not options.infile:
+        print "RSpec file not specified"
+        return 
+        
+    rspec = RSpec()
+    try:
+        rspec.parseFile(options.infile)
+    except:
+        print "Error reading rspec file"
+
+    if options.tag:
+        tag_name = options.tag
+        rspec_dicts = rspec.getDictsByTagName(tag_name)
+        rspec_dict = {tag_name: rspec_dicts}
+    else:
+        rspec_dict = rspec.toDict()  
+  
+    print_dict(rspec_dict, options)
+
+    return
+
+if __name__ == '__main__':
+    try: main()
+    except Exception, e:
+        raise
+        print e
diff --git a/sfa/client/getRecord.py b/sfa/client/getRecord.py
new file mode 100755 (executable)
index 0000000..3926027
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+
+"""
+Filters/Prints record objects
+
+
+faiyaza at cs dot princeton dot edu
+Copyright (c) 2009 Board of Trustees, Princeton University
+
+$Id: getRecord.py 14252 2009-07-03 14:40:59Z thierry $
+$HeadURL$
+"""
+
+import sys
+import os
+from optparse import OptionParser
+from pprint import pprint
+from xml.parsers.expat import ExpatError
+
+from sfa.util.rspec import RecordSpec
+
+
+def create_parser():
+    command = sys.argv[0]
+    argv = sys.argv[1:]
+    usage = "%(command)s [options]" % locals()
+    description = """getRecord will parse a supplied (via stdin) record and print all values or key/values, and filter results based on a given key or set of keys."""
+    parser = OptionParser(usage=usage,description=description)
+    parser.add_option("-d", "--debug", dest="DEBUG", action="store_true",
+        default=False,  help = "record file path")
+    parser.add_option("-k", "--key", dest="withkey", action="store_true",
+        default=False,  help = "print SSH keys and certificates")
+    parser.add_option("-p", "--plinfo", dest="plinfo", action="store_true",
+        default=False,  help = "print PlanetLab specific internal fields")
+   
+    return parser    
+
+
+def printRec(record, filters, options):
+    line = ""
+    if len(filters):
+        for filter in filters:
+            if options.DEBUG:  print "Filtering on %s" %filter
+            line += "%s: %s\n" % (filter, 
+                printVal(record.dict["record"].get(filter, None)))
+        print line
+    else:
+        # print the wole thing
+        for (key, value) in record.dict["record"].iteritems():
+            if (not options.withkey and key in ('gid', 'keys')) or\
+                (not options.plinfo and key == 'pl_info'):
+                continue
+            line += "%s: %s\n" % (key, printVal(value))
+        print line
+
+
+# fix the iteratable values
+def printVal(value):
+    line = ""
+    if type(value) in (tuple, list):
+        for i in value:
+            line += "%s " % i
+    elif value != None:
+        line += value
+    return line.rstrip("\n")
+
+
+def main():
+    parser = create_parser(); 
+    (options, args) = parser.parse_args()
+
+    stdin = sys.stdin.read()
+    
+    record = RecordSpec(xml = stdin)
+    
+    if not record.dict.has_key("record"):
+        raise "RecordError", "Input record does not have 'record' tag."
+
+    if options.DEBUG: 
+        record.pprint()
+        print "#####################################################"
+
+    printRec(record, args, options)
+
+if __name__ == '__main__':
+    try: main()
+    except ExpatError, e:
+        print "RecordError.  Is your record valid XML?"
+        print e
+    except Exception, e:
+        print e
diff --git a/sfa/client/setRecord.py b/sfa/client/setRecord.py
new file mode 100755 (executable)
index 0000000..1591128
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/python
+
+"""
+Updates record objects
+
+
+faiyaza at cs dot princeton dot edu
+Copyright (c) 2009 Board of Trustees, Princeton University
+
+$Id: setRecord.py 16200 2009-12-16 04:44:09Z thierry $
+$HeadURL$
+"""
+
+import sys
+sys.path.append('.')
+import os
+from optparse import OptionParser
+from pprint import pprint
+
+from sfa.util.rspec import RecordSpec
+
+
+def create_parser():
+    command = sys.argv[0]
+    argv = sys.argv[1:]
+    usage = "%(command)s [options]" % locals()
+    description = """setRecord will edit a record (from stdin), modify its contents, then print the new record to stdout"""
+    parser = OptionParser(usage=usage,description=description)
+    parser.add_option("-d", "--debug", dest="DEBUG", action="store_true",
+        default=False,  help = "print debug info")
+   
+    return parser    
+
+
+def editDict(args, recordDict, options):
+    """
+    Takes the arg list, seperates into tag/value, creates a dict, then munges args.
+    """
+    # find out if its iterable.
+    for vect in args:
+        if vect.count("+="):
+            # append value
+            modDict({vect.split("+=")[0]: returnVal(vect.split("+=")[1])},
+                         recordDict, options) 
+        elif vect.count("="):
+            # reassign value
+            replaceDict({vect.split("=")[0]: returnVal("=".join(vect.split("=")[1:]))},
+                         recordDict, options) 
+        else:
+            if vect in recordDict:
+                del recordDict[vect]
+            else:
+                raise TypeError, "Argument error: Records are updated with \n" \
+                            "key=val1,val2,valN or\n" \
+                            "key+=val1,val2,valN \n%s Unknown key/val" % vect
+
+
+def replaceDict(newval, recordDict, options):
+    """
+    Replaces field in dict
+    """
+    # Check type of old field matches type of new field
+    for (key, val) in newval.iteritems():
+        recordDict[key] = val
+
+def modDict(newval, recordDict, options):
+    """
+    Checks type of existing field, addends new field
+    """
+    for (key, val) in newval.iteritems():
+        if (type(recordDict[key]) == list):
+            if (type(val) == list):
+                recordDict[key] = recordDict[key] + val
+            else:
+                recordDict[key].append(val)
+        elif type(val) == list:
+            val.append(recordDict[key])
+            recordDict[key] = val
+        else:
+            recordDict[key] = [recordDict[key], val]
+
+
+def returnVal(arg):
+    """
+    if given input has ",", then its assumed to be a list.
+    """
+    if arg.count(","):
+        return list(arg.split(","))
+    else:
+        return arg
+
+def main():
+    parser = create_parser(); 
+    (options, args) = parser.parse_args()
+
+    record = RecordSpec(xml = sys.stdin.read())
+
+    if args:
+        editDict(args, record.dict["record"], options)
+    if options.DEBUG:
+        print "New Record:\n%s" % record.dict
+        record.pprint()
+
+    record.parseDict(record.dict)
+    s = record.toxml()
+    sys.stdout.write(s)
+
+if __name__ == '__main__':
+    try: main()
+    except Exception, e:
+        print e
diff --git a/sfa/client/sfadump.py b/sfa/client/sfadump.py
new file mode 100644 (file)
index 0000000..9d96da7
--- /dev/null
@@ -0,0 +1,94 @@
+#! /usr/bin/env python
+# $Id: sfadump.py 16477 2010-01-05 16:31:37Z thierry $
+# $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/client/sfadump.py $
+from __future__ import with_statement
+
+import sys
+import os, os.path
+import tempfile
+import xmlrpclib
+from types import StringTypes, ListType
+from optparse import OptionParser
+
+from sfa.trust.certificate import Certificate
+from sfa.trust.credential import Credential
+from sfa.util.record import SfaRecord
+from sfa.util.rspec import RSpec
+
+def determine_sfa_filekind(fn):
+    cert = Certificate(filename = fn)
+
+    data = cert.get_data()
+    if data:
+        dict = xmlrpclib.loads(data)[0][0]
+    else:
+        dict = {}
+
+    if "gidCaller" in dict:
+        return "credential"
+
+    if "uuid" in dict:
+        return "gid"
+
+    return "unknown"
+
+def save_gid(gid):
+   hrn = gid.get_hrn()
+   lastpart = hrn.split(".")[-1]
+   filename = lastpart + ".gid"
+
+   if os.path.exists(filename):
+       print filename, ": already exists... skipping"
+       return
+
+   print filename, ": extracting gid of", hrn
+
+   gid.save_to_file(filename, save_parents = True)
+
+def extract_gids(cred, extract_parents):
+   gidCaller = cred.get_gid_caller()
+   if gidCaller:
+       save_gid(gidCaller)
+
+   gidObject = cred.get_gid_object()
+   if gidObject and ((gidCaller == None) or (gidCaller.get_hrn() != gidObject.get_hrn())):
+       save_gid(gidObject)
+
+   if extract_parents:
+       parent = cred.get_parent()
+       if parent:
+           extract_gids(parent, extract_parents)
+
+def create_parser():
+   # Generate command line parser
+   parser = OptionParser(usage="%prog [options] filename")
+
+   parser.add_option("-e", "--extractgids", action="store_true", dest="extract_gids", default=False, help="Extract GIDs from credentials")
+   parser.add_option("-p", "--dumpparents", action="store_true", dest="dump_parents", default=False, help="Show parents")
+
+   return parser
+
+def main():
+   parser = create_parser()
+   (options, args) = parser.parse_args()
+
+   if len(args) <= 0:
+        print "No filename given. Use -h for help."
+        return -1
+
+   filename = args[0]
+   kind = determine_sfa_filekind(filename)
+
+   if kind=="credential":
+       cred = Credential(filename = filename)
+       cred.dump(dump_parents = options.dump_parents)
+       if options.extract_gids:
+           extract_gids(cred, extract_parents = options.dump_parents)
+   elif kind=="gid":
+       gid = Gid(filename = filename)
+       gid.dump(dump_parents = options.dump_parents)
+   else:
+       print "unknown filekind", kind
+
+if __name__=="__main__":
+   main()
diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py
new file mode 100755 (executable)
index 0000000..d299b89
--- /dev/null
@@ -0,0 +1,937 @@
+#! /usr/bin/env python
+
+# sfi -- slice-based facility interface
+
+import sys
+sys.path.append('.')
+import os, os.path
+import tempfile
+import traceback
+import socket
+import random
+import datetime
+from lxml import etree
+from StringIO import StringIO
+from types import StringTypes, ListType
+from optparse import OptionParser
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.credential import Credential
+from sfa.util.sfaticket import SfaTicket
+from sfa.util.record import *
+from sfa.util.namespace import *
+from sfa.util.xmlrpcprotocol import ServerException
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol
+from sfa.util.config import Config
+import zlib
+
+AGGREGATE_PORT=12346
+CM_PORT=12346
+
+# utility methods here
+# display methods
+def display_rspec(rspec, format='rspec'):
+    if format in ['dns']:
+        tree = etree.parse(StringIO(rspec))
+        root = tree.getroot()
+        result = root.xpath("./network/site/node/hostname/text()")
+    elif format in ['ip']:
+        # The IP address is not yet part of the new RSpec
+        # so this doesn't do anything yet.
+        tree = etree.parse(StringIO(rspec))
+        root = tree.getroot()
+        result = root.xpath("./network/site/node/ipv4/text()")
+    else:
+        result = rspec
+
+    print result
+    return
+
+def display_list(results):
+    for result in results:
+        print result
+
+def display_records(recordList, dump=False):
+    ''' Print all fields in the record'''
+    for record in recordList:
+        display_record(record, dump)
+
+def display_record(record, dump=False):
+    if dump:
+        record.dump()
+    else:
+        info = record.getdict()
+        print "%s (%s)" % (info['hrn'], info['type'])
+    return
+
+
+def filter_records(type, records):
+    filtered_records = []
+    for record in records:
+        if (record['type'] == type) or (type == "all"):
+            filtered_records.append(record)
+    return filtered_records
+
+
+# save methods
+def save_rspec_to_file(rspec, filename):
+    if not filename.endswith(".rspec"):
+        filename = filename + ".rspec"
+
+    f = open(filename, 'w')
+    f.write(rspec)
+    f.close()
+    return
+
+def save_records_to_file(filename, recordList):
+    index = 0
+    for record in recordList:
+        if index > 0:
+            save_record_to_file(filename + "." + str(index), record)
+        else:
+            save_record_to_file(filename, record)
+        index = index + 1
+
+def save_record_to_file(filename, record):
+    if record['type'] in ['user']:
+        record = UserRecord(dict=record)
+    elif record['type'] in ['slice']:
+        record = SliceRecord(dict=record)
+    elif record['type'] in ['node']:
+        record = NodeRecord(dict=record)
+    elif record['type'] in ['authority', 'ma', 'sa']:
+        record = AuthorityRecord(dict=record)
+    else:
+        record = SfaRecord(dict=record)
+    str = record.save_to_string()
+    file(filename, "w").write(str)
+    return
+
+
+# load methods
+def load_record_from_file(filename):
+    str = file(filename, "r").read()
+    record = SfaRecord(string=str)
+    return record
+
+
+
+class Sfi:
+
+    slicemgr = None
+    registry = None
+    user = None
+    authority = None
+    options = None
+    hashrequest = False
+   
+    def create_cmd_parser(self, command, additional_cmdargs=None):
+        cmdargs = {"list": "name",
+                  "show": "name",
+                  "remove": "name",
+                  "add": "record",
+                  "update": "record",
+                  "aggregates": "[name]",
+                  "registries": "[name]",
+                  "get_gid": [],  
+                  "get_trusted_certs": "cred",
+                  "slices": "",
+                  "resources": "[name]",
+                  "create": "name rspec",
+                  "get_ticket": "name rspec",
+                  "redeem_ticket": "ticket",
+                  "delete": "name",
+                  "reset": "name",
+                  "start": "name",
+                  "stop": "name",
+                  "delegate": "name",
+                  "status": "name",
+                  "renew": "name",
+                  "shutdown": "name",
+                  "version": "",  
+                 }
+
+        if additional_cmdargs:
+            cmdargs.update(additional_cmdargs)
+
+        if command not in cmdargs:
+            print "Invalid command\n"
+            print "Commands: ",
+            for key in cmdargs.keys():
+                print key + ",",
+            print ""
+            sys.exit(2)
+
+        parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
+                                     % (command, cmdargs[command]))
+
+        # user specifies remote aggregate/sm/component                          
+        if command in ("resources", "slices", "create", "delete", "start", "stop", "restart", "get_ticket", "redeem_ticket"):
+            parser.add_option("-a", "--aggregate", dest="aggregate",
+                             default=None, help="aggregate host")
+            parser.add_option("-p", "--port", dest="port",
+                             default=AGGREGATE_PORT, help="aggregate port")
+            parser.add_option("-c", "--component", dest="component", default=None,
+                             help="component hrn")
+        
+        # registy filter option    
+        if command in ("list", "show", "remove"):
+            parser.add_option("-t", "--type", dest="type", type="choice",
+                            help="type filter ([all]|user|slice|authority|node|aggregate)",
+                            choices=("all", "user", "slice", "authority", "node", "aggregate"),
+                            default="all")
+        # display formats
+        if command in ("resources"):
+            parser.add_option("-f", "--format", dest="format", type="choice",
+                             help="display format ([xml]|dns|ip)", default="xml",
+                             choices=("xml", "dns", "ip"))
+
+        if command in ("resources", "show", "list"):
+           parser.add_option("-o", "--output", dest="file",
+                            help="output XML to file", metavar="FILE", default=None)
+        
+        if command in ("show", "list"):
+           parser.add_option("-f", "--format", dest="format", type="choice",
+                             help="display format ([text]|xml)", default="text",
+                             choices=("text", "xml"))
+
+        if command in ("delegate"):
+           parser.add_option("-u", "--user",
+                            action="store_true", dest="delegate_user", default=False,
+                            help="delegate user credential")
+           parser.add_option("-s", "--slice", dest="delegate_slice",
+                            help="delegate slice credential", metavar="HRN", default=None)
+        
+        return parser
+
+        
+    def create_parser(self):
+
+        # Generate command line parser
+        parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
+                             description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
+        parser.add_option("-r", "--registry", dest="registry",
+                         help="root registry", metavar="URL", default=None)
+        parser.add_option("-s", "--slicemgr", dest="sm",
+                         help="slice manager", metavar="URL", default=None)
+        default_sfi_dir = os.path.expanduser("~/.sfi/")
+        parser.add_option("-d", "--dir", dest="sfi_dir",
+                         help="config & working directory - default is " + default_sfi_dir,
+                         metavar="PATH", default=default_sfi_dir)
+        parser.add_option("-u", "--user", dest="user",
+                         help="user name", metavar="HRN", default=None)
+        parser.add_option("-a", "--auth", dest="auth",
+                         help="authority name", metavar="HRN", default=None)
+        parser.add_option("-v", "--verbose",
+                         action="store_true", dest="verbose", default=False,
+                         help="verbose mode")
+        parser.add_option("-D", "--debug",
+                          action="store_true", dest="debug", default=False,
+                          help="Debug (xml-rpc) protocol messages")
+        parser.add_option("-p", "--protocol",
+                         dest="protocol", default="xmlrpc",
+                         help="RPC protocol (xmlrpc or soap)")
+        parser.add_option("-k", "--hashrequest",
+                         action="store_true", dest="hashrequest", default=False,
+                         help="Create a hash of the request that will be authenticated on the server")
+        parser.disable_interspersed_args()
+
+        return parser
+        
+    #
+    # Establish Connection to SliceMgr and Registry Servers
+    #
+    def set_servers(self):
+       config_file = self.options.sfi_dir + os.sep + "sfi_config"
+       try:
+          config = Config (config_file)
+       except:
+          print "Failed to read configuration file", config_file
+          print "Make sure to remove the export clauses and to add quotes"
+          if not self.options.verbose:
+             print "Re-run with -v for more details"
+          else:
+             traceback.print_exc()
+          sys.exit(1)
+    
+       errors = 0
+       # Set SliceMgr URL
+       if (self.options.sm is not None):
+          sm_url = self.options.sm
+       elif hasattr(config, "SFI_SM"):
+          sm_url = config.SFI_SM
+       else:
+          print "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file
+          errors += 1 
+    
+       # Set Registry URL
+       if (self.options.registry is not None):
+          reg_url = self.options.registry
+       elif hasattr(config, "SFI_REGISTRY"):
+          reg_url = config.SFI_REGISTRY
+       else:
+          print "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file
+          errors += 1 
+          
+
+       # Set user HRN
+       if (self.options.user is not None):
+          self.user = self.options.user
+       elif hasattr(config, "SFI_USER"):
+          self.user = config.SFI_USER
+       else:
+          print "You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file
+          errors += 1 
+    
+       # Set authority HRN
+       if (self.options.auth is not None):
+          self.authority = self.options.auth
+       elif hasattr(config, "SFI_AUTH"):
+          self.authority = config.SFI_AUTH
+       else:
+          print "You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file
+          errors += 1 
+    
+       if errors:
+          sys.exit(1)
+    
+       if self.options.verbose :
+          print "Contacting Slice Manager at:", sm_url
+          print "Contacting Registry at:", reg_url
+    
+       # Get key and certificate
+       key_file = self.get_key_file()
+       cert_file = self.get_cert_file(key_file)
+       self.key = Keypair(filename=key_file) 
+       self.key_file = key_file
+       self.cert_file = cert_file
+       self.cert = Certificate(filename=cert_file) 
+       # Establish connection to server(s)
+       self.registry = xmlrpcprotocol.get_server(reg_url, key_file, cert_file, self.options.debug)  
+       self.slicemgr = xmlrpcprotocol.get_server(sm_url, key_file, cert_file, self.options.debug)
+
+       return
+    
+    #
+    # Get various credential and spec files
+    #
+    # Establishes limiting conventions
+    #   - conflates MAs and SAs
+    #   - assumes last token in slice name is unique
+    #
+    # Bootstraps credentials
+    #   - bootstrap user credential from self-signed certificate
+    #   - bootstrap authority credential from user credential
+    #   - bootstrap slice credential from user credential
+    #
+    
+    
+    def get_key_file(self):
+       file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
+       #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".pkey")
+       if (os.path.isfile(file)):
+          return file
+       else:
+          print "Key file", file, "does not exist"
+          sys.exit(-1)
+       return
+    
+    def get_cert_file(self, key_file):
+    
+       #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".cert")
+       file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
+       if (os.path.isfile(file)):
+          return file
+       else:
+          k = Keypair(filename=key_file)
+          cert = Certificate(subject=self.user)
+          cert.set_pubkey(k)
+          cert.set_issuer(k, self.user)
+          cert.sign()
+          if self.options.verbose :
+             print "Writing self-signed certificate to", file
+          cert.save_to_file(file)
+          return file
+
+    def get_cached_gid(self, file):
+        """
+        Return a cached gid    
+        """
+        gid = None 
+        if (os.path.isfile(file)):
+            gid = GID(filename=file)
+        return gid
+
+    def get_gid(self, opts, args):
+        hrn = None
+        if args:
+            hrn = args[0]
+        gid = self._get_gid(hrn)
+        print gid.save_to_string(save_parents=True)
+        return gid
+
+    def _get_gid(self, hrn=None):
+        if not hrn:
+            hrn = self.user
+        gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
+        gid = self.get_cached_gid(gidfile)
+        if not gid:
+            user_cred = self.get_user_cred()
+            records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
+            if not records:
+                raise RecordNotFound(args[0])
+            gid = GID(string=records[0]['gid'])
+            if self.options.verbose:
+                print "Writing gid to ", gidfile 
+            gid.save_to_file(filename=gidfile)
+        return gid   
+                
+     
+    def get_cached_credential(self, file):
+        """
+        Return a cached credential only if it hasn't expired.
+        """
+        if (os.path.isfile(file)):
+            credential = Credential(filename=file)
+            # make sure it isnt expired 
+            if not credential.get_lifetime or \
+               datetime.datetime.today() < credential.get_lifetime():
+                return credential
+        return None 
+    def get_user_cred(self):
+        #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".cred")
+        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
+        return self.get_cred(file, 'user', self.user)
+
+    def get_auth_cred(self):
+        if not self.authority:
+            print "no authority specified. Use -a or set SF_AUTH"
+            sys.exit(-1)
+        file = os.path.join(self.options.sfi_dir, get_leaf("authority") + ".cred")
+        return self.get_cred(file, 'authority', self.authority)
+
+    def get_slice_cred(self, name):
+        file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
+        return self.get_cred(file, 'slice', name)
+    def get_cred(self, file, type, hrn):
+        # attempt to load a cached credential 
+        cred = self.get_cached_credential(file)    
+        if not cred:
+            if type in ['user']:
+                cert_string = self.cert.save_to_string(save_parents=True)
+                user_name = self.user.replace(self.authority + ".", '')
+                if user_name.count(".") > 0:
+                    user_name = user_name.replace(".", '_')
+                    self.user = self.authority + "." + user_name
+                cred_str = self.registry.get_self_credential(cert_string, "user", hrn)
+            else:
+                # bootstrap slice credential from user credential
+                user_cred = self.get_user_cred().save_to_string(save_parents=True)
+                cred_str = self.registry.get_credential(user_cred, type, hrn)
+            
+            if not cred_str:
+                print "Failed to get %s credential" % (type)
+                sys.exit(-1)
+                
+            cred = Credential(string=cred_str)
+            cred.save_to_file(file, save_parents=True)
+            if self.options.verbose:
+                print "Writing %s credential to %s" %(type, file)
+
+        return cred
+    
+    def get_rspec_file(self, rspec):
+       if (os.path.isabs(rspec)):
+          file = rspec
+       else:
+          file = os.path.join(self.options.sfi_dir, rspec)
+       if (os.path.isfile(file)):
+          return file
+       else:
+          print "No such rspec file", rspec
+          sys.exit(1)
+    
+    def get_record_file(self, record):
+       if (os.path.isabs(record)):
+          file = record
+       else:
+          file = os.path.join(self.options.sfi_dir, record)
+       if (os.path.isfile(file)):
+          return file
+       else:
+          print "No such registry record file", record
+          sys.exit(1)
+    
+    def load_publickey_string(self, fn):
+       f = file(fn, "r")
+       key_string = f.read()
+    
+       # if the filename is a private key file, then extract the public key
+       if "PRIVATE KEY" in key_string:
+           outfn = tempfile.mktemp()
+           cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
+           os.system(cmd)
+           f = file(outfn, "r")
+           key_string = f.read()
+           os.remove(outfn)
+    
+       return key_string
+
+    def get_component_server_from_hrn(self, hrn):
+        # direct connection to the nodes component manager interface
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        records = self.registry.Resolve(hrn, user_cred)
+        records = filter_records('node', records)
+        if not records:
+            print "No such component:", opts.component
+        record = records[0]
+  
+        return self.get_server(record['hostname'], CM_PORT, self.key_file, \
+                               self.cert_file, self.options.debug)
+    def get_server(self, host, port, keyfile, certfile, debug):
+        """
+        Return an instnace of an xmlrpc server connection    
+        """
+        url = "http://%s:%s" % (host, port)
+        return xmlrpcprotocol.get_server(url, keyfile, certfile, debug)
+
+    def get_server_from_opts(self, opts):
+        """
+        Return instance of an xmlrpc connection to a slice manager, aggregate
+        or component server depending on the specified opts
+        """
+        server = self.slicemgr
+        # direct connection to an aggregate
+        if hasattr(opts, 'aggregate') and opts.aggregate:
+            server = self.get_server(opts.aggregate, opts.port, self.key_file, \
+                                     self.cert_file, self.options.debug)
+        # direct connection to the nodes component manager interface
+        if hasattr(opts, 'component') and opts.component:
+            server = self.get_component_server_from_hrn(opts.component)    
+        return server
+    #==========================================================================
+    # Following functions implement the commands
+    #
+    # Registry-related commands
+    #==========================================================================
+  
+    def dispatch(self, command, cmd_opts, cmd_args):
+        getattr(self, command)(cmd_opts, cmd_args)
+    # list entires in named authority registry
+    def list(self, opts, args):
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        hrn = args[0]
+        try:
+            list = self.registry.List(hrn, user_cred)
+        except IndexError:
+            raise Exception, "Not enough parameters for the 'list' command"
+          
+        # filter on person, slice, site, node, etc.  
+        # THis really should be in the self.filter_records funct def comment...
+        list = filter_records(opts.type, list)
+        for record in list:
+            print "%s (%s)" % (record['hrn'], record['type'])     
+        if opts.file:
+            file = opts.file
+            if not file.startswith(os.sep):
+                file = os.path.join(self.options.sfi_dir, file)
+            save_records_to_file(file, list)
+        return
+    
+    # show named registry record
+    def show(self, opts, args):
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        hrn = args[0]
+        records = self.registry.Resolve(hrn, user_cred)
+        records = filter_records(opts.type, records)
+        if not records:
+            print "No record of type", opts.type
+        for record in records:
+            if record['type'] in ['user']:
+                record = UserRecord(dict=record)
+            elif record['type'] in ['slice']:
+                record = SliceRecord(dict=record)
+            elif record['type'] in ['node']:
+                record = NodeRecord(dict=record)
+            elif record['type'] in ['authority', 'ma', 'sa']:
+                record = AuthorityRecord(dict=record)
+            else:
+                record = SfaRecord(dict=record)
+            if (opts.format == "text"): 
+                record.dump()  
+            else:
+                print record.save_to_string() 
+       
+        if opts.file:
+            file = opts.file
+            if not file.startswith(os.sep):
+                file = os.path.join(self.options.sfi_dir, file)
+            save_records_to_file(file, records)
+        return
+    
+    def delegate(self, opts, args):
+
+        delegee_hrn = args[0]
+        if opts.delegate_user:
+            user_cred = self.get_user_cred()
+            cred = self.delegate_cred(user_cred, delegee_hrn)
+        elif opts.delegate_slice:
+            slice_cred = self.get_slice_cred(opts.delegate_slice)
+            cred = self.delegate_cred(slice_cred, delegee_hrn)
+        else:
+            print "Must specify either --user or --slice <hrn>"
+            return
+        delegated_cred = Credential(string=cred)
+        object_hrn = delegated_cred.get_gid_object().get_hrn()
+        if opts.delegate_user:
+            dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
+                                  + get_leaf(object_hrn) + ".cred")
+        elif opts.delegate_slice:
+            dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
+                                  + get_leaf(object_hrn) + ".cred")
+
+        delegated_cred.save_to_file(dest_fn, save_parents=True)
+
+        print "delegated credential for", object_hrn, "to", delegee_hrn, "and wrote to", dest_fn
+    
+    def delegate_cred(self, object_cred, hrn):
+        # the gid and hrn of the object we are delegating
+        if isinstance(object_cred, str):
+            object_cred = Credential(string=object_cred) 
+        object_gid = object_cred.get_gid_object()
+        object_hrn = object_gid.get_hrn()
+    
+        if not object_cred.get_privileges().get_all_delegate():
+            print "Error: Object credential", object_hrn, "does not have delegate bit set"
+            return
+
+        # the delegating user's gid
+        caller_gid = self._get_gid(self.user)
+        caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
+  
+        # the gid of the user who will be delegated to
+        delegee_gid = self._get_gid(hrn)
+        delegee_hrn = delegee_gid.get_hrn()
+        delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
+        delegee_gid.save_to_file(filename=delegee_gidfile)
+        dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
+        return dcred.save_to_string(save_parents=True)
+     
+    # removed named registry record
+    #   - have to first retrieve the record to be removed
+    def remove(self, opts, args):
+        auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
+        hrn = args[0]
+        type = opts.type 
+        if type in ['all']:
+            type = '*'
+        return self.registry.Remove(hrn, auth_cred, type)
+    
+    # add named registry record
+    def add(self, opts, args):
+        auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
+        record_filepath = args[0]
+        rec_file = self.get_record_file(record_filepath)
+        record = load_record_from_file(rec_file).as_dict()
+        return self.registry.Register(record, auth_cred)
+    
+    # update named registry entry
+    def update(self, opts, args):
+        user_cred = self.get_user_cred()
+        rec_file = self.get_record_file(args[0])
+        record = load_record_from_file(rec_file)
+        if record['type'] == "user":
+            if record.get_name() == user_cred.get_gid_object().get_hrn():
+                cred = user_cred.save_to_string(save_parents=True)
+            else:
+                cred = self.get_auth_cred().save_to_string(save_parents=True)
+        elif record['type'] in ["slice"]:
+            try:
+                cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
+            except ServerException, e:
+               # XXX smbaker -- once we have better error return codes, update this
+               # to do something better than a string compare
+               if "Permission error" in e.args[0]:
+                   cred = self.get_auth_cred().save_to_string(save_parents=True)
+               else:
+                   raise
+        elif record.get_type() in ["authority"]:
+            cred = self.get_auth_cred().save_to_string(save_parents=True)
+        elif record.get_type() == 'node':
+            cred = self.get_auth_cred().save_to_string(save_parents=True)
+        else:
+            raise "unknown record type" + record.get_type()
+        record = record.as_dict()
+        return self.registry.Update(record, cred)
+  
+    def get_trusted_certs(self, opts, args):
+        """
+        return uhe trusted certs at this interface 
+        """ 
+        trusted_certs = self.registry.get_trusted_certs()
+        for trusted_cert in trusted_certs:
+            cert = Certificate(string=trusted_cert)
+            print cert.get_subject()
+        return 
+
+    def aggregates(self, opts, args):
+        """
+        return a list of details about known aggregates
+        """
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        hrn = None
+        if args:
+            hrn = args[0]
+
+        result = self.registry.get_aggregates(user_cred, hrn)
+        display_list(result)
+        return 
+
+    def registries(self, opts, args):
+        """
+        return a list of details about known registries
+        """
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        hrn = None
+        if args:
+            hrn = args[0]
+        result = self.registry.get_registries(user_cred, hrn)
+        display_list(result)
+        return
+
+    # ==================================================================
+    # Slice-related commands
+    # ==================================================================
+    
+
+    def version(self, opts, args):
+        server = self.get_server_from_opts(opts)
+        
+        print server.GetVersion()
+
+    # list instantiated slices
+    def slices(self, opts, args):
+        """
+        list instantiated slices
+        """
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
+        creds = [user_cred, delegated_cred]
+        server = self.get_server_from_opts(opts)
+        results = server.ListSlices(creds)
+        display_list(results)
+        return
+    
+    # show rspec for named slice
+    def resources(self, opts, args):
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        server = self.slicemgr
+        call_options = {}
+        server = self.get_server_from_opts(opts)
+        
+        if args:
+            cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
+            hrn = args[0]
+            call_options = {'geni_slice_urn': hrn_to_urn(hrn, 'slice')}
+        else:
+            cred = user_cred
+            hrn = None
+     
+        delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
+        creds = [cred, delegated_cred] 
+        result = server.ListResources(creds, call_options)
+        format = opts.format
+        display_rspec(result, format)
+        if (opts.file is not None):
+            file = opts.file
+            if not file.startswith(os.sep):
+                file = os.path.join(self.options.sfi_dir, file)
+            save_rspec_to_file(result, file)
+        return
+    
+    # created named slice with given rspec
+    def create(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        user_cred = self.get_user_cred()
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        rspec_file = self.get_rspec_file(args[1])
+        rspec = open(rspec_file).read()
+        server = self.get_server_from_opts(opts)
+        result =  server.CreateSliver(slice_urn, creds, rspec, [])
+        print result
+        return result
+
+    # get a ticket for the specified slice
+    def get_ticket(self, opts, args):
+        slice_hrn, rspec_path = args[0], args[1]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice')
+        user_cred = self.get_user_cred()
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        rspec_file = self.get_rspec_file(rspec_path) 
+        rspec = open(rspec_file).read()
+        server = self.get_server_from_opts(opts)
+        ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
+        file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
+        print "writing ticket to ", file        
+        ticket = SfaTicket(string=ticket_string)
+        ticket.save_to_file(filename=file, save_parents=True)
+
+    def redeem_ticket(self, opts, args):
+        ticket_file = args[0]
+        
+        # get slice hrn from the ticket
+        # use this to get the right slice credential 
+        ticket = SfaTicket(filename=ticket_file)
+        ticket.decode()
+        slice_hrn = ticket.gidObject.get_hrn()
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        #slice_hrn = ticket.attributes['slivers'][0]['hrn']
+        user_cred = self.get_user_cred()
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        
+        # get a list of node hostnames from the RSpec 
+        tree = etree.parse(StringIO(ticket.rspec))
+        root = tree.getroot()
+        hostnames = root.xpath("./network/site/node/hostname/text()")
+        
+        # create an xmlrpc connection to the component manager at each of these
+        # components and gall redeem_ticket
+        connections = {}
+        for hostname in hostnames:
+            try:
+                print "Calling redeem_ticket at %(hostname)s " % locals(),
+                server = self.get_server(hostname, CM_PORT, self.key_file, \
+                                         self.cert_file, self.options.debug)
+                server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
+                print "Success"
+            except socket.gaierror:
+                print "Failed:",
+                print "Componet Manager not accepting requests" 
+            except Exception, e:
+                print "Failed:", e.message
+        return
+    # delete named slice
+    def delete(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        server = self.get_server_from_opts(opts)
+        return server.DeleteSliver(slice_urn, creds)
+    
+    # start named slice
+    def start(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        server = self.get_server_from_opts(opts)
+        return server.Start(slice_urn, creds)
+    
+    # stop named slice
+    def stop(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        server = self.get_server_from_opts(opts)
+        return server.Stop(slice_urn, creds)
+    
+    # reset named slice
+    def reset(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        server = self.get_server_from_opts(opts)
+        slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        return server.reset_slice(creds, slice_urn)
+
+    def renew(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        server = self.get_server_from_opts(opts)
+        slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        time = args[1]
+        return server.RenewSliver(slice_urn, creds, time)
+
+
+    def status(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+        creds = [slice_cred, delegated_cred]
+        server = self.get_server_from_opts(opts)
+        print server.SliverStatus(slice_urn, creds)
+
+
+    def shutdown(self, opts, args):
+        slice_hrn = args[0]
+        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
+        delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
+
+        server = self.get_server_from_opts(opts)
+        return server.Shutdown(slice_urn, [slice_cred])         
+    
+    #
+    # Main: parse arguments and dispatch to command
+    #
+    def main(self):
+        parser = self.create_parser()
+        (options, args) = parser.parse_args()
+        self.options = options
+   
+        if options.hashrequest:
+            self.hashrequest = True
+        if len(args) <= 0:
+            print "No command given. Use -h for help."
+            return - 1
+    
+        command = args[0]
+        (cmd_opts, cmd_args) = self.create_cmd_parser(command).parse_args(args[1:])
+        if self.options.verbose :
+            print "Registry %s, sm %s, dir %s, user %s, auth %s" % (options.registry, options.sm,
+                                                                   options.sfi_dir, options.user,
+                                                                   options.auth)
+            print "Command %s" % command
+            if command in ("resources"):
+                print "resources cmd_opts %s" % cmd_opts.format
+            elif command in ("list", "show", "remove"):
+                print "cmd_opts.type %s" % cmd_opts.type
+            print "cmd_args %s" % cmd_args
+    
+        self.set_servers()
+    
+        try:
+            self.dispatch(command, cmd_opts, cmd_args)
+        except KeyError:
+            raise 
+            print "Command not found:", command
+            sys.exit(1)
+    
+        return
+    
+if __name__ == "__main__":
+   Sfi().main()
diff --git a/sfa/client/sfiAddAttribute.py b/sfa/client/sfiAddAttribute.py
new file mode 100755 (executable)
index 0000000..33b94a3
--- /dev/null
@@ -0,0 +1,36 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options] [node1 node2...]",
+                   description="Add sliver attributes to the RSpec. " +
+                   "This command reads in an RSpec and outputs a modified " +
+                   "RSpec. Use this to add attributes to individual nodes " +
+                   "in your slice.  If no nodes are specified, the " +
+                   "attributes will be added to ALL nodes.",
+                   epilog="NOTE: Only admins can actually set these " +
+                   "attributes, with the exception of --delegations")
+command.add_nodefile_option()
+command.add_attribute_options()
+command.prep()
+
+attrs = command.get_attribute_dict()
+for name in attrs:
+    for value in attrs[name]:
+        if not command.nodes:
+            try:
+                command.rspec.add_default_sliver_attribute(name, value)
+            except:
+                print >> sys.stderr, "FAILED: on all nodes: %s=%s" % (name, value)
+        else:
+            for node in command.nodes:
+                try:
+                    command.rspec.add_sliver_attribute(node, name, value)
+                except:
+                    print >> sys.stderr, "FAILED: on node %s: %s=%s" % (node, name, value)
+
+print command.rspec
+    
+
+    
diff --git a/sfa/client/sfiAddSliver.py b/sfa/client/sfiAddSliver.py
new file mode 100755 (executable)
index 0000000..be1c51c
--- /dev/null
@@ -0,0 +1,22 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options] node1 node2...",
+                   description="Add slivers to the RSpec. " +
+                   "This command reads in an RSpec and outputs a modified " +
+                   "RSpec. Use this to add nodes to your slice.")
+command.add_nodefile_option()
+command.prep()
+
+for node in command.nodes:
+    try:
+        command.rspec.add_sliver(node)
+    except:
+        print >> sys.stderr, "FAILED: %s" % node
+
+print command.rspec
+    
+
+    
diff --git a/sfa/client/sfiDeleteAttribute.py b/sfa/client/sfiDeleteAttribute.py
new file mode 100755 (executable)
index 0000000..8e8f0a2
--- /dev/null
@@ -0,0 +1,37 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options] [node1 node2...]",
+                   description="Delete sliver attributes from the RSpec. " +
+                   "This command reads in an RSpec and outputs a modified " +
+                   "RSpec. Use this to remove attributes from nodes " +
+                   "in your slice.  If no nodes are specified, the " +
+                   "attributes will be removed from ALL nodes.",
+                   epilog="NOTE: Only admins can actually set these " +
+                   "attributes, with the exception of --delegations")
+command.add_nodefile_option()
+command.add_attribute_options()
+command.prep()
+
+attrs = command.get_attribute_dict()
+for name in attrs:
+    print >> sys.stderr, name, attrs[name]
+    for value in attrs[name]:
+        if not command.nodes:
+            try:
+                command.rspec.remove_default_sliver_attribute(name, value)
+            except:
+                print >> sys.stderr, "FAILED: on all nodes: %s=%s" % (name, value)
+            else:
+                for node in command.nodes:
+                    try:
+                        command.rspec.remove_sliver_attribute(node, name, value)
+                    except:
+                        print >> sys.stderr, "FAILED: on node %s: %s=%s" % (node, name, value)
+
+print command.rspec
+    
+
+    
diff --git a/sfa/client/sfiDeleteSliver.py b/sfa/client/sfiDeleteSliver.py
new file mode 100755 (executable)
index 0000000..c8e769a
--- /dev/null
@@ -0,0 +1,22 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options] node1 node2...",
+                   description="Delete slivers from the RSpec. " +
+                   "This command reads in an RSpec and outputs a modified " +
+                   "RSpec. Use this to remove nodes from your slice.")
+command.add_nodefile_option()
+command.prep()
+
+for node in command.nodes:
+    try:
+        command.rspec.remove_sliver(node)
+    except:
+        print >> sys.stderr, "FAILED: %s" % node
+
+print command.rspec
+    
+
+    
diff --git a/sfa/client/sfiListNodes.py b/sfa/client/sfiListNodes.py
new file mode 100755 (executable)
index 0000000..df14fe3
--- /dev/null
@@ -0,0 +1,17 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options]",
+                   description="List all nodes in the RSpec. " + 
+                   "Use this to display the list of nodes on which it is " + 
+                   "possible to create a slice.")
+command.prep()
+
+nodes = command.rspec.get_node_list()
+for node in nodes:
+    print node
+
+
+    
diff --git a/sfa/client/sfiListSlivers.py b/sfa/client/sfiListSlivers.py
new file mode 100755 (executable)
index 0000000..685521c
--- /dev/null
@@ -0,0 +1,28 @@
+#! /usr/bin/env python
+
+import sys
+from sfa.util.rspecHelper import RSpec, Commands
+
+command = Commands(usage="%prog [options]",
+                   description="List all slivers in the RSpec. " + 
+                   "Use this to display the list of nodes belonging to " + 
+                   "the slice.")
+command.add_show_attributes_option()
+command.prep()
+
+nodes = command.rspec.get_sliver_list()
+if command.opts.showatt:
+    defaults = command.rspec.get_default_sliver_attributes()
+    if defaults:
+        print "ALL NODES"
+        for (name, value) in defaults:
+            print "  %s: %s" % (name, value)
+
+for node in nodes:
+    print node
+    if command.opts.showatt:
+        atts = command.rspec.get_sliver_attributes(node)
+        for (name, value) in atts:
+            print "  %s: %s" % (name, value)
+
+    
diff --git a/sfa/cron.d/sfa.cron b/sfa/cron.d/sfa.cron
new file mode 100644 (file)
index 0000000..c92afb6
--- /dev/null
@@ -0,0 +1,8 @@
+SHELL=/bin/bash
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+MAILTO=server-msgs@planet-lab.org
+HOME=/
+#
+# minute hour day-of-month month day-of-week user command
+0 * * * *  /usr/bin/sfa-import-plc.py > /dev/null 2>&1
+0 0 * * * /usr/bin/sfa-clean-peer-records.py > /dev/null 2>&1  
diff --git a/sfa/init.d/sfa b/sfa/init.d/sfa
new file mode 100755 (executable)
index 0000000..77490c7
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/bash
+#
+# sfa   Wraps PLCAPI into the SFA compliant API
+#
+# chkconfig: 2345 5 99
+#
+# description:   Wraps PLCAPI into the SFA compliant API
+#
+# $Id: sfa 18662 2010-08-24 16:53:40Z tmack $
+# $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/init.d/sfa $
+#
+
+# Source config
+[ -f /etc/sfa/sfa_config ] && . /etc/sfa/sfa_config
+
+# source function library
+. /etc/init.d/functions
+
+# Regenerate configuration files - almost verbatim from plc.init
+reload ()
+{
+    force=$1
+
+    # Regenerate the main configuration file from default values
+    # overlaid with site-specific and current values.
+    # Thierry -- 2007-07-05 : values in plc_config.xml are *not* taken into account here
+    files=(
+       /etc/sfa/default_config.xml 
+       /etc/sfa/configs/site.xml
+    )
+    for file in "${files[@]}" ; do
+       if [ -n "$force" -o $file -nt /etc/sfa/sfa_config.xml ] ; then
+           tmp=$(mktemp /tmp/sfa_config.xml.XXXXXX)
+           plc-config --xml "${files[@]}" >$tmp
+           if [ $? -eq 0 ] ; then
+               mv $tmp /etc/sfa/sfa_config.xml
+               chmod 444 /etc/sfa/sfa_config.xml
+           else
+               echo "SFA: Warning: Invalid configuration file(s) detected"
+               rm -f $tmp
+           fi
+           break
+       fi
+    done
+
+    # Convert configuration to various formats
+    if [ -n "$force" -o /etc/sfa/sfa_config.xml -nt /etc/sfa/sfa_config ] ; then
+       plc-config --shell /etc/sfa/sfa_config.xml >/etc/sfa/sfa_config
+    fi
+    if [ -n "$force" -o /etc/sfa/sfa_config.xml -nt /etc/sfa/sfa_config.py ] ; then
+       plc-config --python /etc/sfa/sfa_config.xml >/etc/sfa/sfa_config.py
+    fi
+#    if [ -n "$force" -o /etc/sfa/sfa_config.xml -nt /etc/sfa/php/sfa_config.php ] ; then
+#      mkdir -p /etc/sfa/php
+#      plc-config --php  /etc/sfa/sfa_config.xml >/etc/sfa/php/sfa_config.php
+#    fi
+
+    # [re]generate the sfa_component_config
+    gen-sfa-cm-config.py        
+}
+
+start() {
+    
+    reload
+
+    if [ "$SFA_REGISTRY_ENABLED" ]; then
+        action $"SFA Registry" daemon /usr/bin/sfa-server.py -r -d $OPTIONS
+    fi
+
+    if [ "$SFA_AGGREGATE_ENABLED" ]; then
+        action $"SFA Aggregate" daemon /usr/bin/sfa-server.py -a -d $OPTIONS
+    fi
+        
+    if [ "$SFA_SM_ENABLED" ]; then
+        action "SFA SliceMgr" daemon /usr/bin/sfa-server.py -s -d $OPTIONS
+    fi
+
+    RETVAL=$?
+    [ $RETVAL -eq 0 ] && touch /var/lock/subsys/sfa
+
+}
+
+stop() {
+    action $"Shutting down SFA" killproc sfa-server.py
+    RETVAL=$?
+
+    [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/sfa
+}
+
+
+case "$1" in
+    start) start ;;
+    stop) stop ;;
+    reload) reload force ;;
+    restart) stop; start ;;
+    condrestart)
+       if [ -f /var/lock/subsys/sfa ]; then
+            stop
+            start
+       fi
+       ;;
+    status)
+       status sfa
+       RETVAL=$?
+       ;;
+    *)
+       echo $"Usage: $0 {start|stop|reload|restart|condrestart|status}"
+       exit 1
+       ;;
+esac
+
+exit $RETVAL
+
diff --git a/sfa/init.d/sfa-cm b/sfa/init.d/sfa-cm
new file mode 100755 (executable)
index 0000000..e3bbd96
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# sfa   Wraps PLCAPI into the SFA compliant API
+#
+# chkconfig: 2345 5 99
+#
+# description:   Wraps PLCAPI into the SFA compliant API
+#
+# $Id: sfa 14304 2009-07-06 20:19:51Z thierry $
+# $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/init.d/sfa $
+#
+
+# Source config
+. /etc/sfa/sfa_config
+
+# source function library
+. /etc/init.d/functions
+
+init_key() {
+    # if key doesnt exist use sfa_componenet_setup to get it  
+    if [ ! -f /var/lib/sfa/server.key ]; then
+        /usr/bin/sfa_component_setup.py -k
+    fi
+}
+
+start() {
+        echo -n $"Starting SFA:  "
+
+        if [ "$SFA_CM_ENABLED" ]; then
+            echo "Component Mgr"
+            # make sure server key (nodes private key) exists first
+            init_key
+            /usr/bin/sfa-server.py -c -d $OPTIONS
+        fi
+
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/sfa
+
+}
+
+stop() {
+    echo -n $"Shutting down SFA: "
+    killproc sfa-server.py
+    RETVAL=$?
+
+    echo
+    [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/sfa
+}
+
+
+
+case "$1" in
+  start)
+    start
+    ;;
+  stop)
+    stop
+    ;;
+  restart|reload)
+    stop
+    start
+    ;;
+  condrestart)
+    if [ -f /var/lock/subsys/sfa ]; then
+        stop
+        start
+    fi
+    ;;
+  status)
+    status sfa
+    RETVAL=$?
+    ;;
+  *)
+    echo $"Usage: $0 {start|stop|restart|condrestart|status}"
+    exit 1
+esac
+
+exit $RETVAL
+
diff --git a/sfa/managers/__init__.py b/sfa/managers/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/managers/aggregate_manager_eucalyptus.py b/sfa/managers/aggregate_manager_eucalyptus.py
new file mode 100644 (file)
index 0000000..06a1f70
--- /dev/null
@@ -0,0 +1,507 @@
+from __future__ import with_statement 
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.rspec import RSpec
+from sfa.server.registry import Registries
+
+import boto
+from boto.ec2.regioninfo import RegionInfo
+from boto.exception import EC2ResponseError
+from ConfigParser import ConfigParser
+from xmlbuilder import XMLBuilder
+from lxml import etree as ET
+from sqlobject import *
+
+import sys
+import os
+
+##
+# The data structure used to represent a cloud.
+# It contains the cloud name, its ip address, image information,
+# key pairs, and clusters information.
+#
+cloud = {}
+
+##
+# The location of the RelaxNG schema.
+#
+EUCALYPTUS_RSPEC_SCHEMA='/etc/sfa/eucalyptus.rng'
+
+##
+# A representation of an Eucalyptus instance. This is a support class
+# for instance <-> slice mapping.
+#
+class EucaInstance(SQLObject):
+    instance_id = StringCol(unique=True, default=None)
+    kernel_id   = StringCol()
+    image_id    = StringCol()
+    ramdisk_id  = StringCol()
+    inst_type   = StringCol()
+    key_pair    = StringCol()
+    slice = ForeignKey('Slice')
+
+    ##
+    # Contacts Eucalyptus and tries to reserve this instance.
+    # 
+    # @param botoConn A connection to Eucalyptus.
+    #
+    def reserveInstance(self, botoConn):
+        print >>sys.stderr, 'Reserving an instance: image: %s, kernel: ' \
+                            '%s, ramdisk: %s, type: %s, key: %s' % \
+                            (self.image_id, self.kernel_id, self.ramdisk_id, 
+                             self.inst_type, self.key_pair)
+
+        # XXX The return statement is for testing. REMOVE in production
+        #return
+
+        try:
+            reservation = botoConn.run_instances(self.image_id,
+                                                 kernel_id = self.kernel_id,
+                                                 ramdisk_id = self.ramdisk_id,
+                                                 instance_type = self.inst_type,
+                                                 key_name  = self.key_pair)
+            for instance in reservation.instances:
+                self.instance_id = instance.id
+
+        # If there is an error, destroy itself.
+        except EC2ResponseError, ec2RespErr:
+            errTree = ET.fromstring(ec2RespErr.body)
+            msg = errTree.find('.//Message')
+            print >>sys.stderr, msg.text
+            self.destroySelf()
+
+##
+# A representation of a PlanetLab slice. This is a support class
+# for instance <-> slice mapping.
+#
+class Slice(SQLObject):
+    slice_hrn = StringCol()
+    #slice_index = DatabaseIndex('slice_hrn')
+    instances = MultipleJoin('EucaInstance')
+
+##
+# Initialize the aggregate manager by reading a configuration file.
+#
+def init_server():
+    configParser = ConfigParser()
+    configParser.read(['/etc/sfa/eucalyptus_aggregate.conf', 'eucalyptus_aggregate.conf'])
+    if len(configParser.sections()) < 1:
+        print >>sys.stderr, 'No cloud defined in the config file'
+        raise Exception('Cannot find cloud definition in configuration file.')
+
+    # Only read the first section.
+    cloudSec = configParser.sections()[0]
+    cloud['name'] = cloudSec
+    cloud['access_key'] = configParser.get(cloudSec, 'access_key')
+    cloud['secret_key'] = configParser.get(cloudSec, 'secret_key')
+    cloud['cloud_url']  = configParser.get(cloudSec, 'cloud_url')
+    cloudURL = cloud['cloud_url']
+    if cloudURL.find('https://') >= 0:
+        cloudURL = cloudURL.replace('https://', '')
+    elif cloudURL.find('http://') >= 0:
+        cloudURL = cloudURL.replace('http://', '')
+    (cloud['ip'], parts) = cloudURL.split(':')
+
+    # Initialize sqlite3 database.
+    dbPath = '/etc/sfa/db'
+    dbName = 'euca_aggregate.db'
+
+    if not os.path.isdir(dbPath):
+        print >>sys.stderr, '%s not found. Creating directory ...' % dbPath
+        os.mkdir(dbPath)
+
+    conn = connectionForURI('sqlite://%s/%s' % (dbPath, dbName))
+    sqlhub.processConnection = conn
+    Slice.createTable(ifNotExists=True)
+    EucaInstance.createTable(ifNotExists=True)
+
+    # Make sure the schema exists.
+    if not os.path.exists(EUCALYPTUS_RSPEC_SCHEMA):
+        err = 'Cannot location schema at %s' % EUCALYPTUS_RSPEC_SCHEMA
+        print >>sys.stderr, err
+        raise Exception(err)
+
+##
+# Creates a connection to Eucalytpus. This function is inspired by 
+# the make_connection() in Euca2ools.
+#
+# @return A connection object or None
+#
+def getEucaConnection():
+    global cloud
+    accessKey = cloud['access_key']
+    secretKey = cloud['secret_key']
+    eucaURL   = cloud['cloud_url']
+    useSSL    = False
+    srvPath   = '/'
+    eucaPort  = 8773
+
+    if not accessKey or not secretKey or not eucaURL:
+        print >>sys.stderr, 'Please set ALL of the required environment ' \
+                            'variables by sourcing the eucarc file.'
+        return None
+    
+    # Split the url into parts
+    if eucaURL.find('https://') >= 0:
+        useSSL  = True
+        eucaURL = eucaURL.replace('https://', '')
+    elif eucaURL.find('http://') >= 0:
+        useSSL  = False
+        eucaURL = eucaURL.replace('http://', '')
+    (eucaHost, parts) = eucaURL.split(':')
+    if len(parts) > 1:
+        parts = parts.split('/')
+        eucaPort = int(parts[0])
+        parts = parts[1:]
+        srvPath = '/'.join(parts)
+
+    return boto.connect_ec2(aws_access_key_id=accessKey,
+                            aws_secret_access_key=secretKey,
+                            is_secure=useSSL,
+                            region=RegionInfo(None, 'eucalyptus', eucaHost), 
+                            port=eucaPort,
+                            path=srvPath)
+
+##
+# A class that builds the RSpec for Eucalyptus.
+#
+class EucaRSpecBuilder(object):
+    ##
+    # Initizes a RSpec builder
+    #
+    # @param cloud A dictionary containing data about a 
+    #              cloud (ex. clusters, ip)
+    def __init__(self, cloud):
+        self.eucaRSpec = XMLBuilder(format = True, tab_step = "  ")
+        self.cloudInfo = cloud
+
+    ##
+    # Creates a request stanza.
+    # 
+    # @param num The number of instances to create.
+    # @param image The disk image id.
+    # @param kernel The kernel image id.
+    # @param keypair Key pair to embed.
+    # @param ramdisk Ramdisk id (optional).
+    #
+    def __requestXML(self, num, image, kernel, keypair, ramdisk = ''):
+        xml = self.eucaRSpec
+        with xml.request:
+            with xml.instances:
+                xml << str(num)
+            with xml.kernel_image(id=kernel):
+                xml << ''
+            if ramdisk == '':
+                with xml.ramdisk:
+                    xml << ''
+            else:
+                with xml.ramdisk(id=ramdisk):
+                    xml << ''
+            with xml.disk_image(id=image):
+                xml << ''
+            with xml.keypair:
+                xml << keypair
+
+    ##
+    # Creates the cluster stanza.
+    #
+    # @param clusters Clusters information.
+    #
+    def __clustersXML(self, clusters):
+        cloud = self.cloudInfo
+        xml = self.eucaRSpec
+
+        for cluster in clusters:
+            instances = cluster['instances']
+            with xml.cluster(id=cluster['name']):
+                with xml.ipv4:
+                    xml << cluster['ip']
+                with xml.vm_types:
+                    for inst in instances:
+                        with xml.vm_type(name=inst[0]):
+                            with xml.free_slots:
+                                xml << str(inst[1])
+                            with xml.max_instances:
+                                xml << str(inst[2])
+                            with xml.cores:
+                                xml << str(inst[3])
+                            with xml.memory(unit='MB'):
+                                xml << str(inst[4])
+                            with xml.disk_space(unit='GB'):
+                                xml << str(inst[5])
+                            if inst[0] == 'm1.small':
+                                self.__requestXML(1, 'emi-88760F45', 'eki-F26610C6', 'cortex')
+                            if 'instances' in cloud and inst[0] in cloud['instances']:
+                                existingEucaInstances = cloud['instances'][inst[0]]
+                                with xml.euca_instances:
+                                    for eucaInst in existingEucaInstances:
+                                        with xml.euca_instance(id=eucaInst['id']):
+                                            with xml.state:
+                                                xml << eucaInst['state']
+                                            with xml.public_dns:
+                                                xml << eucaInst['public_dns']
+                                            with xml.keypair:
+                                                xml << eucaInst['key']
+
+    ##
+    # Creates the Images stanza.
+    #
+    # @param images A list of images in Eucalyptus.
+    #
+    def __imagesXML(self, images):
+        xml = self.eucaRSpec
+        with xml.images:
+            for image in images:
+                with xml.image(id=image.id):
+                    with xml.type:
+                        xml << image.type
+                    with xml.arch:
+                        xml << image.architecture
+                    with xml.state:
+                        xml << image.state
+                    with xml.location:
+                        xml << image.location
+
+    ##
+    # Creates the KeyPairs stanza.
+    #
+    # @param keypairs A list of key pairs in Eucalyptus.
+    #
+    def __keyPairsXML(self, keypairs):
+        xml = self.eucaRSpec
+        with xml.keypairs:
+            for key in keypairs:
+                with xml.keypair:
+                    xml << key.name
+
+    ##
+    # Generates the RSpec.
+    #
+    def toXML(self):
+        if not self.cloudInfo:
+            print >>sys.stderr, 'No cloud information'
+            return ''
+
+        xml = self.eucaRSpec
+        cloud = self.cloudInfo
+        with xml.RSpec(type='eucalyptus'):
+            with xml.cloud(id=cloud['name']):
+                with xml.ipv4:
+                    xml << cloud['ip']
+                self.__keyPairsXML(cloud['keypairs'])
+                self.__imagesXML(cloud['images'])
+                self.__clustersXML(cloud['clusters'])
+        return str(xml)
+
+##
+# A parser to parse the output of availability-zones.
+#
+# Note: Only one cluster is supported. If more than one, this will
+#       not work.
+#
+class ZoneResultParser(object):
+    def __init__(self, zones):
+        self.zones = zones
+
+    def parse(self):
+        if len(self.zones) < 3:
+            return
+        clusterList = []
+        cluster = {} 
+        instList = []
+
+        cluster['name'] = self.zones[0].name
+        cluster['ip']   = self.zones[0].state
+
+        for i in range(2, len(self.zones)):
+            currZone = self.zones[i]
+            instType = currZone.name.split()[1]
+
+            stateString = currZone.state.split('/')
+            rscString   = stateString[1].split()
+
+            instFree      = int(stateString[0])
+            instMax       = int(rscString[0])
+            instNumCpu    = int(rscString[1])
+            instRam       = int(rscString[2])
+            instDiskSpace = int(rscString[3])
+
+            instTuple = (instType, instFree, instMax, instNumCpu, instRam, instDiskSpace)
+            instList.append(instTuple)
+        cluster['instances'] = instList
+        clusterList.append(cluster)
+
+        return clusterList
+
+def get_rspec(api, creds, options): 
+    global cloud
+    # get slice's hrn from options
+    xrn = options.get('geni_slice_urn', None)
+    hrn, type = urn_to_hrn(xrn)
+
+    # get hrn of the original caller
+    origin_hrn = options.get('origin_hrn', None)
+    if not origin_hrn:
+        origin_hrn = Credential(string=creds[0]).get_gid_caller().get_hrn()
+
+    conn = getEucaConnection()
+
+    if not conn:
+        print >>sys.stderr, 'Error: Cannot create a connection to Eucalyptus'
+        return 'Cannot create a connection to Eucalyptus'
+
+    try:
+        # Zones
+        zones = conn.get_all_zones(['verbose'])
+        p = ZoneResultParser(zones)
+        clusters = p.parse()
+        cloud['clusters'] = clusters
+        
+        # Images
+        images = conn.get_all_images()
+        cloud['images'] = images
+
+        # Key Pairs
+        keyPairs = conn.get_all_key_pairs()
+        cloud['keypairs'] = keyPairs
+
+        if hrn:
+            instanceId = []
+            instances  = []
+
+            # Get the instances that belong to the given slice from sqlite3
+            # XXX use getOne() in production because the slice's hrn is supposed
+            # to be unique. For testing, uniqueness is turned off in the db.
+            # If the slice isn't found in the database, create a record for the 
+            # slice.
+            matchedSlices = list(Slice.select(Slice.q.slice_hrn == hrn))
+            if matchedSlices:
+                theSlice = matchedSlices[-1]
+            else:
+                theSlice = Slice(slice_hrn = hrn)
+            for instance in theSlice.instances:
+                instanceId.append(instance.instance_id)
+
+            # Get the information about those instances using their ids.
+            if len(instanceId) > 0:
+                reservations = conn.get_all_instances(instanceId)
+            else:
+                reservations = []
+            for reservation in reservations:
+                for instance in reservation.instances:
+                    instances.append(instance)
+
+            # Construct a dictory for the EucaRSpecBuilder
+            instancesDict = {}
+            for instance in instances:
+                instList = instancesDict.setdefault(instance.instance_type, [])
+                instInfoDict = {} 
+
+                instInfoDict['id'] = instance.id
+                instInfoDict['public_dns'] = instance.public_dns_name
+                instInfoDict['state'] = instance.state
+                instInfoDict['key'] = instance.key_name
+
+                instList.append(instInfoDict)
+            cloud['instances'] = instancesDict
+
+    except EC2ResponseError, ec2RespErr:
+        errTree = ET.fromstring(ec2RespErr.body)
+        errMsgE = errTree.find('.//Message')
+        print >>sys.stderr, errMsgE.text
+
+    rspec = EucaRSpecBuilder(cloud).toXML()
+
+    # Remove the instances records so next time they won't 
+    # show up.
+    if 'instances' in cloud:
+        del cloud['instances']
+
+    return rspec
+
+"""
+Hook called via 'sfi.py create'
+"""
+def create_slice(api, xrn, creds, xml, users):
+    global cloud
+    hrn = urn_to_hrn(xrn)[0]
+
+    conn = getEucaConnection()
+    if not conn:
+        print >>sys.stderr, 'Error: Cannot create a connection to Eucalyptus'
+        return False
+
+    # Validate RSpec
+    schemaXML = ET.parse(EUCALYPTUS_RSPEC_SCHEMA)
+    rspecValidator = ET.RelaxNG(schemaXML)
+    rspecXML = ET.XML(xml)
+    if not rspecValidator(rspecXML):
+        error = rspecValidator.error_log.last_error
+        message = '%s (line %s)' % (error.message, error.line) 
+        # XXX: InvalidRSpec is new. Currently, I am not working with Trunk code.
+        #raise InvalidRSpec(message)
+        raise Exception(message)
+
+    # Get the slice from db or create one.
+    s = Slice.select(Slice.q.slice_hrn == hrn).getOne(None)
+    if s is None:
+        s = Slice(slice_hrn = hrn)
+
+    # Process any changes in existing instance allocation
+    pendingRmInst = []
+    for sliceInst in s.instances:
+        pendingRmInst.append(sliceInst.instance_id)
+    existingInstGroup = rspecXML.findall('.//euca_instances')
+    for instGroup in existingInstGroup:
+        for existingInst in instGroup:
+            if existingInst.get('id') in pendingRmInst:
+                pendingRmInst.remove(existingInst.get('id'))
+    for inst in pendingRmInst:
+        print >>sys.stderr, 'Instance %s will be terminated' % inst
+        dbInst = EucaInstance.select(EucaInstance.q.instance_id == inst).getOne(None)
+        dbInst.destroySelf()
+    conn.terminate_instances(pendingRmInst)
+
+    # Process new instance requests
+    requests = rspecXML.findall('.//request')
+    for req in requests:
+        vmTypeElement = req.getparent()
+        instType = vmTypeElement.get('name')
+        numInst  = int(req.find('instances').text)
+        instKernel  = req.find('kernel_image').get('id')
+        instDiskImg = req.find('disk_image').get('id')
+        instKey     = req.find('keypair').text
+        
+        ramDiskElement = req.find('ramdisk')
+        ramDiskAttr    = ramDiskElement.attrib
+        if 'id' in ramDiskAttr:
+            instRamDisk = ramDiskAttr['id']
+        else:
+            instRamDisk = None
+
+        # Create the instances
+        for i in range(0, numInst):
+            eucaInst = EucaInstance(slice = s, 
+                                    kernel_id = instKernel,
+                                    image_id = instDiskImg,
+                                    ramdisk_id = instRamDisk,
+                                    key_pair = instKey,
+                                    inst_type = instType)
+            eucaInst.reserveInstance(conn)
+
+    return True
+
+def main():
+    init_server()
+
+    theRSpec = None
+    with open(sys.argv[1]) as xml:
+        theRSpec = xml.read()
+    create_slice(None, 'planetcloud.pc.test', theRSpec)
+
+    #rspec = get_rspec('euca', 'planetcloud.pc.test', 'planetcloud.pc.marcoy')
+    #print rspec
+
+if __name__ == "__main__":
+    main()
+
diff --git a/sfa/managers/aggregate_manager_max.py b/sfa/managers/aggregate_manager_max.py
new file mode 100644 (file)
index 0000000..d7aed8e
--- /dev/null
@@ -0,0 +1,335 @@
+#!/usr/bin/python
+
+from sfa.util.rspec import RSpec
+import sys
+import pdb
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.storage import *
+from sfa.util.policy import Policy
+from sfa.util.debug import log
+from sfa.server.aggregate import Aggregates
+from sfa.server.registry import Registries
+from sfa.util.faults import *
+
+import xml.dom.minidom
+
+SFA_MAX_CONF_FILE = '/etc/sfa/max_allocations'
+SFA_MAX_DEFAULT_RSPEC = '/etc/sfa/max_physical.xml'
+SFA_MAX_CANNED_RSPEC = '/etc/sfa/max_physical_canned.xml'
+
+topology = {}
+
+class SfaOutOfResource(SfaFault):
+    def __init__(self, interface):
+        faultString = "Interface " + interface + " not available"
+        SfaFault.__init__(self, 100, faultString, '')
+
+class SfaNoPairRSpec(SfaFault):
+    def __init__(self, interface, interface2):
+        faultString = "Interface " + interface + " should be paired with " + interface2
+        SfaFault.__init__(self, 100, faultString, '')
+
+# Returns a mapping from interfaces to the nodes they lie on and their peer interfaces
+# i -> node,i_peer
+
+def get_interface_map():
+    r = RSpec()
+    r.parseFile(SFA_MAX_DEFAULT_RSPEC)
+    rspec = r.toDict()
+    capacity = rspec['rspec']['capacity']
+    netspec = capacity[0]['netspec'][0]
+    linkdefs = {}
+    for n in netspec['nodespec']:
+        ifspecs = n['ifspec']
+        nodename = n['node']
+        for i in ifspecs:
+            ifname = i['name']
+            linkid = i['linkid']
+
+            if (linkdefs.has_key(linkid)):
+                linkdefs[linkid].extend([(nodename,ifname)])
+            else:
+                linkdefs[linkid]=[(nodename,ifname)]
+    
+    # topology maps interface x interface -> link,node1,node2
+    topology={}
+
+    for k in linkdefs.keys():
+        (n1,i1) = linkdefs[k][0]
+        (n2,i2) = linkdefs[k][1]
+
+        topology[i1] = (n1, i2)
+        topology[i2] = (n2, i1)
+        
+
+    return topology    
+
+    
+def allocations_to_rspec(allocations):
+    rspec = xml.dom.minidom.parse(SFA_MAX_DEFAULT_RSPEC)
+    req = rspec.firstChild.appendChild(rspec.createElement("request"))
+    for (iname,ip) in allocations:
+        ifspec = req.appendChild(rspec.createElement("ifspec"))
+        ifspec.setAttribute("name","tns:"+iname)
+        ifspec.setAttribute("ip",ip)
+
+    return rspec.toxml()
+        
+    
+def if_endpoints(ifs):
+    nodes=[]
+    for l in ifs:
+        nodes.extend(topology[l][0])
+    return nodes
+
+def lock_state_file():
+    # Noop for demo
+    return True
+
+def unlock_state_file():
+    return True
+    # Noop for demo
+
+def read_alloc_dict():
+    alloc_dict={}
+    rows = open(SFA_MAX_CONF_FILE).read().split('\n')
+    for r in rows:
+        columns = r.split(' ')
+        if (len(columns)==2):
+            hrn = columns[0]
+            allocs = columns[1].split(',')
+            ipallocs = map(lambda alloc:alloc.split('/'), allocs)
+            alloc_dict[hrn]=ipallocs
+    return alloc_dict
+
+def commit_alloc_dict(d):
+    f = open(SFA_MAX_CONF_FILE, 'w')
+    for hrn in d.keys():
+        columns = d[hrn]
+        ipcolumns = map(lambda x:"/".join(x), columns)
+        row = hrn+' '+','.join(ipcolumns)+'\n'
+        f.write(row)
+    f.close()
+
+def collapse_alloc_dict(d):
+    ret = []
+    for k in d.keys():
+        ret.extend(d[k])
+    return ret
+
+
+def alloc_links(api, hrn, links_to_add, links_to_drop):
+    slicename=hrn_to_pl_slicename(hrn)
+    for (iface,ip) in links_to_add:
+        node = topology[iface][0][0]
+        try:
+            api.plshell.AddSliceTag(api.plauth, slicename, "ip_addresses", ip, node)
+            api.plshell.AddSliceTag(api.plauth, slicename, "vsys", "getvlan", node)
+        except Exception: 
+            # Probably a duplicate tag. XXX July 21
+            pass
+    return True
+
+def alloc_nodes(api,hrn, requested_ifs):
+    requested_nodes = if_endpoints(requested_ifs)
+    create_slice_max_aggregate(api, hrn, requested_nodes)
+
+# Taken from slices.py
+
+def create_slice_max_aggregate(api, hrn, nodes):
+    # Get the slice record 
+    global topology
+    topology = get_interface_map()
+    slice = {}
+    registries = Registries(api)
+    registry = registries[api.hrn]
+    credential = api.getCredential()
+    records = registry.resolve(credential, hrn)
+    for record in records:
+        if record.get_type() in ['slice']:
+            slice = record.as_dict()
+    if not slice:
+        raise RecordNotFound(hrn)   
+
+    # Make sure slice exists at plc, if it doesnt add it
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, [slicename], ['node_ids'])
+    if not slices:
+        parts = slicename.split("_")
+        login_base = parts[0]
+        # if site doesnt exist add it
+        sites = api.plshell.GetSites(api.plauth, [login_base])
+        if not sites:
+            authority = get_authority(hrn)
+            site_records = registry.resolve(credential, authority)
+            site_record = {}
+            if not site_records:
+                raise RecordNotFound(authority)
+            site_record = site_records[0]
+            site = site_record.as_dict()
+                
+            # add the site
+            site.pop('site_id')
+            site_id = api.plshell.AddSite(api.plauth, site)
+        else:
+            site = sites[0]
+            
+        slice_fields = {}
+        slice_keys = ['name', 'url', 'description']
+        for key in slice_keys:
+            if key in slice and slice[key]:
+                slice_fields[key] = slice[key]  
+        api.plshell.AddSlice(api.plauth, slice_fields)
+        slice = slice_fields
+        slice['node_ids'] = 0
+    else:
+        slice = slices[0]    
+
+    # get the list of valid slice users from the registry and make 
+    # they are added to the slice 
+    researchers = record.get('researcher', [])
+    for researcher in researchers:
+        person_record = {}
+        person_records = registry.resolve(credential, researcher)
+        for record in person_records:
+            if record.get_type() in ['user']:
+                person_record = record
+        if not person_record:
+            pass
+        person_dict = person_record.as_dict()
+        persons = api.plshell.GetPersons(api.plauth, [person_dict['email']],
+                                         ['person_id', 'key_ids'])
+
+        # Create the person record 
+        if not persons:
+            person_id=api.plshell.AddPerson(api.plauth, person_dict)
+
+            # The line below enables the user account on the remote aggregate
+            # soon after it is created.
+            # without this the user key is not transfered to the slice
+            # (as GetSlivers returns key of only enabled users),
+            # which prevents the user from login to the slice.
+            # We may do additional checks before enabling the user.
+
+            api.plshell.UpdatePerson(api.plauth, person_id, {'enabled' : True})
+            key_ids = []
+        else:
+            key_ids = persons[0]['key_ids']
+
+        api.plshell.AddPersonToSlice(api.plauth, person_dict['email'],
+                                     slicename)        
+
+        # Get this users local keys
+        keylist = api.plshell.GetKeys(api.plauth, key_ids, ['key'])
+        keys = [key['key'] for key in keylist]
+
+        # add keys that arent already there 
+        for personkey in person_dict['keys']:
+            if personkey not in keys:
+                key = {'key_type': 'ssh', 'key': personkey}
+                api.plshell.AddPersonKey(api.plauth, person_dict['email'], key)
+
+    # find out where this slice is currently running
+    nodelist = api.plshell.GetNodes(api.plauth, slice['node_ids'],
+                                    ['hostname'])
+    hostnames = [node['hostname'] for node in nodelist]
+
+    # remove nodes not in rspec
+    deleted_nodes = list(set(hostnames).difference(nodes))
+    # add nodes from rspec
+    added_nodes = list(set(nodes).difference(hostnames))
+
+    api.plshell.AddSliceToNodes(api.plauth, slicename, added_nodes) 
+    api.plshell.DeleteSliceFromNodes(api.plauth, slicename, deleted_nodes)
+
+    return 1
+
+
+def get_rspec(api, creds, options):
+    # get slice's hrn from options
+    xrn = options.get('geni_slice_urn', None)
+    hrn, type = urn_to_hrn(xrn)
+    # Eg. config line:
+    # plc.princeton.sapan vlan23,vlan45
+
+    allocations = read_alloc_dict()
+    if (hrn and allocations.has_key(hrn)):
+            ret_rspec = allocations_to_rspec(allocations[hrn])
+    else:
+        ret_rspec = open(SFA_MAX_CANNED_RSPEC).read()
+
+    return (ret_rspec)
+
+
+def create_slice(api, xrn, creds, rspec_xml, users):
+    global topology
+    hrn = urn_to_hrn(xrn)[0]
+    topology = get_interface_map()
+
+    # Check if everything in rspec is either allocated by hrn
+    # or not allocated at all.
+    r = RSpec()
+    r.parseString(rspec_xml)
+    rspec = r.toDict()
+
+    lock_state_file()
+
+    allocations = read_alloc_dict()
+    requested_allocations = rspec_to_allocations (rspec)
+    current_allocations = collapse_alloc_dict(allocations)
+    try:
+        current_hrn_allocations=allocations[hrn]
+    except KeyError:
+        current_hrn_allocations=[]
+
+    # Check request against current allocations
+    requested_interfaces = map(lambda(elt):elt[0], requested_allocations)
+    current_interfaces = map(lambda(elt):elt[0], current_allocations)
+    current_hrn_interfaces = map(lambda(elt):elt[0], current_hrn_allocations)
+
+    for a in requested_interfaces:
+        if (a not in current_hrn_interfaces and a in current_interfaces):
+            raise SfaOutOfResource(a)
+        if (topology[a][1] not in requested_interfaces):
+            raise SfaNoPairRSpec(a,topology[a][1])
+    # Request OK
+
+    # Allocations to delete
+    allocations_to_delete = []
+    for a in current_hrn_allocations:
+        if (a not in requested_allocations):
+            allocations_to_delete.extend([a])
+
+    # Ok, let's do our thing
+    alloc_nodes(api, hrn, requested_interfaces)
+    alloc_links(api, hrn, requested_allocations, allocations_to_delete)
+    allocations[hrn] = requested_allocations
+    commit_alloc_dict(allocations)
+
+    unlock_state_file()
+
+    return True
+
+def rspec_to_allocations(rspec):
+    ifs = []
+    try:
+        ifspecs = rspec['rspec']['request'][0]['ifspec']
+        for l in ifspecs:
+            ifs.extend([(l['name'].replace('tns:',''),l['ip'])])
+    except KeyError:
+        # Bad RSpec
+        pass
+    return ifs
+
+def main():
+    t = get_interface_map()
+    r = RSpec()
+    rspec_xml = open(sys.argv[1]).read()
+    #get_rspec(None,'foo')
+    create_slice(None, "plc.princeton.sap0", rspec_xml)
+    
+if __name__ == "__main__":
+    main()
diff --git a/sfa/managers/aggregate_manager_openflow.py b/sfa/managers/aggregate_manager_openflow.py
new file mode 100755 (executable)
index 0000000..d2507eb
--- /dev/null
@@ -0,0 +1,172 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.rspec import RSpec
+from sfa.server.registry import Registries
+from sfa.util.config import Config
+from sfa.plc.nodes import *
+import sys
+
+#The following is not essential
+#from soaplib.wsgi_soap import SimpleWSGISoapApp
+#from soaplib.serializers.primitive import *
+#from soaplib.serializers.clazz import *
+
+import socket
+import struct
+
+# Message IDs for all the SFA light calls
+# This will be used by the aggrMgr controller
+SFA_GET_RESOURCES = 101
+SFA_CREATE_SLICE = 102
+SFA_START_SLICE = 103
+SFA_STOP_SLICE = 104
+SFA_DELETE_SLICE = 105
+SFA_GET_SLICES = 106
+SFA_RESET_SLICES = 107
+
+DEBUG = 1
+
+def print_buffer(buf):
+    for i in range(0,len(buf)):
+        print('%x' % buf[i])
+
+def extract(sock):
+    # Shud we first obtain the message length?
+    # msg_len = socket.ntohs(sock.recv(2))
+    msg = ""
+
+    while (1):
+        try:
+            chunk = sock.recv(1)
+        except socket.error, message:
+            if 'timed out' in message:
+                break
+            else:
+                sys.exit("Socket error: " + message)
+
+        if len(chunk) == 0:
+            break
+        msg += chunk
+
+    print 'Done extracting %d bytes of response from aggrMgr' % len(msg)
+    return msg
+   
+def connect(server, port):
+    '''Connect to the Aggregate Manager module'''
+    sock = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
+    sock.connect ( ( server, port) )
+    sock.settimeout(1)
+    if DEBUG: print 'Connected!'
+    return sock
+    
+def connect_aggrMgr():
+    (aggr_mgr_ip, aggr_mgr_port) = Config().get_openflow_aggrMgr_info()
+    if DEBUG: print """Connecting to port %d of %s""" % (aggr_mgr_port, aggr_mgr_ip)
+    return connect(aggr_mgr_ip, aggr_mgr_port)
+
+def generate_slide_id(cred, hrn):
+    if cred == None:
+        cred = ""
+    if hrn == None:
+        hrn = ""
+    #return cred + '_' + hrn
+    return str(hrn)
+
+def msg_aggrMgr(cred, hrn, msg_id):
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds' % len(slice_id), msg_id, slice_id)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        aggrMgr_sock.close()
+        return 1
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return 0
+
+def start_slice(cred, xrn):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received start_slice call"
+    return msg_aggrMgr(SFA_START_SLICE)
+
+def stop_slice(cred, xrn):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received stop_slice call"
+    return msg_aggrMgr(SFA_STOP_SLICE)
+
+def delete_slice(cred, xrn):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received delete_slice call"
+    return msg_aggrMgr(SFA_DELETE_SLICE)
+
+def reset_slices(cred, xrn):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received reset_slices call"
+    return msg_aggrMgr(SFA_RESET_SLICES)
+
+def create_slice(cred, xrn, rspec):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received create_slice call"
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds%ds' % (len(slice_id)+1, len(rspec)), SFA_CREATE_SLICE, slice_id, rspec)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        if DEBUG: print "Sent %d bytes and closing connection" % len(buf)
+        aggrMgr_sock.close()
+
+        if DEBUG: print "----------------"
+        return 1
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return 0
+
+def get_rspec(cred, xrn=None):
+    hrn = urn_to_hrn(xrn)[0]
+    if DEBUG: print "Received get_rspec call"
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds' % len(slice_id), SFA_GET_RESOURCES, slice_id)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        resource_list = extract(aggrMgr_sock);
+        aggrMgr_sock.close()
+
+        if DEBUG: print "----------------"
+        return resource_list 
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return None
+
+"""
+Returns the request context required by sfatables. At some point, this mechanism should be changed
+to refer to "contexts", which is the information that sfatables is requesting. But for now, we just
+return the basic information needed in a dict.
+"""
+def fetch_context(slice_hrn, user_hrn, contexts):
+    base_context = {'sfa':{'user':{'hrn':user_hrn}}}
+    return base_context
+
+def main():
+    r = RSpec()
+    r.parseFile(sys.argv[1])
+    rspec = r.toDict()
+    create_slice(None,'plc',rspec)
+    
+if __name__ == "__main__":
+    main()
diff --git a/sfa/managers/aggregate_manager_pl.py b/sfa/managers/aggregate_manager_pl.py
new file mode 100644 (file)
index 0000000..fecc3d7
--- /dev/null
@@ -0,0 +1,326 @@
+### $Id: slices.py 15842 2009-11-22 09:56:13Z anil $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/slices.py $
+
+import datetime
+import time
+import traceback
+import sys
+
+from types import StringTypes
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.record import SfaRecord
+from sfa.util.policy import Policy
+from sfa.util.record import *
+from sfa.util.sfaticket import SfaTicket
+from sfa.util.debug import log
+from sfa.plc.slices import Slices
+from sfa.trust.credential import Credential
+import sfa.plc.peers as peers
+from sfa.plc.network import *
+from sfa.plc.api import SfaAPI
+from sfa.plc.slices import *
+
+
+def __get_registry_objects(slice_xrn, creds, users):
+    """
+
+    """
+    hrn, type = urn_to_hrn(slice_xrn)
+
+    hrn_auth = get_authority(hrn)
+
+    # Build up objects that an SFA registry would return if SFA
+    # could contact the slice's registry directly
+    reg_objects = None
+
+    if users:
+        reg_objects = {}
+
+        site = {}
+        site['site_id'] = 0
+        site['name'] = 'geni.%s' % hrn_auth
+        site['enabled'] = True
+        site['max_slices'] = 100
+
+        # Note:
+        # Is it okay if this login base is the same as one already at this myplc site?
+        # Do we need uniqueness?  Should use hrn_auth instead of just the leaf perhaps?
+        site['login_base'] = get_leaf(hrn_auth)
+        site['abbreviated_name'] = hrn
+        site['max_slivers'] = 1000
+        reg_objects['site'] = site
+
+        slice = {}
+        slice['expires'] = int(time.mktime(Credential(string=creds[0]).get_lifetime().timetuple()))
+        slice['hrn'] = hrn
+        slice['name'] = site['login_base'] + "_" +  get_leaf(hrn)
+        slice['url'] = hrn
+        slice['description'] = hrn
+        slice['pointer'] = 0
+        reg_objects['slice_record'] = slice
+
+        reg_objects['users'] = {}
+        for user in users:
+            user['key_ids'] = []
+            hrn, _ = urn_to_hrn(user['urn'])
+            user['email'] = hrn + "@geni.net"
+            user['first_name'] = hrn
+            user['last_name'] = hrn
+            reg_objects['users'][user['email']] = user
+
+        return reg_objects
+
+def __get_hostnames(nodes):
+    hostnames = []
+    for node in nodes:
+        hostnames.append(node.hostname)
+    return hostnames
+
+def get_version():
+    version = {}
+    version['geni_api'] = 1
+    version['sfa'] = 1
+    return version
+
+def slice_status(api, slice_xrn, creds):
+    result = {}
+    result['geni_urn'] = slice_xrn
+    result['geni_status'] = 'unknown'
+    result['geni_resources'] = {}
+    return result
+
+def create_slice(api, slice_xrn, creds, rspec, users):
+    """
+    Create the sliver[s] (slice) at this aggregate.    
+    Verify HRN and initialize the slice record in PLC if necessary.
+    """
+
+    reg_objects = __get_registry_objects(slice_xrn, creds, users)
+
+    hrn, type = urn_to_hrn(slice_xrn)
+    peer = None
+    slices = Slices(api)
+    peer = slices.get_peer(hrn)
+    sfa_peer = slices.get_sfa_peer(hrn)
+    registry = api.registries[api.hrn]
+    credential = api.getCredential()
+    site_id, remote_site_id = slices.verify_site(registry, credential, hrn, 
+                                                 peer, sfa_peer, reg_objects)
+
+    slice_record = slices.verify_slice(registry, credential, hrn, site_id, 
+                                remote_site_id, peer, sfa_peer, reg_objects)
+     
+    network = Network(api)
+
+    slice = network.get_slice(api, hrn)
+    slice.peer_id = slice_record['peer_slice_id']
+    current = __get_hostnames(slice.get_nodes())
+    
+    network.addRSpec(rspec, api.config.SFA_AGGREGATE_RSPEC_SCHEMA)
+    request = __get_hostnames(network.nodesWithSlivers())
+    
+    # remove nodes not in rspec
+    deleted_nodes = list(set(current).difference(request))
+
+    # add nodes from rspec
+    added_nodes = list(set(request).difference(current))
+    
+    if peer:
+        api.plshell.UnBindObjectFromPeer(api.plauth, 'slice', slice.id, peer)
+
+    api.plshell.AddSliceToNodes(api.plauth, slice.name, added_nodes) 
+    api.plshell.DeleteSliceFromNodes(api.plauth, slice.name, deleted_nodes)
+
+    network.updateSliceTags()
+
+    if peer:
+        api.plshell.BindObjectToPeer(api.plauth, 'slice', slice.id, peer, 
+                                     slice.peer_id)
+
+    # print network.toxml()
+
+    return True
+
+
+def renew_slice(api, xrn, creds, exipration_time):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, {'name': slicename}, ['slice_id'])
+    if not slices:
+        raise RecordNotFound(hrn)
+    slice = slices[0]
+    slice['expires'] = expiration_time
+    api.plshell.UpdateSlice(api.plauth, slice['slice_id'], slice)
+    return 1         
+
+def start_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, {'name': slicename}, ['slice_id'])
+    if not slices:
+        raise RecordNotFound(hrn)
+    slice_id = slices[0]['slice_id']
+    slice_tags = api.plshell.GetSliceTags(api.plauth, {'slice_id': slice_id, 'tagname': 'enabled'}, ['slice_tag_id'])
+    # just remove the tag if it exists
+    if slice_tags:
+        api.plshell.DeleteSliceTag(api.plauth, slice_tags[0]['slice_tag_id'])
+
+    return 1
+def stop_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, {'name': slicename}, ['slice_id'])
+    if not slices:
+        raise RecordNotFound(hrn)
+    slice_id = slices[0]['slice_id']
+    slice_tags = api.plshell.GetSliceTags(api.plauth, {'slice_id': slice_id, 'tagname': 'enabled'})
+    if not slice_tags:
+        api.plshell.AddSliceTag(api.plauth, slice_id, 'enabled', '0')
+    elif slice_tags[0]['value'] != "0":
+        tag_id = attributes[0]['slice_tag_id']
+        api.plshell.UpdateSliceTag(api.plauth, tag_id, '0')
+    return 1
+
+def reset_slice(api, xrn):
+    # XX not implemented at this interface
+    return 1
+
+def delete_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, {'name': slicename})
+    if not slices:
+        return 1
+    slice = slices[0]
+
+    # determine if this is a peer slice
+    peer = peers.get_peer(api, hrn)
+    if peer:
+        api.plshell.UnBindObjectFromPeer(api.plauth, 'slice', slice['slice_id'], peer)
+    api.plshell.DeleteSliceFromNodes(api.plauth, slicename, slice['node_ids'])
+    if peer:
+        api.plshell.BindObjectToPeer(api.plauth, 'slice', slice['slice_id'], peer, slice['peer_slice_id'])
+    return 1
+
+def get_slices(api, creds):
+    # look in cache first
+    if api.cache:
+        slices = api.cache.get('slices')
+        if slices:
+            return slices
+
+    # get data from db 
+    slices = api.plshell.GetSlices(api.plauth, {'peer_id': None}, ['name'])
+    slice_hrns = [slicename_to_hrn(api.hrn, slice['name']) for slice in slices]
+    slice_urns = [hrn_to_urn(slice_hrn, 'slice') for slice_hrn in slice_hrns]
+
+    # cache the result
+    if api.cache:
+        api.cache.add('slices', slice_urns) 
+
+    return slice_urns
+    
+def get_rspec(api, creds, options):
+    # get slice's hrn from options
+    xrn = options.get('geni_slice_urn', None)
+    hrn, type = urn_to_hrn(xrn)
+
+    # look in cache first
+    if api.cache and not xrn:
+        rspec = api.cache.get('nodes')
+        if rspec:
+            return rspec 
+
+    network = Network(api)
+    if (hrn):
+        if network.get_slice(api, hrn):
+            network.addSlice()
+
+    rspec = network.toxml()
+
+    # cache the result
+    if api.cache and not xrn:
+        api.cache.add('nodes', rspec)
+
+    return rspec
+
+
+def get_ticket(api, xrn, creds, rspec, users):
+
+    reg_objects = __get_registry_objects(xrn, creds, users)
+
+    slice_hrn, type = urn_to_hrn(xrn)
+    slices = Slices(api)
+    peer = slices.get_peer(slice_hrn)
+    sfa_peer = slices.get_sfa_peer(slice_hrn)
+
+    # get the slice record
+    registry = api.registries[api.hrn]
+    credential = api.getCredential()
+    records = registry.Resolve(xrn, credential)
+
+    # similar to create_slice, we must verify that the required records exist
+    # at this aggregate before we can issue a ticket
+    site_id, remote_site_id = slices.verify_site(registry, credential, slice_hrn,
+                                                 peer, sfa_peer, reg_objects)
+    slice = slices.verify_slice(registry, credential, slice_hrn, site_id,
+                                remote_site_id, peer, sfa_peer, reg_objects)
+
+    # make sure we get a local slice record
+    record = None
+    for tmp_record in records:
+        if tmp_record['type'] == 'slice' and \
+           not tmp_record['peer_authority']:
+            record = SliceRecord(dict=tmp_record)
+    if not record:
+        raise RecordNotFound(slice_hrn)
+
+    # get sliver info
+    slivers = Slices(api).get_slivers(slice_hrn)
+    if not slivers:
+        raise SliverDoesNotExist(slice_hrn)
+
+    # get initscripts
+    initscripts = []
+    data = {
+        'timestamp': int(time.time()),
+        'initscripts': initscripts,
+        'slivers': slivers
+    }
+
+    # create the ticket
+    object_gid = record.get_gid_object()
+    new_ticket = SfaTicket(subject = object_gid.get_subject())
+    new_ticket.set_gid_caller(api.auth.client_gid)
+    new_ticket.set_gid_object(object_gid)
+    new_ticket.set_issuer(key=api.key, subject=api.hrn)
+    new_ticket.set_pubkey(object_gid.get_pubkey())
+    new_ticket.set_attributes(data)
+    new_ticket.set_rspec(rspec)
+    #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
+    new_ticket.encode()
+    new_ticket.sign()
+
+    return new_ticket.save_to_string(save_parents=True)
+
+
+
+def main():
+    api = SfaAPI()
+    """
+    rspec = get_rspec(api, "plc.princeton.sapan", None)
+    #rspec = get_rspec(api, "plc.princeton.coblitz", None)
+    #rspec = get_rspec(api, "plc.pl.sirius", None)
+    print rspec
+    """
+    f = open(sys.argv[1])
+    xml = f.read()
+    f.close()
+    create_slice(api, "plc.princeton.sapan", xml)
+
+if __name__ == "__main__":
+    main()
diff --git a/sfa/managers/aggregate_manager_vini.py b/sfa/managers/aggregate_manager_vini.py
new file mode 100644 (file)
index 0000000..51b74d5
--- /dev/null
@@ -0,0 +1,137 @@
+### $Id: slices.py 15842 2009-11-22 09:56:13Z anil $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/slices.py $
+
+import datetime
+import time
+import traceback
+import sys
+
+from types import StringTypes
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.record import SfaRecord
+from sfa.util.policy import Policy
+from sfa.util.record import *
+from sfa.util.sfaticket import SfaTicket
+from sfa.server.registry import Registries
+from sfa.util.debug import log
+from sfa.plc.slices import Slices
+import sfa.plc.peers as peers
+from sfa.managers.vini.vini_network import *
+from sfa.plc.api import SfaAPI
+from sfa.plc.slices import *
+from sfa.managers.aggregate_manager_pl import __get_registry_objects, __get_hostnames
+
+# VINI aggregate is almost identical to PLC aggregate for many operations, 
+# so lets just import the methods form the PLC manager
+
+from sfa.managers.aggregate_manager_pl import (
+start_slice, stop_slice, renew_slice, reset_slice, get_slices, get_ticket)
+
+
+def get_version():
+    version = {}
+    version['geni_api'] = 1
+    version['sfa'] = 1
+    return version
+
+def slice_status(api, slice_xrn, creds):
+    result = {}
+    result['geni_urn'] = slice_xrn
+    result['geni_status'] = 'unknown'
+    result['geni_resources'] = {}
+    return result
+
+
+def delete_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, {'name': slicename})
+    if not slices:
+        return 1
+    slice = slices[0]
+
+    # determine if this is a peer slice
+    peer = peers.get_peer(api, hrn)
+    if peer:
+        api.plshell.UnBindObjectFromPeer(api.plauth, 'slice', slice['slice_id'], peer)
+    api.plshell.DeleteSliceFromNodes(api.plauth, slicename, slice['node_ids'])
+    if peer:
+        api.plshell.BindObjectToPeer(api.plauth, 'slice', slice['slice_id'], peer, slice['peer_slice_id'])
+    return 1
+
+def create_slice(api, xrn, creds, xml, users):
+    """
+    Verify HRN and initialize the slice record in PLC if necessary.
+    """
+
+    hrn, type = urn_to_hrn(xrn)
+    peer = None
+    reg_objects = __get_registry_objects(slice_xrn, creds, users)
+    slices = Slices(api)
+    peer = slices.get_peer(hrn)
+    sfa_peer = slices.get_sfa_peer(hrn)
+    registries = Registries(api)
+    registry = registries[api.hrn]
+    credential = api.getCredential()
+    site_id, remote_site_id = slices.verify_site(registry, credential, hrn, 
+                                                 peer, sfa_peer, reg_objects)
+    slice = slices.verify_slice(registry, credential, hrn, site_id, 
+                                remote_site_id, peer, sfa_peer, reg_objects)
+
+    network = ViniNetwork(api)
+
+    slice = network.get_slice(api, hrn)
+    current = __get_hostnames(slice.get_nodes())
+
+    network.addRSpec(xml, "/var/www/html/schemas/vini.rng")
+    #network.addRSpec(xml, "/root/SVN/sfa/trunk/sfa/managers/vini/vini.rng")
+    request = __get_hostnames(network.nodesWithSlivers())
+    
+    # remove nodes not in rspec
+    deleted_nodes = list(set(current).difference(request))
+
+    # add nodes from rspec
+    added_nodes = list(set(request).difference(current))
+
+    if peer:
+        api.plshell.UnBindObjectFromPeer(api.plauth, 'slice', slice.id, peer)
+
+    api.plshell.AddSliceToNodes(api.plauth, slice.name, added_nodes) 
+    api.plshell.DeleteSliceFromNodes(api.plauth, slice.name, deleted_nodes)
+
+    network.updateSliceTags()
+
+    if peer:
+        api.plshell.BindObjectToPeer(api.plauth, 'slice', slice.id, peer, 
+                                     slice.peer_id)
+
+    # print network.toxml()
+
+    return True
+
+def get_rspec(api, xrn=None, origin_hrn=None):
+    hrn, type = urn_to_hrn(xrn)
+    network = ViniNetwork(api)
+    if (hrn):
+        if network.get_slice(api, hrn):
+            network.addSlice()
+
+    return network.toxml()
+
+def main():
+    api = SfaAPI()
+    """
+    #rspec = get_rspec(api, None, None)
+    rspec = get_rspec(api, "plc.princeton.iias", None)
+    print rspec
+    """
+    f = open(sys.argv[1])
+    xml = f.read()
+    f.close()
+    create_slice(api, "plc.princeton.iias", xml)
+
+if __name__ == "__main__":
+    main()
diff --git a/sfa/managers/component_manager_default.py b/sfa/managers/component_manager_default.py
new file mode 100644 (file)
index 0000000..e6482d4
--- /dev/null
@@ -0,0 +1,22 @@
+
+def start_slice(api, slicename):
+    return
+
+def stop_slice(api, slicename):
+    return
+
+def delete_slice(api, slicename):
+    return
+
+def reset_slice(api, slicename):
+    return 
+def get_slices(api):
+    return 
+
+def reboot():
+    return
+
+def redeem_ticket(api, ticket_string):
+    return 
+
diff --git a/sfa/managers/component_manager_pl.py b/sfa/managers/component_manager_pl.py
new file mode 100644 (file)
index 0000000..c26ca4d
--- /dev/null
@@ -0,0 +1,73 @@
+import os
+import xmlrpclib
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.sfaticket import SfaTicket
+
+def init_server():
+    from sfa.server import sfa_component_setup
+    # get current trusted gids
+    try:
+        sfa_component_setup.get_trusted_certs()
+    except:
+        # our keypair may be old, try refreshing
+        sfa_component_setup.get_node_key()
+        sfa_component_setup.get_credential(force=True)
+        sfa_component_setup.get_trusted_certs()
+
+def get_version():
+    version = {}
+    version['geni_api'] = 1
+    return version
+
+def slice_status(api, slice_xrn, creds):
+    result = {}
+    result['geni_urn'] = slice_xrn
+    result['geni_status'] = 'unknown'
+    result['geni_resources'] = {}
+    return result
+           
+def start_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    api.nodemanger.Start(slicename)
+
+def stop_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    api.nodemanager.Stop(slicename)
+
+def delete_slice(api, xrn, creds):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    api.nodemanager.Destroy(slicename)
+
+def reset_slice(api, xrn):
+    hrn, type = urn_to_hrn(xrn)
+    slicename = hrn_to_pl_slicename(hrn)
+    if not api.sliver_exists(slicename):
+        raise SliverDoesNotExist(slicename)
+    api.nodemanager.ReCreate(slicename)
+def get_slices(api):
+    # this returns a tuple, the data we want is at index 1 
+    xids = api.nodemanager.GetXIDs()
+    # unfortunately the data we want is given to us as 
+    # a string but we really want it as a dict
+    # lets eval it
+    slices = eval(xids[1])
+    return slices.keys()
+
+def redeem_ticket(api, ticket_string):
+    ticket = SfaTicket(string=ticket_string)
+    ticket.decode()
+    hrn = ticket.attributes['slivers'][0]['hrn']
+    slicename = hrn_to_pl_slicename(hrn)
+    if not api.sliver_exists(slicename):
+        raise SliverDoesNotExist(slicename)
+
+    # convert ticket to format nm is used to
+    nm_ticket = xmlrpclib.dumps((ticket.attributes,), methodresponse=True)
+    api.nodemanager.AdminTicket(nm_ticket)
+    
+
diff --git a/sfa/managers/eucalyptus/eucalyptus.rnc b/sfa/managers/eucalyptus/eucalyptus.rnc
new file mode 100644 (file)
index 0000000..271ca81
--- /dev/null
@@ -0,0 +1,89 @@
+start = RSpec
+RSpec = element RSpec {
+   attribute type { xsd:NMTOKEN },
+   cloud
+}
+cloud = element cloud {
+   attribute id { xsd:NMTOKEN },
+   user_info?,
+   ipv4,
+   keypairs,
+   images,
+   cluster+
+}
+user_info = element user_info { 
+   credential 
+}
+keypairs = element keypairs { keypair+ }
+images = element images { image+ }
+image = element image {
+   attribute id { xsd:ID },
+   type,
+   arch,
+   state,
+   location
+}
+cluster = element cluster {
+   attribute id { xsd:ID },
+   ipv4,
+   vm_types
+}
+vm_types = element vm_types { vm_type+ }
+vm_type = element vm_type {
+   attribute name { xsd:ID },
+   free_slots,
+   max_instances,
+   cores,
+   memory,
+   disk_space,
+   request?,
+   euca_instances?
+}
+request = element request {
+   instances,
+   kernel_image,
+   ramdisk,
+   disk_image,
+   keypair
+}
+euca_instances = element euca_instances {
+   euca_instance+
+}
+euca_instance = element euca_instance {
+   attribute id { xsd:ID },
+   state,
+   public_dns,
+   keypair
+}
+credential = element credential { text }
+ipv4 = element ipv4 { text }
+keypair = element keypair { text }
+type = element type { text }
+arch = element arch { text }
+state = element state { text }
+location = element location { text }
+free_slots = element free_slots { text }
+max_instances = element max_instances { text }
+cores = element cores { text }
+public_dns = element public_dns { text }
+memory = element memory {
+   attribute unit { xsd:NMTOKEN },
+   text
+}
+disk_space = element disk_space {
+   attribute unit { xsd:NMTOKEN },
+   text
+}
+instances = element instances { xsd:unsignedInt }
+kernel_image = element kernel_image {
+   attribute id { xsd:IDREF },
+   empty
+}
+ramdisk = element ramdisk {
+   attribute id { xsd:IDREF }?,
+   empty
+}
+disk_image = element disk_image {
+   attribute id { xsd:IDREF },
+   empty
+}
diff --git a/sfa/managers/eucalyptus/eucalyptus.rng b/sfa/managers/eucalyptus/eucalyptus.rng
new file mode 100644 (file)
index 0000000..43c3f9a
--- /dev/null
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <start>
+    <ref name="RSpec"/>
+  </start>
+  <define name="RSpec">
+    <element name="RSpec">
+      <attribute name="type">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <ref name="cloud"/>
+    </element>
+  </define>
+  <define name="cloud">
+    <element name="cloud">
+      <attribute name="id">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <optional>
+        <ref name="user_info"/>
+      </optional>
+      <ref name="ipv4"/>
+      <ref name="keypairs"/>
+      <ref name="images"/>
+      <oneOrMore>
+        <ref name="cluster"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="user_info">
+    <element name="user_info">
+      <ref name="credential"/>
+    </element>
+  </define>
+  <define name="keypairs">
+    <element name="keypairs">
+      <oneOrMore>
+        <ref name="keypair"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="images">
+    <element name="images">
+      <oneOrMore>
+        <ref name="image"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="image">
+    <element name="image">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <ref name="type"/>
+      <ref name="arch"/>
+      <ref name="state"/>
+      <ref name="location"/>
+    </element>
+  </define>
+  <define name="cluster">
+    <element name="cluster">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <ref name="ipv4"/>
+      <ref name="vm_types"/>
+    </element>
+  </define>
+  <define name="vm_types">
+    <element name="vm_types">
+      <oneOrMore>
+        <ref name="vm_type"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="vm_type">
+    <element name="vm_type">
+      <attribute name="name">
+        <data type="ID"/>
+      </attribute>
+      <ref name="free_slots"/>
+      <ref name="max_instances"/>
+      <ref name="cores"/>
+      <ref name="memory"/>
+      <ref name="disk_space"/>
+      <optional>
+        <ref name="request"/>
+      </optional>
+      <optional>
+        <ref name="euca_instances"/>
+      </optional>
+    </element>
+  </define>
+  <define name="request">
+    <element name="request">
+      <ref name="instances"/>
+      <ref name="kernel_image"/>
+      <ref name="ramdisk"/>
+      <ref name="disk_image"/>
+      <ref name="keypair"/>
+    </element>
+  </define>
+  <define name="euca_instances">
+    <element name="euca_instances">
+      <oneOrMore>
+        <ref name="euca_instance"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="euca_instance">
+    <element name="euca_instance">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <ref name="state"/>
+      <ref name="public_dns"/>
+      <ref name="keypair"/>
+    </element>
+  </define>
+  <define name="credential">
+    <element name="credential">
+      <text/>
+    </element>
+  </define>
+  <define name="ipv4">
+    <element name="ipv4">
+      <text/>
+    </element>
+  </define>
+  <define name="keypair">
+    <element name="keypair">
+      <text/>
+    </element>
+  </define>
+  <define name="type">
+    <element name="type">
+      <text/>
+    </element>
+  </define>
+  <define name="arch">
+    <element name="arch">
+      <text/>
+    </element>
+  </define>
+  <define name="state">
+    <element name="state">
+      <text/>
+    </element>
+  </define>
+  <define name="location">
+    <element name="location">
+      <text/>
+    </element>
+  </define>
+  <define name="free_slots">
+    <element name="free_slots">
+      <text/>
+    </element>
+  </define>
+  <define name="max_instances">
+    <element name="max_instances">
+      <text/>
+    </element>
+  </define>
+  <define name="cores">
+    <element name="cores">
+      <text/>
+    </element>
+  </define>
+  <define name="public_dns">
+    <element name="public_dns">
+      <text/>
+    </element>
+  </define>
+  <define name="memory">
+    <element name="memory">
+      <attribute name="unit">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <text/>
+    </element>
+  </define>
+  <define name="disk_space">
+    <element name="disk_space">
+      <attribute name="unit">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <text/>
+    </element>
+  </define>
+  <define name="instances">
+    <element name="instances">
+      <data type="unsignedInt"/>
+    </element>
+  </define>
+  <define name="kernel_image">
+    <element name="kernel_image">
+      <attribute name="id">
+        <data type="IDREF"/>
+      </attribute>
+      <empty/>
+    </element>
+  </define>
+  <define name="ramdisk">
+    <element name="ramdisk">
+      <optional>
+        <attribute name="id">
+          <data type="IDREF"/>
+        </attribute>
+      </optional>
+      <empty/>
+    </element>
+  </define>
+  <define name="disk_image">
+    <element name="disk_image">
+      <attribute name="id">
+        <data type="IDREF"/>
+      </attribute>
+      <empty/>
+    </element>
+  </define>
+</grammar>
diff --git a/sfa/managers/eucalyptus/eucalyptus.xml b/sfa/managers/eucalyptus/eucalyptus.xml
new file mode 100644 (file)
index 0000000..9aca652
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<RSpec type="eucalyptus">
+  <cloud id="Emulab-Cloud">
+    <!-- This element added by the user -->
+    <user_info>
+      <credential>foobarbaz</credential>
+    </user_info>
+    <ipv4>155.98.39.85</ipv4>
+    <keypairs>
+      <keypair>cortex</keypair>
+      <keypair>mykey</keypair>
+    </keypairs>
+    <images>
+      <image id="emi-88760F45">
+        <type>machine</type>
+        <arch>x86_64</arch>
+        <state>available</state>
+        <location>images/ttylinux.img.manifest.xml</location>
+      </image>
+      <image id="eki-F26610C6">
+        <type>kernel</type>
+        <arch>x86_64</arch>
+        <state>available</state>
+        <location>images/vmlinuz-2.6.16.33-xen.manifest.xml</location>
+      </image>
+    </images>
+    <cluster id="pcluster">
+      <ipv4>10.1.1.2</ipv4>
+      <vm_types>
+        <vm_type name="m1.small">
+          <free_slots>2</free_slots>
+          <max_instances>2</max_instances>
+          <cores>1</cores>
+          <memory unit="MB">128</memory>
+          <disk_space unit="GB">2</disk_space>
+          <!-- A request for an instance of this type, added by the user -->
+          <request>
+            <instances>1</instances>
+            <kernel_image id="eki-F26610C6"/>
+            <ramdisk/>
+            <disk_image id="emi-88760F45"/>
+            <keypair>cortex</keypair>
+          </request>
+        </vm_type>
+        <vm_type name="c1.medium">
+          <free_slots>1</free_slots>
+          <max_instances>2</max_instances>
+          <cores>1</cores>
+          <memory unit="MB">256</memory>
+          <disk_space unit="GB">5</disk_space>
+        </vm_type>
+        <vm_type name="m1.large">
+          <free_slots>0</free_slots>
+          <max_instances>1</max_instances>
+          <cores>2</cores>
+          <memory unit="MB">512</memory>
+          <disk_space unit="GB">10</disk_space>
+        </vm_type>
+        <vm_type name="m1.xlarge">
+          <free_slots>0</free_slots>
+          <max_instances>1</max_instances>
+          <cores>2</cores>
+          <memory unit="MB">1024</memory>
+          <disk_space unit="GB">20</disk_space>
+        </vm_type>
+        <vm_type name="c1.xlarge">
+          <free_slots>0</free_slots>
+          <max_instances>0</max_instances>
+          <cores>4</cores>
+          <memory unit="MB">2048</memory>
+          <disk_space unit="GB">20</disk_space>
+        </vm_type>
+      </vm_types>
+    </cluster>
+  </cloud>
+</RSpec>
diff --git a/sfa/managers/max/max.xml b/sfa/managers/max/max.xml
new file mode 100644 (file)
index 0000000..f549ab7
--- /dev/null
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rspec id="max_rspec_slice1" xmlns="http://geni.maxgigapop.net/aggregate/rspec/20100412/" schemaLocation="http://geni.dragon.maxgigapop.net/max-rspec.xsd" 
+  xmlns:CtrlPlane="http://ogf.org/schema/network/topology/ctrlPlane/20080828/" CtrlPlane:schemaLocation="http://www.controlplane.net/idcp-v1.1/nmtopo-ctrlp.xsd">
+    <aggregate>geni.maxgigapop.net</aggregate>
+    <description>Example MAX RSpec</description>
+    <lifetime id="time-1271533930-1271563981">
+        <CtrlPlane:start type="CtrlPlane:TimeContent">1279848020</CtrlPlane:start>
+        <CtrlPlane:end type="CtrlPlane:TimeContent">1280712039</CtrlPlane:end>
+    </lifetime>
+    <computeResource id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1">
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3">
+            <address>206.196.176.55</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_1">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.10.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_1</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_2">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.30.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_2</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5">
+            <address>206.196.176.138</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_3">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.20.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_3</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_2">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.30.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_2</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2">
+            <address>206.196.176.133</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_1">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.10.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_1</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_3">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.20.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_3</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+    </computeResource>
+</rspec>
diff --git a/sfa/managers/pl/pl.rnc b/sfa/managers/pl/pl.rnc
new file mode 100644 (file)
index 0000000..098aa2e
--- /dev/null
@@ -0,0 +1,56 @@
+start = RSpec
+RSpec = element RSpec { 
+   attribute type { xsd:NMTOKEN },
+   ( network | request )
+}
+network = element network {
+   attribute name { xsd:NMTOKEN },
+   attribute slice { xsd:NMTOKEN }?,
+   sliver_defaults?,
+   site+
+}
+sliver_defaults = element sliver_defaults {
+   sliver_elements
+}
+site = element site { 
+   attribute id { xsd:ID },
+   element name { text },
+   node*
+}
+node = element node {
+   attribute id { xsd:ID },
+   element hostname { text },
+   element bw_limit { attribute units { xsd:NMTOKEN }, text }?,
+   sliver*
+}
+request = element request {
+   attribute name { xsd:NMTOKEN },
+   sliver_defaults?,
+   sliver*
+}
+sliver = element sliver { 
+   attribute nodeid { xsd:ID }?,
+   sliver_elements
+}
+sliver_elements = ( 
+   element capabilities { text }? 
+ & element codemux { text }* 
+ & element cpu_pct { text }?
+ & element cpu_share { text }?
+ & element delegations { text }?
+ & element disk_max { text }?
+ & element initscript { text }?
+ & element ip_addresses {text }*
+ & element net_i2_max_kbyte { text }?
+ & element net_i2_max_rate { text }?
+ & element net_i2_min_rate { text }?
+ & element net_i2_share { text }?
+ & element net_i2_thresh_kbyte { text }?
+ & element net_max_kbyte { text }?
+ & element net_max_rate { text }?
+ & element net_min_rate { text }?
+ & element net_share { text }?
+ & element net_thresh_kbyte { text }?
+ & element vsys {text}*
+ & element vsys_vnet { text }?
+)
diff --git a/sfa/managers/pl/pl.rng b/sfa/managers/pl/pl.rng
new file mode 100644 (file)
index 0000000..3b46f88
--- /dev/null
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <start>
+    <ref name="RSpec"/>
+  </start>
+  <define name="RSpec">
+    <element name="RSpec">
+      <attribute name="type">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <choice>
+        <ref name="network"/>
+        <ref name="request"/>
+      </choice>
+    </element>
+  </define>
+  <define name="network">
+    <element name="network">
+      <attribute name="name">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <optional>
+        <attribute name="slice">
+          <data type="NMTOKEN"/>
+        </attribute>
+      </optional>
+      <optional>
+        <ref name="sliver_defaults"/>
+      </optional>
+      <oneOrMore>
+        <ref name="site"/>
+      </oneOrMore>
+    </element>
+  </define>
+  <define name="sliver_defaults">
+    <element name="sliver_defaults">
+      <ref name="sliver_elements"/>
+    </element>
+  </define>
+  <define name="site">
+    <element name="site">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <element name="name">
+        <text/>
+      </element>
+      <zeroOrMore>
+        <ref name="node"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="node">
+    <element name="node">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <element name="hostname">
+        <text/>
+      </element>
+      <optional>
+        <element name="bw_limit">
+          <attribute name="units">
+            <data type="NMTOKEN"/>
+          </attribute>
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <ref name="sliver"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="request">
+    <element name="request">
+      <attribute name="name">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <optional>
+        <ref name="sliver_defaults"/>
+      </optional>
+      <zeroOrMore>
+        <ref name="sliver"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="sliver">
+    <element name="sliver">
+      <optional>
+        <attribute name="nodeid">
+          <data type="ID"/>
+        </attribute>
+      </optional>
+      <ref name="sliver_elements"/>
+    </element>
+  </define>
+  <define name="sliver_elements">
+    <interleave>
+      <optional>
+        <element name="capabilities">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="codemux">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="cpu_pct">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="cpu_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="delegations">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="disk_max">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="initscript">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="ip_addresses">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="net_i2_max_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_max_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_min_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_thresh_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_max_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_max_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_min_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_thresh_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="vsys">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="vsys_vnet">
+          <text/>
+        </element>
+      </optional>
+    </interleave>
+  </define>
+</grammar>
diff --git a/sfa/managers/pl/pl.xml b/sfa/managers/pl/pl.xml
new file mode 100644 (file)
index 0000000..3824f30
--- /dev/null
@@ -0,0 +1,3420 @@
+<?xml version="1.0"?>
+<RSpec type="SFA">
+  <network name="plc">
+    <site id="s10242">
+      <name>HU Berlin - IWI</name>
+      <node id="n10855">
+        <hostname>planetlab1.wiwi.hu-berlin.de</hostname>
+      </node>
+      <node id="n10856">
+        <hostname>planetlab2.wiwi.hu-berlin.de</hostname>
+      </node>
+    </site>
+    <site id="s10243">
+      <name>Williams College</name>
+      <node id="n10858">
+        <hostname>planetlab1.williams.edu</hostname>
+      </node>
+      <node id="n10859">
+        <hostname>planetlab2.williams.edu</hostname>
+      </node>
+      <node id="n10860">
+        <hostname>planetlab3.williams.edu</hostname>
+      </node>
+      <node id="n11059">
+        <hostname>planetlab4.williams.edu</hostname>
+      </node>
+      <node id="n11060">
+        <hostname>planetlab5.williams.edu</hostname>
+      </node>
+    </site>
+    <site id="s4">
+      <name>Kentucky</name>
+      <node id="n73">
+        <hostname>planetlab1.netlab.uky.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n74">
+        <hostname>planetlab2.netlab.uky.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s6">
+      <name>UCSD</name>
+      <node id="n10544">
+        <hostname>planetlab1.ucsd.edu</hostname>
+      </node>
+      <node id="n10545">
+        <hostname>planetlab2.ucsd.edu</hostname>
+      </node>
+      <node id="n10546">
+        <hostname>planetlab3.ucsd.edu</hostname>
+      </node>
+    </site>
+    <site id="s7">
+      <name>Arizona</name>
+      <node id="n10746">
+        <hostname>planetlab1.arizona-gigapop.net</hostname>
+      </node>
+      <node id="n11076">
+        <hostname>planetlab2.arizona-gigapop.net</hostname>
+      </node>
+    </site>
+    <site id="s8">
+      <name>LBL</name>
+      <node id="n8159">
+        <hostname>node1.lbnl.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8160">
+        <hostname>node2.lbnl.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s9">
+      <name>UPenn</name>
+      <node id="n84">
+        <hostname>planetlab1.cis.upenn.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n85">
+        <hostname>planetlab2.cis.upenn.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10">
+      <name>IRP</name>
+      <node id="n30">
+        <hostname>planet1.pittsburgh.intel-research.net</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n31">
+        <hostname>planet2.pittsburgh.intel-research.net</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n32">
+        <hostname>planet3.pittsburgh.intel-research.net</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s11">
+      <name>IRB</name>
+      <node id="n10459">
+        <hostname>planet4.berkeley.intel-research.net</hostname>
+      </node>
+      <node id="n10460">
+        <hostname>planet5.berkeley.intel-research.net</hostname>
+      </node>
+      <node id="n10461">
+        <hostname>planet6.berkeley.intel-research.net</hostname>
+      </node>
+    </site>
+    <site id="s12">
+      <name>Cambridge</name>
+      <node id="n56">
+        <hostname>planetlab1.xeno.cl.cam.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n57">
+        <hostname>planetlab2.xeno.cl.cam.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n58">
+        <hostname>planetlab3.xeno.cl.cam.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s2">
+      <name>Harvard</name>
+      <node id="n77">
+        <hostname>lefthand.eecs.harvard.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n78">
+        <hostname>righthand.eecs.harvard.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s14">
+      <name>Wash</name>
+      <node id="n34">
+        <hostname>planetlab01.cs.washington.edu</hostname>
+      </node>
+      <node id="n35">
+        <hostname>planetlab02.cs.washington.edu</hostname>
+      </node>
+      <node id="n36">
+        <hostname>planetlab03.cs.washington.edu</hostname>
+      </node>
+      <node id="n10525">
+        <hostname>planetlab04.cs.washington.edu</hostname>
+      </node>
+      <node id="n13513">
+        <hostname>planetlab05.cs.washington.edu</hostname>
+      </node>
+      <node id="n13514">
+        <hostname>planetlab06.cs.washington.edu</hostname>
+      </node>
+    </site>
+    <site id="s15">
+      <name>CarnegieMellon</name>
+      <node id="n40">
+        <hostname>planetlab-1.cmcl.cs.cmu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n41">
+        <hostname>planetlab-2.cmcl.cs.cmu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n42">
+        <hostname>planetlab-3.cmcl.cs.cmu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10256">
+      <name>Michigan Technological University</name>
+      <node id="n10944">
+        <hostname>mtuplanetlab1.cs.mtu.edu</hostname>
+        <bw_limit units="kbps">500000</bw_limit>
+      </node>
+      <node id="n13125">
+        <hostname>mtuplanetlab2.cs.mtu.edu</hostname>
+      </node>
+    </site>
+    <site id="s17">
+      <name>Duke</name>
+      <node id="n46">
+        <hostname>planetlab1.cs.duke.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n47">
+        <hostname>planetlab2.cs.duke.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n48">
+        <hostname>planetlab3.cs.duke.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10646">
+        <hostname>planetlab4.cs.duke.edu</hostname>
+      </node>
+      <node id="n10647">
+        <hostname>planetlab5.cs.duke.edu</hostname>
+      </node>
+      <node id="n13481">
+        <hostname>planetlab6.cs.duke.edu</hostname>
+      </node>
+      <node id="n13483">
+        <hostname>planetlab7.cs.duke.edu</hostname>
+      </node>
+    </site>
+    <site id="s18">
+      <name>ISI</name>
+      <node id="n33">
+        <hostname>planetlab1.postel.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n86">
+        <hostname>planetlab2.postel.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11104">
+        <hostname>planetlab3.postel.org</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n11105">
+        <hostname>planetlab4.postel.org</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s19">
+      <name>Kansas</name>
+      <node id="n92">
+        <hostname>kupl1.ittc.ku.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n93">
+        <hostname>kupl2.ittc.ku.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s20">
+      <name>WashU</name>
+      <node id="n11462">
+        <hostname>vn4.cse.wustl.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n13116">
+        <hostname>vn5.cse.wustl.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s21">
+      <name>Michigan</name>
+      <node id="n90">
+        <hostname>planetlab1.eecs.umich.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n91">
+        <hostname>planetlab2.eecs.umich.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11077">
+        <hostname>planetlab3.eecs.umich.edu</hostname>
+      </node>
+      <node id="n11078">
+        <hostname>planetlab4.eecs.umich.edu</hostname>
+      </node>
+      <node id="n13487">
+        <hostname>planetlab5.eecs.umich.edu</hostname>
+      </node>
+    </site>
+    <site id="s22">
+      <name>UCSB</name>
+      <node id="n1">
+        <hostname>planet1.cs.ucsb.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n89">
+        <hostname>planet2.cs.ucsb.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11382">
+        <hostname>planet3.cs.ucsb.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n11383">
+        <hostname>planet4.cs.ucsb.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s23">
+      <name>MIT</name>
+      <node id="n49">
+        <hostname>planetlab1.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n50">
+        <hostname>planetlab3.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n51">
+        <hostname>planetlab2.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n360">
+        <hostname>planetlab6.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n361">
+        <hostname>planetlab5.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n362">
+        <hostname>planetlab7.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n363">
+        <hostname>planetlab4.csail.mit.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s24">
+      <name>UCB</name>
+      <node id="n3214">
+        <hostname>planetlab4.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3215">
+        <hostname>planetlab12.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3216">
+        <hostname>planetlab5.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3217">
+        <hostname>planetlab13.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3218">
+        <hostname>planetlab14.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3220">
+        <hostname>planetlab6.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3221">
+        <hostname>planetlab7.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3222">
+        <hostname>planetlab10.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3223">
+        <hostname>planetlab11.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3224">
+        <hostname>planetlab9.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3225">
+        <hostname>planetlab8.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8181">
+        <hostname>planetlab3.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8182">
+        <hostname>planetlab1.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8183">
+        <hostname>planetlab2.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10976">
+        <hostname>planetlab15.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10977">
+        <hostname>planetlab16.millennium.berkeley.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s26">
+      <name>Rice</name>
+      <node id="n7">
+        <hostname>ricepl-1.cs.rice.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n87">
+        <hostname>ricepl-2.cs.rice.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n88">
+        <hostname>ricepl-3.cs.rice.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13565">
+        <hostname>ricepl-4.cs.rice.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13566">
+        <hostname>ricepl-5.cs.rice.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10267">
+      <name>Kent State University</name>
+      <node id="n11488">
+        <hostname>pl1.planet.cs.kent.edu</hostname>
+      </node>
+      <node id="n11489">
+        <hostname>pl2.planet.cs.kent.edu</hostname>
+      </node>
+      <node id="n11490">
+        <hostname>pl3.planet.cs.kent.edu</hostname>
+      </node>
+    </site>
+    <site id="s28">
+      <name>Bologna</name>
+      <node id="n43">
+        <hostname>planetlab1.cs.unibo.it</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n44">
+        <hostname>planetlab2.cs.unibo.it</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s29">
+      <name>Austin</name>
+      <node id="n55">
+        <hostname>planetlab2.csres.utexas.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n79">
+        <hostname>planetlab1.csres.utexas.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n80">
+        <hostname>planetlab3.csres.utexas.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11024">
+        <hostname>planetlab4.csres.utexas.edu</hostname>
+      </node>
+      <node id="n11039">
+        <hostname>planetlab5.csres.utexas.edu</hostname>
+      </node>
+      <node id="n13562">
+        <hostname>planetlab6.csres.utexas.edu</hostname>
+      </node>
+      <node id="n13563">
+        <hostname>planetlab7.csres.utexas.edu</hostname>
+      </node>
+    </site>
+    <site id="s30">
+      <name>Utah</name>
+      <node id="n10712">
+        <hostname>planetlab4.flux.utah.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10713">
+        <hostname>planetlab5.flux.utah.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11074">
+        <hostname>planetlab6.flux.utah.edu</hostname>
+      </node>
+      <node id="n11075">
+        <hostname>planetlab7.flux.utah.edu</hostname>
+      </node>
+    </site>
+    <site id="s31">
+      <name>UCLA</name>
+      <node id="n11403">
+        <hostname>planetlab1.cs.ucla.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11404">
+        <hostname>planetlab2.cs.ucla.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s33">
+      <name>Cornell</name>
+      <node id="n39">
+        <hostname>planetlab2.cs.cornell.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n67">
+        <hostname>planetlab1.cs.cornell.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10239">
+        <hostname>planetlab3-dsl.cs.cornell.edu</hostname>
+      </node>
+      <node id="n10240">
+        <hostname>planetlab4-dsl.cs.cornell.edu</hostname>
+      </node>
+      <node id="n10648">
+        <hostname>planetlab5.cs.cornell.edu</hostname>
+      </node>
+      <node id="n10649">
+        <hostname>planetlab6.cs.cornell.edu</hostname>
+      </node>
+    </site>
+    <site id="s34">
+      <name>Tennessee</name>
+      <node id="n11">
+        <hostname>pl1.eecs.utk.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n12">
+        <hostname>pl2.eecs.utk.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s37">
+      <name>CalTech</name>
+      <node id="n16">
+        <hostname>planlab1.cs.caltech.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n21">
+        <hostname>planlab2.cs.caltech.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s38">
+      <name>UMass</name>
+      <node id="n19">
+        <hostname>planetlab1.cs.umass.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n20">
+        <hostname>planetlab2.cs.umass.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10279">
+      <name>Emory University</name>
+      <node id="n11057">
+        <hostname>node1.planetlab.mathcs.emory.edu</hostname>
+      </node>
+      <node id="n11072">
+        <hostname>node2.planetlab.mathcs.emory.edu</hostname>
+      </node>
+    </site>
+    <site id="s10280">
+      <name>Universidad de Buenos Aires</name>
+      <node id="n13157">
+        <hostname>planet-lab1.uba.ar</hostname>
+      </node>
+      <node id="n13266">
+        <hostname>planet-lab2.uba.ar</hostname>
+      </node>
+    </site>
+    <site id="s10281">
+      <name>Kookmin University</name>
+      <node id="n11043">
+        <hostname>netapp6.cs.kookmin.ac.kr</hostname>
+      </node>
+      <node id="n11044">
+        <hostname>netapp7.cs.kookmin.ac.kr</hostname>
+      </node>
+      <node id="n11466">
+        <hostname>dplab03.kookmin.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s42">
+      <name>Canterbury</name>
+      <node id="n13209">
+        <hostname>planetlab1.cosc.canterbury.ac.nz</hostname>
+      </node>
+      <node id="n13210">
+        <hostname>planetlab2.cosc.canterbury.ac.nz</hostname>
+      </node>
+    </site>
+    <site id="s10247">
+      <name>UT Dallas</name>
+      <node id="n11090">
+        <hostname>planetlab1.utdallas.edu</hostname>
+      </node>
+      <node id="n11091">
+        <hostname>planetlab2.utdallas.edu</hostname>
+      </node>
+    </site>
+    <site id="s44">
+      <name>CUHK</name>
+      <node id="n10154">
+        <hostname>planetlab1.ie.cuhk.edu.hk</hostname>
+      </node>
+      <node id="n10185">
+        <hostname>planetlab2.ie.cuhk.edu.hk</hostname>
+      </node>
+      <node id="n10655">
+        <hostname>planetlab3.ie.cuhk.edu.hk</hostname>
+      </node>
+      <node id="n10656">
+        <hostname>planetlab4.ie.cuhk.edu.hk</hostname>
+      </node>
+      <node id="n10658">
+        <hostname>planetlab5.ie.cuhk.edu.hk</hostname>
+      </node>
+    </site>
+    <site id="s45">
+      <name>WayneState</name>
+      <node id="n10189">
+        <hostname>planetlab1.cs.wayne.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10190">
+        <hostname>planetlab2.cs.wayne.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s10286">
+      <name>Osaka City University</name>
+      <node id="n11019">
+        <hostname>planetlab1.n.info.eng.osaka-cu.ac.jp</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n11058">
+        <hostname>planetlab2.n.info.eng.osaka-cu.ac.jp</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13323">
+        <hostname>planetlab3.n.info.eng.osaka-cu.ac.jp</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13342">
+        <hostname>planetlab4.n.info.eng.osaka-cu.ac.jp</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s47">
+      <name>Berlin</name>
+      <node id="n13130">
+        <hostname>planetlab01.tkn.tu-berlin.de</hostname>
+      </node>
+      <node id="n13131">
+        <hostname>planetlab02.tkn.tu-berlin.de</hostname>
+      </node>
+    </site>
+    <site id="s48">
+      <name>Maryland</name>
+      <node id="n10425">
+        <hostname>salt.planetlab.cs.umd.edu</hostname>
+      </node>
+      <node id="n13231">
+        <hostname>osiris.planetlab.cs.umd.edu</hostname>
+      </node>
+      <node id="n13232">
+        <hostname>whitefall.planetlab.cs.umd.edu</hostname>
+      </node>
+      <node id="n13586">
+        <hostname>miranda.planetlab.cs.umd.edu</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s49">
+      <name>Toronto</name>
+      <node id="n10277">
+        <hostname>pl2.csl.utoronto.ca</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10278">
+        <hostname>pl1.csl.utoronto.ca</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10291">
+      <name>Koc University</name>
+      <node id="n10999">
+        <hostname>planet1.ku.edu.tr</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n11000">
+        <hostname>planet2.ku.edu.tr</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+    </site>
+    <site id="s10249">
+      <name>RIT</name>
+      <node id="n11502">
+        <hostname>planet1.cs.rit.edu</hostname>
+      </node>
+      <node id="n11503">
+        <hostname>planet2.cs.rit.edu</hostname>
+      </node>
+    </site>
+    <site id="s56">
+      <name>Northwestern</name>
+      <node id="n8224">
+        <hostname>planetlab1.eecs.northwestern.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8226">
+        <hostname>planetlab2.eecs.northwestern.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11048">
+        <hostname>planetlab3.eecs.northwestern.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s57">
+      <name>ChapelHill</name>
+      <node id="n10096">
+        <hostname>planetlab1.cs.unc.edu</hostname>
+      </node>
+      <node id="n10932">
+        <hostname>planetlab2.cs.unc.edu</hostname>
+      </node>
+    </site>
+    <site id="s58">
+      <name>Illinois</name>
+      <node id="n107">
+        <hostname>planetlab2.cs.uiuc.edu</hostname>
+      </node>
+      <node id="n108">
+        <hostname>planetlab1.cs.uiuc.edu</hostname>
+      </node>
+      <node id="n10424">
+        <hostname>planetlab3.cs.uiuc.edu</hostname>
+      </node>
+      <node id="n10431">
+        <hostname>planetlab4.cs.uiuc.edu</hostname>
+      </node>
+      <node id="n13493">
+        <hostname>planetlab5.cs.uiuc.edu</hostname>
+      </node>
+      <node id="n13494">
+        <hostname>planetlab6.cs.uiuc.edu</hostname>
+      </node>
+    </site>
+    <site id="s59">
+      <name>London</name>
+      <node id="n13179">
+        <hostname>planetlab1.cs.ucl.ac.uk</hostname>
+      </node>
+      <node id="n13180">
+        <hostname>planetlab2.cs.ucl.ac.uk</hostname>
+      </node>
+    </site>
+    <site id="s10300">
+      <name>University of Patras</name>
+      <node id="n11079">
+        <hostname>planetlab1.ceid.upatras.gr</hostname>
+      </node>
+      <node id="n11080">
+        <hostname>planetlab2.ceid.upatras.gr</hostname>
+      </node>
+    </site>
+    <site id="s10250">
+      <name>Toyohashi Univ of Tech</name>
+      <node id="n10881">
+        <hostname>pl1.planetlab.ics.tut.ac.jp</hostname>
+      </node>
+      <node id="n10882">
+        <hostname>pl2.planetlab.ics.tut.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s66">
+      <name>NTHU</name>
+      <node id="n152">
+        <hostname>pads23.cs.nthu.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n155">
+        <hostname>pads21.cs.nthu.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s76">
+      <name>Rochester</name>
+      <node id="n156">
+        <hostname>planet2.cs.rochester.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3395">
+        <hostname>planet1.cs.rochester.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s77">
+      <name>Purdue</name>
+      <node id="n159">
+        <hostname>planetlab1.cs.purdue.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n160">
+        <hostname>planetlab2.cs.purdue.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s78">
+      <name>Rutgers</name>
+      <node id="n10203">
+        <hostname>planetlab2.rutgers.edu</hostname>
+      </node>
+      <node id="n10236">
+        <hostname>planetlab1.rutgers.edu</hostname>
+      </node>
+    </site>
+    <site id="s79">
+      <name>HPLabs</name>
+      <node id="n3412">
+        <hostname>pli1-pa-4.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8290">
+        <hostname>pli1-pa-5.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11511">
+        <hostname>pli1-pa-6.hpl.hp.com</hostname>
+      </node>
+    </site>
+    <site id="s80">
+      <name>MichState</name>
+      <node id="n10432">
+        <hostname>planetlab1.cse.msu.edu</hostname>
+      </node>
+      <node id="n10454">
+        <hostname>planetlab2.cse.msu.edu</hostname>
+      </node>
+    </site>
+    <site id="s81">
+      <name>Hopkins</name>
+      <node id="n164">
+        <hostname>planetlab1.cnds.jhu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n165">
+        <hostname>planetlab2.cnds.jhu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11486">
+        <hostname>planetlab3.cnds.jhu.edu</hostname>
+      </node>
+      <node id="n11487">
+        <hostname>planetlab4.cnds.jhu.edu</hostname>
+      </node>
+    </site>
+    <site id="s10323">
+      <name>SRI International</name>
+      <node id="n11498">
+        <hostname>pl-node-0.csl.sri.com</hostname>
+        <bw_limit units="kbps">524</bw_limit>
+      </node>
+      <node id="n11499">
+        <hostname>pl-node-1.csl.sri.com</hostname>
+        <bw_limit units="kbps">524</bw_limit>
+      </node>
+    </site>
+    <site id="s10326">
+      <name>University of Ottawa</name>
+      <node id="n13334">
+        <hostname>pl1.rcc.uottawa.ca</hostname>
+      </node>
+      <node id="n13335">
+        <hostname>pl2.rcc.uottawa.ca</hostname>
+      </node>
+    </site>
+    <site id="s87">
+      <name>NotreDame</name>
+      <node id="n184">
+        <hostname>planetlab2.cse.nd.edu</hostname>
+      </node>
+      <node id="n185">
+        <hostname>planetlab1.cse.nd.edu</hostname>
+      </node>
+      <node id="n11070">
+        <hostname>planetlab3.cse.nd.edu</hostname>
+      </node>
+      <node id="n11071">
+        <hostname>planetlab4.cse.nd.edu</hostname>
+      </node>
+    </site>
+    <site id="s90">
+      <name>GT</name>
+      <node id="n83">
+        <hostname>planet1.cc.gt.atl.ga.us</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10589">
+        <hostname>planet2.cc.gt.atl.ga.us</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10590">
+        <hostname>planet3.cc.gt.atl.ga.us</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10591">
+        <hostname>planet4.cc.gt.atl.ga.us</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s91">
+      <name>SBU</name>
+      <node id="n13273">
+        <hostname>pl1.cewit.stonybrook.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13274">
+        <hostname>pl2.cewit.stonybrook.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s92">
+      <name>UFMG</name>
+      <node id="n201">
+        <hostname>planetlab1.pop-mg.rnp.br</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n202">
+        <hostname>planetlab2.pop-mg.rnp.br</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s94">
+      <name>Vrije</name>
+      <node id="n204">
+        <hostname>planetlab2.cs.vu.nl</hostname>
+      </node>
+      <node id="n205">
+        <hostname>planetlab1.cs.vu.nl</hostname>
+      </node>
+    </site>
+    <site id="s16">
+      <name>Columbia</name>
+      <node id="n94">
+        <hostname>planetlab1.cs.columbia.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n95">
+        <hostname>planetlab2.cs.columbia.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n96">
+        <hostname>planetlab3.cs.columbia.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s105">
+      <name>McGill</name>
+      <node id="n13123">
+        <hostname>node-1.mcgillplanetlab.org</hostname>
+      </node>
+      <node id="n13124">
+        <hostname>node-2.mcgillplanetlab.org</hostname>
+      </node>
+    </site>
+    <site id="s109">
+      <name>TAMU</name>
+      <node id="n224">
+        <hostname>planetlab1.tamu.edu</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n225">
+        <hostname>planetlab2.tamu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s113">
+      <name>NICT</name>
+      <node id="n13126">
+        <hostname>planetlab1.koganei.wide.ad.jp</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13127">
+        <hostname>planetlab2.koganei.wide.ad.jp</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13128">
+        <hostname>planetlab1.nvlab.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s114">
+      <name>DePaul</name>
+      <node id="n8185">
+        <hostname>planetlab2.mnlab.cti.depaul.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10093">
+        <hostname>planetlab1.mnlab.cti.depaul.edu</hostname>
+      </node>
+    </site>
+    <site id="s10259">
+      <name>Universidade Federal do ABC</name>
+      <node id="n10926">
+        <hostname>planet-lab1.ufabc.edu.br</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n10927">
+        <hostname>planet-lab2.ufabc.edu.br</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s116">
+      <name>HPLabsI2</name>
+      <node id="n183">
+        <hostname>pli2-pa-1.hpl.hp.com</hostname>
+      </node>
+      <node id="n237">
+        <hostname>pli2-pa-2.hpl.hp.com</hostname>
+      </node>
+      <node id="n238">
+        <hostname>pli2-pa-3.hpl.hp.com</hostname>
+      </node>
+    </site>
+    <site id="s117">
+      <name>UCD</name>
+      <node id="n3206">
+        <hostname>planetlab2.ece.ucdavis.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3408">
+        <hostname>planetlab1.ece.ucdavis.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s118">
+      <name>kaist</name>
+      <node id="n327">
+        <hostname>csplanetlab1.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n329">
+        <hostname>csplanetlab2.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n330">
+        <hostname>csplanetlab4.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n331">
+        <hostname>csplanetlab3.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s119">
+      <name>UTokyo</name>
+      <node id="n10262">
+        <hostname>planetlab1.iii.u-tokyo.ac.jp</hostname>
+      </node>
+      <node id="n10263">
+        <hostname>planetlab2.iii.u-tokyo.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s120">
+      <name>UOregon</name>
+      <node id="n13428">
+        <hostname>planetlab4.cs.uoregon.edu</hostname>
+      </node>
+      <node id="n13429">
+        <hostname>planetlab2.cs.uoregon.edu</hostname>
+      </node>
+      <node id="n13430">
+        <hostname>planetlab3.cs.uoregon.edu</hostname>
+      </node>
+      <node id="n13431">
+        <hostname>planetlab1.cs.uoregon.edu</hostname>
+      </node>
+    </site>
+    <site id="s121">
+      <name>HP Labs, Bristol</name>
+      <node id="n3232">
+        <hostname>pli1-br-2.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3233">
+        <hostname>pli1-br-3.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3234">
+        <hostname>pli1-br-1.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3237">
+        <hostname>pli2-br-2.hpl.hp.com</hostname>
+      </node>
+      <node id="n3238">
+        <hostname>pli2-br-1.hpl.hp.com</hostname>
+      </node>
+      <node id="n3413">
+        <hostname>pli1-tlnx.hpl.hp.com</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s122">
+      <name>Eurecom</name>
+      <node id="n10015">
+        <hostname>planetlab2.eurecom.fr</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10016">
+        <hostname>planetlab1.eurecom.fr</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s128">
+      <name>Technion</name>
+      <node id="n3209">
+        <hostname>ds-pl3.technion.ac.il</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n3424">
+        <hostname>ds-pl1.technion.ac.il</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n10170">
+        <hostname>ds-pl2.technion.ac.il</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+    </site>
+    <site id="s129">
+      <name>TU Madrid</name>
+      <node id="n3240">
+        <hostname>planetlab1.ls.fi.upm.es</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3351">
+        <hostname>planetlab2.ls.fi.upm.es</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s131">
+      <name>Ben-Gurion</name>
+      <node id="n3230">
+        <hostname>planetlab1.bgu.ac.il</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3231">
+        <hostname>planetlab2.bgu.ac.il</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s132">
+      <name>National Taiwan</name>
+      <node id="n3241">
+        <hostname>planetlab1.ntu.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n3242">
+        <hostname>planetlab2.ntu.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+    </site>
+    <site id="s135">
+      <name>SNU</name>
+      <node id="n3207">
+        <hostname>arari.snu.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3210">
+        <hostname>recall.snu.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s137">
+      <name>NCSU</name>
+      <node id="n3306">
+        <hostname>planet02.csc.ncsu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3307">
+        <hostname>planet03.csc.ncsu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13486">
+        <hostname>planet11.csc.ncsu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13488">
+        <hostname>planet12.csc.ncsu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s138">
+      <name>UCSC</name>
+      <node id="n3303">
+        <hostname>planetslug3.cse.ucsc.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11064">
+        <hostname>planetslug4.cse.ucsc.edu</hostname>
+      </node>
+      <node id="n11065">
+        <hostname>planetslug5.cse.ucsc.edu</hostname>
+      </node>
+      <node id="n13519">
+        <hostname>planetslug6.cse.ucsc.edu</hostname>
+      </node>
+      <node id="n13520">
+        <hostname>planetslug7.cse.ucsc.edu</hostname>
+      </node>
+    </site>
+    <site id="s139">
+      <name>Nebraska</name>
+      <node id="n11087">
+        <hostname>planetlab1.unl.edu</hostname>
+      </node>
+      <node id="n11088">
+        <hostname>planetlab2.unl.edu</hostname>
+      </node>
+    </site>
+    <site id="s141">
+      <name>SICS</name>
+      <node id="n3349">
+        <hostname>planetlab1.sics.se</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3350">
+        <hostname>planetlab2.sics.se</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s144">
+      <name>Dartmouth</name>
+      <node id="n3320">
+        <hostname>planetlab1.cs.dartmouth.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3321">
+        <hostname>planetlab2.cs.dartmouth.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s150">
+      <name>Warsaw</name>
+      <node id="n10699">
+        <hostname>planetlab3.mini.pw.edu.pl</hostname>
+      </node>
+      <node id="n10700">
+        <hostname>planetlab4.mini.pw.edu.pl</hostname>
+      </node>
+    </site>
+    <site id="s25">
+      <name>Stanford</name>
+      <node id="n4">
+        <hostname>planet1.scs.stanford.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n45">
+        <hostname>planet2.scs.stanford.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s155">
+      <name>StevensTech</name>
+      <node id="n3397">
+        <hostname>planetlab2.cs.stevens-tech.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3398">
+        <hostname>planetlab1.cs.stevens-tech.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s5">
+      <name>NYU</name>
+      <node id="n2">
+        <hostname>planet1.scs.cs.nyu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3">
+        <hostname>planet2.scs.cs.nyu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8314">
+        <hostname>planetx.scs.cs.nyu.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s159">
+      <name>ICS-Forth</name>
+      <node id="n11095">
+        <hostname>planetlab1.ics.forth.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+      <node id="n11099">
+        <hostname>planetlab2.ics.forth.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+    </site>
+    <site id="s160">
+      <name>Passau</name>
+      <node id="n10129">
+        <hostname>dschinni.planetlab.extranet.uni-passau.de</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10130">
+        <hostname>aladdin.planetlab.extranet.uni-passau.de</hostname>
+      </node>
+    </site>
+    <site id="s161">
+      <name>Polito</name>
+      <node id="n3396">
+        <hostname>planetlab1.polito.it</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n3402">
+        <hostname>planetlab2.polito.it</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s163">
+      <name>Howard</name>
+      <node id="n3405">
+        <hostname>nodeb.howard.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n3416">
+        <hostname>nodea.howard.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s164">
+      <name>Torino</name>
+      <node id="n10698">
+        <hostname>planetlab1.di.unito.it</hostname>
+      </node>
+      <node id="n10724">
+        <hostname>planetlab2.di.unito.it</hostname>
+      </node>
+    </site>
+    <site id="s10268">
+      <name>PLA Univ of Sci and Tech</name>
+      <node id="n13163">
+        <hostname>plnodeb.plaust.edu.cn</hostname>
+      </node>
+      <node id="n13411">
+        <hostname>plnodea.plaust.edu.cn</hostname>
+      </node>
+    </site>
+    <site id="s175">
+      <name>Delft</name>
+      <node id="n8161">
+        <hostname>planetlab1.ewi.tudelft.nl</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n8162">
+        <hostname>planetlab2.ewi.tudelft.nl</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s178">
+      <name>ICU</name>
+      <node id="n13495">
+        <hostname>planetlab5.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13496">
+        <hostname>planetlab6.kaist.ac.kr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s179">
+      <name>St Andrews</name>
+      <node id="n10825">
+        <hostname>planetlab3.cs.st-andrews.ac.uk</hostname>
+      </node>
+      <node id="n10826">
+        <hostname>planetlab4.cs.st-andrews.ac.uk</hostname>
+      </node>
+    </site>
+    <site id="s180">
+      <name>CSLAB-ICCS at NTUA</name>
+      <node id="n10851">
+        <hostname>planetlab1.cslab.ece.ntua.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+      <node id="n10852">
+        <hostname>planetlab2.cslab.ece.ntua.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+      <node id="n10853">
+        <hostname>planetlab3.cslab.ece.ntua.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+      <node id="n13115">
+        <hostname>planetlab4.cslab.ece.ntua.gr</hostname>
+        <bw_limit units="kbps">104857</bw_limit>
+      </node>
+    </site>
+    <site id="s184">
+      <name>Kaiserslautern</name>
+      <node id="n8238">
+        <hostname>planetlab1.informatik.uni-kl.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8239">
+        <hostname>planetlab2.informatik.uni-kl.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s188">
+      <name>Victoria</name>
+      <node id="n10890">
+        <hostname>pl1.planetlab.uvic.ca</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10891">
+        <hostname>pl2.planetlab.uvic.ca</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10892">
+        <hostname>pl3.planetlab.uvic.ca</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10893">
+        <hostname>pl4.planetlab.uvic.ca</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10894">
+        <hostname>pl5.planetlab.uvic.ca</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s189">
+      <name>nus</name>
+      <node id="n10904">
+        <hostname>planetlab1.comp.nus.edu.sg</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10905">
+        <hostname>planetlab2.comp.nus.edu.sg</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11089">
+        <hostname>planetlab3.comp.nus.edu.sg</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s190">
+      <name>KIT</name>
+      <node id="n11093">
+        <hostname>iraplab1.iralab.uni-karlsruhe.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11094">
+        <hostname>iraplab2.iralab.uni-karlsruhe.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s191">
+      <name>UST-HK</name>
+      <node id="n8207">
+        <hostname>plab1.cs.ust.hk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8214">
+        <hostname>plab2.cs.ust.hk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s194">
+      <name>Tromso</name>
+      <node id="n10949">
+        <hostname>planetlab1.cs.uit.no</hostname>
+      </node>
+      <node id="n10950">
+        <hostname>planetlab2.cs.uit.no</hostname>
+      </node>
+    </site>
+    <site id="s195">
+      <name>KJIST</name>
+      <node id="n10397">
+        <hostname>planetlab1.netmedia.gist.ac.kr</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10406">
+        <hostname>planetlab2.netmedia.gist.ac.kr</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10540">
+        <hostname>planetlab3.netmedia.gist.ac.kr</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s203">
+      <name>Cincinnati</name>
+      <node id="n8228">
+        <hostname>planetlab1.uc.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8229">
+        <hostname>planetlab2.uc.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s204">
+      <name>Polytechnic</name>
+      <node id="n8227">
+        <hostname>planetlab1.poly.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n8284">
+        <hostname>planetlab2.poly.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+    </site>
+    <site id="s205">
+      <name>University of Minnesota</name>
+      <node id="n8254">
+        <hostname>planetlab1.dtc.umn.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8255">
+        <hostname>planetlab2.dtc.umn.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s206">
+      <name>ATT</name>
+      <node id="n8235">
+        <hostname>planet2.att.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8236">
+        <hostname>planet1.att.nodes.planet-lab.org</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s211">
+      <name>TRLabs</name>
+      <node id="n8248">
+        <hostname>planetlab1.win.trlabs.ca</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8249">
+        <hostname>planetlab2.win.trlabs.ca</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s212">
+      <name>EPFL</name>
+      <node id="n10205">
+        <hostname>lsirextpc01.epfl.ch</hostname>
+      </node>
+      <node id="n10206">
+        <hostname>lsirextpc02.epfl.ch</hostname>
+      </node>
+    </site>
+    <site id="s217">
+      <name>Brown</name>
+      <node id="n10909">
+        <hostname>saturn.cs.brown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10910">
+        <hostname>jupiter.cs.brown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10911">
+        <hostname>pluto.cs.brown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10912">
+        <hostname>earth.cs.brown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s220">
+      <name>University of Melbourne - CSSE</name>
+      <node id="n8261">
+        <hostname>plnode01.cs.mu.oz.au</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n8262">
+        <hostname>plnode02.cs.mu.oz.au</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s222">
+      <name>UiO</name>
+      <node id="n8289">
+        <hostname>planetlab2.ifi.uio.no</hostname>
+      </node>
+      <node id="n10981">
+        <hostname>planetlab1.ifi.uio.no</hostname>
+      </node>
+    </site>
+    <site id="s224">
+      <name>RNP</name>
+      <node id="n13597">
+        <hostname>planetlab1.pop-pa.rnp.br</hostname>
+      </node>
+      <node id="n13598">
+        <hostname>planetlab2.pop-pa.rnp.br</hostname>
+      </node>
+    </site>
+    <site id="s228">
+      <name>RNP - CE</name>
+      <node id="n8315">
+        <hostname>planetlab1.pop-ce.rnp.br</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8316">
+        <hostname>planetlab2.pop-ce.rnp.br</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s229">
+      <name>RNP - RJ</name>
+      <node id="n10022">
+        <hostname>planetlab1.pop-rj.rnp.br</hostname>
+      </node>
+      <node id="n10023">
+        <hostname>planetlab2.pop-rj.rnp.br</hostname>
+      </node>
+    </site>
+    <site id="s230">
+      <name>RNP - RS</name>
+      <node id="n8320">
+        <hostname>planetlab1.pop-rs.rnp.br</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n8321">
+        <hostname>planetlab2.pop-rs.rnp.br</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s231">
+      <name>Simula Research Laboratory</name>
+      <node id="n13424">
+        <hostname>planetlab1.simula.no</hostname>
+      </node>
+      <node id="n13470">
+        <hostname>planetlab2.simula.no</hostname>
+      </node>
+    </site>
+    <site id="s233">
+      <name>NCCU</name>
+      <node id="n8336">
+        <hostname>ent1.cs.nccu.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10144">
+        <hostname>ent2.cs.nccu.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s235">
+      <name>Wuerzburg</name>
+      <node id="n8332">
+        <hostname>planetlab2.informatik.uni-wuerzburg.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10105">
+        <hostname>planetlab1.informatik.uni-wuerzburg.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s236">
+      <name>Hannover L3S</name>
+      <node id="n8335">
+        <hostname>planet1.l3s.uni-hannover.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10149">
+        <hostname>planet2.l3s.uni-hannover.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s41">
+      <name>Wisconsin</name>
+      <node id="n25">
+        <hostname>planetlab2.cs.wisc.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11083">
+        <hostname>planetlab3.wail.wisc.edu</hostname>
+      </node>
+      <node id="n11084">
+        <hostname>planetlab4.wail.wisc.edu</hostname>
+      </node>
+    </site>
+    <site id="s43">
+      <name>UChicago</name>
+      <node id="n27">
+        <hostname>planetlab1.cs.uchicago.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n28">
+        <hostname>planetlab2.cs.uchicago.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n29">
+        <hostname>planetlab3.cs.uchicago.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13537">
+        <hostname>planetlab4.cs.uchicago.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10285">
+      <name>National University of Kaohsiung</name>
+      <node id="n11004">
+        <hostname>planetlab1.csie.nuk.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n11005">
+        <hostname>planetlab2.csie.nuk.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s61">
+      <name>Taiwan</name>
+      <node id="n150">
+        <hostname>planetlab1.iis.sinica.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n151">
+        <hostname>planetlab2.iis.sinica.edu.tw</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s63">
+      <name>BostonU</name>
+      <node id="n10803">
+        <hostname>planetlab-01.bu.edu</hostname>
+      </node>
+      <node id="n10815">
+        <hostname>planetlab-02.bu.edu</hostname>
+      </node>
+    </site>
+    <site id="s13">
+      <name>Princeton</name>
+      <node id="n10025">
+        <hostname>alice.cs.princeton.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10328">
+        <hostname>planetlab-9.cs.princeton.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10845">
+        <hostname>delta.cs.princeton.edu</hostname>
+      </node>
+      <node id="n10846">
+        <hostname>echo.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13254">
+        <hostname>hptest-1.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13293">
+        <hostname>planetlab-01.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13294">
+        <hostname>planetlab-02.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13295">
+        <hostname>planetlab-03.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13296">
+        <hostname>planetlab-04.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13297">
+        <hostname>planetlab-05.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13298">
+        <hostname>planetlab-06.cs.princeton.edu</hostname>
+      </node>
+      <node id="n13596">
+        <hostname>hp-dl320-g6-loaner.cs.princeton.edu</hostname>
+      </node>
+    </site>
+    <site id="s10244">
+      <name>SUNY Binghamton</name>
+      <node id="n13275">
+        <hostname>pluto.cs.binghamton.edu</hostname>
+      </node>
+      <node id="n13276">
+        <hostname>charon.cs.binghamton.edu</hostname>
+      </node>
+    </site>
+    <site id="s86">
+      <name>NECLabs</name>
+      <node id="n10234">
+        <hostname>plab2.nec-labs.com</hostname>
+      </node>
+      <node id="n10235">
+        <hostname>plab1.nec-labs.com</hostname>
+      </node>
+    </site>
+    <site id="s10248">
+      <name>UVA</name>
+      <node id="n10883">
+        <hostname>planetlab01.sys.virginia.edu</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10884">
+        <hostname>planetlab02.sys.virginia.edu</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s157">
+      <name>JHU Info Security</name>
+      <node id="n11061">
+        <hostname>planetlab1.isi.jhu.edu</hostname>
+      </node>
+      <node id="n11062">
+        <hostname>planetlab2.isi.jhu.edu</hostname>
+      </node>
+    </site>
+    <site id="s11387">
+      <name>Instituto Tecnologico Buenos Aires</name>
+      <node id="n13134">
+        <hostname>planet-lab1.itba.edu.ar</hostname>
+        <bw_limit units="kbps">256000</bw_limit>
+      </node>
+      <node id="n13158">
+        <hostname>planet-lab2.itba.edu.ar</hostname>
+        <bw_limit units="kbps">256000</bw_limit>
+      </node>
+    </site>
+    <site id="s11389">
+      <name>Cal Poly SLO</name>
+      <node id="n13149">
+        <hostname>planetlab-1.calpoly-netlab.net</hostname>
+      </node>
+      <node id="n13150">
+        <hostname>planetlab-2.calpoly-netlab.net</hostname>
+      </node>
+    </site>
+    <site id="s11390">
+      <name>Doshisha University</name>
+      <node id="n13145">
+        <hostname>nis-planet1.doshisha.ac.jp</hostname>
+      </node>
+      <node id="n13146">
+        <hostname>nis-planet2.doshisha.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s11393">
+      <name>University of Waterloo</name>
+      <node id="n13165">
+        <hostname>plink.cs.uwaterloo.ca</hostname>
+      </node>
+      <node id="n13166">
+        <hostname>plonk.cs.uwaterloo.ca</hostname>
+      </node>
+    </site>
+    <site id="s11394">
+      <name>UFPR/C3SL</name>
+      <node id="n13167">
+        <hostname>planetlab1.c3sl.ufpr.br</hostname>
+      </node>
+      <node id="n13168">
+        <hostname>planetlab2.c3sl.ufpr.br</hostname>
+      </node>
+    </site>
+    <site id="s11395">
+      <name>University of Otago</name>
+      <node id="n13169">
+        <hostname>planetlab1.cs.otago.ac.nz</hostname>
+      </node>
+      <node id="n13170">
+        <hostname>planetlab2.cs.otago.ac.nz</hostname>
+      </node>
+    </site>
+    <site id="s11397">
+      <name>University of Oklahoma</name>
+      <node id="n13211">
+        <hostname>roam1.cs.ou.edu</hostname>
+      </node>
+      <node id="n13212">
+        <hostname>roam2.cs.ou.edu</hostname>
+      </node>
+    </site>
+    <site id="s11399">
+      <name>MLab - NUQ01</name>
+    </site>
+    <site id="s11401">
+      <name>MLab - ORD01</name>
+    </site>
+    <site id="s11402">
+      <name>MLab - MIA01</name>
+    </site>
+    <site id="s11403">
+      <name>MLab - LAX01</name>
+    </site>
+    <site id="s11404">
+      <name>MLab - LGA01</name>
+    </site>
+    <site id="s11405">
+      <name>MLab - DFW01</name>
+    </site>
+    <site id="s11406">
+      <name>MLab - ATL01</name>
+    </site>
+    <site id="s11410">
+      <name>SUNY Albany</name>
+      <node id="n13204">
+        <hostname>node1.planetlab.albany.edu</hostname>
+        <bw_limit units="kbps">6291</bw_limit>
+      </node>
+      <node id="n13205">
+        <hostname>node2.planetlab.albany.edu</hostname>
+        <bw_limit units="kbps">6291</bw_limit>
+      </node>
+    </site>
+    <site id="s11415">
+      <name>University of Nevada, Reno</name>
+      <node id="n13351">
+        <hostname>planetlab1.scsr.nevada.edu</hostname>
+      </node>
+      <node id="n13352">
+        <hostname>planetlab2.scsr.nevada.edu</hostname>
+      </node>
+    </site>
+    <site id="s11416">
+      <name>Cleveland State University</name>
+      <node id="n13400">
+        <hostname>planetlab2.csuohio.edu</hostname>
+      </node>
+      <node id="n13405">
+        <hostname>planetlab1.csuohio.edu</hostname>
+      </node>
+    </site>
+    <site id="s11424">
+      <name>Tokyo Institute of Technology</name>
+      <node id="n13260">
+        <hostname>node1.planet-lab.titech.ac.jp</hostname>
+      </node>
+      <node id="n13261">
+        <hostname>node2.planet-lab.titech.ac.jp</hostname>
+      </node>
+      <node id="n13262">
+        <hostname>node3.planet-lab.titech.ac.jp</hostname>
+      </node>
+      <node id="n13263">
+        <hostname>node4.planet-lab.titech.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s11425">
+      <name>Colgate University</name>
+      <node id="n13413">
+        <hostname>flow.colgate.edu</hostname>
+      </node>
+      <node id="n13489">
+        <hostname>ebb.colgate.edu</hostname>
+      </node>
+    </site>
+    <site id="s11426">
+      <name>Ege University</name>
+      <node id="n13502">
+        <hostname>planetlab1.plab.ege.edu.tr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13510">
+        <hostname>planetlab2.plab.ege.edu.tr</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s11427">
+      <name>MLab - LGA02</name>
+    </site>
+    <site id="s11428">
+      <name>MLab - SEA01</name>
+    </site>
+    <site id="s11432">
+      <name>Huawei Technologies</name>
+      <node id="n13317">
+        <hostname>pn1-planetlab.huawei.com</hostname>
+      </node>
+      <node id="n13318">
+        <hostname>pn2-planetlab.huawei.com</hostname>
+      </node>
+    </site>
+    <site id="s11433">
+      <name>Peking University</name>
+      <node id="n13498">
+        <hostname>pl1.pku.edu.cn</hostname>
+      </node>
+      <node id="n13582">
+        <hostname>pl3.pku.edu.cn</hostname>
+      </node>
+      <node id="n13583">
+        <hostname>pl2.pku.edu.cn</hostname>
+      </node>
+    </site>
+    <site id="s11434">
+      <name>University of Auckland</name>
+      <node id="n13319">
+        <hostname>planetlab-1.cs.auckland.ac.nz</hostname>
+      </node>
+      <node id="n13320">
+        <hostname>planetlab-2.cs.auckland.ac.nz</hostname>
+      </node>
+    </site>
+    <site id="s11436">
+      <name>Loyola University Chicago</name>
+      <node id="n13414">
+        <hostname>node0.planetlab.etl.luc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13415">
+        <hostname>node1.planetlab.etl.luc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s11437">
+      <name>MLab - AMS01</name>
+    </site>
+    <site id="s11438">
+      <name>MLab - AMS02</name>
+    </site>
+    <site id="s11439">
+      <name>MLab - LHR01</name>
+    </site>
+    <site id="s11440">
+      <name>MLab - PAR01</name>
+    </site>
+    <site id="s11441">
+      <name>MLab - HAM01</name>
+    </site>
+    <site id="s11444">
+      <name>Nile University</name>
+      <node id="n13383">
+        <hostname>planetlab2.nileu.edu.eg</hostname>
+        <bw_limit units="kbps">1024</bw_limit>
+      </node>
+      <node id="n13387">
+        <hostname>planetlab1.nileu.edu.eg</hostname>
+        <bw_limit units="kbps">1024</bw_limit>
+      </node>
+    </site>
+    <site id="s11445">
+      <name>University of Illinois at Chicago</name>
+      <node id="n13409">
+        <hostname>planetlab-1.cs.uic.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13410">
+        <hostname>planetlab-2.cs.uic.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s11450">
+      <name>Suffolk University</name>
+      <node id="n13422">
+        <hostname>metis.mcs.suffolk.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n13423">
+        <hostname>adrastea.mcs.suffolk.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s11454">
+      <name>National Chung Cheng University</name>
+      <node id="n13416">
+        <hostname>planetlab-1.webedu.ccu.edu.tw</hostname>
+      </node>
+      <node id="n13417">
+        <hostname>planetlab-2.webedu.ccu.edu.tw</hostname>
+      </node>
+    </site>
+    <site id="s11457">
+      <name>Shanghai Jiaotong University</name>
+      <node id="n13458">
+        <hostname>planetlab-1.sjtu.edu.cn</hostname>
+      </node>
+      <node id="n13459">
+        <hostname>planetlab-2.sjtu.edu.cn</hostname>
+      </node>
+    </site>
+    <site id="s11459">
+      <name>Jordan University of Science and Technology</name>
+      <node id="n13464">
+        <hostname>planetlab1.just.edu.jo</hostname>
+      </node>
+      <node id="n13468">
+        <hostname>planetlab2.just.edu.jo</hostname>
+      </node>
+    </site>
+    <site id="s11460">
+      <name>Hellenic Telecommunications and Post Commission</name>
+      <node id="n13434">
+        <hostname>mlab1.gr-ix.gr</hostname>
+      </node>
+      <node id="n13435">
+        <hostname>mlab2.gr-ix.gr</hostname>
+      </node>
+      <node id="n13437">
+        <hostname>mlab3.gr-ix.gr</hostname>
+      </node>
+    </site>
+    <site id="s11461">
+      <name>Nanyang Technological University</name>
+      <node id="n13581">
+        <hostname>pnode2.pdcc-ntu.singaren.net.sg</hostname>
+      </node>
+      <node id="n13587">
+        <hostname>pnode1.pdcc-ntu.singaren.net.sg</hostname>
+      </node>
+    </site>
+    <site id="s11464">
+      <name>MLab - ATH01</name>
+    </site>
+    <site id="s11467">
+      <name>Stanford OpenFlow</name>
+    </site>
+    <site id="s11470">
+      <name>Monash University</name>
+      <node id="n13533">
+        <hostname>pl1.eng.monash.edu.au</hostname>
+      </node>
+      <node id="n13536">
+        <hostname>pl2.eng.monash.edu.au</hostname>
+      </node>
+    </site>
+    <site id="s11471">
+      <name>Pusan National University</name>
+      <node id="n13509">
+        <hostname>mnc1.pusan.ac.kr</hostname>
+      </node>
+      <node id="n13517">
+        <hostname>mnc2.pusan.ac.kr</hostname>
+      </node>
+    </site>
+    <site id="s11472">
+      <name>BBN Technologies</name>
+      <node id="n13539">
+        <hostname>plnode-01.gpolab.bbn.com</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13540">
+        <hostname>plnode-02.gpolab.bbn.com</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13599">
+        <hostname>plnode-03.gpolab.bbn.com</hostname>
+      </node>
+    </site>
+    <site id="s11478">
+      <name>Hiroshima City University</name>
+      <node id="n13545">
+        <hostname>pl1.sos.info.hiroshima-cu.ac.jp</hostname>
+      </node>
+      <node id="n13546">
+        <hostname>pl2.sos.info.hiroshima-cu.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s11480">
+      <name>National University of Defense Technology</name>
+      <node id="n13549">
+        <hostname>planetlab-2.pdl.nudt.edu.cn</hostname>
+      </node>
+      <node id="n13550">
+        <hostname>planetlab-1.pdl.nudt.edu.cn</hostname>
+      </node>
+    </site>
+    <site id="s11486">
+      <name>MLab - SYD01</name>
+    </site>
+    <site id="s10001">
+      <name>CWRU</name>
+      <node id="n11484">
+        <hostname>planetlab-4.EECS.CWRU.Edu</hostname>
+      </node>
+      <node id="n11485">
+        <hostname>planetlab-5.EECS.CWRU.Edu</hostname>
+      </node>
+    </site>
+    <site id="s10006">
+      <name>NEU</name>
+      <node id="n10918">
+        <hostname>planetlabone.ccs.neu.edu</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10919">
+        <hostname>planetlabtwo.ccs.neu.edu</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s10037">
+      <name>Intel IT - Folsom</name>
+      <node id="n10740">
+        <hostname>planetdev02.fm.intel.com</hostname>
+      </node>
+      <node id="n10741">
+        <hostname>planetdev01.fm.intel.com</hostname>
+      </node>
+      <node id="n10837">
+        <hostname>planetdev04.fm.intel.com</hostname>
+      </node>
+      <node id="n10838">
+        <hostname>planetdev05.fm.intel.com</hostname>
+      </node>
+    </site>
+    <site id="s10038">
+      <name>University of Colorado</name>
+      <node id="n10112">
+        <hostname>planetlab1.cs.colorado.edu</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n13524">
+        <hostname>planetlab2.cs.colorado.edu</hostname>
+      </node>
+    </site>
+    <site id="s10039">
+      <name>HIIT</name>
+      <node id="n10106">
+        <hostname>planetlab1.hiit.fi</hostname>
+      </node>
+      <node id="n10107">
+        <hostname>planetlab2.hiit.fi</hostname>
+      </node>
+      <node id="n11020">
+        <hostname>planetlab3.hiit.fi</hostname>
+      </node>
+      <node id="n11021">
+        <hostname>planetlab4.hiit.fi</hostname>
+      </node>
+    </site>
+    <site id="s10044">
+      <name>Georgetown</name>
+      <node id="n10117">
+        <hostname>planetlab1.georgetown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10118">
+        <hostname>planetlab2.georgetown.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10045">
+      <name>UDel</name>
+      <node id="n10255">
+        <hostname>planetlab1.pc.cis.udel.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10256">
+        <hostname>planetlab2.pc.cis.udel.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10046">
+      <name>Neuchatel</name>
+      <node id="n10119">
+        <hostname>planetlab1.unineuchatel.ch</hostname>
+      </node>
+      <node id="n10120">
+        <hostname>planetlab2.unineuchatel.ch</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10048">
+      <name>University of Zurich</name>
+      <node id="n10388">
+        <hostname>planetlab1.csg.uzh.ch</hostname>
+      </node>
+      <node id="n10389">
+        <hostname>planetlab2.csg.uzh.ch</hostname>
+      </node>
+    </site>
+    <site id="s10049">
+      <name>Fraunhofer Institut</name>
+      <node id="n10242">
+        <hostname>planetlab2.itwm.fhg.de</hostname>
+      </node>
+      <node id="n10518">
+        <hostname>planetlab1.itwm.fhg.de</hostname>
+      </node>
+      <node id="n13541">
+        <hostname>planetlab3.itwm.fraunhofer.de</hostname>
+      </node>
+      <node id="n13542">
+        <hostname>planetlab4.itwm.fraunhofer.de</hostname>
+      </node>
+      <node id="n13543">
+        <hostname>planetlab5.itwm.fraunhofer.de</hostname>
+      </node>
+    </site>
+    <site id="s10050">
+      <name>Iowa State ECE</name>
+      <node id="n10125">
+        <hostname>planetlab-1.ece.iastate.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10126">
+        <hostname>planetlab-2.ece.iastate.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10163">
+        <hostname>planetlab-3.ece.iastate.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10165">
+        <hostname>planetlab-4.ece.iastate.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n11494">
+        <hostname>Planetlab-6.ece.iastate.edu</hostname>
+      </node>
+      <node id="n11514">
+        <hostname>planetlab-5.ece.iastate.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10053">
+      <name>Darmstadt University of Technology</name>
+      <node id="n10415">
+        <hostname>host1.planetlab.informatik.tu-darmstadt.de</hostname>
+      </node>
+      <node id="n10416">
+        <hostname>host2.planetlab.informatik.tu-darmstadt.de</hostname>
+      </node>
+      <node id="n10417">
+        <hostname>host3.planetlab.informatik.tu-darmstadt.de</hostname>
+      </node>
+    </site>
+    <site id="s10057">
+      <name>Simon Fraser University</name>
+      <node id="n11003">
+        <hostname>cs-planetlab3.cs.surrey.sfu.ca</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n11038">
+        <hostname>cs-planetlab4.cs.surrey.sfu.ca</hostname>
+      </node>
+    </site>
+    <site id="s10059">
+      <name>UConn</name>
+      <node id="n10157">
+        <hostname>planetlab1.engr.uconn.edu</hostname>
+      </node>
+      <node id="n10353">
+        <hostname>planetlab2.engr.uconn.edu</hostname>
+      </node>
+    </site>
+    <site id="s10060">
+      <name>TUM</name>
+      <node id="n10160">
+        <hostname>planetlab2.lkn.ei.tum.de</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10161">
+        <hostname>planetlab1.lkn.ei.tum.de</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10061">
+      <name>University of Florida</name>
+      <node id="n10172">
+        <hostname>planetlab1.acis.ufl.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10173">
+        <hostname>planetlab2.acis.ufl.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10064">
+      <name>GMU</name>
+      <node id="n10195">
+        <hostname>plgmu1.ite.gmu.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n10254">
+        <hostname>plgmu2.ite.gmu.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n11504">
+        <hostname>plgmu3.ite.gmu.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+      <node id="n11505">
+        <hostname>plgmu4.ite.gmu.edu</hostname>
+        <bw_limit units="kbps">2000</bw_limit>
+      </node>
+    </site>
+    <site id="s10065">
+      <name>Universitat Rovira i Virgili</name>
+      <node id="n10179">
+        <hostname>planetlab.urv.net</hostname>
+      </node>
+      <node id="n10184">
+        <hostname>planetlab2.urv.net</hostname>
+      </node>
+    </site>
+    <site id="s10066">
+      <name>University of Pittsburgh</name>
+      <node id="n10175">
+        <hostname>planetlab1.cs.pitt.edu</hostname>
+      </node>
+      <node id="n10176">
+        <hostname>planetlab2.cs.pitt.edu</hostname>
+      </node>
+    </site>
+    <site id="s10067">
+      <name>UNC Charlotte</name>
+      <node id="n10177">
+        <hostname>planetlab02.uncc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10326">
+        <hostname>planetlab01.uncc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n13135">
+        <hostname>planetlab03.uncc.edu</hostname>
+      </node>
+      <node id="n13136">
+        <hostname>planetlab04.uncc.edu</hostname>
+      </node>
+    </site>
+    <site id="s10069">
+      <name>Telekomunikacja Polska</name>
+      <node id="n13391">
+        <hostname>planetlab2.warsaw.rd.tp.pl</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10070">
+      <name>Imperial College London</name>
+      <node id="n10310">
+        <hostname>planetlab-1.imperial.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10311">
+        <hostname>planetlab-2.imperial.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10765">
+        <hostname>planetlab-3.imperial.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10782">
+        <hostname>planetlab-4.imperial.ac.uk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10071">
+      <name>FAU Erlangen</name>
+      <node id="n10212">
+        <hostname>planetlab1.informatik.uni-erlangen.de</hostname>
+      </node>
+      <node id="n13418">
+        <hostname>planetlab2.informatik.uni-erlangen.de</hostname>
+      </node>
+    </site>
+    <site id="s10074">
+      <name>KTH</name>
+      <node id="n10208">
+        <hostname>planetlab-1.ssvl.kth.se</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10222">
+        <hostname>planetlab-2.ssvl.kth.se</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10075">
+      <name>NAIST</name>
+      <node id="n10226">
+        <hostname>planetlab-01.naist.jp</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10227">
+        <hostname>planetlab-02.naist.jp</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10313">
+        <hostname>planetlab-03.naist.jp</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10691">
+        <hostname>planetlab-04.naist.jp</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s10076">
+      <name>Telekomunikacja Polska - Swidnik</name>
+    </site>
+    <site id="s10079">
+      <name>Duisburg-Essen</name>
+      <node id="n10251">
+        <hostname>planetlab1.exp-math.uni-essen.de</hostname>
+        <bw_limit units="kbps">3000</bw_limit>
+      </node>
+      <node id="n10252">
+        <hostname>planetlab2.exp-math.uni-essen.de</hostname>
+        <bw_limit units="kbps">3000</bw_limit>
+      </node>
+    </site>
+    <site id="s10085">
+      <name>Colorado State University</name>
+      <node id="n10267">
+        <hostname>planetlab-1.cs.colostate.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10268">
+        <hostname>planetlab-2.cs.colostate.edu</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10089">
+      <name>The University of Hong Kong</name>
+      <node id="n10276">
+        <hostname>planetlab1.eee.hku.hk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10279">
+        <hostname>planetlab2.eee.hku.hk</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10094">
+      <name>UOI</name>
+      <node id="n10315">
+        <hostname>planetlab2.cs.uoi.gr</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10376">
+        <hostname>planetlab1.cs.uoi.gr</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10095">
+      <name>Technical University Ilmenau</name>
+      <node id="n10316">
+        <hostname>planet1.prakinf.tu-ilmenau.de</hostname>
+      </node>
+      <node id="n10317">
+        <hostname>planet2.prakinf.tu-ilmenau.de</hostname>
+      </node>
+      <node id="n10633">
+        <hostname>planet3.prakinf.tu-ilmenau.de</hostname>
+      </node>
+    </site>
+    <site id="s10096">
+      <name>ADETTI/ISCTE</name>
+      <node id="n10324">
+        <hostname>planetlab-1.iscte.pt</hostname>
+      </node>
+      <node id="n10325">
+        <hostname>planetlab-2.iscte.pt</hostname>
+      </node>
+    </site>
+    <site id="s10097">
+      <name>Brigham Young University</name>
+      <node id="n10329">
+        <hostname>planetlab1.byu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10330">
+        <hostname>planetlab2.byu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10098">
+      <name>MPISWS</name>
+      <node id="n10331">
+        <hostname>planetlab01.mpi-sws.mpg.de</hostname>
+      </node>
+      <node id="n10332">
+        <hostname>planetlab02.mpi-sws.mpg.de</hostname>
+      </node>
+      <node id="n10333">
+        <hostname>planetlab03.mpi-sws.mpg.de</hostname>
+      </node>
+      <node id="n10334">
+        <hostname>planetlab04.mpi-sws.mpg.de</hostname>
+      </node>
+      <node id="n10335">
+        <hostname>planetlab05.mpi-sws.mpg.de</hostname>
+      </node>
+      <node id="n10336">
+        <hostname>planetlab06.mpi-sws.mpg.de</hostname>
+      </node>
+    </site>
+    <site id="s10099">
+      <name>University of Toronto</name>
+      <node id="n10337">
+        <hostname>planetlab01.erin.utoronto.ca</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10338">
+        <hostname>planetlab02.erin.utoronto.ca</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10100">
+      <name>Indiana</name>
+      <node id="n11014">
+        <hostname>pl1.ucs.indiana.edu</hostname>
+      </node>
+      <node id="n11015">
+        <hostname>pl2.ucs.indiana.edu</hostname>
+      </node>
+    </site>
+    <site id="s10103">
+      <name>University of Bern, IAM</name>
+      <node id="n10343">
+        <hostname>planetlab01.cnds.unibe.ch</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10344">
+        <hostname>planetlab02.cnds.unibe.ch</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n11374">
+        <hostname>planetlab03.cnds.unibe.ch</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n11395">
+        <hostname>planetlab04.cnds.unibe.ch</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+    </site>
+    <site id="s10104">
+      <name>Collegium Budapest</name>
+      <node id="n10345">
+        <hostname>planet1.colbud.hu</hostname>
+      </node>
+      <node id="n10346">
+        <hostname>planet2.colbud.hu</hostname>
+      </node>
+    </site>
+    <site id="s10105">
+      <name>Politecnico di Milano</name>
+      <node id="n10358">
+        <hostname>planetlab1.elet.polimi.it</hostname>
+      </node>
+      <node id="n10359">
+        <hostname>planetlab2.elet.polimi.it</hostname>
+      </node>
+    </site>
+    <site id="s10106">
+      <name>Jacobs University Bremen</name>
+      <node id="n10831">
+        <hostname>planetlab1.eecs.jacobs-university.de</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10832">
+        <hostname>planetlab2.eecs.jacobs-university.de</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10107">
+      <name>University of South Florida</name>
+      <node id="n10383">
+        <hostname>planetlab1.csee.usf.edu</hostname>
+      </node>
+      <node id="n10390">
+        <hostname>planetlab2.csee.usf.edu</hostname>
+      </node>
+      <node id="n11011">
+        <hostname>planetlab3.csee.usf.edu</hostname>
+      </node>
+      <node id="n11042">
+        <hostname>planetlab4.csee.usf.edu</hostname>
+      </node>
+      <node id="n13529">
+        <hostname>planetlab5.csee.usf.edu</hostname>
+      </node>
+      <node id="n13538">
+        <hostname>planetlab6.csee.usf.edu</hostname>
+      </node>
+    </site>
+    <site id="s10108">
+      <name>Orbit</name>
+      <node id="n10378">
+        <hostname>orbpl1.rutgers.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10379">
+        <hostname>orbpl2.rutgers.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10109">
+      <name>UC Riverside</name>
+      <node id="n10381">
+        <hostname>planet-lab1.cs.ucr.edu</hostname>
+      </node>
+      <node id="n10382">
+        <hostname>planet-lab2.cs.ucr.edu</hostname>
+      </node>
+    </site>
+    <site id="s10110">
+      <name>Ohio State University</name>
+      <node id="n10395">
+        <hostname>planetlab-1.cse.ohio-state.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10396">
+        <hostname>planetlab-2.cse.ohio-state.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10111">
+      <name>University of Goettingen</name>
+      <node id="n10393">
+        <hostname>planetlab1.informatik.uni-goettingen.de</hostname>
+      </node>
+      <node id="n10394">
+        <hostname>planetlab2.informatik.uni-goettingen.de</hostname>
+      </node>
+    </site>
+    <site id="s10114">
+      <name>University of Cyprus</name>
+      <node id="n10408">
+        <hostname>planetlab-1.cs.ucy.ac.cy</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10409">
+        <hostname>planetlab-2.cs.ucy.ac.cy</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10116">
+      <name>University of Puerto Rico at Mayaguez</name>
+      <node id="n10428">
+        <hostname>planetlab-01.ece.uprm.edu</hostname>
+      </node>
+      <node id="n10429">
+        <hostname>planetlab-02.ece.uprm.edu</hostname>
+      </node>
+    </site>
+    <site id="s10117">
+      <name>University of Texas at El Paso</name>
+      <node id="n10418">
+        <hostname>planetlab1.utep.edu</hostname>
+      </node>
+      <node id="n10419">
+        <hostname>planetlab2.utep.edu</hostname>
+      </node>
+    </site>
+    <site id="s10118">
+      <name>Osaka University</name>
+      <node id="n10422">
+        <hostname>pl2-higashi.ics.es.osaka-u.ac.jp</hostname>
+      </node>
+      <node id="n10423">
+        <hostname>pl1-higashi.ics.es.osaka-u.ac.jp</hostname>
+      </node>
+      <node id="n10719">
+        <hostname>pub1-s.ane.cmc.osaka-u.ac.jp</hostname>
+      </node>
+      <node id="n10720">
+        <hostname>pub2-s.ane.cmc.osaka-u.ac.jp</hostname>
+      </node>
+      <node id="n13119">
+        <hostname>int-pl1.ise.eng.osaka-u.ac.jp</hostname>
+      </node>
+      <node id="n13120">
+        <hostname>int-pl2.ise.eng.osaka-u.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s10121">
+      <name>Fraunhofer HHI</name>
+      <node id="n10474">
+        <hostname>planet01.hhi.fraunhofer.de</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10475">
+        <hostname>planet02.hhi.fraunhofer.de</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10125">
+      <name>Trinity College Dublin</name>
+      <node id="n10441">
+        <hostname>planetlab01.cs.tcd.ie</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10442">
+        <hostname>planetlab02.cs.tcd.ie</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10126">
+      <name>University of California, Irvine</name>
+      <node id="n10444">
+        <hostname>planetlab-1a.ics.uci.edu</hostname>
+      </node>
+      <node id="n10445">
+        <hostname>planetlab-2a.ics.uci.edu</hostname>
+      </node>
+      <node id="n13521">
+        <hostname>planetlab-3.ics.uci.edu</hostname>
+      </node>
+    </site>
+    <site id="s10127">
+      <name>JAIST</name>
+      <node id="n10446">
+        <hostname>planet0.jaist.ac.jp</hostname>
+      </node>
+      <node id="n10447">
+        <hostname>planet1.jaist.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s10128">
+      <name>University of Saskatchewan</name>
+      <node id="n10469">
+        <hostname>planetlab-1.usask.ca</hostname>
+      </node>
+      <node id="n10470">
+        <hostname>planetlab-2.usask.ca</hostname>
+      </node>
+    </site>
+    <site id="s10132">
+      <name>Queen Mary, University of London</name>
+      <node id="n10466">
+        <hostname>planetlab1.nrl.dcs.qmul.ac.uk</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n10467">
+        <hostname>planetlab2.nrl.dcs.qmul.ac.uk</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s10133">
+      <name>San Jose State University</name>
+      <node id="n10580">
+        <hostname>plab1.engr.sjsu.edu</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10581">
+        <hostname>plab2.engr.sjsu.edu</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10134">
+      <name>IIT-Chicago</name>
+      <node id="n10464">
+        <hostname>server1.planetlab.iit-tech.net</hostname>
+        <bw_limit units="kbps">10485</bw_limit>
+      </node>
+      <node id="n10465">
+        <hostname>server2.planetlab.iit-tech.net</hostname>
+        <bw_limit units="kbps">10485</bw_limit>
+      </node>
+      <node id="n11096">
+        <hostname>server3.planetlab.iit-tech.net</hostname>
+        <bw_limit units="kbps">10485</bw_limit>
+      </node>
+      <node id="n11097">
+        <hostname>server4.planetlab.iit-tech.net</hostname>
+        <bw_limit units="kbps">10485</bw_limit>
+      </node>
+    </site>
+    <site id="s10135">
+      <name>Wroclaw University of Technology</name>
+      <node id="n10472">
+        <hostname>planetlab2.ci.pwr.wroc.pl</hostname>
+      </node>
+      <node id="n10478">
+        <hostname>planetlab1.ci.pwr.wroc.pl</hostname>
+      </node>
+    </site>
+    <site id="s10136">
+      <name>LARC - University of Sao Paulo</name>
+      <node id="n11068">
+        <hostname>plab1.larc.usp.br</hostname>
+      </node>
+      <node id="n11069">
+        <hostname>plab2.larc.usp.br</hostname>
+      </node>
+    </site>
+    <site id="s10137">
+      <name>vanderbilt</name>
+      <node id="n10482">
+        <hostname>planetlab-1.vuse.vanderbilt.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10483">
+        <hostname>planetlab-2.vuse.vanderbilt.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10138">
+      <name>Washington State University</name>
+      <node id="n10484">
+        <hostname>planetlab1.eecs.wsu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+      <node id="n10485">
+        <hostname>planetlab2.eecs.wsu.edu</hostname>
+        <bw_limit units="kbps">5000</bw_limit>
+      </node>
+    </site>
+    <site id="s10141">
+      <name>Kansas State University</name>
+      <node id="n11101">
+        <hostname>plab4.eece.ksu.edu</hostname>
+      </node>
+      <node id="n11106">
+        <hostname>plab3.eece.ksu.edu</hostname>
+      </node>
+    </site>
+    <site id="s10143">
+      <name>Universita</name>
+      <node id="n10494">
+        <hostname>planetlab-1.dis.uniroma1.it</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10495">
+        <hostname>planetlab-2.dis.uniroma1.it</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10144">
+      <name>FeM TU-Ilmenau</name>
+      <node id="n10498">
+        <hostname>planetlab1.fem.tu-ilmenau.de</hostname>
+      </node>
+      <node id="n10499">
+        <hostname>planetlab2.fem.tu-ilmenau.de</hostname>
+      </node>
+    </site>
+    <site id="s10145">
+      <name>Keio University</name>
+      <node id="n10505">
+        <hostname>planetlab1.sfc.wide.ad.jp</hostname>
+      </node>
+      <node id="n10506">
+        <hostname>planetlab2.sfc.wide.ad.jp</hostname>
+      </node>
+    </site>
+    <site id="s10146">
+      <name>University of Missouri Kansas City</name>
+      <node id="n10529">
+        <hostname>kc-sce-plab1.umkc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10534">
+        <hostname>kc-sce-plab2.umkc.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10147">
+      <name>Klagenfurt University</name>
+      <node id="n10509">
+        <hostname>plab1-itec.uni-klu.ac.at</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10510">
+        <hostname>plab2-itec.uni-klu.ac.at</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10148">
+      <name>University of Central Florida</name>
+      <node id="n10841">
+        <hostname>planetlab1.eecs.ucf.edu</hostname>
+      </node>
+      <node id="n10842">
+        <hostname>planetlab2.eecs.ucf.edu</hostname>
+      </node>
+    </site>
+    <site id="s10149">
+      <name>Technische Universitaet Dresden</name>
+      <node id="n10516">
+        <hostname>planet1.inf.tu-dresden.de</hostname>
+      </node>
+      <node id="n10517">
+        <hostname>planet2.inf.tu-dresden.de</hostname>
+      </node>
+    </site>
+    <site id="s10150">
+      <name>UTA</name>
+      <node id="n10522">
+        <hostname>planetlab1.uta.edu</hostname>
+      </node>
+      <node id="n11510">
+        <hostname>planetlab2.uta.edu</hostname>
+      </node>
+    </site>
+    <site id="s10151">
+      <name>CESNET</name>
+      <node id="n10524">
+        <hostname>planetlab1.cesnet.cz</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10527">
+        <hostname>planetlab2.cesnet.cz</hostname>
+      </node>
+      <node id="n10692">
+        <hostname>planetlab1.fit.vutbr.cz</hostname>
+      </node>
+    </site>
+    <site id="s10152">
+      <name>University of Lisbon</name>
+      <node id="n10535">
+        <hostname>planetlab-1.di.fc.ul.pt</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10536">
+        <hostname>planetlab-2.di.fc.ul.pt</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10156">
+      <name>Beijing Institute of Technology</name>
+      <node id="n10629">
+        <hostname>planetlab1.iin-bit.com.cn</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10742">
+        <hostname>planetlab2.iin-bit.com.cn</hostname>
+      </node>
+    </site>
+    <site id="s10158">
+      <name>Singapore AREN</name>
+      <node id="n10599">
+        <hostname>planetlab1.singaren.net.sg</hostname>
+      </node>
+      <node id="n10600">
+        <hostname>planetlab2.singaren.net.sg</hostname>
+      </node>
+      <node id="n10601">
+        <hostname>planetlab3.singaren.net.sg</hostname>
+      </node>
+      <node id="n13343">
+        <hostname>planetlab4.singaren.net.sg</hostname>
+      </node>
+    </site>
+    <site id="s10159">
+      <name>WIDE Project</name>
+      <node id="n10595">
+        <hostname>planetlab0.otemachi.wide.ad.jp</hostname>
+      </node>
+      <node id="n10596">
+        <hostname>planetlab1.otemachi.wide.ad.jp</hostname>
+      </node>
+      <node id="n10597">
+        <hostname>planetlab0.dojima.wide.ad.jp</hostname>
+      </node>
+      <node id="n10598">
+        <hostname>planetlab1.dojima.wide.ad.jp</hostname>
+      </node>
+    </site>
+    <site id="s10170">
+      <name>PL Colo - TP Warsaw</name>
+    </site>
+    <site id="s10171">
+      <name>IST</name>
+      <node id="n10621">
+        <hostname>planetlab-1.tagus.ist.utl.pt</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+      <node id="n10622">
+        <hostname>planetlab-2.tagus.ist.utl.pt</hostname>
+        <bw_limit units="kbps">10000</bw_limit>
+      </node>
+    </site>
+    <site id="s10172">
+      <name>CeCalCULA</name>
+      <node id="n10632">
+        <hostname>fobos.cecalc.ula.ve</hostname>
+      </node>
+      <node id="n10652">
+        <hostname>deimos.cecalc.ula.ve</hostname>
+      </node>
+    </site>
+    <site id="s10173">
+      <name>National Taiwan University EE</name>
+      <node id="n10669">
+        <hostname>adam.ee.ntu.edu.tw</hostname>
+      </node>
+      <node id="n10670">
+        <hostname>eve.ee.ntu.edu.tw</hostname>
+      </node>
+    </site>
+    <site id="s10174">
+      <name>RWTH Aachen</name>
+      <node id="n10671">
+        <hostname>freedom.informatik.rwth-aachen.de</hostname>
+      </node>
+      <node id="n10672">
+        <hostname>peace.informatik.rwth-aachen.de</hostname>
+      </node>
+    </site>
+    <site id="s10176">
+      <name>Worcester Polytechnic Institute</name>
+      <node id="n10626">
+        <hostname>75-130-96-12.static.oxfr.ma.charter.com</hostname>
+      </node>
+      <node id="n10627">
+        <hostname>75-130-96-13.static.oxfr.ma.charter.com</hostname>
+      </node>
+    </site>
+    <site id="s10177">
+      <name>Aston University</name>
+      <node id="n10650">
+        <hostname>planetlab1.aston.ac.uk</hostname>
+      </node>
+      <node id="n10651">
+        <hostname>planetlab2.aston.ac.uk</hostname>
+      </node>
+    </site>
+    <site id="s10178">
+      <name>Zuse Institute Berlin</name>
+      <node id="n10641">
+        <hostname>planet1.zib.de</hostname>
+      </node>
+      <node id="n10643">
+        <hostname>planet2.zib.de</hostname>
+      </node>
+    </site>
+    <site id="s10179">
+      <name>University of Innsbruck, Austria.</name>
+      <node id="n10644">
+        <hostname>plab1-c703.uibk.ac.at</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10645">
+        <hostname>plab2-c703.uibk.ac.at</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10180">
+      <name>University of Stuttgart</name>
+      <node id="n10640">
+        <hostname>planetvs1.informatik.uni-stuttgart.de</hostname>
+        <bw_limit units="kbps">550</bw_limit>
+      </node>
+      <node id="n10642">
+        <hostname>planetvs2.informatik.uni-stuttgart.de</hostname>
+        <bw_limit units="kbps">550</bw_limit>
+      </node>
+    </site>
+    <site id="s10181">
+      <name>University of Konstanz</name>
+      <node id="n10716">
+        <hostname>dannan.disy.inf.uni-konstanz.de</hostname>
+      </node>
+      <node id="n10717">
+        <hostname>chronos.disy.inf.uni-konstanz.de</hostname>
+      </node>
+    </site>
+    <site id="s10182">
+      <name>Universidade do Algarve</name>
+      <node id="n10667">
+        <hostname>planetlab1.fct.ualg.pt</hostname>
+      </node>
+      <node id="n10668">
+        <hostname>planetlab2.fct.ualg.pt</hostname>
+      </node>
+    </site>
+    <site id="s10183">
+      <name>University of Vienna</name>
+      <node id="n10817">
+        <hostname>planetlab2.ani.univie.ac.at</hostname>
+      </node>
+      <node id="n10947">
+        <hostname>planetlab3.ani.univie.ac.at</hostname>
+      </node>
+      <node id="n11465">
+        <hostname>planetlab4.ani.univie.ac.at</hostname>
+      </node>
+    </site>
+    <site id="s10185">
+      <name>Waseda University</name>
+      <node id="n13215">
+        <hostname>planetlab4.goto.info.waseda.ac.jp</hostname>
+      </node>
+      <node id="n13286">
+        <hostname>planetlab5.goto.info.waseda.ac.jp</hostname>
+      </node>
+      <node id="n13360">
+        <hostname>planetlab6.goto.info.waseda.ac.jp</hostname>
+      </node>
+    </site>
+    <site id="s10190">
+      <name>University of Houston</name>
+      <node id="n10679">
+        <hostname>planetlab-1.cs.uh.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10680">
+        <hostname>planetlab-2.cs.uh.edu</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10191">
+      <name>IRISA</name>
+      <node id="n10734">
+        <hostname>peeramidion.irisa.fr</hostname>
+      </node>
+      <node id="n10735">
+        <hostname>peeramide.irisa.fr</hostname>
+      </node>
+    </site>
+    <site id="s10193">
+      <name>Arizona State University</name>
+      <node id="n10710">
+        <hostname>planetlab2.eas.asu.edu</hostname>
+      </node>
+      <node id="n10711">
+        <hostname>planetlab1.eas.asu.edu</hostname>
+      </node>
+    </site>
+    <site id="s10194">
+      <name>University of Ljubljana</name>
+      <node id="n10681">
+        <hostname>planetlab1.fri.uni-lj.si</hostname>
+      </node>
+      <node id="n10682">
+        <hostname>planetlab2.fri.uni-lj.si</hostname>
+      </node>
+    </site>
+    <site id="s10195">
+      <name>The Citadel</name>
+      <node id="n10725">
+        <hostname>planetlab1.citadel.edu</hostname>
+      </node>
+      <node id="n10726">
+        <hostname>planetlab2.citadel.edu</hostname>
+      </node>
+    </site>
+    <site id="s10198">
+      <name>BME</name>
+      <node id="n10707">
+        <hostname>planetlab1.tmit.bme.hu</hostname>
+      </node>
+      <node id="n10718">
+        <hostname>planetlab2.tmit.bme.hu</hostname>
+      </node>
+    </site>
+    <site id="s10200">
+      <name>Poznan Supercomputing</name>
+      <node id="n10705">
+        <hostname>planetlab-1.man.poznan.pl</hostname>
+      </node>
+      <node id="n10706">
+        <hostname>planetlab-2.man.poznan.pl</hostname>
+      </node>
+    </site>
+    <site id="s10201">
+      <name>Lahore University of Management Pakistan</name>
+      <node id="n10823">
+        <hostname>planetlab01.lums.edu.pk</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+      <node id="n10824">
+        <hostname>planetlab02.lums.edu.pk</hostname>
+        <bw_limit units="kbps">500</bw_limit>
+      </node>
+    </site>
+    <site id="s10203">
+      <name>Universidad Publica de Navarra</name>
+      <node id="n10714">
+        <hostname>planetlab1.tlm.unavarra.es</hostname>
+      </node>
+      <node id="n10715">
+        <hostname>planetlab2.tlm.unavarra.es</hostname>
+      </node>
+    </site>
+    <site id="s10205">
+      <name>ETH Zuerich - CS</name>
+      <node id="n10695">
+        <hostname>planetlab1.inf.ethz.ch</hostname>
+      </node>
+      <node id="n10697">
+        <hostname>planetlab2.inf.ethz.ch</hostname>
+      </node>
+      <node id="n10736">
+        <hostname>planetlab3.inf.ethz.ch</hostname>
+      </node>
+      <node id="n10737">
+        <hostname>planetlab4.inf.ethz.ch</hostname>
+      </node>
+    </site>
+    <site id="s10206">
+      <name>Seoul National University - MMLAB</name>
+      <node id="n10766">
+        <hostname>pl1snu.koren21.net</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n13504">
+        <hostname>pl2snu.koren21.net</hostname>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s10208">
+      <name>Virginia Tech</name>
+      <node id="n11372">
+        <hostname>planetlab-01.vt.nodes.planet-lab.org</hostname>
+      </node>
+      <node id="n11373">
+        <hostname>planetlab-02.vt.nodes.planet-lab.org</hostname>
+      </node>
+      <node id="n13507">
+        <hostname>planetlab-03.vt.nodes.planet-lab.org</hostname>
+      </node>
+      <node id="n13508">
+        <hostname>planetlab-04.vt.nodes.planet-lab.org</hostname>
+      </node>
+    </site>
+    <site id="s10209">
+      <name>Facultad de Ingenieria - Univ. de la Republica</name>
+      <node id="n10744">
+        <hostname>planetlab-1.fing.edu.uy</hostname>
+      </node>
+      <node id="n10745">
+        <hostname>planetlab-2.fing.edu.uy</hostname>
+      </node>
+    </site>
+    <site id="s10210">
+      <name>HAW Hamburg</name>
+      <node id="n10732">
+        <hostname>merkur.planetlab.haw-hamburg.de</hostname>
+      </node>
+      <node id="n10733">
+        <hostname>mars.planetlab.haw-hamburg.de</hostname>
+      </node>
+    </site>
+    <site id="s10211">
+      <name>TU MIET</name>
+      <node id="n10783">
+        <hostname>mercury.silicon-valley.ru</hostname>
+      </node>
+      <node id="n10784">
+        <hostname>venus.silicon-valley.ru</hostname>
+      </node>
+    </site>
+    <site id="s10213">
+      <name>DoCoMo Labs USA</name>
+      <node id="n10767">
+        <hostname>planetlabnode-1.docomolabs-usa.com</hostname>
+      </node>
+      <node id="n10768">
+        <hostname>planetlabnode-2.docomolabs-usa.com</hostname>
+      </node>
+    </site>
+    <site id="s10214">
+      <name>PL Colo - CLARA Santiago</name>
+      <node id="n10755">
+        <hostname>planetlab1-santiago.lan.redclara.net</hostname>
+      </node>
+      <node id="n10756">
+        <hostname>planetlab2-santiago.lan.redclara.net</hostname>
+      </node>
+    </site>
+    <site id="s10215">
+      <name>PL Colo - CLARA Sao Paulo</name>
+      <node id="n10757">
+        <hostname>planetlab1-saopaulo.lan.redclara.net</hostname>
+      </node>
+      <node id="n10758">
+        <hostname>planetlab2-saopaulo.lan.redclara.net</hostname>
+      </node>
+    </site>
+    <site id="s10216">
+      <name>PL Colo - CLARA Buenos Aires</name>
+      <node id="n10759">
+        <hostname>planetlab1-buenosaires.lan.redclara.net</hostname>
+      </node>
+      <node id="n10760">
+        <hostname>planetlab2-buenosaires.lan.redclara.net</hostname>
+      </node>
+    </site>
+    <site id="s10217">
+      <name>PL Colo - CLARA Tijuana</name>
+      <node id="n10763">
+        <hostname>planetlab1-tijuana.lan.redclara.net</hostname>
+      </node>
+      <node id="n10764">
+        <hostname>planetlab2-tijuana.lan.redclara.net</hostname>
+      </node>
+    </site>
+    <site id="s10225">
+      <name>CWI</name>
+      <node id="n10804">
+        <hostname>146-179.surfsnel.dsl.internl.net</hostname>
+        <bw_limit units="kbps">100000</bw_limit>
+      </node>
+      <node id="n10805">
+        <hostname>147-179.surfsnel.dsl.internl.net</hostname>
+      </node>
+    </site>
+    <site id="s10229">
+      <name>Space Monitoring Data Center SINP MSU</name>
+      <node id="n10792">
+        <hostname>plab-1.sinp.msu.ru</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+      <node id="n10793">
+        <hostname>plab-2.sinp.msu.ru</hostname>
+        <bw_limit units="kbps">1000</bw_limit>
+      </node>
+    </site>
+    <site id="s10230">
+      <name>RRC Kurchatovskiy Instutute</name>
+      <node id="n10818">
+        <hostname>pl1.grid.kiae.ru</hostname>
+      </node>
+      <node id="n10819">
+        <hostname>pl2.grid.kiae.ru</hostname>
+      </node>
+    </site>
+    <site id="s10231">
+      <name>NETMODE Lab of NTUA</name>
+      <node id="n10794">
+        <hostname>vicky.planetlab.ntua.gr</hostname>
+      </node>
+      <node id="n10795">
+        <hostname>stella.planetlab.ntua.gr</hostname>
+      </node>
+    </site>
+  </network>
+</RSpec>
diff --git a/sfa/managers/registry_manager_pl.py b/sfa/managers/registry_manager_pl.py
new file mode 100644 (file)
index 0000000..1636eda
--- /dev/null
@@ -0,0 +1,421 @@
+import types
+import time 
+from sfa.util.prefixTree import prefixTree
+from sfa.util.record import SfaRecord
+from sfa.util.table import SfaTable
+from sfa.util.record import SfaRecord
+from sfa.trust.gid import GID 
+from sfa.util.namespace import *
+from sfa.trust.credential import *
+from sfa.trust.certificate import *
+from sfa.util.faults import *
+
+def get_version(api):
+    version = {}
+    version['geni_api'] = 1
+    version['sfa'] = 1
+    return version
+
+def get_credential(api, xrn, type, is_self=False):
+    # convert xrn to hrn     
+    if type:
+        hrn = urn_to_hrn(xrn)[0]
+    else:
+        hrn, type = urn_to_hrn(xrn)
+        
+    # Is this a root or sub authority
+    auth_hrn = api.auth.get_authority(hrn)
+    if not auth_hrn or hrn == api.config.SFA_INTERFACE_HRN:
+        auth_hrn = hrn
+    # get record info
+    auth_info = api.auth.get_auth_info(auth_hrn)
+    table = SfaTable()
+    records = table.findObjects({'type': type, 'hrn': hrn})
+    if not records:
+        raise RecordNotFound(hrn)
+    record = records[0]
+
+    # verify_cancreate_credential requires that the member lists
+    # (researchers, pis, etc) be filled in
+    api.fill_record_info(record)
+    if record['type']=='user':
+       if not record['enabled']:
+          raise AccountNotEnabled(": PlanetLab account %s is not enabled. Please contact your site PI" %(record['email']))
+
+    # get the callers gid
+    # if this is a self cred the record's gid is the caller's gid
+    if is_self:
+        caller_hrn = hrn
+        caller_gid = record.get_gid_object()
+    else:
+        caller_gid = api.auth.client_cred.get_gid_caller() 
+        caller_hrn = caller_gid.get_hrn()
+    
+    object_hrn = record.get_gid_object().get_hrn()
+    rights = api.auth.determine_user_rights(caller_hrn, record)
+    # make sure caller has rights to this object
+    if rights.is_empty():
+        raise PermissionError(caller_hrn + " has no rights to " + record['name'])
+
+    object_gid = GID(string=record['gid'])
+    new_cred = Credential(subject = object_gid.get_subject())
+    new_cred.set_gid_caller(caller_gid)
+    new_cred.set_gid_object(object_gid)
+    new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
+    #new_cred.set_pubkey(object_gid.get_pubkey())
+    new_cred.set_privileges(rights)
+    new_cred.get_privileges().delegate_all_privileges(True)
+    auth_kind = "authority,ma,sa"
+    # Parent not necessary, verify with certs
+    #new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
+    new_cred.encode()
+    new_cred.sign()
+
+    return new_cred.save_to_string(save_parents=True)
+
+
+# The GENI GetVersion call
+def GetVersion():
+    version = {}
+    version['geni_api'] = 1
+    return version
+
+def resolve(api, xrns, type=None, full=True):
+
+    # load all know registry names into a prefix tree and attempt to find
+    # the longest matching prefix
+    if not isinstance(xrns, types.ListType):
+        xrns = [xrns]
+    hrns = [urn_to_hrn(xrn)[0] for xrn in xrns] 
+    # create a dict whre key is an registry hrn and its value is a
+    # hrns at that registry (determined by the known prefix tree).  
+    xrn_dict = {}
+    registries = api.registries
+    tree = prefixTree()
+    registry_hrns = registries.keys()
+    tree.load(registry_hrns)
+    for xrn in xrns:
+        registry_hrn = tree.best_match(urn_to_hrn(xrn)[0])
+        if registry_hrn not in xrn_dict:
+            xrn_dict[registry_hrn] = []
+        xrn_dict[registry_hrn].append(xrn)
+        
+    records = [] 
+    for registry_hrn in xrn_dict:
+        # skip the hrn without a registry hrn
+        # XX should we let the user know the authority is unknown?       
+        if not registry_hrn:
+            continue
+
+        # if the best match (longest matching hrn) is not the local registry,
+        # forward the request
+        xrns = xrn_dict[registry_hrn]
+        if registry_hrn != api.hrn:
+            credential = api.getCredential()
+            peer_records = registries[registry_hrn].Resolve(xrns, credential)
+            records.extend([SfaRecord(dict=record).as_dict() for record in peer_records])
+
+    # try resolving the remaining unfound records at the local registry
+    remaining_hrns = set(hrns).difference([record['hrn'] for record in records])
+    # convert set to list
+    remaining_hrns = [hrn for hrn in remaining_hrns] 
+    table = SfaTable()
+    local_records = table.findObjects({'hrn': remaining_hrns})
+    if full:
+        api.fill_record_info(local_records)
+    
+    # convert local record objects to dicts
+    records.extend([dict(record) for record in local_records])
+    if not records:
+        raise RecordNotFound(str(hrns))
+
+    if type:
+        records = filter(lambda rec: rec['type'] in [type], records)
+
+    return records
+
+def list(api, xrn, origin_hrn=None):
+    hrn, type = urn_to_hrn(xrn)
+    # load all know registry names into a prefix tree and attempt to find
+    # the longest matching prefix
+    records = []
+    registries = api.registries
+    registry_hrns = registries.keys()
+    tree = prefixTree()
+    tree.load(registry_hrns)
+    registry_hrn = tree.best_match(hrn)
+   
+    #if there was no match then this record belongs to an unknow registry
+    if not registry_hrn:
+        raise MissingAuthority(xrn)
+    
+    # if the best match (longest matching hrn) is not the local registry,
+    # forward the request
+    records = []    
+    if registry_hrn != api.hrn:
+        credential = api.getCredential()
+        record_list = registries[registry_hrn].list(credential, xrn, origin_hrn)
+        records = [SfaRecord(dict=record).as_dict() for record in record_list]
+    
+    # if we still havnt found the record yet, try the local registry
+    if not records:
+        if not api.auth.hierarchy.auth_exists(hrn):
+            raise MissingAuthority(hrn)
+
+        table = SfaTable()
+        records = table.find({'authority': hrn})
+
+    return records
+
+
+def register(api, record):
+
+    hrn, type = record['hrn'], record['type']
+    urn = hrn_to_urn(hrn,type)
+    # validate the type
+    if type not in ['authority', 'slice', 'node', 'user']:
+        raise UnknownSfaType(type) 
+    
+    # check if record already exists
+    table = SfaTable()
+    existing_records = table.find({'type': type, 'hrn': hrn})
+    if existing_records:
+        raise ExistingRecord(hrn)
+       
+    record = SfaRecord(dict = record)
+    record['authority'] = get_authority(record['hrn'])
+    type = record['type']
+    hrn = record['hrn']
+    api.auth.verify_object_permission(hrn)
+    auth_info = api.auth.get_auth_info(record['authority'])
+    pub_key = None
+    # make sure record has a gid
+    if 'gid' not in record:
+        uuid = create_uuid()
+        pkey = Keypair(create=True)
+        if 'key' in record and record['key']:
+            if isinstance(record['key'], types.ListType):
+                pub_key = record['key'][0]
+            else:
+                pub_key = record['key']
+            pkey = convert_public_key(pub_key)
+
+        gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
+        gid = gid_object.save_to_string(save_parents=True)
+        record['gid'] = gid
+        record.set_gid(gid)
+
+    if type in ["authority"]:
+        # update the tree
+        if not api.auth.hierarchy.auth_exists(hrn):
+            api.auth.hierarchy.create_auth(hrn_to_urn(hrn,'authority'))
+
+        # get the GID from the newly created authority
+        gid = auth_info.get_gid_object()
+        record.set_gid(gid.save_to_string(save_parents=True))
+        pl_record = api.sfa_fields_to_pl_fields(type, hrn, record)
+        sites = api.plshell.GetSites(api.plauth, [pl_record['login_base']])
+        if not sites:
+            pointer = api.plshell.AddSite(api.plauth, pl_record)
+        else:
+            pointer = sites[0]['site_id']
+
+        record.set_pointer(pointer)
+        record['pointer'] = pointer
+
+    elif (type == "slice"):
+        acceptable_fields=['url', 'instantiation', 'name', 'description']
+        pl_record = api.sfa_fields_to_pl_fields(type, hrn, record)
+        for key in pl_record.keys():
+            if key not in acceptable_fields:
+                pl_record.pop(key)
+        slices = api.plshell.GetSlices(api.plauth, [pl_record['name']])
+        if not slices:
+             pointer = api.plshell.AddSlice(api.plauth, pl_record)
+        else:
+             pointer = slices[0]['slice_id']
+        record.set_pointer(pointer)
+        record['pointer'] = pointer
+
+    elif  (type == "user"):
+        persons = api.plshell.GetPersons(api.plauth, [record['email']])
+        if not persons:
+            pointer = api.plshell.AddPerson(api.plauth, dict(record))
+        else:
+            pointer = persons[0]['person_id']
+
+        if 'enabled' in record and record['enabled']:
+            api.plshell.UpdatePerson(api.plauth, pointer, {'enabled': record['enabled']})
+        # add this persons to the site only if he is being added for the first
+        # time by sfa and doesont already exist in plc
+        if not persons or not persons[0]['site_ids']:
+            login_base = get_leaf(record['authority'])
+            api.plshell.AddPersonToSite(api.plauth, pointer, login_base)
+
+        # What roles should this user have?
+        api.plshell.AddRoleToPerson(api.plauth, 'user', pointer)
+        # Add the user's key
+        if pub_key:
+            api.plshell.AddPersonKey(api.plauth, pointer, {'key_type' : 'ssh', 'key' : pub_key})
+
+    elif (type == "node"):
+        pl_record = api.sfa_fields_to_pl_fields(type, hrn, record)
+        login_base = hrn_to_pl_login_base(record['authority'])
+        nodes = api.plshell.GetNodes(api.plauth, [pl_record['hostname']])
+        if not nodes:
+            pointer = api.plshell.AddNode(api.plauth, login_base, pl_record)
+        else:
+            pointer = nodes[0]['node_id']
+
+    record['pointer'] = pointer
+    record.set_pointer(pointer)
+    record_id = table.insert(record)
+    record['record_id'] = record_id
+
+    # update membership for researchers, pis, owners, operators
+    api.update_membership(None, record)
+
+    return record.get_gid_object().save_to_string(save_parents=True)
+
+def update(api, record_dict):
+    new_record = SfaRecord(dict = record_dict)
+    type = new_record['type']
+    hrn = new_record['hrn']
+    urn = hrn_to_urn(hrn,type)
+    api.auth.verify_object_permission(hrn)
+    table = SfaTable()
+    # make sure the record exists
+    records = table.findObjects({'type': type, 'hrn': hrn})
+    if not records:
+        raise RecordNotFound(hrn)
+    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.fill_record_info(record)
+
+    # 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.plshell.UpdateSite(api.plauth, pointer, new_record)
+
+    elif type == "slice":
+        pl_record=api.sfa_fields_to_pl_fields(type, hrn, new_record)
+        if 'name' in pl_record:
+            pl_record.pop('name')
+            api.plshell.UpdateSlice(api.plauth, 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.plshell.UpdatePerson(api.plauth, 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.plshell.GetPersons(api.plauth, [pointer], ['key_ids'])
+            person = persons[0]
+            keys = person['key_ids']
+            keys = api.plshell.GetKeys(api.plauth, 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.plshell.DeleteKey(api.plauth, key['key_id'])
+                else:
+                    key_exists = True
+            if not key_exists:
+                api.plshell.AddPersonKey(api.plauth, 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.plshell.UpdateNode(api.plauth, pointer, new_record)
+
+    else:
+        raise UnknownSfaType(type)
+
+    # update membership for researchers, pis, owners, operators
+    api.update_membership(record, new_record)
+    
+    return 1 
+
+def remove(api, xrn, type, origin_hrn=None):
+    # convert xrn to hrn     
+    if type:
+        hrn = urn_to_hrn(xrn)[0]
+    else:
+        hrn, type = urn_to_hrn(xrn)    
+
+    table = SfaTable()
+    filter = {'hrn': hrn}
+    if type and type not in ['all', '*']:
+        filter['type'] = type
+    records = table.find(filter)
+    if not records:
+        raise RecordNotFound(hrn)
+    record = records[0]
+    type = record['type']
+
+    credential = api.getCredential()
+    registries = api.registries
+
+    # Try to remove the object from the PLCDB of federated agg.
+    # This is attempted before removing the object from the local agg's PLCDB and sfa table
+    if hrn.startswith(api.hrn) and type in ['user', 'slice', 'authority']:
+        for registry in registries:
+            if registry not in [api.hrn]:
+                try:
+                    result=registries[registry].remove_peer_object(credential, record, origin_hrn)
+                except:
+                    pass
+    if type == "user":
+        persons = api.plshell.GetPersons(api.plauth, 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.plshell.DeletePerson(api.plauth, record['pointer'])
+    elif type == "slice":
+        if api.plshell.GetSlices(api.plauth, record['pointer']):
+            api.plshell.DeleteSlice(api.plauth, record['pointer'])
+    elif type == "node":
+        if api.plshell.GetNodes(api.plauth, record['pointer']):
+            api.plshell.DeleteNode(api.plauth, record['pointer'])
+    elif type == "authority":
+        if api.plshell.GetSites(api.plauth, record['pointer']):
+            api.plshell.DeleteSite(api.plauth, record['pointer'])
+    else:
+        raise UnknownSfaType(type)
+
+    table.remove(record)
+
+    return 1
+
+def remove_peer_object(api, record, origin_hrn=None):
+    pass
+
+def register_peer_object(api, record, origin_hrn=None):
+    pass
diff --git a/sfa/managers/slice_manager_pl.py b/sfa/managers/slice_manager_pl.py
new file mode 100644 (file)
index 0000000..4230978
--- /dev/null
@@ -0,0 +1,308 @@
+### $Id: slices.py 15842 2009-11-22 09:56:13Z anil $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/slices.py $
+
+import datetime
+import time
+import traceback
+import sys
+from copy import deepcopy
+from lxml import etree
+from StringIO import StringIO
+from types import StringTypes
+from sfa.util.rspecHelper import merge_rspecs
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.record import SfaRecord
+from sfa.util.policy import Policy
+from sfa.util.prefixTree import prefixTree
+from sfa.util.sfaticket import *
+from sfa.trust.credential import Credential
+from sfa.util.threadmanager import ThreadManager
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol     
+from sfa.util.debug import log
+import sfa.plc.peers as peers
+from copy import copy
+
+def get_version():
+    version = {}
+    version['geni_api'] = 1
+    version['sfa'] = 1
+    return version
+
+def slice_status(api, slice_xrn, creds ):
+    result = {}
+    result['geni_urn'] = slice_xrn
+    result['geni_status'] = 'unknown'
+    result['geni_resources'] = {}
+    return result
+
+def create_slice(api, xrn, creds, rspec, users):
+    hrn, type = urn_to_hrn(xrn)
+
+    # Validate the RSpec against PlanetLab's schema --disabled for now
+    # The schema used here needs to aggregate the PL and VINI schemas
+    # schema = "/var/www/html/schemas/pl.rng"
+    schema = None
+    if schema:
+        try:
+            tree = etree.parse(StringIO(rspec))
+        except etree.XMLSyntaxError:
+            message = str(sys.exc_info()[1])
+            raise InvalidRSpec(message)
+
+        relaxng_doc = etree.parse(schema)
+        relaxng = etree.RelaxNG(relaxng_doc)
+        
+        if not relaxng(tree):
+            error = relaxng.error_log.last_error
+            message = "%s (line %s)" % (error.message, error.line)
+            raise InvalidRSpec(message)
+
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:     
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        # Just send entire RSpec to each aggregate
+        server = api.aggregates[aggregate]
+        threads.run(server.CreateSliver, xrn, credential, rspec, users)
+            
+    results = threads.get_results() 
+    merged_rspec = merge_rspecs(results)
+    return merged_rspec
+
+def renew_slice(api, xrn, creds, expiration_time):
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        server = api.aggregates[aggregate]
+        threads.run(server.RenewSliver, xrn, credential, expiration_time)
+    threads.get_results()
+    return 1
+
+def get_ticket(api, xrn, creds, rspec, users):
+    slice_hrn, type = urn_to_hrn(xrn)
+    # get the netspecs contained within the clients rspec
+    aggregate_rspecs = {}
+    tree= etree.parse(StringIO(rspec))
+    elements = tree.findall('./network')
+    for element in elements:
+        aggregate_hrn = element.values()[0]
+        aggregate_rspecs[aggregate_hrn] = rspec 
+
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential() 
+    threads = ThreadManager()
+    for aggregate, aggregate_rspec in aggregate_rspecs.items():
+        server = None
+        if aggregate in api.aggregates:
+            server = api.aggregates[aggregate]
+        else:
+            net_urn = hrn_to_urn(aggregate, 'authority')     
+            # we may have a peer that knows about this aggregate
+            for agg in api.aggregates:
+                target_aggs = api.aggregates[agg].get_aggregates(credential, net_urn)
+                if not target_aggs or not 'hrn' in target_aggs[0]:
+                    continue
+                # send the request to this address 
+                url = target_aggs[0]['url']
+                server = xmlrpcprotocol.get_server(url, api.key_file, api.cert_file)
+                # aggregate found, no need to keep looping
+                break   
+        if server is None:
+            continue 
+        threads.run(server.GetTicket, xrn, credential, aggregate_rspec, users)
+
+    results = threads.get_results()
+    
+    # gather information from each ticket 
+    rspecs = []
+    initscripts = []
+    slivers = [] 
+    object_gid = None  
+    for result in results:
+        agg_ticket = SfaTicket(string=result)
+        attrs = agg_ticket.get_attributes()
+        if not object_gid:
+            object_gid = agg_ticket.get_gid_object()
+        rspecs.append(agg_ticket.get_rspec())
+        initscripts.extend(attrs.get('initscripts', [])) 
+        slivers.extend(attrs.get('slivers', [])) 
+    
+    # merge info
+    attributes = {'initscripts': initscripts,
+                 'slivers': slivers}
+    merged_rspec = merge_rspecs(rspecs) 
+
+    # create a new ticket
+    ticket = SfaTicket(subject = slice_hrn)
+    ticket.set_gid_caller(api.auth.client_gid)
+    ticket.set_issuer(key=api.key, subject=api.hrn)
+    ticket.set_gid_object(object_gid)
+    ticket.set_pubkey(object_gid.get_pubkey())
+    #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
+    ticket.set_attributes(attributes)
+    ticket.set_rspec(merged_rspec)
+    ticket.encode()
+    ticket.sign()          
+    return ticket.save_to_string(save_parents=True)
+
+
+def delete_slice(api, xrn, creds):
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        server = api.aggregates[aggregate]
+        threads.run(server.DeleteSliver, xrn, credential)
+    threads.get_results()
+    return 1
+
+def start_slice(api, xrn, creds):
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        server = api.aggregates[aggregate]
+        threads.run(server.Start, xrn, credential)
+    threads.get_results()    
+    return 1
+def stop_slice(api, xrn, creds):
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        server = api.aggregates[aggregate]
+        threads.run(server.Stop, xrn, credential)
+    threads.get_results()    
+    return 1
+
+def reset_slice(api, xrn):
+    """
+    Not implemented
+    """
+    return 1
+
+def shutdown(api, xrn, creds):
+    """
+    Not implemented   
+    """
+    return 1
+
+def status(api, xrn, creds):
+    """
+    Not implemented 
+    """
+    return 1
+
+def get_slices(api, creds):
+    # look in cache first
+    if api.cache:
+        slices = api.cache.get('slices')
+        if slices:
+            return slices    
+
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    # fetch from aggregates
+    for aggregate in api.aggregates:
+        server = api.aggregates[aggregate]
+        threads.run(server.ListSlices, credential)
+
+    # combime results
+    results = threads.get_results()
+    slices = []
+    for result in results:
+        slices.extend(result)
+    
+    # cache the result
+    if api.cache:
+        api.cache.add('slices', slices)
+
+    return slices
+def get_rspec(api, creds, options):
+    # get slice's hrn from options
+    xrn = options.get('geni_slice_urn', None)
+    hrn, type = urn_to_hrn(xrn)
+
+    # get hrn of the original caller
+    origin_hrn = options.get('origin_hrn', None)
+    if not origin_hrn:
+        origin_hrn = Credential(string=creds[0]).get_gid_caller().get_hrn()
+    
+    # look in cache first 
+    if api.cache and not xrn:
+        rspec =  api.cache.get('nodes')
+        if rspec:
+            return rspec
+
+    hrn, type = urn_to_hrn(xrn)
+    rspec = None
+    
+    # attempt to use delegated credential first
+    credential = api.getDelegatedCredential(creds)
+    if not credential:
+        credential = api.getCredential()
+    threads = ThreadManager()
+    for aggregate in api.aggregates:
+        # get the rspec from the aggregate
+        server = api.aggregates[aggregate]
+        my_opts = copy(options)
+        my_opts['geni_compressed'] = False
+        threads.run(server.ListResources, credential, my_opts)
+        #threads.run(server.get_resources, cred, xrn, origin_hrn)
+                    
+    results = threads.get_results()
+    # combine the rspecs into a single rspec 
+    for agg_rspec in results:
+        try:
+            tree = etree.parse(StringIO(agg_rspec))
+        except etree.XMLSyntaxError:
+            message = str(agg_rspec) + ": " + str(sys.exc_info()[1])
+            raise InvalidRSpec(message)
+
+        root = tree.getroot()
+        if root.get("type") in ["SFA"]:
+            if rspec == None:
+                rspec = root
+            else:
+                for network in root.iterfind("./network"):
+                    rspec.append(deepcopy(network))
+                for request in root.iterfind("./request"):
+                    rspec.append(deepcopy(request))
+    
+    rspec =  etree.tostring(rspec, xml_declaration=True, pretty_print=True)
+    # cache the result
+    if api.cache and not xrn:
+        api.cache.add('nodes', rspec)
+    return rspec
+
+def main():
+    r = RSpec()
+    r.parseFile(sys.argv[1])
+    rspec = r.toDict()
+    create_slice(None,'plc.princeton.tmacktestslice',rspec)
+
+if __name__ == "__main__":
+    main()
+    
diff --git a/sfa/managers/vini/__init__.py b/sfa/managers/vini/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/managers/vini/request.xml b/sfa/managers/vini/request.xml
new file mode 100644 (file)
index 0000000..111f7b0
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<RSpec type="SFA">
+  <request name="plc.vini">
+    <sliver nodeid="n18"/>
+    <sliver nodeid="n20"/>
+    <sliver nodeid="n22"/>
+    <sliver nodeid="n26"/>
+    <sliver nodeid="n28"/>
+    <sliver nodeid="n30"/>
+    <sliver nodeid="n32"/>
+    <sliver nodeid="n34"/>
+    <sliver nodeid="n36"/>
+    <vlink endpoints="n18 n22"/>
+    <vlink endpoints="n18 n26"/>
+    <vlink endpoints="n18 n28"/>
+    <vlink endpoints="n20 n22"/>
+    <vlink endpoints="n22 n26"/>
+    <vlink endpoints="n26 n30"/>
+    <vlink endpoints="n28 n30"/>
+    <vlink endpoints="n28 n32"/>
+    <vlink endpoints="n30 n36"/>
+    <vlink endpoints="n34 n36"/>
+    <vlink endpoints="n32 n36"/>
+    <vlink endpoints="n32 n34"/>
+  </request>
+</RSpec>
diff --git a/sfa/managers/vini/topology.py b/sfa/managers/vini/topology.py
new file mode 100755 (executable)
index 0000000..cb65fb5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+
+# $Id: topology.py 14181 2009-07-01 19:46:07Z acb $
+# $URL: https://svn.planet-lab.org/svn/NodeManager-topo/trunk/topology.py $
+
+#
+# Links in the physical topology, gleaned from looking at the Internet2
+# and NLR topology maps.  Link (a, b) connects sites with IDs a and b.
+#
+PhysicalLinks = [(2, 12),  # I2 Princeton - New York 
+         (4, 5),   # NLR Chicago - Houston
+         (4, 6),   # NLR Chicago - Atlanta
+         (4, 7),   # NLR Chicago - Seattle
+         (4, 9),   # NLR Chicago - New York
+         (4, 10),  # NLR Chicago - Wash DC
+         (5, 6),   # NLR Houston - Atlanta
+         (5, 8),   # NLR Houston - Los Angeles
+         (6, 10),  # NLR Atlanta - Wash DC
+         (6, 14),  # NLR Atlanta - Ga Tech
+         (7, 8),   # NLR Seattle - Los Angeles
+         (9, 10),  # NLR New York - Wash DC
+         (11, 13), # I2 Chicago - Wash DC
+         (11, 15), # I2 Chicago - Atlanta
+         (11, 16), # I2 Chicago - CESNET
+         (11, 17), # I2 Chicago - Kansas City
+         (12, 13), # I2 New York - Wash DC
+         (13, 15), # I2 Wash DC - Atlanta
+         (14, 15), # Ga Tech - I2 Atlanta
+         (15, 19), # I2 Atlanta - Houston
+         (17, 19), # I2 Kansas City - Houston
+         (17, 22), # I2 Kansas City - Salt Lake City
+         (17, 24), # I2 Kansas City - UMKC
+         (19, 20), # I2 Houston - Los Angeles
+         (20, 21), # I2 Los Angeles - Seattle
+         (20, 22), # I2 Los Angeles - Salt Lake City
+         (21, 22)] # I2 Seattle - Salt Lake City
+
+
diff --git a/sfa/managers/vini/utils.py b/sfa/managers/vini/utils.py
new file mode 100644 (file)
index 0000000..b0d7e51
--- /dev/null
@@ -0,0 +1,719 @@
+from __future__ import with_statement
+import re
+import socket
+from sfa.util.faults import *
+from sfa.managers.vini.topology import PhysicalLinks
+from xmlbuilder import XMLBuilder
+from lxml import etree
+import sys
+from StringIO import StringIO
+
+VINI_RELAXNG_SCHEMA = "/var/www/html/schemas/vini.rng"
+
+# Taken from bwlimit.py
+#
+# See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
+# warned that older versions of tc interpret "kbps", "mbps", "mbit",
+# and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
+# "kibit" and that if an older version is installed, all rates will
+# be off by a small fraction.
+suffixes = {
+    "":         1,
+    "bit":     1,
+    "kibit":   1024,
+    "kbit":    1000,
+    "mibit":   1024*1024,
+    "mbit":    1000000,
+    "gibit":   1024*1024*1024,
+    "gbit":    1000000000,
+    "tibit":   1024*1024*1024*1024,
+    "tbit":    1000000000000,
+    "bps":     8,
+    "kibps":   8*1024,
+    "kbps":    8000,
+    "mibps":   8*1024*1024,
+    "mbps":    8000000,
+    "gibps":   8*1024*1024*1024,
+    "gbps":    8000000000,
+    "tibps":   8*1024*1024*1024*1024,
+    "tbps":    8000000000000
+}
+
+
+def get_tc_rate(s):
+    """
+    Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
+    """
+
+    if type(s) == int:
+        return s
+    m = re.match(r"([0-9.]+)(\D*)", s)
+    if m is None:
+        return -1
+    suffix = m.group(2).lower()
+    if suffixes.has_key(suffix):
+        return int(float(m.group(1)) * suffixes[suffix])
+    else:
+        return -1
+
+def format_tc_rate(rate):
+    """
+    Formats a bits/second rate into a tc rate string
+    """
+
+    if rate >= 1000000000 and (rate % 1000000000) == 0:
+        return "%.0fgbit" % (rate / 1000000000.)
+    elif rate >= 1000000 and (rate % 1000000) == 0:
+        return "%.0fmbit" % (rate / 1000000.)
+    elif rate >= 1000:
+        return "%.0fkbit" % (rate / 1000.)
+    else:
+        return "%.0fbit" % rate
+
+
+class Node:
+    def __init__(self, node, bps = 1000 * 1000000):
+        self.id = node['node_id']
+        self.idtag = "n%s" % self.id
+        self.hostname = node['hostname']
+        self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
+        self.site_id = node['site_id']
+        self.ipaddr = socket.gethostbyname(self.hostname)
+        self.bps = bps
+        self.links = set()
+        self.sliver = False
+
+    def get_link_id(self, remote):
+        if self.id < remote.id:
+            link = (self.id<<7) + remote.id
+        else:
+            link = (remote.id<<7) + self.id
+        return link
+        
+    def get_iface_id(self, remote):
+        if self.id < remote.id:
+            iface = 1
+        else:
+            iface = 2
+        return iface
+    
+    def get_virt_ip(self, remote):
+        link = self.get_link_id(remote)
+        iface = self.get_iface_id(remote)
+        first = link >> 6
+        second = ((link & 0x3f)<<2) + iface
+        return "192.168.%d.%d" % (first, second)
+
+    def get_virt_net(self, remote):
+        link = self.get_link_id(remote)
+        first = link >> 6
+        second = (link & 0x3f)<<2
+        return "192.168.%d.%d/30" % (first, second)
+        
+    def get_site(self, sites):
+        return sites[self.site_id]
+    
+    def get_topo_rspec(self, link):
+        if link.end1 == self:
+            remote = link.end2
+        elif link.end2 == self:
+            remote = link.end1
+        else:
+            raise Error("Link does not connect to Node")
+            
+        my_ip = self.get_virt_ip(remote)
+        remote_ip = remote.get_virt_ip(self)
+        net = self.get_virt_net(remote)
+        bw = format_tc_rate(link.bps)
+        return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
+        
+    def add_link(self, link):
+        self.links.add(link)
+        
+    def add_tag(self, sites):
+        s = self.get_site(sites)
+        words = self.hostname.split(".")
+        index = words[0].replace("node", "")
+        if index.isdigit():
+            self.tag = s.tag + index
+        else:
+            self.tag = None
+
+    # Assumes there is at most one Link between two sites
+    def get_sitelink(self, node, sites):
+        site1 = sites[self.site_id]
+        site2 = sites[node.site_id]
+        sl = site1.links.intersection(site2.links)
+        if len(sl):
+            return sl.pop()
+        return None
+
+    def add_sliver(self):
+        self.sliver = True
+
+    def toxml(self, xml, hrn):
+        if not self.tag:
+            return
+        with xml.node(id = self.idtag):
+            with xml.hostname:
+                xml << self.hostname
+            with xml.kbps:
+                xml << str(int(self.bps/1000))
+            if self.sliver:
+                with xml.sliver:
+                    pass
+    
+
+class Link:
+    def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
+        self.end1 = end1
+        self.end2 = end2
+        self.bps = bps
+        self.parent = parent
+        self.children = []
+
+        end1.add_link(self)
+        end2.add_link(self)
+        
+        if self.parent:
+            self.parent.children.append(self)
+            
+    def toxml(self, xml):
+        end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
+
+        if self.parent:
+            element = xml.vlink(endpoints=end_ids)
+        else:
+            element = xml.link(endpoints=end_ids)
+
+        with element:
+            with xml.description:
+                xml << "%s -- %s" % (self.end1.name, self.end2.name)
+            with xml.kbps:
+                xml << str(int(self.bps/1000))
+            for child in self.children:
+                child.toxml(xml)
+        
+
+class Site:
+    def __init__(self, site):
+        self.id = site['site_id']
+        self.idtag = "s%s" % self.id
+        self.node_ids = site['node_ids']
+        self.name = site['abbreviated_name'].replace(" ", "_")
+        self.tag = site['login_base']
+        self.public = site['is_public']
+        self.enabled = site['enabled']
+        self.links = set()
+
+    def get_sitenodes(self, nodes):
+        n = []
+        for i in self.node_ids:
+            n.append(nodes[i])
+        return n
+    
+    def add_link(self, link):
+        self.links.add(link)
+
+    def toxml(self, xml, hrn, nodes):
+        if not (self.public and self.enabled and self.node_ids):
+            return
+        with xml.site(id = self.idtag):
+            with xml.name:
+                xml << self.name
+                
+            for node in self.get_sitenodes(nodes):
+                node.toxml(xml, hrn)
+   
+    
+class Slice:
+    def __init__(self, slice):
+        self.id = slice['slice_id']
+        self.name = slice['name']
+        self.node_ids = set(slice['node_ids'])
+        self.slice_tag_ids = slice['slice_tag_ids']
+    
+    def get_tag(self, tagname, slicetags, node = None):
+        for i in self.slice_tag_ids:
+            tag = slicetags[i]
+            if tag.tagname == tagname:
+                if (not node) or (node.id == tag.node_id):
+                    return tag
+        else:
+            return None
+        
+    def get_nodes(self, nodes):
+        n = []
+        for id in self.node_ids:
+            n.append(nodes[id])
+        return n
+             
+    
+    # Add a new slice tag   
+    def add_tag(self, tagname, value, slicetags, node = None):
+        record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
+        if node:
+            record['node_id'] = node.id
+        else:
+            record['node_id'] = None
+        tag = Slicetag(record)
+        slicetags[tag.id] = tag
+        self.slice_tag_ids.append(tag.id)
+        tag.changed = True       
+        tag.updated = True
+        return tag
+    
+    # Update a slice tag if it exists, else add it             
+    def update_tag(self, tagname, value, slicetags, node = None):
+        tag = self.get_tag(tagname, slicetags, node)
+        if tag and tag.value == value:
+            value = "no change"
+        elif tag:
+            tag.value = value
+            tag.changed = True
+        else:
+            tag = self.add_tag(tagname, value, slicetags, node)
+        tag.updated = True
+            
+    def assign_egre_key(self, slicetags):
+        if not self.get_tag('egre_key', slicetags):
+            try:
+                key = free_egre_key(slicetags)
+                self.update_tag('egre_key', key, slicetags)
+            except:
+                # Should handle this case...
+                pass
+        return
+            
+    def turn_on_netns(self, slicetags):
+        tag = self.get_tag('netns', slicetags)
+        if (not tag) or (tag.value != '1'):
+            self.update_tag('netns', '1', slicetags)
+        return
+   
+    def turn_off_netns(self, slicetags):
+        tag = self.get_tag('netns', slicetags)
+        if tag and (tag.value != '0'):
+            tag.delete()
+        return
+    
+    def add_cap_net_admin(self, slicetags):
+        tag = self.get_tag('capabilities', slicetags)
+        if tag:
+            caps = tag.value.split(',')
+            for cap in caps:
+                if cap == "CAP_NET_ADMIN":
+                    return
+            else:
+                newcaps = "CAP_NET_ADMIN," + tag.value
+                self.update_tag('capabilities', newcaps, slicetags)
+        else:
+            self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
+        return
+    
+    def remove_cap_net_admin(self, slicetags):
+        tag = self.get_tag('capabilities', slicetags)
+        if tag:
+            caps = tag.value.split(',')
+            newcaps = []
+            for cap in caps:
+                if cap != "CAP_NET_ADMIN":
+                    newcaps.append(cap)
+            if newcaps:
+                value = ','.join(newcaps)
+                self.update_tag('capabilities', value, slicetags)
+            else:
+                tag.delete()
+        return
+
+    # Update the vsys/setup-link and vsys/setup-nat slice tags.
+    def add_vsys_tags(self, slicetags):
+        link = nat = False
+        for i in self.slice_tag_ids:
+            tag = slicetags[i]
+            if tag.tagname == 'vsys':
+                if tag.value == 'setup-link':
+                    link = True
+                elif tag.value == 'setup-nat':
+                    nat = True
+        if not link:
+            self.add_tag('vsys', 'setup-link', slicetags)
+        if not nat:
+            self.add_tag('vsys', 'setup-nat', slicetags)
+        return
+
+
+class Slicetag:
+    newid = -1 
+    def __init__(self, tag):
+        self.id = tag['slice_tag_id']
+        if not self.id:
+            # Make one up for the time being...
+            self.id = Slicetag.newid
+            Slicetag.newid -= 1
+        self.slice_id = tag['slice_id']
+        self.tagname = tag['tagname']
+        self.value = tag['value']
+        self.node_id = tag['node_id']
+        self.updated = False
+        self.changed = False
+        self.deleted = False
+    
+    # Mark a tag as deleted
+    def delete(self):
+        self.deleted = True
+        self.updated = True
+    
+    def write(self, api):
+        if self.changed:
+            if int(self.id) > 0:
+                api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
+            else:
+                api.plshell.AddSliceTag(api.plauth, self.slice_id, 
+                                        self.tagname, self.value, self.node_id)
+        elif self.deleted and int(self.id) > 0:
+            api.plshell.DeleteSliceTag(api.plauth, self.id)
+
+
+"""
+A topology is a compound object consisting of:
+* a dictionary mapping site IDs to Site objects
+* a dictionary mapping node IDs to Node objects
+* the Site objects are connected via SiteLink objects representing
+  the physical topology and available bandwidth
+* the Node objects are connected via Link objects representing
+  the requested or assigned virtual topology of a slice
+"""
+class Topology:
+    def __init__(self, api):
+        self.api = api
+        self.sites = get_sites(api)
+        self.nodes = get_nodes(api)
+        self.tags = get_slice_tags(api)
+        self.sitelinks = []
+        self.nodelinks = []
+    
+        for (s1, s2) in PhysicalLinks:
+            self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
+        
+        for id in self.nodes:
+            self.nodes[id].add_tag(self.sites)
+        
+        for t in self.tags:
+            tag = self.tags[t]
+            if tag.tagname == 'topo_rspec':
+                node1 = self.nodes[tag.node_id]
+                l = eval(tag.value)
+                for (id, realip, bw, lvip, rvip, vnet) in l:
+                    allocbps = get_tc_rate(bw)
+                    node1.bps -= allocbps
+                    try:
+                        node2 = self.nodes[id]
+                        if node1.id < node2.id:
+                            sl = node1.get_sitelink(node2, self.sites)
+                            sl.bps -= allocbps
+                    except:
+                        pass
+
+    
+    """ Lookup site based on id or idtag value """
+    def lookupSite(self, id):
+        val = None
+        if isinstance(id, basestring):
+            id = int(id.lstrip('s'))
+        try:
+            val = self.sites[id]
+        except:
+            raise KeyError("site ID %s not found" % id)
+        return val
+    
+    def getSites(self):
+        sites = []
+        for s in self.sites:
+            sites.append(self.sites[s])
+        return sites
+        
+    """ Lookup node based on id or idtag value """
+    def lookupNode(self, id):
+        val = None
+        if isinstance(id, basestring):
+            id = int(id.lstrip('n'))
+        try:
+            val = self.nodes[id]
+        except:
+            raise KeyError("node ID %s not found" % id)
+        return val
+    
+    def getNodes(self):
+        nodes = []
+        for n in self.nodes:
+            nodes.append(self.nodes[n])
+        return nodes
+    
+    def nodesInTopo(self):
+        nodes = []
+        for n in self.nodes:
+            node = self.nodes[n]
+            if node.sliver:
+                nodes.append(node)
+        return nodes
+            
+    def lookupSliceTag(self, id):
+        val = None
+        try:
+            val = self.tags[id]
+        except:
+            raise KeyError("slicetag ID %s not found" % id)
+        return val
+    
+    def getSliceTags(self):
+        tags = []
+        for t in self.tags:
+            tags.append(self.tags[t])
+        return tags
+    
+    def lookupSiteLink(self, node1, node2):
+        site1 = self.sites[node1.site_id]
+        site2 = self.sites[node2.site_id]
+        for link in self.sitelinks:
+            if site1 == link.end1 and site2 == link.end2:
+                return link
+            if site2 == link.end1 and site1 == link.end2:
+                return link
+        return None
+    
+
+    def __add_vlink(self, vlink, slicenodes, parent = None):
+        n1 = n2 = None
+        endpoints = vlink.get("endpoints")
+        if endpoints:
+            (end1, end2) = endpoints.split()
+            n1 = self.lookupNode(end1)
+            n2 = self.lookupNode(end2)
+        elif parent:
+            """ Try to infer the endpoints for the virtual link """
+            site_endpoints = parent.get("endpoints")
+            (n1, n2) = self.__infer_endpoints(site_endpoints, slicenodes)
+        else:
+            raise Error("no endpoints given")
+
+        #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
+        bps = int(vlink.findtext("kbps")) * 1000
+        sitelink = self.lookupSiteLink(n1, n2)
+        if not sitelink:
+            raise PermissionError("nodes %s and %s not adjacent" % 
+                                  (n1.idtag, n2.idtag))
+        self.nodelinks.append(Link(n1, n2, bps, sitelink))
+        return
+
+    """ 
+    Infer the endpoints of the virtual link.  If the slice exists on 
+    only a single node at each end of the physical link, we'll assume that
+    the user wants the virtual link to terminate at these nodes.
+    """
+    def __infer_endpoints(self, endpoints, slicenodes):
+        n = []
+        ends = endpoints.split()
+        for end in ends:
+            found = 0
+            site = self.lookupSite(end)
+            for id in site.node_ids:
+                if id in slicenodes:
+                    n.append(slicenodes[id])
+                    found += 1
+            if found != 1:
+                raise Error("could not infer endpoint for site %s" % site.id)
+        #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
+        return n
+        
+    def nodeTopoFromRSpec(self, xml):
+        if self.nodelinks:
+            raise Error("virtual topology already present")
+            
+        nodedict = {}
+        for node in self.getNodes():
+            nodedict[node.idtag] = node
+            
+        slicenodes = {}
+
+        tree = etree.parse(StringIO(xml))
+
+        # Validate the incoming request against the RelaxNG schema
+        relaxng_doc = etree.parse(VINI_RELAXNG_SCHEMA)
+        relaxng = etree.RelaxNG(relaxng_doc)
+        
+        if not relaxng(tree):
+            error = relaxng.error_log.last_error
+            message = "%s (line %s)" % (error.message, error.line)
+            raise InvalidRSpec(message)
+
+        rspec = tree.getroot()
+
+        """
+        Handle requests where the user has annotated a description of the
+        physical resources (nodes and links) with virtual ones (slivers
+        and vlinks).
+        """
+        # Find slivers under node elements
+        for sliver in rspec.iterfind("./network/site/node/sliver"):
+            elem = sliver.getparent()
+            node = nodedict[elem.get("id")]
+            slicenodes[node.id] = node
+            node.add_sliver()
+
+        # Find vlinks under link elements
+        for vlink in rspec.iterfind("./network/link/vlink"):
+            link = vlink.getparent()
+            self.__add_vlink(vlink, slicenodes, link)
+
+        """
+        Handle requests where the user has listed the virtual resources only
+        """
+        # Find slivers that specify nodeid
+        for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
+            node = nodedict[sliver.get("nodeid")]
+            slicenodes[node.id] = node
+            node.add_sliver()
+
+        # Find vlinks that specify endpoints
+        for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
+            self.__add_vlink(vlink, slicenodes)
+
+        return
+
+    def nodeTopoFromSliceTags(self, slice):
+        if self.nodelinks:
+            raise Error("virtual topology already present")
+            
+        for node in slice.get_nodes(self.nodes):
+            node.sliver = True
+            linktag = slice.get_tag('topo_rspec', self.tags, node)
+            if linktag:
+                l = eval(linktag.value)
+                for (id, realip, bw, lvip, rvip, vnet) in l:
+                    if node.id < id:
+                        bps = get_tc_rate(bw)
+                        remote = self.lookupNode(id)
+                        sitelink = self.lookupSiteLink(node, remote)
+                        self.nodelinks.append(Link(node,remote,bps,sitelink))
+
+    def updateSliceTags(self, slice):
+        if not self.nodelinks:
+            return
+        slice.update_tag('vini_topo', 'manual', self.tags)
+        slice.assign_egre_key(self.tags)
+        slice.turn_on_netns(self.tags)
+        slice.add_cap_net_admin(self.tags)
+
+        for node in slice.get_nodes(self.nodes):
+            linkdesc = []
+            for link in node.links:
+                linkdesc.append(node.get_topo_rspec(link))
+            if linkdesc:
+                topo_str = "%s" % linkdesc
+                slice.update_tag('topo_rspec', topo_str, self.tags, node)
+
+        # Update slice tags in database
+        for tag in self.getSliceTags():
+            if tag.slice_id == slice.id:
+                if tag.tagname == 'topo_rspec' and not tag.updated:
+                    tag.delete()
+                tag.write(self.api)
+                
+    """
+    Check the requested topology against the available topology and capacity
+    """
+    def verifyNodeTopo(self, hrn, topo):
+        for link in self.nodelinks:
+            if link.bps <= 0:
+                raise GeniInvalidArgument(bw, "BW")
+                
+            n1 = link.end1
+            n2 = link.end2
+            sitelink = self.lookupSiteLink(n1, n2)
+            if not sitelink:
+                raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
+            if sitelink.bps < link.bps:
+                raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
+                
+    """
+    Produce XML directly from the topology specification.
+    """
+    def toxml(self, hrn = None):
+        xml = XMLBuilder(format = True, tab_step = "  ")
+        with xml.RSpec(type="VINI"):
+            if hrn:
+                element = xml.network(name="Public_VINI", slice=hrn)
+            else:
+                element = xml.network(name="Public_VINI")
+                
+            with element:
+                for site in self.getSites():
+                    site.toxml(xml, hrn, self.nodes)
+                for link in self.sitelinks:
+                    link.toxml(xml)
+
+        header = '<?xml version="1.0"?>\n'
+        return header + str(xml)
+
+"""
+Create a dictionary of site objects keyed by site ID
+"""
+def get_sites(api):
+    tmp = []
+    for site in api.plshell.GetSites(api.plauth):
+        t = site['site_id'], Site(site)
+        tmp.append(t)
+    return dict(tmp)
+
+
+"""
+Create a dictionary of node objects keyed by node ID
+"""
+def get_nodes(api):
+    tmp = []
+    for node in api.plshell.GetNodes(api.plauth):
+        t = node['node_id'], Node(node)
+        tmp.append(t)
+    return dict(tmp)
+
+"""
+Create a dictionary of slice objects keyed by slice ID
+"""
+def get_slice(api, slicename):
+    slice = api.plshell.GetSlices(api.plauth, [slicename])
+    if slice:
+        return Slice(slice[0])
+    else:
+        return None
+
+"""
+Create a dictionary of slicetag objects keyed by slice tag ID
+"""
+def get_slice_tags(api):
+    tmp = []
+    for tag in api.plshell.GetSliceTags(api.plauth):
+        t = tag['slice_tag_id'], Slicetag(tag)
+        tmp.append(t)
+    return dict(tmp)
+    
+"""
+Find a free EGRE key
+"""
+def free_egre_key(slicetags):
+    used = set()
+    for i in slicetags:
+        tag = slicetags[i]
+        if tag.tagname == 'egre_key':
+            used.add(int(tag.value))
+                
+    for i in range(1, 256):
+        if i not in used:
+            key = i
+            break
+    else:
+        raise KeyError("No more EGRE keys available")
+        
+    return "%s" % key
+   
diff --git a/sfa/managers/vini/vini.rnc b/sfa/managers/vini/vini.rnc
new file mode 100644 (file)
index 0000000..0be7640
--- /dev/null
@@ -0,0 +1,70 @@
+start = RSpec
+RSpec = element RSpec { 
+   attribute type { xsd:NMTOKEN },
+   ( network | request )
+}
+network = element network {
+   attribute name { xsd:NMTOKEN },
+   attribute slice { xsd:NMTOKEN }?,
+   sliver_defaults?,
+   site+,
+   link*
+}
+sliver_defaults = element sliver_defaults {
+   sliver_elements
+}
+site = element site { 
+   attribute id { xsd:ID },
+   element name { text },
+   node*
+}
+node = element node {
+   attribute id { xsd:ID },
+   element hostname { text },
+   element bw_unallocated { attribute units { xsd:NMTOKEN }, text }?,
+   element bw_limit { attribute units { xsd:NMTOKEN }, text }?,
+   sliver*
+}
+link = element link { 
+   attribute endpoints { xsd:IDREFS },
+   element description { text }?,
+   element bw_unallocated { attribute units { xsd:NMTOKEN }, text },
+   vlink*
+}
+request = element request {
+   attribute name { xsd:NMTOKEN },
+   sliver_defaults?,
+   sliver*,
+   vlink*
+}
+sliver = element sliver { 
+   attribute nodeid { xsd:ID }?,
+   sliver_elements
+}
+sliver_elements = ( 
+   element capabilities { text }? 
+ & element codemux { text }* 
+ & element cpu_pct { text }?
+ & element cpu_share { text }?
+ & element delegations { text }?
+ & element disk_max { text }?
+ & element initscript { text }?
+ & element ip_addresses {text }*
+ & element net_i2_max_kbyte { text }?
+ & element net_i2_max_rate { text }?
+ & element net_i2_min_rate { text }?
+ & element net_i2_share { text }?
+ & element net_i2_thresh_kbyte { text }?
+ & element net_max_kbyte { text }?
+ & element net_max_rate { text }?
+ & element net_min_rate { text }?
+ & element net_share { text }?
+ & element net_thresh_kbyte { text }?
+ & element vsys {text}*
+ & element vsys_vnet { text }?
+)
+vlink = element vlink { 
+   attribute endpoints { xsd:IDREFS }?,
+   element description { text }?,
+   element kbps { text }? 
+}
diff --git a/sfa/managers/vini/vini.rng b/sfa/managers/vini/vini.rng
new file mode 100644 (file)
index 0000000..1545cb5
--- /dev/null
@@ -0,0 +1,255 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <start>
+    <ref name="RSpec"/>
+  </start>
+  <define name="RSpec">
+    <element name="RSpec">
+      <attribute name="type">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <choice>
+        <ref name="network"/>
+        <ref name="request"/>
+      </choice>
+    </element>
+  </define>
+  <define name="network">
+    <element name="network">
+      <attribute name="name">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <optional>
+        <attribute name="slice">
+          <data type="NMTOKEN"/>
+        </attribute>
+      </optional>
+      <optional>
+        <ref name="sliver_defaults"/>
+      </optional>
+      <oneOrMore>
+        <ref name="site"/>
+      </oneOrMore>
+      <zeroOrMore>
+        <ref name="link"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="sliver_defaults">
+    <element name="sliver_defaults">
+      <ref name="sliver_elements"/>
+    </element>
+  </define>
+  <define name="site">
+    <element name="site">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <element name="name">
+        <text/>
+      </element>
+      <zeroOrMore>
+        <ref name="node"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="node">
+    <element name="node">
+      <attribute name="id">
+        <data type="ID"/>
+      </attribute>
+      <element name="hostname">
+        <text/>
+      </element>
+      <optional>
+        <element name="bw_unallocated">
+          <attribute name="units">
+            <data type="NMTOKEN"/>
+          </attribute>
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="bw_limit">
+          <attribute name="units">
+            <data type="NMTOKEN"/>
+          </attribute>
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <ref name="sliver"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="link">
+    <element name="link">
+      <attribute name="endpoints">
+        <data type="IDREFS"/>
+      </attribute>
+      <optional>
+        <element name="description">
+          <text/>
+        </element>
+      </optional>
+      <element name="bw_unallocated">
+        <attribute name="units">
+          <data type="NMTOKEN"/>
+        </attribute>
+        <text/>
+      </element>
+      <zeroOrMore>
+        <ref name="vlink"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="request">
+    <element name="request">
+      <attribute name="name">
+        <data type="NMTOKEN"/>
+      </attribute>
+      <optional>
+        <ref name="sliver_defaults"/>
+      </optional>
+      <zeroOrMore>
+        <ref name="sliver"/>
+      </zeroOrMore>
+      <zeroOrMore>
+        <ref name="vlink"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="sliver">
+    <element name="sliver">
+      <optional>
+        <attribute name="nodeid">
+          <data type="ID"/>
+        </attribute>
+      </optional>
+      <ref name="sliver_elements"/>
+    </element>
+  </define>
+  <define name="sliver_elements">
+    <interleave>
+      <optional>
+        <element name="capabilities">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="codemux">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="cpu_pct">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="cpu_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="delegations">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="disk_max">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="initscript">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="ip_addresses">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="net_i2_max_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_max_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_min_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_i2_thresh_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_max_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_max_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_min_rate">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_share">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="net_thresh_kbyte">
+          <text/>
+        </element>
+      </optional>
+      <zeroOrMore>
+        <element name="vsys">
+          <text/>
+        </element>
+      </zeroOrMore>
+      <optional>
+        <element name="vsys_vnet">
+          <text/>
+        </element>
+      </optional>
+    </interleave>
+  </define>
+  <define name="vlink">
+    <element name="vlink">
+      <optional>
+        <attribute name="endpoints">
+          <data type="IDREFS"/>
+        </attribute>
+      </optional>
+      <optional>
+        <element name="description">
+          <text/>
+        </element>
+      </optional>
+      <optional>
+        <element name="kbps">
+          <text/>
+        </element>
+      </optional>
+    </element>
+  </define>
+</grammar>
diff --git a/sfa/managers/vini/vini.xml b/sfa/managers/vini/vini.xml
new file mode 100644 (file)
index 0000000..eb0049b
--- /dev/null
@@ -0,0 +1,373 @@
+<?xml version="1.0"?>
+<RSpec type="SFA">
+  <network name="plc.vini">
+    <site id="s2">
+      <name>Princeton</name>
+      <node id="n1">
+        <hostname>node1.princeton.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">999000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s3">
+      <name>PSG</name>
+      <node id="n6">
+        <hostname>node1.psg.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n25">
+        <hostname>node2.psg.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s4">
+      <name>NLR Chicago</name>
+      <node id="n2">
+        <hostname>node1.chic.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n3">
+        <hostname>node2.chic.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s5">
+      <name>NLR Houston</name>
+      <node id="n4">
+        <hostname>node1.hous.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n5">
+        <hostname>node2.hous.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s6">
+      <name>NLR Atlanta</name>
+      <node id="n8">
+        <hostname>node1.atla.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n9">
+        <hostname>node2.atla.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s7">
+      <name>NLR Seattle</name>
+      <node id="n10">
+        <hostname>node1.seat.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n11">
+        <hostname>node2.seat.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s8">
+      <name>NLR Los Angeles</name>
+      <node id="n12">
+        <hostname>node1.losa.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n13">
+        <hostname>node2.losa.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s9">
+      <name>NLR New York</name>
+      <node id="n14">
+        <hostname>node1.newy.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n15">
+        <hostname>node2.newy.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s10">
+      <name>NLR Wash DC</name>
+      <node id="n16">
+        <hostname>node1.wash.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n17">
+        <hostname>node2.wash.nlr.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s11">
+      <name>I2 Chicago</name>
+      <node id="n18">
+        <hostname>node1.chic.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">963000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n19">
+        <hostname>node2.chic.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s12">
+      <name>I2 New York</name>
+      <node id="n20">
+        <hostname>node1.newy.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">988000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n21">
+        <hostname>node2.newy.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s13">
+      <name>I2 Wash DC</name>
+      <node id="n22">
+        <hostname>node1.wash.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">964000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n23">
+        <hostname>node2.wash.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s14">
+      <name>Georgia Tech</name>
+      <node id="n45">
+        <hostname>node1.gatech.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">999000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s15">
+      <name>I2 Atlanta</name>
+      <node id="n26">
+        <hostname>node1.atla.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">964000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n27">
+        <hostname>node2.atla.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s16">
+      <name>CESNET</name>
+      <node id="n42">
+        <hostname>node1.cesnet.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n43">
+        <hostname>node2.cesnet.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+      </node>
+    </site>
+    <site id="s17">
+      <name>I2 Kansas City</name>
+      <node id="n28">
+        <hostname>node1.kans.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">961000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n29">
+        <hostname>node2.kans.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s19">
+      <name>I2 Houston</name>
+      <node id="n30">
+        <hostname>node1.hous.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">964000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n31">
+        <hostname>node2.hous.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s20">
+      <name>I2 Los Angeles</name>
+      <node id="n36">
+        <hostname>node1.losa.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">964000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n37">
+        <hostname>node2.losa.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s21">
+      <name>I2 Seattle</name>
+      <node id="n34">
+        <hostname>node1.seat.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">975000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n35">
+        <hostname>node2.seat.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s22">
+      <name>I2 Salt Lake</name>
+      <node id="n32">
+        <hostname>node1.salt.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">962000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+      <node id="n33">
+        <hostname>node2.salt.internet2.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+        <bw_limit units="kbps">1000000</bw_limit>
+      </node>
+    </site>
+    <site id="s24">
+      <name>UMKC</name>
+      <node id="n48">
+        <hostname>node1.umkc.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">999000</bw_unallocated>
+      </node>
+      <node id="n50">
+        <hostname>node2.umkc.vini-veritas.net</hostname>
+        <bw_unallocated units="kbps">1000000</bw_unallocated>
+      </node>
+    </site>
+    <link endpoints="s2 s12">
+      <description>Princeton -- I2 New York</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s4 s5">
+      <description>NLR Chicago -- NLR Houston</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s4 s6">
+      <description>NLR Chicago -- NLR Atlanta</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s4 s7">
+      <description>NLR Chicago -- NLR Seattle</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s4 s9">
+      <description>NLR Chicago -- NLR New York</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s4 s10">
+      <description>NLR Chicago -- NLR Wash DC</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s5 s6">
+      <description>NLR Houston -- NLR Atlanta</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s5 s8">
+      <description>NLR Houston -- NLR Los Angeles</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s6 s10">
+      <description>NLR Atlanta -- NLR Wash DC</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s6 s14">
+      <description>NLR Atlanta -- Georgia Tech</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s7 s8">
+      <description>NLR Seattle -- NLR Los Angeles</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s9 s10">
+      <description>NLR New York -- NLR Wash DC</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s11 s13">
+      <description>I2 Chicago -- I2 Wash DC</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s11 s15">
+      <description>I2 Chicago -- I2 Atlanta</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s11 s16">
+      <description>I2 Chicago -- CESNET</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s11 s17">
+      <description>I2 Chicago -- I2 Kansas City</description>
+      <bw_unallocated units="kbps">987000</bw_unallocated>
+    </link>
+    <link endpoints="s12 s13">
+      <description>I2 New York -- I2 Wash DC</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s13 s15">
+      <description>I2 Wash DC -- I2 Atlanta</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s14 s15">
+      <description>Georgia Tech -- I2 Atlanta</description>
+      <bw_unallocated units="kbps">1000000</bw_unallocated>
+    </link>
+    <link endpoints="s15 s19">
+      <description>I2 Atlanta -- I2 Houston</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s17 s19">
+      <description>I2 Kansas City -- I2 Houston</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s17 s22">
+      <description>I2 Kansas City -- I2 Salt Lake</description>
+      <bw_unallocated units="kbps">987000</bw_unallocated>
+    </link>
+    <link endpoints="s17 s24">
+      <description>I2 Kansas City -- UMKC</description>
+      <bw_unallocated units="kbps">999000</bw_unallocated>
+    </link>
+    <link endpoints="s19 s20">
+      <description>I2 Houston -- I2 Los Angeles</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s20 s21">
+      <description>I2 Los Angeles -- I2 Seattle</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s20 s22">
+      <description>I2 Los Angeles -- I2 Salt Lake</description>
+      <bw_unallocated units="kbps">988000</bw_unallocated>
+    </link>
+    <link endpoints="s21 s22">
+      <description>I2 Seattle -- I2 Salt Lake</description>
+      <bw_unallocated units="kbps">987000</bw_unallocated>
+    </link>
+  </network>
+</RSpec>
diff --git a/sfa/managers/vini/vini_network.py b/sfa/managers/vini/vini_network.py
new file mode 100644 (file)
index 0000000..26d8a33
--- /dev/null
@@ -0,0 +1,469 @@
+from __future__ import with_statement
+from sfa.util.faults import *
+from xmlbuilder import XMLBuilder
+from lxml import etree
+import sys
+from sfa.plc.network import *
+from sfa.managers.vini.topology import PhysicalLinks
+
+# Taken from bwlimit.py
+#
+# See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
+# warned that older versions of tc interpret "kbps", "mbps", "mbit",
+# and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
+# "kibit" and that if an older version is installed, all rates will
+# be off by a small fraction.
+suffixes = {
+    "":         1,
+    "bit":     1,
+    "kibit":   1024,
+    "kbit":    1000,
+    "mibit":   1024*1024,
+    "mbit":    1000000,
+    "gibit":   1024*1024*1024,
+    "gbit":    1000000000,
+    "tibit":   1024*1024*1024*1024,
+    "tbit":    1000000000000,
+    "bps":     8,
+    "kibps":   8*1024,
+    "kbps":    8000,
+    "mibps":   8*1024*1024,
+    "mbps":    8000000,
+    "gibps":   8*1024*1024*1024,
+    "gbps":    8000000000,
+    "tibps":   8*1024*1024*1024*1024,
+    "tbps":    8000000000000
+}
+
+
+def get_tc_rate(s):
+    """
+    Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
+    """
+
+    if type(s) == int:
+        return s
+    m = re.match(r"([0-9.]+)(\D*)", s)
+    if m is None:
+        return -1
+    suffix = m.group(2).lower()
+    if suffixes.has_key(suffix):
+        return int(float(m.group(1)) * suffixes[suffix])
+    else:
+        return -1
+
+def format_tc_rate(rate):
+    """
+    Formats a bits/second rate into a tc rate string
+    """
+
+    if rate >= 1000000000 and (rate % 1000000000) == 0:
+        return "%.0fgbit" % (rate / 1000000000.)
+    elif rate >= 1000000 and (rate % 1000000) == 0:
+        return "%.0fmbit" % (rate / 1000000.)
+    elif rate >= 1000:
+        return "%.0fkbit" % (rate / 1000.)
+    else:
+        return "%.0fbit" % rate
+
+
+class ViniNode(Node):
+    def __init__(self, network, node, bps = 1000 * 1000000):
+        Node.__init__(self, network, node)
+        self.bps = bps
+        self.links = set()
+        self.name = self.hostname.replace('.vini-veritas.net', '')
+
+    def get_link_id(self, remote):
+        if self.id < remote.id:
+            link = (self.id<<7) + remote.id
+        else:
+            link = (remote.id<<7) + self.id
+        return link
+        
+    def get_iface_id(self, remote):
+        if self.id < remote.id:
+            iface = 1
+        else:
+            iface = 2
+        return iface
+    
+    def get_virt_ip(self, remote):
+        link = self.get_link_id(remote)
+        iface = self.get_iface_id(remote)
+        first = link >> 6
+        second = ((link & 0x3f)<<2) + iface
+        return "192.168.%d.%d" % (first, second)
+
+    def get_virt_net(self, remote):
+        link = self.get_link_id(remote)
+        first = link >> 6
+        second = (link & 0x3f)<<2
+        return "192.168.%d.%d/30" % (first, second)
+        
+    def get_topo_rspec(self, link):
+        if link.end1 == self:
+            remote = link.end2
+        elif link.end2 == self:
+            remote = link.end1
+        else:
+            raise Error("Link does not connect to Node")
+            
+        my_ip = self.get_virt_ip(remote)
+        remote_ip = remote.get_virt_ip(self)
+        net = self.get_virt_net(remote)
+        bw = format_tc_rate(link.bps)
+        ipaddr = remote.get_primary_iface().ipv4
+        return (remote.id, ipaddr, bw, my_ip, remote_ip, net)
+        
+    def add_link(self, link):
+        self.links.add(link)
+        
+    # Assumes there is at most one Link between two sites
+    def get_sitelink(self, node):
+        site1 = self.network.sites[self.site_id]
+        site2 = self.network.sites[node.site_id]
+        sl = site1.links.intersection(site2.links)
+        if len(sl):
+            return sl.pop()
+        return None
+
+    def toxml(self, xml):
+        slice = self.network.slice
+        if self.whitelist and not self.sliver:
+            if not slice or slice.id not in self.whitelist:
+                return
+
+        with xml.node(id = self.idtag):
+            with xml.hostname:
+                xml << self.hostname
+            with xml.bw_unallocated(units="kbps"):
+                xml << str(int(self.bps/1000))
+            self.get_primary_iface().toxml(xml)
+            if self.sliver:
+                self.sliver.toxml(xml)
+
+
+class ViniSite(Site):
+    def __init__(self, network, site):
+        Site.__init__(self, network, site)
+        self.links = set()
+
+    def add_link(self, link):
+        self.links.add(link)
+
+class ViniSlice(Slice):
+    def assign_egre_key(self):
+        tag = self.get_tag('egre_key')
+        if not tag:
+            try:
+                key = self.network.free_egre_key()
+            except:
+                raise InvalidRSpec("ran out of EGRE keys!")
+            tag = self.update_tag('egre_key', key, None, 10)
+        return
+            
+    def turn_on_netns(self):
+        tag = self.get_tag('netns')
+        if (not tag) or (tag.value != '1'):
+            tag = self.update_tag('netns', '1', None, 10)
+        return
+   
+    def turn_off_netns(self):
+        tag = self.get_tag('netns')
+        if tag and (tag.value != '0'):
+            tag.delete()
+        return
+    
+    def add_cap_net_admin(self):
+        tag = self.get_tag('capabilities')
+        if tag:
+            caps = tag.value.split(',')
+            for cap in caps:
+                if cap == "CAP_NET_ADMIN":
+                    newcaps = tag.value
+                    break
+            else:
+                newcaps = "CAP_NET_ADMIN," + tag.value
+            self.update_tag('capabilities', newcaps, None, 10)
+        else:
+            tag = self.add_tag('capabilities', 'CAP_NET_ADMIN', None, 10)
+        return
+    
+    def remove_cap_net_admin(self):
+        tag = self.get_tag('capabilities')
+        if tag:
+            caps = tag.value.split(',')
+            newcaps = []
+            for cap in caps:
+                if cap != "CAP_NET_ADMIN":
+                    newcaps.append(cap)
+            if newcaps:
+                value = ','.join(newcaps)
+                self.update_tag('capabilities', value, None, 10)
+            else:
+                tag.delete()
+        return
+
+class Link:
+    def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
+        self.end1 = end1
+        self.end2 = end2
+        self.bps = bps
+        self.parent = parent
+        self.children = []
+
+        end1.add_link(self)
+        end2.add_link(self)
+        
+        if self.parent:
+            self.parent.children.append(self)
+            
+    def toxml(self, xml):
+        end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
+
+        if self.parent:
+            with  xml.vlink(endpoints=end_ids):
+                with xml.description:
+                    xml << "%s -- %s" % (self.end1.name, self.end2.name)
+                with xml.kbps:
+                    xml << str(int(self.bps/1000))
+        else:
+            with xml.link(endpoints=end_ids):
+                with xml.description:
+                    xml << "%s -- %s" % (self.end1.name, self.end2.name)
+                with xml.bw_unallocated(units="kbps"):
+                    xml << str(int(self.bps/1000))
+                for child in self.children:
+                    child.toxml(xml)
+        
+
+
+class ViniNetwork(Network):
+    def __init__(self, api, type = "SFA"):
+        Network.__init__(self, api, type)
+        self.sitelinks = []
+        self.nodelinks = []
+    
+        for (s1, s2) in PhysicalLinks:
+            self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
+        
+        for t in self.tags:
+            tag = self.tags[t]
+            if tag.tagname == 'topo_rspec':
+                node1 = self.nodes[tag.node_id]
+                l = eval(tag.value)
+                for (id, realip, bw, lvip, rvip, vnet) in l:
+                    allocbps = get_tc_rate(bw)
+                    node1.bps -= allocbps
+                    try:
+                        node2 = self.nodes[id]
+                        if node1.id < node2.id:
+                            sl = node1.get_sitelink(node2)
+                            sl.bps -= allocbps
+                    except:
+                        pass
+
+    def lookupSiteLink(self, node1, node2):
+        site1 = self.sites[node1.site_id]
+        site2 = self.sites[node2.site_id]
+        for link in self.sitelinks:
+            if site1 == link.end1 and site2 == link.end2:
+                return link
+            if site2 == link.end1 and site1 == link.end2:
+                return link
+        return None
+    
+
+    """
+    Check the requested topology against the available topology and capacity
+    """
+    def verifyTopology(self):
+        for link in self.nodelinks:
+            if link.bps <= 0:
+                raise InvalidRSpec("must request positive bandwidth")
+                
+            n1 = link.end1
+            n2 = link.end2
+            sitelink = self.lookupSiteLink(n1, n2)
+            if not sitelink:
+                raise InvalidRSpec("nodes %s and %s are not adjacent" % 
+                                   (n1.idtag, n2.idtag))
+            if sitelink.bps < link.bps:
+                raise InvalidRSpec("not enough capacity between %s and %s" % 
+                                   (n1.idtag, n2.idtag))
+                
+    def __add_vlink(self, vlink, parent = None):
+        n1 = n2 = None
+        endpoints = vlink.get("endpoints")
+        if endpoints:
+            (end1, end2) = endpoints.split()
+            n1 = self.lookupNode(end1)
+            n2 = self.lookupNode(end2)
+        elif parent:
+            """ Try to infer the endpoints for the virtual link """
+            site_endpoints = parent.get("endpoints")
+            (n1, n2) = self.__infer_endpoints(site_endpoints)
+        else:
+            raise InvalidRSpec("no endpoints given")
+
+        #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
+        bps = int(vlink.findtext("kbps")) * 1000
+        sitelink = self.lookupSiteLink(n1, n2)
+        if not sitelink:
+            raise InvalidRSpec("nodes %s and %s are not adjacent" % 
+                                  (n1.idtag, n2.idtag))
+        self.nodelinks.append(Link(n1, n2, bps, sitelink))
+        return
+
+    """ 
+    Infer the endpoints of the virtual link.  If the slice exists on 
+    only a single node at each end of the physical link, we'll assume that
+    the user wants the virtual link to terminate at these nodes.
+    """
+    def __infer_endpoints(self, endpoints):
+        n = []
+        ends = endpoints.split()
+        for end in ends:
+            found = 0
+            site = self.lookupSite(end)
+            for id in site.node_ids:
+                if id in self.nodedict:
+                    n.append(self.nodedict[id])
+                    found += 1
+            if found != 1:
+                raise InvalidRSpec("could not infer endpoint for site %s" % 
+                                   site.idtag)
+        #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
+        return n
+        
+    def addRSpec(self, xml, schema = None):
+        Network.addRSpec(self, xml, schema)
+        self.nodedict = {}
+        for node in self.nodesWithSlivers():
+            self.nodedict[node.id] = node
+        
+        # Find vlinks under link elements
+        for vlink in self.rspec.iterfind("./network/link/vlink"):
+            link = vlink.getparent()
+            self.__add_vlink(vlink, link)
+
+        # Find vlinks that specify endpoints
+        for vlink in self.rspec.iterfind("./request/vlink[@endpoints]"):
+            self.__add_vlink(vlink)
+
+
+    def addSlice(self):
+        Network.addSlice(self)
+
+        for node in self.slice.get_nodes():
+            linktag = self.slice.get_tag('topo_rspec', node)
+            if linktag:
+                l = eval(linktag.value)
+                for (id, realip, bw, lvip, rvip, vnet) in l:
+                    if node.id < id:
+                        bps = get_tc_rate(bw)
+                        remote = self.lookupNode(id)
+                        sitelink = self.lookupSiteLink(node, remote)
+                        self.nodelinks.append(Link(node,remote,bps,sitelink))
+
+
+    def updateSliceTags(self):
+        slice = self.slice
+
+        tag = slice.update_tag('vini_topo', 'manual', None, 10)
+        slice.assign_egre_key()
+        slice.turn_on_netns()
+        slice.add_cap_net_admin()
+
+        for node in self.nodesWithSlivers():
+            linkdesc = []
+            for link in node.links:
+                linkdesc.append(node.get_topo_rspec(link))
+            if linkdesc:
+                topo_str = "%s" % linkdesc
+                tag = slice.update_tag('topo_rspec', topo_str, node, 10)
+
+        # Expire the un-updated topo_rspec tags
+        for tag in self.getSliceTags():
+            if tag.tagname in ['topo_rspec']:
+                if not tag.was_updated():
+                    tag.delete()
+
+        Network.updateSliceTags(self)
+
+    """
+    Find a free EGRE key
+    """
+    def free_egre_key(self):
+        used = set()
+        for tag in self.getSliceTags():
+            if tag.tagname == 'egre_key':
+                used.add(int(tag.value))
+                
+        for i in range(1, 256):
+            if i not in used:
+                key = i
+                break
+        else:
+            raise KeyError("No more EGRE keys available")
+        
+        return str(key)
+
+    """
+    Produce XML directly from the topology specification.
+    """
+    def toxml(self):
+        xml = XMLBuilder(format = True, tab_step = "  ")
+        with xml.RSpec(type=self.type):
+            if self.slice:
+                element = xml.network(name=self.api.hrn, slice=self.slice.hrn)
+            else:
+                element = xml.network(name=self.api.hrn)
+
+            with element:
+                if self.slice:
+                    self.slice.toxml(xml)
+                for site in self.getSites():
+                    site.toxml(xml)
+                for link in self.sitelinks:
+                    link.toxml(xml)
+
+        header = '<?xml version="1.0"?>\n'
+        return header + str(xml)
+
+    """
+    Create a dictionary of ViniSite objects keyed by site ID
+    """
+    def get_sites(self, api):
+        tmp = []
+        for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
+            t = site['site_id'], ViniSite(self, site)
+            tmp.append(t)
+        return dict(tmp)
+
+
+    """
+    Create a dictionary of ViniNode objects keyed by node ID
+    """
+    def get_nodes(self, api):
+        tmp = []
+        for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
+            t = node['node_id'], ViniNode(self, node)
+            tmp.append(t)
+        return dict(tmp)
+
+    """
+    Return a ViniSlice object for a single slice
+    """
+    def get_slice(self, api, hrn):
+        slicename = hrn_to_pl_slicename(hrn)
+        slice = api.plshell.GetSlices(api.plauth, [slicename])
+        if slice:
+            self.slice = ViniSlice(self, slicename, slice[0])
+            return self.slice
+        else:
+            return None
+
+
+    
diff --git a/sfa/methods/CreateSliver.py b/sfa/methods/CreateSliver.py
new file mode 100644 (file)
index 0000000..c21ceb4
--- /dev/null
@@ -0,0 +1,52 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.sfatablesRuntime import run_sfatables
+import sys
+from sfa.trust.credential import Credential
+from sfa.util.sfalogging import logger
+
+class CreateSliver(Method):
+    """
+    Allocate resources to a slice.  This operation is expected to
+    start the allocated resources asynchornously after the operation
+    has successfully completed.  Callers can check on the status of
+    the resources using SliverStatus.
+
+    @param slice_urn (string) URN of slice to allocate to
+    @param credentials ([string]) of credentials
+    @param rspec (string) rspec to allocate
+    
+    """
+    interfaces = ['aggregate', 'slicemgr']
+    accepts = [
+        Parameter(str, "Slice URN"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        Parameter(str, "RSpec"),
+        Parameter(type([]), "List of user information")
+        ]
+    returns = Parameter(str, "Allocated RSpec")
+
+    def call(self, slice_xrn, creds, rspec, users):
+        hrn, type = urn_to_hrn(slice_xrn)
+
+        self.api.logger.info("interface: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, hrn, self.name))
+
+        # Find the valid credentials
+        valid_creds = self.api.auth.checkCredentials(creds, 'createsliver', hrn)
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+
+        manager = self.api.get_interface_manager()
+        
+        # flter rspec through sfatables
+        if self.api.interface in ['aggregate']:
+            chain_name = 'OUTGOING'
+        elif self.api.interface in ['slicemgr']:
+            chain_name = 'FORWARD-OUTGOING'
+        rspec = run_sfatables(chain_name, hrn, origin_hrn, rspec)
+        allocated = manager.create_slice(self.api, slice_xrn, creds, rspec, users)
+
+        return rspec 
+    
diff --git a/sfa/methods/DeleteSliver.py b/sfa/methods/DeleteSliver.py
new file mode 100644 (file)
index 0000000..3ed3b47
--- /dev/null
@@ -0,0 +1,41 @@
+### $Id: stop_slice.py 17732 2010-04-19 21:10:45Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/stop_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.credential import Credential
+
+class DeleteSliver(Method):
+    """
+    Remove the slice from all nodes and free the allocated resources        
+
+    @param xrn human readable name of slice to instantiate (hrn or urn)
+    @param cred credential string specifying the rights of the caller
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Human readable name of slice to delete (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, xrn, creds):
+        hrn, type = urn_to_hrn(xrn)
+        valid_creds = self.api.auth.checkCredentials(creds, 'deletesliver', hrn)
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+
+        manager = self.api.get_interface_manager() 
+        manager.delete_slice(self.api, xrn, creds)
+        return 1 
diff --git a/sfa/methods/GetCredential.py b/sfa/methods/GetCredential.py
new file mode 100644 (file)
index 0000000..df6e868
--- /dev/null
@@ -0,0 +1,55 @@
+### $Id: get_credential.py 17576 2010-04-05 20:56:15Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_credential.py $
+
+from sfa.trust.credential import *
+from sfa.trust.rights import *
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+
+class GetCredential(Method):
+    """
+    Retrive a credential for an object
+    If cred == Nonee then the behavior reverts to get_self_credential
+
+    @param hrn human readable name of object (hrn or urn)
+    @param cred credential object specifying rights of the caller
+    @param type type of object (user | slice | node | authority )
+
+    @return the string representation of a credential object  
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")), 
+        Parameter(str, "Human readable name (hrn or urn)"),
+        Mixed(Parameter(str, "Record type"),
+              Parameter(None, "Type not specified")),
+        ]
+
+    returns = Parameter(str, "String representation of a credential object")
+
+    def call(self, creds, xrn, type):
+    
+        if type:
+            hrn = urn_to_hrn(xrn)[0]
+        else:
+            hrn, type = urn_to_hrn(xrn)
+
+        # check creds
+        valid_creds = self.api.auth.checkCredentials(creds, 'getcredential')
+        self.api.auth.verify_object_belongs_to_me(hrn)
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))        
+
+        manager = self.api.get_interface_manager()
+        
+        return manager.get_credential(self.api, xrn, type)
+
diff --git a/sfa/methods/GetGids.py b/sfa/methods/GetGids.py
new file mode 100644 (file)
index 0000000..37ad796
--- /dev/null
@@ -0,0 +1,47 @@
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.gid import GID
+from sfa.trust.certificate import Certificate
+from sfa.trust.credential import Credential
+
+class GetGids(Method):
+    """
+    Get a list of record information (hrn, gid and type) for 
+    the specified hrns.
+
+    @param cred credential string 
+    @param cert certificate string 
+    @return    
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Mixed(Parameter(str, "Human readable name (hrn or xrn)"), 
+              Parameter(type([str]), "List of Human readable names (hrn or xrn)")),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")), 
+        ]
+
+    returns = [Parameter(dict, "Dictionary of gids keyed on hrn")]
+    
+    def call(self, xrns, creds):
+        # validate the credential
+        valid_creds = self.api.auth.checkCredentials(creds, 'getgids')
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        
+        # resolve the record
+        manager = self.api.get_interface_manager()
+        records = manager.resolve(self.api, xrns, full = False)
+        if not records:
+            raise RecordNotFound(hrns)
+
+        gids = []
+        allowed_fields =  ['hrn', 'type', 'gid']
+        for record in records:
+            for key in record.keys():
+                if key not in allowed_fields:
+                    del(record[key])
+        return records    
diff --git a/sfa/methods/GetSelfCredential.py b/sfa/methods/GetSelfCredential.py
new file mode 100644 (file)
index 0000000..a5c7d40
--- /dev/null
@@ -0,0 +1,75 @@
+### $Id: get_credential.py 15321 2009-10-15 05:01:21Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_credential.py $
+
+from sfa.trust.credential import *
+from sfa.trust.rights import *
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.util.debug import log
+from sfa.trust.certificate import Certificate
+
+class GetSelfCredential(Method):
+    """
+    Retrive a credential for an object
+    @param cert certificate string 
+    @param type type of object (user | slice | sa | ma | node)
+    @param hrn human readable name of object (hrn or urn)
+
+    @return the string representation of a credential object  
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "certificate"),
+        Parameter(str, "Human readable name (hrn or urn)"),
+        Mixed(Parameter(str, "Record type"),
+              Parameter(None, "Type not specified")),
+        ]
+
+    returns = Parameter(str, "String representation of a credential object")
+
+    def call(self, cert, xrn, type):
+        """
+        get_self_credential a degenerate version of get_credential used by a client
+        to get his initial credential when de doesnt have one. This is the same as
+        get_credetial(..., cred = None, ...)
+
+        The registry ensures that the client is the principal that is named by
+        (type, name) by comparing the public key in the record's  GID to the
+        private key used to encrypt the client side of the HTTPS connection. Thus
+        it is impossible for one principal to retrive another principal's
+        credential without having the appropriate private key.
+
+        @param type type of object (user | slice | sa | ma | node)
+        @param hrn human readable name of authority to list
+        @return string representation of a credential object
+        """
+        if type:
+            hrn = urn_to_hrn(xrn)[0]
+        else:
+            hrn, type = urn_to_hrn(xrn) 
+        self.api.auth.verify_object_belongs_to_me(hrn)
+
+        origin_hrn = Certificate(string=cert).get_subject()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+        
+        manager = self.api.get_interface_manager()
+        # authenticate the gid
+        records = manager.resolve(self.api, xrn, type)
+        if not records:
+            raise RecordNotFound(hrn)
+        record = SfaRecord(dict=records[0])
+        gid = record.get_gid_object()
+        gid_str = gid.save_to_string(save_parents=True)
+        self.api.auth.authenticateGid(gid_str, [cert, type, hrn])
+        # authenticate the certificate against the gid in the db
+        certificate = Certificate(string=cert)
+        if not certificate.is_pubkey(gid.get_pubkey()):
+            raise ConnectionKeyGIDMismatch(gid.get_subject())
+        
+        return manager.get_credential(self.api, xrn, type, is_self=True)
diff --git a/sfa/methods/GetTicket.py b/sfa/methods/GetTicket.py
new file mode 100644 (file)
index 0000000..119ae48
--- /dev/null
@@ -0,0 +1,64 @@
+### $Id: get_ticket.py 17732 2010-04-19 21:10:45Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_ticket.py $
+import time
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.util.config import Config
+from sfa.trust.credential import Credential
+from sfa.util.sfatablesRuntime import run_sfatables
+
+class GetTicket(Method):
+    """
+    Retrieve a ticket. This operation is currently implemented on PLC
+    only (see SFA, engineering decisions); it is not implemented on
+    components.
+    
+    The ticket is filled in with information from the PLC database. This
+    information includes resources, and attributes such as user keys and
+    initscripts.
+    
+    @param cred credential string
+    @param name name of the slice to retrieve a ticket for (hrn or urn)
+    @param rspec resource specification dictionary
+    
+    @return the string representation of a ticket object
+    """
+
+    interfaces = ['aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Human readable name of slice to retrive a ticket for (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        Parameter(str, "Resource specification (rspec)"),
+        Parameter(type([]), "List of user information")  
+        ]
+
+    returns = Parameter(str, "String represeneation of a ticket object")
+    
+    def call(self, xrn, creds, rspec, users):
+        hrn, type = urn_to_hrn(xrn)
+        # Find the valid credentials
+        valid_creds = self.api.auth.checkCredentials(creds, 'getticket', hrn)
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn() 
+
+        #log the call
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+
+        manager = self.api.get_interface_manager()
+
+        # flter rspec through sfatables
+        if self.api.interface in ['aggregate']:
+            chain_name = 'OUTGOING'
+        elif self.api.interface in ['slicemgr']:
+            chain_name = 'FORWARD-OUTGOING'
+        rspec = run_sfatables(chain_name, hrn, origin_hrn, rspec)
+        
+        # remove nodes that are not available at this interface from the rspec
+        ticket = manager.get_ticket(self.api, xrn, creds, rspec, users)
+        
+        return ticket
+        
diff --git a/sfa/methods/GetVersion.py b/sfa/methods/GetVersion.py
new file mode 100644 (file)
index 0000000..8552daf
--- /dev/null
@@ -0,0 +1,21 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter
+
+
+class GetVersion(Method):
+    """
+    Returns this GENI Aggregate Manager's Version Information
+    @return version
+    """
+    interfaces = ['registry','aggregate', 'slicemgr', 'component']
+    accepts = []
+    returns = Parameter(dict, "Version information")
+
+    def call(self):
+        self.api.logger.info("interface: %s\tmethod-name: %s" % (self.api.interface, self.name))
+        manager = self.api.get_interface_manager()
+    
+        return manager.get_version()
+    
diff --git a/sfa/methods/List.py b/sfa/methods/List.py
new file mode 100644 (file)
index 0000000..0730f2b
--- /dev/null
@@ -0,0 +1,38 @@
+### $Id: list.py 16588 2010-01-13 17:53:44Z anil $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/list.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.trust.credential import Credential
+
+class List(Method):
+    """
+    List the records in an authority. 
+
+    @param cred credential string specifying the rights of the caller
+    @param hrn human readable name of authority to list (hrn or urn)
+    @return list of record dictionaries         
+    """
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Human readable name (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = [SfaRecord]
+    
+    def call(self, xrn, creds):
+        hrn, type = urn_to_hrn(xrn)
+        valid_creds = self.api.auth.checkCredentials(creds, 'list')
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+       
+        manager = self.api.get_interface_manager()
+        return manager.list(self.api, xrn) 
diff --git a/sfa/methods/ListResources.py b/sfa/methods/ListResources.py
new file mode 100644 (file)
index 0000000..80b483e
--- /dev/null
@@ -0,0 +1,55 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.credential import Credential
+from sfa.util.sfatablesRuntime import run_sfatables
+import sys
+import zlib
+
+class ListResources(Method):
+    """
+    Returns information about available resources or resources allocated to this slice
+    @param credential list
+    @param options dictionary
+    @return string
+    """
+    interfaces = ['aggregate', 'slicemgr']
+    accepts = [
+        Mixed(Parameter(str, "Credential string"), 
+              Parameter(type([str]), "List of credentials")),
+        Parameter(dict, "Options")
+        ]
+    returns = Parameter(str, "List of resources")
+
+    def call(self, creds, options):
+        self.api.logger.info("interface: %s\tmethod-name: %s" % (self.api.interface, self.name))
+        
+        # get slice's hrn from options    
+        xrn = options.get('geni_slice_urn', None)
+        hrn, _ = urn_to_hrn(xrn)
+
+        # Find the valid credentials
+        valid_creds = self.api.auth.checkCredentials(creds, 'listnodes', hrn)
+
+        # get hrn of the original caller 
+        origin_hrn = options.get('origin_hrn', None)
+        if not origin_hrn:
+            origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        # get manager for this interface    
+        manager = self.api.get_interface_manager()
+        rspec = manager.get_rspec(self.api, creds, options)
+
+        # filter rspec through sfatables 
+        if self.api.interface in ['aggregate']:
+            chain_name = 'OUTGOING'
+        elif self.api.interface in ['slicemgr']: 
+            chain_name = 'FORWARD-OUTGOING'
+        filtered_rspec = run_sfatables(chain_name, hrn, origin_hrn, rspec) 
+        if options.has_key('geni_compressed') and options['geni_compressed'] == True:
+            filtered_rspec = zlib.compress(filtered_rspec).encode('base64')
+
+        return filtered_rspec  
+    
+    
diff --git a/sfa/methods/ListSlices.py b/sfa/methods/ListSlices.py
new file mode 100644 (file)
index 0000000..236d34f
--- /dev/null
@@ -0,0 +1,37 @@
+### $Id: stop_slice.py 17732 2010-04-19 21:10:45Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/stop_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.credential import Credential
+class ListSlices(Method):
+    """
+    List the slices instantiated at this interface       
+
+    @param cred credential string specifying the rights of the caller
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = Parameter(list, "List of slice names")
+    
+    def call(self, creds):
+        valid_creds = self.api.auth.checkCredentials(creds, 'listslices')
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, self.name))
+
+        manager = self.api.get_interface_manager() 
+        return manager.get_slices(self.api, creds)
diff --git a/sfa/methods/RedeemTicket.py b/sfa/methods/RedeemTicket.py
new file mode 100644 (file)
index 0000000..c471d5a
--- /dev/null
@@ -0,0 +1,35 @@
+### $Id: reset_slice.py 15428 2009-10-23 15:28:03Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfacomponent/methods/reset_slice.py $
+import xmlrpclib
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+
+class RedeemTicket(Method):
+    """
+    Deprecated. Use RedeemTicket instead.
+
+    @param cred credential string specifying the rights of the caller
+    @param ticket 
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['component']
+    
+    accepts = [
+        Parameter(str, "Ticket  string representation of SFA ticket"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = [Parameter(int, "1 if successful")]
+    
+    def call(self, ticket, creds):
+        valid_creds = self.api.auth.checkCredentials(cred, 'redeemticket')
+        self.api.auth.check_ticket(ticket)
+
+        
+        # send the call to the right manager
+        manager = self.api.get_interface_manager()
+        manager.redeem_ticket(self.api, ticket) 
+        return 1 
diff --git a/sfa/methods/Register.py b/sfa/methods/Register.py
new file mode 100644 (file)
index 0000000..4f5a452
--- /dev/null
@@ -0,0 +1,51 @@
+### $Id: register.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/register.py $
+
+from sfa.trust.certificate import Keypair, convert_public_key
+from sfa.trust.gid import *
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.util.debug import log
+from sfa.trust.auth import Auth
+from sfa.trust.gid import create_uuid
+from sfa.trust.credential import Credential
+
+class Register(Method):
+    """
+    Register an object with the registry. In addition to being stored in the
+    SFA database, the appropriate records will also be created in the
+    PLC databases
+    
+    @param cred credential string
+    @param record_dict dictionary containing record fields
+    
+    @return gid string representation
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(dict, "Record dictionary containing record fields"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = Parameter(int, "String representation of gid object")
+    
+    def call(self, record, creds):
+        
+        valid_creds = self.api.auth.checkCredentials(creds, 'register')
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+
+        hrn = None
+        if 'hrn' in record:
+            hrn = record['hrn']
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+        
+        manager = self.api.get_interface_manager()
+
+        return manager.register(self.api, record)
diff --git a/sfa/methods/RegisterPeerObject.py b/sfa/methods/RegisterPeerObject.py
new file mode 100644 (file)
index 0000000..68b3105
--- /dev/null
@@ -0,0 +1,73 @@
+### $Id: register.py 15001 2009-09-11 20:18:54Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/register.py $
+
+from sfa.trust.certificate import Keypair, convert_public_key
+from sfa.trust.gid import *
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.util.table import SfaTable
+from sfa.util.debug import log
+from sfa.trust.auth import Auth
+from sfa.trust.gid import create_uuid
+from sfa.trust.credential import Credential
+
+class register_peer_object(Method):
+    """
+    Register a peer object with the registry. In addition to being stored in the
+    SFA database, the appropriate records will also be created in the
+    PLC databases
+    
+    @param cred credential string
+    @param record_dict dictionary containing record fields
+    @return gid string representation
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary containing record fields"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, record_dict, origin_hrn=None):
+        user_cred = Credential(string=cred)
+
+        #log the call
+        if not origin_hrn:
+            origin_hrn = user_cred.get_gid_caller().get_hrn()    
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, None, self.name))
+
+        # validate the cred
+        self.api.auth.check(cred, "register")
+
+        # make sure this is a peer record
+        if 'peer_authority' not in record_dict or \
+           not record_dict['peer_authority']: 
+            raise SfaInvalidArgument, "peer_authority must be specified" 
+
+        record = SfaRecord(dict = record_dict)
+        type, hrn, peer_authority = record['type'], record['hrn'], record['peer_authority']
+        record['authority'] = get_authority(record['hrn'])
+        # verify permissions
+        self.api.auth.verify_cred_is_me(cred)
+
+        # check if record already exists
+        table = SfaTable()
+        existing_records = table.find({'type': type, 'hrn': hrn, 'peer_authority': peer_authority})
+        if existing_records:
+            for existing_record in existing_records:
+                if existing_record['pointer'] != record['pointer']:
+                    record['record_id'] = existing_record['record_id']
+                    table.update(record)
+        else:
+            record_id = table.insert(record)
+        return 1
diff --git a/sfa/methods/Remove.py b/sfa/methods/Remove.py
new file mode 100644 (file)
index 0000000..82050b4
--- /dev/null
@@ -0,0 +1,52 @@
+### $Id: remove.py 16497 2010-01-07 03:33:24Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/remove.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+
+class Remove(Method):
+    """
+    Remove an object from the registry. If the object represents a PLC object,
+    then the PLC records will also be removed.
+    
+    @param cred credential string
+    @param type record type
+    @param xrn human readable name of record to remove (hrn or urn)
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Human readable name of slice to instantiate (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        Mixed(Parameter(str, "Record type"),
+              Parameter(None, "Type not specified")),
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, xrn, creds, type):
+        if not type:
+            hrn = urn_to_hrn(xrn)[0]
+        else: 
+            hrn, type = urn_to_hrn(xrn)
+        
+        # validate the cred
+        valid_creds = self.api.auth.checkCredentials(creds, "remove")
+        self.api.auth.verify_object_permission(hrn)
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+
+
+        manager = self.api.get_interface_manager()
+
+        return manager.remove(self.api, xrn, type) 
diff --git a/sfa/methods/RemovePeerObject.py b/sfa/methods/RemovePeerObject.py
new file mode 100644 (file)
index 0000000..ed46ea7
--- /dev/null
@@ -0,0 +1,103 @@
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.util.record import SfaRecord
+from sfa.util.table import SfaTable
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+from types import StringTypes
+
+class remove_peer_object(Method):
+    """
+    Remove an peer object from the PLC records of a local aggregate. 
+    This method will be called by registry.remove() while removing 
+    a record from the local aggreage's PLCDB and sfa table. This 
+    method need not be directly called by end-user.
+    
+    @param cred credential string
+    @param record record as stored in the local registry
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, record, origin_hrn=None):
+        user_cred = Credential(string=cred)
+
+        #log the call
+        if not origin_hrn:
+            origin_hrn = user_cred.get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, record['hrn'], self.name))
+
+        self.api.auth.check(cred, "remove")
+
+        # Only allow the local interface or record owner to delete peer_records 
+        try: self.api.auth.verify_object_permission(record['hrn'])
+        except: self.api.auth.verify_cred_is_me(cred)
+        
+        table = SfaTable()
+        hrn, type = record['hrn'], record['type']
+        records = table.find({'hrn': hrn, 'type': type })
+        for record in records:
+            if record['peer_authority']:
+                self.remove_plc_record(record)
+                table.remove(record)
+            
+        return 1
+
+    def remove_plc_record(self, record):
+        type = record['type']        
+        if type == "user":
+            persons = self.api.plshell.GetPersons(self.api.plauth, {'person_id' : record['pointer']})
+            if not persons:
+                return 1
+            person = persons[0]
+            if person['peer_id']:
+                peer = self.get_peer_name(person['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'person', person['person_id'], peer)
+            self.api.plshell.DeletePerson(self.api.plauth, person['person_id'])
+           
+        elif type == "slice":
+            slices=self.api.plshell.GetSlices(self.api.plauth, {'slice_id' : record['pointer']})
+            if not slices:
+                return 1
+            slice=slices[0]
+            if slice['peer_id']:
+                peer = self.get_peer_name(slice['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'slice', slice['slice_id'], peer)
+            self.api.plshell.DeleteSlice(self.api.plauth, slice['slice_id'])
+        elif type == "authority":
+            sites=self.api.plshell.GetSites(self.api.plauth, {'site_id' : record['pointer']})
+            if not sites:
+                return 1
+            site=sites[0]
+            if site['peer_id']:
+                peer = self.get_peer_name(site['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'site', site['site_id'], peer)
+            self.api.plshell.DeleteSite(self.api.plauth, site['site_id'])
+           
+        else:
+            raise UnknownSfaType(type)
+
+        return 1
+
+    def get_peer_name(self, peer_id):
+        peers = self.api.plshell.GetPeers(self.api.plauth, [peer_id], ['peername', 'shortname', 'hrn_root'])
+        if not peers:
+            raise SfaInvalidArgument, "No such peer"
+        peer = peers[0]
+        return peer['shortname'] 
+
+
+
diff --git a/sfa/methods/RenewSliver.py b/sfa/methods/RenewSliver.py
new file mode 100644 (file)
index 0000000..efce45d
--- /dev/null
@@ -0,0 +1,41 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter
+from sfa.trust.credential import Credential
+from dateutil.parser import parse
+
+class RenewSliver(Method):
+    """
+    Renews the resources in a sliver, extending the lifetime of the slice.    
+    @param slice_urn (string) URN of slice to renew
+    @param credentials ([string]) of credentials
+    @param expiration_time (string) requested time of expiration
+    
+    """
+    interfaces = ['aggregate', 'slicemgr']
+    accepts = [
+        Parameter(str, "Slice URN"),
+        Parameter(type([str]), "List of credentials"),
+        Parameter(str, "Expiration time in RFC 3339 format")
+        ]
+    returns = Parameter(bool, "Success or Failure")
+
+    def call(self, slice_xrn, creds, expiration_time):
+        hrn, type = urn_to_hrn(slice_xrn)
+
+        self.api.logger.info("interface: %s\ttarget-hrn: %s\tcaller-creds: %s\tmethod-name: %s"%(self.api.interface, hrn, creds, self.name))
+
+        # Find the valid credentials
+        valid_creds = self.api.auth.checkCredentials(creds, 'renewsliver', hrn)
+
+        # Validate that the time does not go beyond the credential's expiration time
+        requested_time = parse(expiration_time)
+        if requested_time > Credential(string=valid_creds[0]).get_lifetime():
+            raise InsufficientRights('SliverStatus: Credential expires before requested expiration time')
+       
+        manager = self.api.get_interface_manager()
+        manager.renew_slice(self.api, xrn, valid_creds, requested_time)    
+        return 1
+    
diff --git a/sfa/methods/Resolve.py b/sfa/methods/Resolve.py
new file mode 100644 (file)
index 0000000..ec47e41
--- /dev/null
@@ -0,0 +1,50 @@
+### $Id: resolve.py 17157 2010-02-21 04:19:34Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/resolve.py $
+import traceback
+import types
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+from sfa.util.record import SfaRecord
+
+class Resolve(Method):
+    """
+    Resolve a record.
+
+    @param cred credential string authorizing the caller
+    @param hrn human readable name to resolve (hrn or urn) 
+    @return a list of record dictionaries or empty list     
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Mixed(Parameter(str, "Human readable name (hrn or urn)"),
+              Parameter(list, "List of Human readable names ([hrn])")),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(list, "List of credentials)"))  
+        ]
+
+    returns = [SfaRecord]
+    
+    def call(self, xrns, creds):
+        if not isinstance(xrns, types.ListType):
+            xrns=[xrns]
+        hrns = [urn_to_hrn(xrn)[0] for xrn in xrns]
+        
+        #find valid credentials
+        valid_creds = self.api.auth.checkCredentials(creds, 'resolve')
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrns, self.name))
+        # send the call to the right manager
+        manager = self.api.get_interface_manager()
+        return manager.resolve(self.api, xrns)
+
+
+            
diff --git a/sfa/methods/ResolveGENI.py b/sfa/methods/ResolveGENI.py
new file mode 100644 (file)
index 0000000..c223bbe
--- /dev/null
@@ -0,0 +1,30 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter
+from sfa.trust.credential import Credential
+
+class Resolve(Method):
+    """
+    Lookup a URN and return information about the corresponding object.
+    @param urn
+    """
+
+    interfaces = ['registry']
+    accepts = [
+        Parameter(str, "URN"),
+        Parameter(type([str]), "List of credentials"),
+        ]
+    returns = Parameter(bool, "Success or Failure")
+
+    def call(self, xrn):
+
+        manager_base = 'sfa.managers'
+
+        if self.api.interface in ['registry']:
+            mgr_type = self.api.config.SFA_REGISTRY_TYPE
+            manager_module = manager_base + ".registry_manager_%s" % mgr_type
+            manager = __import__(manager_module, fromlist=[manager_base])
+            return manager.Resolve(self.api, xrn, '')
+               
+        return {}
diff --git a/sfa/methods/Shutdown.py b/sfa/methods/Shutdown.py
new file mode 100644 (file)
index 0000000..f8e3294
--- /dev/null
@@ -0,0 +1,25 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter
+from sfa.methods.Stop import Stop
+
+class Shutdown(Stop):
+    """
+    Perform an emergency shut down of a sliver. This operation is intended for administrative use. 
+    The sliver is shut down but remains available for further forensics.
+
+    @param slice_urn (string) URN of slice to renew
+    @param credentials ([string]) of credentials    
+    """
+    interfaces = ['aggregate', 'slicemgr']
+    accepts = [
+        Parameter(str, "Slice URN"),
+        Parameter(type([str]), "List of credentials"),
+        ]
+    returns = Parameter(bool, "Success or Failure")
+
+    def call(self, slice_xrn, creds):
+
+        return Stop.call(self, slice_xrn, creds)
+    
diff --git a/sfa/methods/SliverStatus.py b/sfa/methods/SliverStatus.py
new file mode 100644 (file)
index 0000000..2233b70
--- /dev/null
@@ -0,0 +1,31 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+
+class SliverStatus(Method):
+    """
+    Get the status of a sliver
+    
+    @param slice_urn (string) URN of slice to allocate to
+    
+    """
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    accepts = [
+        Parameter(str, "Slice URN"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+    returns = Parameter(dict, "Status details")
+
+    def call(self, slice_xrn, creds):
+        hrn, type = urn_to_hrn(slice_xrn)
+        valid_creds = self.api.auth.checkCredentials(creds, 'sliverstatus', hrn)
+
+        self.api.logger.info("interface: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, hrn, self.name))
+    
+        manager = self.api.get_interface_manager()
+        status = manager.slice_status(self.api, hrn, valid_creds)
+
+        return status
+    
diff --git a/sfa/methods/Start.py b/sfa/methods/Start.py
new file mode 100644 (file)
index 0000000..958e34f
--- /dev/null
@@ -0,0 +1,41 @@
+### $Id: stop_slice.py 17732 2010-04-19 21:10:45Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/stop_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.credential import Credential
+
+class Start(Method):
+    """
+    Start the specified slice      
+
+    @param xrn human readable name of slice to instantiate (hrn or urn)
+    @param cred credential string specifying the rights of the caller
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Human readable name of slice to start (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, xrn, creds):
+        hrn, type = urn_to_hrn(xrn)
+        valid_creds = self.api.auth.checkCredentials(creds, 'startslice', hrn)
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+
+        manager = self.api.get_interface_manager() 
+        manager.start_slice(self.api, xrn, creds)
+        return 1 
diff --git a/sfa/methods/Stop.py b/sfa/methods/Stop.py
new file mode 100644 (file)
index 0000000..c871e05
--- /dev/null
@@ -0,0 +1,41 @@
+### $Id: stop_slice.py 17732 2010-04-19 21:10:45Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/stop_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.credential import Credential
+class Stop(Method):
+    """
+    Stop the specified slice      
+
+    @param cred credential string specifying the rights of the caller
+    @param xrn human readable name of slice to instantiate (hrn or urn)
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Human readable name of slice to instantiate (hrn or urn)"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, xrn, creds):
+        hrn, type = urn_to_hrn(xrn)
+        valid_creds = self.api.auth.checkCredentials(creds, 'stopslice', hrn)
+
+        #log the call
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, hrn, self.name))
+
+        manager = self.api.get_interface_manager() 
+        manager.stop_slice(self.api, xrn, creds)
+        return 1 
diff --git a/sfa/methods/Update.py b/sfa/methods/Update.py
new file mode 100644 (file)
index 0000000..3b8b55a
--- /dev/null
@@ -0,0 +1,41 @@
+### $Id: update.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/update.py $
+
+import time
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+
+class Update(Method):
+    """
+    Update an object in the registry. Currently, this only updates the
+    PLC information associated with the record. The SFA fields (name, type,
+    GID) are fixed.
+    
+    @param cred credential string specifying rights of the caller
+    @param record a record dictionary to be updated
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(dict, "Record dictionary to be updated"),
+        Parameter(str, "Credential string"),
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, record_dict, creds):
+        # validate the cred
+        valid_creds = self.api.auth.checkCredentials(creds, "update")
+        origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, None, self.name))
+       
+        manager = self.api.get_interface_manager()
+        return manager.update(self.api, record_dict)
+
diff --git a/sfa/methods/UpdateSliver.py b/sfa/methods/UpdateSliver.py
new file mode 100644 (file)
index 0000000..37252f2
--- /dev/null
@@ -0,0 +1,35 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+import sys
+from sfa.methods.CreateSliver import CreateSliver
+
+class UpdateSliver(CreateSliver):
+    """
+    Allocate resources to a slice.  This operation is expected to
+    start the allocated resources asynchornously after the operation
+    has successfully completed.  Callers can check on the status of
+    the resources using SliverStatus.
+
+    @param slice_urn (string) URN of slice to allocate to
+    @param credentials ([string]) of credentials
+    @param rspec (string) rspec to allocate
+    
+    """
+    interfaces = ['aggregate', 'slicemgr']
+    accepts = [
+        Parameter(str, "Slice URN"),
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(type([str]), "List of credentials")),
+        Parameter(str, "RSpec"),
+        Parameter(type([]), "List of user information")
+        ]
+    returns = Parameter(str, "Allocated RSpec")
+
+
+
+    def call(self, slice_xrn, creds, rspec, users):
+
+        return CreateSliver.call(self, slice_xrn, creds, rspec, users)
+    
diff --git a/sfa/methods/__init__.py b/sfa/methods/__init__.py
new file mode 100644 (file)
index 0000000..bca484b
--- /dev/null
@@ -0,0 +1,47 @@
+## Please use make index to update this file
+all = """
+create_slice
+delete_slice
+get_aggregates
+get_credential
+get_gids
+get_key
+get_registries
+get_resources
+get_self_credential
+get_slices
+get_ticket
+get_trusted_certs
+list
+List
+redeem_ticket
+RedeemTicket
+register
+Register
+register_peer_object
+remove
+Remove
+reset_slice
+resolve
+Resolve
+start_slice
+stop_slice
+update
+Update
+remove_peer_object
+GetTicket
+GetVersion
+RedeemTicket
+ListResources
+ListSlices
+CreateSliver
+UpdateSliver
+get_geni_aggregates
+DeleteSliver
+SliverStatus
+RenewSliver
+Start
+Stop
+Shutdown
+ResolveGENI
+""".split()
diff --git a/sfa/methods/create_slice.py b/sfa/methods/create_slice.py
new file mode 100644 (file)
index 0000000..cee2c4d
--- /dev/null
@@ -0,0 +1,35 @@
+### $Id: create_slice.py 18560 2010-08-03 21:13:46Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/create_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.CreateSliver import CreateSliver
+
+class create_slice(CreateSliver):
+    """
+    Deprecated. Use CreateSliver instead.
+    Instantiate the specified slice according to whats defined in the specified rspec      
+
+    @param cred credential string specifying the rights of the caller
+    @param hrn human readable name of slice to instantiate (hrn or xrn)
+    @param rspec resource specification
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to instantiate (hrn or xrn)"),
+        Parameter(str, "Resource specification"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+        
+    def call(self, cred, xrn, requested_rspec, origin_hrn=None):
+
+        return CreateSliver.call(self, xrn, cred, requested_rspec, []) 
diff --git a/sfa/methods/delete_slice.py b/sfa/methods/delete_slice.py
new file mode 100644 (file)
index 0000000..06005ec
--- /dev/null
@@ -0,0 +1,34 @@
+### $Id: delete_slice.py 18565 2010-08-03 22:24:09Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/delete_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.DeleteSliver import DeleteSliver
+
+class delete_slice(DeleteSliver):
+    """
+    Deprecated. Use delete instead.
+
+    Remove the slice from all nodes.      
+
+    @param cred credential string specifying the rights of the caller
+    @param xrn human readable name specifying the slice to delete (hrn or urn)
+    @return 1 if successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to delete (hrn or urn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, xrn, origin_hrn=None):
+
+        return DeleteSliver.call(self, xrn, cred)
diff --git a/sfa/methods/get_aggregates.py b/sfa/methods/get_aggregates.py
new file mode 100644 (file)
index 0000000..c960974
--- /dev/null
@@ -0,0 +1,36 @@
+### $Id: get_slices.py 14387 2009-07-08 18:19:11Z faiyaza $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_aggregates.py $
+from types import StringTypes
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.server.aggregate import Aggregates
+
+class get_aggregates(Method):
+    """
+    Get a list of connection information for all known aggregates.      
+
+    @param cred credential string specifying the rights of the caller
+    @param a Human readable name (hrn or urn), or list of hrns or None
+    @return list of dictionaries with aggregate information.  
+    """
+
+    interfaces = ['registry', 'aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Mixed(Parameter(str, "Human readable name (hrn or urn)"),
+              Parameter(None, "hrn not specified"))
+        ]
+
+    returns = [Parameter(dict, "Aggregate interface information")]
+    
+    def call(self, cred, xrn = None):
+        hrn, type = urn_to_hrn(xrn)
+        self.api.auth.check(cred, 'list')
+        aggregates = Aggregates(self.api).interfaces.values()
+        if hrn:
+            aggregates = [agg for agg in aggregates if agg['hrn'] == hrn]
+        return aggregates
diff --git a/sfa/methods/get_credential.py b/sfa/methods/get_credential.py
new file mode 100644 (file)
index 0000000..aa5c4c6
--- /dev/null
@@ -0,0 +1,37 @@
+### $Id: get_credential.py 18596 2010-08-06 20:58:41Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_credential.py $
+
+from sfa.trust.credential import *
+from sfa.trust.rights import *
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.GetCredential import GetCredential
+
+class get_credential(GetCredential):
+    """
+    Deprecated. Use GetCredential instead.
+
+    Retrive a credential for an object
+    If cred == Nonee then the behavior reverts to get_self_credential
+
+    @param cred credential object specifying rights of the caller
+    @param type type of object (user | slice | sa | ma | node)
+    @param hrn human readable name of object (hrn or urn)
+
+    @return the string representation of a credential object  
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Mixed(Parameter(str, "credential"),
+              Parameter(None, "No credential")),  
+        Parameter(str, "Human readable name (hrn or urn)")
+        ]
+
+    returns = Parameter(str, "String representation of a credential object")
+
+    def call(self, cred, type, xrn, origin_hrn=None):
+        return GetCredential.call(self, cred, xrn, type)
diff --git a/sfa/methods/get_gids.py b/sfa/methods/get_gids.py
new file mode 100644 (file)
index 0000000..c54af0a
--- /dev/null
@@ -0,0 +1,30 @@
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.GetGids import GetGids
+
+class get_gids(GetGids):
+    """
+    Deprecated. Use GetGids instead.
+
+    Get a list of record information (hrn, gid and type) for 
+    the specified hrns.
+
+    @param cred credential string 
+    @param cert certificate string 
+    @return    
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Certificate string"),
+        Mixed(Parameter(str, "Human readable name (hrn or xrn)"), 
+              Parameter(type([str]), "List of Human readable names (hrn or xrn)")) 
+        ]
+
+    returns = [Parameter(dict, "Dictionary of gids keyed on hrn")]
+    
+    def call(self, cred, xrns):
+        
+        return GetGids.call(self, xrns, cred)     
diff --git a/sfa/methods/get_key.py b/sfa/methods/get_key.py
new file mode 100644 (file)
index 0000000..46af369
--- /dev/null
@@ -0,0 +1,88 @@
+### $Id:  $
+### $URL:  $
+import os
+import tempfile
+import commands
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.util.table import SfaTable
+from sfa.trust.certificate import Keypair
+from sfa.trust.gid import create_uuid
+
+class get_key(Method):
+    """
+    Generate a new keypair and gid for requesting caller (component).     
+    @return 1 If successful  
+    """
+
+    interfaces = ['registry']
+    
+    accepts = []
+
+    returns = Parameter(int, "1 if successful, faults otherwise")
+    
+    def call(self):
+        # verify that the callers's ip address exist in the db and is an inteface
+        # for a node in the db
+        (ip, port) = self.api.remote_addr
+        interfaces = self.api.plshell.GetInterfaces(self.api.plauth, {'ip': ip}, ['node_id'])
+        if not interfaces:
+            raise NonExistingRecord("no such ip %(ip)s" % locals())
+        nodes = self.api.plshell.GetNodes(self.api.plauth, [interfaces[0]['node_id']], ['node_id', 'hostname'])
+        if not nodes:
+            raise NonExistingRecord("no such node using ip %(ip)s" % locals())
+        node = nodes[0]
+       
+        # look up the sfa record
+        table = SfaTable()
+        records = table.findObjects({'type': 'node', 'pointer': node['node_id']})
+        if not records:
+            raise RecordNotFound("pointer:" + str(node['node_id']))  
+        record = records[0]
+        
+        # generate a new keypair and gid
+        uuid = create_uuid()
+        pkey = Keypair(create=True)
+        urn = hrn_to_urn(record['hrn'], record['type'])
+        gid_object = self.api.auth.hierarchy.create_gid(urn, uuid, pkey)
+        gid = gid_object.save_to_string(save_parents=True)
+        record['gid'] = gid
+        record.set_gid(gid)
+
+        # update the record
+        table.update(record)
+  
+        # attempt the scp the key
+        # and gid onto the node
+        # this will only work for planetlab based components
+        (kfd, key_filename) = tempfile.mkstemp() 
+        (gfd, gid_filename) = tempfile.mkstemp() 
+        pkey.save_to_file(key_filename)
+        gid_object.save_to_file(gid_filename, save_parents=True)
+        host = node['hostname']
+        key_dest="/etc/sfa/node.key"
+        gid_dest="/etc/sfa/node.gid" 
+        scp = "/usr/bin/scp" 
+        #identity = "/etc/planetlab/root_ssh_key.rsa"
+        identity = "/etc/sfa/root_ssh_key"
+        scp_options=" -i %(identity)s " % locals()
+        scp_options+="-o StrictHostKeyChecking=no " % locals()
+        scp_key_command="%(scp)s %(scp_options)s %(key_filename)s root@%(host)s:%(key_dest)s" %\
+                         locals()
+        scp_gid_command="%(scp)s %(scp_options)s %(gid_filename)s root@%(host)s:%(gid_dest)s" %\
+                         locals()    
+
+        all_commands = [scp_key_command, scp_gid_command]
+        
+        for command in all_commands:
+            (status, output) = commands.getstatusoutput(command)
+            if status:
+                raise Exception, output
+
+        for filename in [key_filename, gid_filename]:
+            os.unlink(filename)
+
+        return 1 
diff --git a/sfa/methods/get_registries.py b/sfa/methods/get_registries.py
new file mode 100644 (file)
index 0000000..358874c
--- /dev/null
@@ -0,0 +1,36 @@
+### $Id: get_slices.py 14387 2009-07-08 18:19:11Z faiyaza $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_registries.py $
+from types import StringTypes
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.server.registry import Registries
+
+class get_registries(Method):
+    """
+    Get a list of connection information for all known registries.      
+
+    @param cred credential string specifying the rights of the caller
+    @param a Human readable name (hrn or urn), or list of names or None
+    @return list of dictionaries with aggregate information.  
+    """
+
+    interfaces = ['registry', 'aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Mixed(Parameter(str, "Human readable name (hrn or urn)"),
+              Parameter(None, "hrn not specified"))
+        ]
+
+    returns = [Parameter(dict, "Registry interface information")]
+    
+    def call(self, cred, xrn = None):
+        hrn, type = urn_to_hrn(xrn)
+        self.api.auth.check(cred, 'list')
+        registries = Registries(self.api).interfaces.values()
+        if hrn:
+            registries = [reg for reg in registries if reg['hrn'] == hrn] 
+        return registries
diff --git a/sfa/methods/get_resources.py b/sfa/methods/get_resources.py
new file mode 100644 (file)
index 0000000..79065e1
--- /dev/null
@@ -0,0 +1,40 @@
+### $Id: get_resources.py 18562 2010-08-03 21:15:52Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_resources.py $
+
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.ListResources import ListResources 
+# RSpecManager_pl is not used. This line is a check that ensures that everything is in place for the import to work.
+import sfa.rspecs.aggregates.rspec_manager_pl
+
+class get_resources(ListResources):
+    """
+    Deprecated. Use ListResources instead. 
+
+    Get an resource specification (rspec). The rspec may describe the resources
+    available at an authority or the resources being used by a slice.      
+
+    @param cred credential string specifying the rights of the caller
+    @param hrn human readable name of the slice we are interesed in or None 
+           for an authority.  
+    """
+
+    interfaces = ['aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Mixed(Parameter(str, "Human readable name (hrn or urn)"),
+              Parameter(None, "hrn not specified")),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(str, "String representatin of an rspec")
+    
+    def call(self, cred, xrn=None, origin_hrn=None):
+        options = {'geni_slice_urn': xrn,
+                   'origin_hrn': origin_hrn
+        }
+                  
+        return ListResources.call(self, cred, options)
diff --git a/sfa/methods/get_self_credential.py b/sfa/methods/get_self_credential.py
new file mode 100644 (file)
index 0000000..3bdaecc
--- /dev/null
@@ -0,0 +1,53 @@
+### $Id: get_credential.py 15321 2009-10-15 05:01:21Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_credential.py $
+
+from sfa.trust.credential import *
+from sfa.trust.rights import *
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.util.debug import log
+from sfa.methods.GetSelfCredential import GetSelfCredential
+
+class get_self_credential(GetSelfCredential):
+    """
+    Deprecated. Use GetSelfCredential instead.
+
+    Retrive a credential for an object
+    @param cert certificate string 
+    @param type type of object (user | slice | sa | ma | node)
+    @param hrn human readable name of object (hrn or urn)
+
+    @return the string representation of a credential object  
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Human readable name (hrn or urn)"),
+        Parameter(str, "certificate"),
+        Mixed(Parameter(str, "Request hash"),
+              Parameter(None, "Request hash not specified"))
+        ]
+
+    returns = Parameter(str, "String representation of a credential object")
+
+    def call(self, cert, type, xrn, origin_hrn=None):
+        """
+        get_self_credential a degenerate version of get_credential used by a client
+        to get his initial credential when de doesnt have one. This is the same as
+        get_credetial(..., cred = None, ...)
+
+        The registry ensures that the client is the principal that is named by
+        (type, name) by comparing the public key in the record's  GID to the
+        private key used to encrypt the client side of the HTTPS connection. Thus
+        it is impossible for one principal to retrive another principal's
+        credential without having the appropriate private key.
+
+        @param type type of object (user | slice | sa | ma | node)
+        @param hrn human readable name of authority to list
+        @return string representation of a credential object
+        """
+        return GetSelfCredential.call(self, cert, xrn, type)
diff --git a/sfa/methods/get_slices.py b/sfa/methods/get_slices.py
new file mode 100644 (file)
index 0000000..7b65b30
--- /dev/null
@@ -0,0 +1,30 @@
+### $Id: get_slices.py 18582 2010-08-05 02:37:55Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_slices.py $
+
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.ListSlices import ListSlices
+
+class get_slices(ListSlices):
+    """
+    Deprecated. Use ListSlices instead.
+    Get a list of instantiated slices at this authority.      
+
+    @param cred credential string specifying the rights of the caller
+    @return list of human readable slice names (hrn).  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = [Parameter(str, "Human readable slice name (hrn)")]
+    
+    def call(self, cred, origin_hrn=None):
+
+        return ListSlices.call(self, cred)
diff --git a/sfa/methods/get_ticket.py b/sfa/methods/get_ticket.py
new file mode 100644 (file)
index 0000000..bd98456
--- /dev/null
@@ -0,0 +1,43 @@
+### $Id: get_ticket.py 18584 2010-08-05 22:46:52Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_ticket.py $
+import time
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.GetTicket import GetTicket
+
+class get_ticket(GetTicket):
+    """
+    Deprecated. Use GetTicket instead.
+
+    Retrieve a ticket. This operation is currently implemented on PLC
+    only (see SFA, engineering decisions); it is not implemented on
+    components.
+    
+    The ticket is filled in with information from the PLC database. This
+    information includes resources, and attributes such as user keys and
+    initscripts.
+    
+    @param cred credential string
+    @param name name of the slice to retrieve a ticket for (hrn or urn)
+    @param rspec resource specification dictionary
+    
+    @return the string representation of a ticket object
+    """
+
+    interfaces = ['aggregate', 'slicemgr']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to retrive a ticket for (hrn or urn)"),
+        Parameter(str, "Resource specification (rspec)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(str, "String represeneation of a ticket object")
+    
+    def call(self, cred, xrn, rspec, origin_hrn=None):
+        
+        return GetTicket.call(self, xrn, cred, rspec, None)        
diff --git a/sfa/methods/get_trusted_certs.py b/sfa/methods/get_trusted_certs.py
new file mode 100644 (file)
index 0000000..6ae4d50
--- /dev/null
@@ -0,0 +1,42 @@
+
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/reset_slices.py $
+
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.trust.credential import Credential
+
+class get_trusted_certs(Method):
+    """
+    @param cred credential string specifying the rights of the caller
+    @return list of gid strings  
+    """
+
+    interfaces = ['registry', 'aggregate', 'slicemgr']
+    
+    accepts = [
+        Mixed(Parameter(str, "Credential string"),
+              Parameter(None, "Credential not specified"))
+        ]
+
+    returns = Parameter(type([str]), "List of GID strings")
+    
+    def call(self, cred = None):
+        # If cred is not specified just return the gid for this interface.
+        # This is true when when a peer is attempting to initiate federation
+        # with this interface 
+        print cred
+        if not cred:
+            gid_strings = []
+            for gid in self.api.auth.trusted_cert_list:
+                if gid.get_hrn() == self.api.config.SFA_INTERFACE_HRN:
+                    gid_strings.append(gid.save_to_string(save_parents=True))   
+            return gid_strings
+
+        # authenticate the cred
+        self.api.auth.check(cred, 'gettrustedcerts')
+        gid_strings = [gid.save_to_string(save_parents=True) for \
+                                gid in self.api.auth.trusted_cert_list] 
+        
+        return gid_strings 
diff --git a/sfa/methods/list.py b/sfa/methods/list.py
new file mode 100644 (file)
index 0000000..0da6c07
--- /dev/null
@@ -0,0 +1,34 @@
+### $Id: list.py 18601 2010-08-06 21:20:23Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/list.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.methods.List import List
+
+class list(List):
+    """
+    Deprecated. Use List instead.
+
+    List the records in an authority. 
+
+    @param cred credential string specifying the rights of the caller
+    @param hrn human readable name of authority to list (hrn or urn)
+    @return list of record dictionaries         
+    """
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name (hrn or urn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = [SfaRecord]
+    
+    def call(self, cred, xrn, origin_hrn=None):
+        
+        return List.call(self, xrn, cred) 
diff --git a/sfa/methods/redeem_ticket.py b/sfa/methods/redeem_ticket.py
new file mode 100644 (file)
index 0000000..82985e7
--- /dev/null
@@ -0,0 +1,31 @@
+### $Id: reset_slice.py 15428 2009-10-23 15:28:03Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfacomponent/methods/reset_slice.py $
+import xmlrpclib
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.RedeemTicket import RedeemTicket
+
+class redeem_ticket(RedeemTicket):
+    """
+    Deprecated. Use RedeemTicket instead.
+
+    Redeem a approved set of resource allocations (ticket).        
+
+    @param cred credential string specifying the rights of the caller
+    @param ticket 
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['component']
+    
+    accepts = [
+        Parameter(str, "Credential string representation of SFA credential"),
+        Parameter(str, "Ticket  string representation of SFA ticket")
+        ]
+
+    returns = [Parameter(int, "1 if successful")]
+    
+    def call(self, cred, ticket):
+
+        return RedeemTicket.call(self, ticket, cred)  
diff --git a/sfa/methods/register.py b/sfa/methods/register.py
new file mode 100644 (file)
index 0000000..f71bfbf
--- /dev/null
@@ -0,0 +1,36 @@
+### $Id: register.py 18605 2010-08-06 21:43:22Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/register.py $
+
+from sfa.trust.certificate import Keypair, convert_public_key
+from sfa.trust.gid import *
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.Register import Register
+
+class register(Register):
+    """
+    Deprecated. Used Register instead.
+
+    Register an object with the registry. In addition to being stored in the
+    SFA database, the appropriate records will also be created in the
+    PLC databases
+    
+    @param cred credential string
+    @param record_dict dictionary containing record fields
+    
+    @return gid string representation
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary containing record fields")
+        ]
+
+    returns = Parameter(int, "String representation of gid object")
+    
+    def call(self, cred, record, origin_hrn=None):
+        
+        return Register.call(self, record, cred)
diff --git a/sfa/methods/register_peer_object.py b/sfa/methods/register_peer_object.py
new file mode 100644 (file)
index 0000000..68b3105
--- /dev/null
@@ -0,0 +1,73 @@
+### $Id: register.py 15001 2009-09-11 20:18:54Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/register.py $
+
+from sfa.trust.certificate import Keypair, convert_public_key
+from sfa.trust.gid import *
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.util.table import SfaTable
+from sfa.util.debug import log
+from sfa.trust.auth import Auth
+from sfa.trust.gid import create_uuid
+from sfa.trust.credential import Credential
+
+class register_peer_object(Method):
+    """
+    Register a peer object with the registry. In addition to being stored in the
+    SFA database, the appropriate records will also be created in the
+    PLC databases
+    
+    @param cred credential string
+    @param record_dict dictionary containing record fields
+    @return gid string representation
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary containing record fields"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, record_dict, origin_hrn=None):
+        user_cred = Credential(string=cred)
+
+        #log the call
+        if not origin_hrn:
+            origin_hrn = user_cred.get_gid_caller().get_hrn()    
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, None, self.name))
+
+        # validate the cred
+        self.api.auth.check(cred, "register")
+
+        # make sure this is a peer record
+        if 'peer_authority' not in record_dict or \
+           not record_dict['peer_authority']: 
+            raise SfaInvalidArgument, "peer_authority must be specified" 
+
+        record = SfaRecord(dict = record_dict)
+        type, hrn, peer_authority = record['type'], record['hrn'], record['peer_authority']
+        record['authority'] = get_authority(record['hrn'])
+        # verify permissions
+        self.api.auth.verify_cred_is_me(cred)
+
+        # check if record already exists
+        table = SfaTable()
+        existing_records = table.find({'type': type, 'hrn': hrn, 'peer_authority': peer_authority})
+        if existing_records:
+            for existing_record in existing_records:
+                if existing_record['pointer'] != record['pointer']:
+                    record['record_id'] = existing_record['record_id']
+                    table.update(record)
+        else:
+            record_id = table.insert(record)
+        return 1
diff --git a/sfa/methods/remove.py b/sfa/methods/remove.py
new file mode 100644 (file)
index 0000000..5d23939
--- /dev/null
@@ -0,0 +1,38 @@
+### $Id: remove.py 18606 2010-08-06 21:57:54Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/remove.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.Remove import Remove
+
+class remove(Remove):
+    """
+    Deprecated. Use Remove instead.
+
+    Remove an object from the registry. If the object represents a PLC object,
+    then the PLC records will also be removed.
+    
+    @param cred credential string
+    @param type record type
+    @param xrn human readable name of record to remove (hrn or urn)
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Record type"),
+        Parameter(str, "Human readable name of slice to instantiate (hrn or urn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, type, xrn, origin_hrn=None):
+        
+        return Remove.call(self, xrn, cred, type) 
diff --git a/sfa/methods/remove_peer_object.py b/sfa/methods/remove_peer_object.py
new file mode 100644 (file)
index 0000000..ed46ea7
--- /dev/null
@@ -0,0 +1,103 @@
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.util.record import SfaRecord
+from sfa.util.table import SfaTable
+from sfa.util.debug import log
+from sfa.trust.credential import Credential
+from types import StringTypes
+
+class remove_peer_object(Method):
+    """
+    Remove an peer object from the PLC records of a local aggregate. 
+    This method will be called by registry.remove() while removing 
+    a record from the local aggreage's PLCDB and sfa table. This 
+    method need not be directly called by end-user.
+    
+    @param cred credential string
+    @param record record as stored in the local registry
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, record, origin_hrn=None):
+        user_cred = Credential(string=cred)
+
+        #log the call
+        if not origin_hrn:
+            origin_hrn = user_cred.get_gid_caller().get_hrn()
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, origin_hrn, record['hrn'], self.name))
+
+        self.api.auth.check(cred, "remove")
+
+        # Only allow the local interface or record owner to delete peer_records 
+        try: self.api.auth.verify_object_permission(record['hrn'])
+        except: self.api.auth.verify_cred_is_me(cred)
+        
+        table = SfaTable()
+        hrn, type = record['hrn'], record['type']
+        records = table.find({'hrn': hrn, 'type': type })
+        for record in records:
+            if record['peer_authority']:
+                self.remove_plc_record(record)
+                table.remove(record)
+            
+        return 1
+
+    def remove_plc_record(self, record):
+        type = record['type']        
+        if type == "user":
+            persons = self.api.plshell.GetPersons(self.api.plauth, {'person_id' : record['pointer']})
+            if not persons:
+                return 1
+            person = persons[0]
+            if person['peer_id']:
+                peer = self.get_peer_name(person['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'person', person['person_id'], peer)
+            self.api.plshell.DeletePerson(self.api.plauth, person['person_id'])
+           
+        elif type == "slice":
+            slices=self.api.plshell.GetSlices(self.api.plauth, {'slice_id' : record['pointer']})
+            if not slices:
+                return 1
+            slice=slices[0]
+            if slice['peer_id']:
+                peer = self.get_peer_name(slice['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'slice', slice['slice_id'], peer)
+            self.api.plshell.DeleteSlice(self.api.plauth, slice['slice_id'])
+        elif type == "authority":
+            sites=self.api.plshell.GetSites(self.api.plauth, {'site_id' : record['pointer']})
+            if not sites:
+                return 1
+            site=sites[0]
+            if site['peer_id']:
+                peer = self.get_peer_name(site['peer_id']) 
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'site', site['site_id'], peer)
+            self.api.plshell.DeleteSite(self.api.plauth, site['site_id'])
+           
+        else:
+            raise UnknownSfaType(type)
+
+        return 1
+
+    def get_peer_name(self, peer_id):
+        peers = self.api.plshell.GetPeers(self.api.plauth, [peer_id], ['peername', 'shortname', 'hrn_root'])
+        if not peers:
+            raise SfaInvalidArgument, "No such peer"
+        peer = peers[0]
+        return peer['shortname'] 
+
+
+
diff --git a/sfa/methods/reset_slice.py b/sfa/methods/reset_slice.py
new file mode 100644 (file)
index 0000000..cd9026c
--- /dev/null
@@ -0,0 +1,52 @@
+### $Id: reset_slices.py 15428 2009-10-23 15:28:03Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/reset_slices.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.trust.auth import Auth
+from sfa.plc.slices import Slices
+
+class reset_slice(Method):
+    """
+    Reset the specified slice      
+
+    @param cred credential string specifying the rights of the caller
+    @param xrn human readable name of slice to instantiate (hrn or urn)
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to instantiate (hrn or urn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, xrn, origin_hrn=None):
+        hrn, type = urn_to_hrn(xrn)
+        self.api.auth.check(cred, 'resetslice', hrn)
+        # send the call to the right manager
+        manager_base = 'sfa.managers'
+        if self.api.interface in ['component']:
+            mgr_type = self.api.config.SFA_CM_TYPE
+            manager_module = manager_base + ".component_manager_%s" % mgr_type
+            manager = __import__(manager_module, fromlist=[manager_base])
+            manager.reset_slice(self.api, xrn)
+        elif self.api.interface in ['aggregate']:
+            mgr_type = self.api.config.SFA_AGGREGATE_TYPE
+            manager_module = manager_base + ".aggregate_manager_%s" % mgr_type
+            manager = __import__(manager_module, fromlist=[manager_base])
+            manager.reset_slice(self.api, xrn)
+        elif self.api.interface in ['slicemgr']:
+            mgr_type = self.api.config.SFA_SM_TYPE
+            manager_module = manager_base + ".slice_manager_%s" % mgr_type
+            manager = __import__(manager_module, fromlist=[manager_base])
+            manager.reset_slice(self.api, xrn) 
+
+        return 1 
diff --git a/sfa/methods/resolve.py b/sfa/methods/resolve.py
new file mode 100644 (file)
index 0000000..84f1208
--- /dev/null
@@ -0,0 +1,36 @@
+### $Id: resolve.py 18594 2010-08-06 20:00:50Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/resolve.py $
+import traceback
+import types
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.util.record import SfaRecord
+from sfa.methods.Resolve import Resolve
+
+class resolve(Resolve):
+    """
+    Deprecated. Use Resolve instead
+    Resolve a record.
+
+    @param cred credential string authorizing the caller
+    @param hrn human readable name to resolve (hrn or urn) 
+    @return a list of record dictionaries or empty list     
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Mixed(Parameter(str, "Human readable name (hrn or urn)"),
+              Parameter(list, "List of Human readable names ([hrn])"))  
+        ]
+
+    returns = [SfaRecord]
+    
+    def call(self, cred, xrns, origin_hrn=None):
+        return Resolve.call(self, xrns, cred)
+
+
+            
diff --git a/sfa/methods/start_slice.py b/sfa/methods/start_slice.py
new file mode 100644 (file)
index 0000000..bfb741c
--- /dev/null
@@ -0,0 +1,34 @@
+### $Id: start_slice.py 18566 2010-08-03 22:24:42Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/start_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.Start import Start
+
+class start_slice(Start):
+    """
+    Deprecated. Use Start instead
+
+    Start the specified slice      
+
+    @param cred credential string specifying the rights of the caller
+    @param hrn human readable name of slice to instantiate (urn or hrn)
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to instantiate (urn or hrn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = [Parameter(int, "1 if successful")]
+    
+    def call(self, cred, xrn, origin_hrn=None):
+        return Start.call(self, xrn, cred) 
diff --git a/sfa/methods/stop_slice.py b/sfa/methods/stop_slice.py
new file mode 100644 (file)
index 0000000..a711c0b
--- /dev/null
@@ -0,0 +1,33 @@
+### $Id: stop_slice.py 18567 2010-08-03 22:25:05Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/stop_slice.py $
+
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.Stop import Stop
+
+class stop_slice(Stop):
+    """
+    Deprecated. Use Stop instead
+    Stop the specified slice      
+
+    @param cred credential string specifying the rights of the caller
+    @param xrn human readable name of slice to instantiate (hrn or urn)
+    @return 1 is successful, faults otherwise  
+    """
+
+    interfaces = ['aggregate', 'slicemgr', 'component']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(str, "Human readable name of slice to instantiate (hrn or urn)"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, xrn, origin_hrn=None):
+        return Stop.call(self, xrn, cred) 
diff --git a/sfa/methods/update.py b/sfa/methods/update.py
new file mode 100644 (file)
index 0000000..56b8ff5
--- /dev/null
@@ -0,0 +1,38 @@
+### $Id: update.py 18606 2010-08-06 21:57:54Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/update.py $
+
+import time
+from sfa.util.faults import *
+from sfa.util.method import Method
+from sfa.util.parameter import Parameter, Mixed
+from sfa.methods.Update import Update
+
+class update(Update):
+    """
+    Deprecated. Use Update instead.
+
+    Update an object in the registry. Currently, this only updates the
+    PLC information associated with the record. The SFA fields (name, type,
+    GID) are fixed.
+    
+    @param cred credential string specifying rights of the caller
+    @param record a record dictionary to be updated
+
+    @return 1 if successful, faults otherwise 
+    """
+
+    interfaces = ['registry']
+    
+    accepts = [
+        Parameter(str, "Credential string"),
+        Parameter(dict, "Record dictionary to be updated"),
+        Mixed(Parameter(str, "Human readable name of the original caller"),
+              Parameter(None, "Origin hrn not specified"))
+        ]
+
+    returns = Parameter(int, "1 if successful")
+    
+    def call(self, cred, record_dict, origin_hrn=None):
+        
+        return Update.call(self, record_dict, cred)
+
diff --git a/sfa/plc/__init__.py b/sfa/plc/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/plc/api-dev.py b/sfa/plc/api-dev.py
new file mode 100644 (file)
index 0000000..29b0cc8
--- /dev/null
@@ -0,0 +1,562 @@
+#
+# SFA XML-RPC and SOAP interfaces
+#
+### $Id: api.py 17793 2010-04-26 21:40:57Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/api.py $
+#
+
+import sys
+import os
+import traceback
+import string
+import xmlrpclib
+from sfa.trust.auth import Auth
+from sfa.util.config import *
+from sfa.util.faults import *
+from sfa.util.debug import *
+from sfa.trust.rights import *
+from sfa.trust.credential import *
+from sfa.trust.certificate import *
+from sfa.util.namespace import *
+from sfa.util.api import *
+from sfa.util.nodemanager import NodeManager
+from sfa.util.sfalogging import *
+from collections import defaultdict
+
+
+class RecordInfo():
+    
+    shell = None
+    auth = None
+    
+    # pl records
+    sites = {}
+    slices = {}
+    persons = {}
+    nodes = {}
+    keys = {}
+
+    # sfa records
+    sfa_authorities = {}
+    sfa_slices = {}
+    sfa_users = {}
+    sfa_nodes = {}
+     
+    records = []
+
+    def __init__(self, api, records):
+        self.api = api
+        self.shell = api.plshell
+        self.auth = api.plauth
+        
+        site_ids = []
+        slice_ids = []
+        person_ids = []
+        node_ids = []
+
+        # put records into groups based on types
+        for record in records:
+            pointer = record['pointer']
+            if record['type'] == 'authority':
+                self.sfa_authorities[pointer] = record
+                self.records.append(record)
+                site_ids.append(record['pointer'])
+            elif record['type'] == 'slice':
+                self.sfa_slices[pointer] = record
+                self.records.append(record)
+                slice_ids.append(record['pointer'])
+            elif record['type'] == 'user':
+                self.sfa_users[pointer] = record
+                self.records.append(record)
+                person_ids.append(record['pointer'])
+            elif record['type'] == 'node':
+                self.sfa_nodes[pointer] = record
+                self.records.append(record)
+                node_ids.append(record['pointer']) 
+
+        # get pl info for these records
+        self.update_pl_sites(site_ids)
+        self.update_pl_slices(slice_ids)
+        self.update_pl_persons(person_ids)
+        self.update_pl_nodes(node_ids)
+       
+        site_ids = []
+        slice_ids = []
+        person_ids = []
+        node_ids = []
+        # now get pl records for all ids associated with 
+        # these records
+        for record in records:
+            if 'site_id' in record:
+                site_ids.append(record['site_id'])
+            if 'site_ids' in records:
+                site_ids.extend(record['site_ids'])
+            if 'person_ids' in record:
+                person_ids.extend(record['person_ids'])
+            if 'slice_ids' in record:
+                slice_ids.extend(record['slice_ids'])
+            if 'node_ids' in record:
+                node_ids.extend(record['node_ids'])
+
+        # get pl info for these records
+        self.update_pl_sites(site_ids)
+        self.update_pl_slices(slice_ids)
+        self.update_pl_persons(person_ids)
+        self.update_pl_nodes(node_ids)
+
+        # convert pl ids to hrns  
+        self.update_hrns()
+
+        # update sfa info
+        self.update_sfa_info(person_ids)
+
+    def update_pl_sites(self, site_ids):
+        """
+        Update site records with PL info 
+        """
+        if not site_ids:
+            return
+        sites = self.shell.GetSites(self.auth, site_ids)
+        for site in sites:
+            site_id = site['site_id']
+            self.sites[site_id] = site
+            if site_id in self.sfa_authorities:
+                self.sfa_authorities[site_id].update(site)
+
+    def update_pl_slices(self, slice_ids):
+        """
+        Update slice records with PL info
+        """
+        if not slice_ids:
+            return
+        slices = self.shell.GetSlices(self.auth, slice_ids)
+        for slice in slices:
+            slice_id = slice['slice_id']
+            self.slices[slice_id] = slice
+            if slice_id in self.sfa_slices:
+                self.sfa_slices[slice_id].update(slice)
+
+    def update_pl_persons(self, person_ids):
+        """
+        Update person records with PL info
+        """
+        key_ids = []
+        if not person_ids:
+            return
+        persons = self.shell.GetPersons(self.auth, person_ids)
+        for person in persons:
+            person_id = person['person_id']
+            self.persons[person_id] = person 
+            key_ids.extend(person['key_ids'])
+            if person_id in self.sfa_users:
+                self.sfa_users[person_id].update(person)
+        self.update_pl_keys(key_ids)
+
+    def update_pl_keys(self, key_ids):
+        """
+        Update user records with PL public key info
+        """
+        if not key_ids:
+            return
+        keys = self.shell.GetKeys(self.auth, key_ids)
+        for key in keys:
+            person_id = key['person_id']
+            self.keys[key['key_id']] = key
+            if person_id in self.sfa_users:
+                person = self.sfa_users[person_id]    
+                if not 'keys' in person:
+                    person['keys'] = [key['key']]
+                else: 
+                    person['keys'].append(key['key'])
+
+    def update_pl_nodes(self, node_ids):
+        """
+        Update node records with PL info
+        """
+        if not node_ids:
+            return 
+        nodes = self.shell.GetNodes(self.auth, node_ids)
+        for node in nodes:
+            node_id = node['node_id']
+            self.nodes[node['node_id']] = node
+            if node_id in self.sfa_nodes:
+                self.sfa_nodes[node_id].update(node)
+    
+
+    def update_hrns(self):
+        """
+        Convert pl ids to hrns
+        """
+        for record in self.records:
+            # get all necessary data
+            type = record['type']
+            pointer = record['pointer']
+            auth_hrn = self.api.hrn
+            login_base = ''
+            if pointer == -1:
+                continue       
+
+            if 'site_id' in record:
+                site = self.sites[record['site_id']]
+                login_base = site['login_base']
+                record['site'] = ".".join([auth_hrn, login_base])
+            if 'person_ids' in record:
+                emails = [self.persons[person_id]['email'] for person_id in record['person_ids'] \
+                          if person_id in self.persons]
+                usernames = [email.split('@')[0] for email in emails]
+                person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
+                record['persons'] = person_hrns
+            if 'slice_ids' in record:
+                slicenames = [self.slices[slice_id]['name'] for slice_id in record['slice_ids'] \
+                              if slice_id in self.slices]
+                slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
+                record['slices'] = slice_hrns
+            if 'node_ids' in record:
+                hostnames = [self.nodes[node_id]['hostname'] for node_id in record['node_ids'] \
+                             if node_id in self.nodes]
+                node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
+                record['nodes'] = node_hrns
+            if 'site_ids' in record:
+                login_bases = [self.sites[site_id]['login_base'] for site_id in record['site_ids'] \
+                               if site_id in self.sites]
+                site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
+                record['sites'] = site_hrns 
+
+    def update_sfa_info(self, person_ids):
+        from sfa.util.table import SfaTable
+        table = SfaTable()
+        persons = table.find({'type': 'user', 'pointer': person_ids})
+        # create a hrns keyed on the sfa record's pointer.
+        # Its possible for  multiple records to have the same pointer so 
+        # the dict's value will be a list of hrns.
+        person_dict = defaultdict(list)
+        for person in persons:
+            person_dict[person['pointer']].append(person['hrn'])                       
+
+        def startswith(prefix, values):
+            return [value for value in values if value.startswith(prefix)]
+
+        for record in self.records:
+            authority = record['authority']
+            if record['pointer'] == -1:
+                continue
+            
+            if record['type'] == 'slice':
+                # all slice users are researchers
+                record['PI'] = []
+                record['researchers'] = []    
+                for person_id in record['person_ids']:
+                    record['researchers'].extend(person_dict[person_id])
+                # also add the pis at the slice's site
+                site = self.sites[record['site_id']]    
+                for person_id in site['person_ids']:
+                    person = self.persons[person_id]
+                    if 'pi' in person['roles']:
+                        # PLCAPI doesn't support per site roles 
+                        # (a pi has the pi role at every site he belongs to).
+                        # We shouldnt allow this in SFA         
+                        record['PI'].extend(startswith(authority, person_dict[person_id]))    
+
+            elif record['type'] == 'authority':
+                record['PI'] = []
+                record['operator'] = []
+                record['owner'] = []
+                for person_id in record['person_ids']:
+                    person = self.persons[person_id]
+                    if 'pi' in person['roles']:
+                        # only get PI's at this site
+                        record['PI'].extend(startswith(record['hrn'], person_dict[person_id]))
+                    if 'tech' in person['roles']:
+                        # only get PI's at this site
+                        record['operator'].extend(startswith(record['hrn'], person_dict[person_id]))
+                    if 'admin' in person['roles']:
+                        record['owner'].extend(startswith(record['hrn'], person_dict[person_id]))
+                            
+            elif record['type'] == 'node':
+                record['dns'] = record['hostname']
+
+            elif record['type'] == 'user':
+                record['email'] = record['email']                   
+                                   
+                  
+                 
+                
+                    
+    def get_records(self):
+        return self.records
+
+class SfaAPI(BaseAPI):
+
+    # flat list of method names
+    import sfa.methods
+    methods = sfa.methods.all
+    
+    def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", 
+                 methods='sfa.methods', peer_cert = None, interface = None, 
+                key_file = None, cert_file = None, cache = None):
+        BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, \
+                         peer_cert=peer_cert, interface=interface, key_file=key_file, \
+                         cert_file=cert_file, cache=cache)
+        self.encoding = encoding
+
+        from sfa.util.table import SfaTable
+        self.SfaTable = SfaTable
+        # Better just be documenting the API
+        if config is None:
+            return
+
+        # Load configuration
+        self.config = Config(config)
+        self.auth = Auth(peer_cert)
+        self.interface = interface
+        self.key_file = key_file
+        self.key = Keypair(filename=self.key_file)
+        self.cert_file = cert_file
+        self.cert = Certificate(filename=self.cert_file)
+        self.credential = None
+        # Initialize the PLC shell only if SFA wraps a myPLC
+        rspec_type = self.config.get_aggregate_type()
+        if (rspec_type == 'pl' or rspec_type == 'vini'):
+            self.plshell = self.getPLCShell()
+            self.plshell_version = "4.3"
+
+        self.hrn = self.config.SFA_INTERFACE_HRN
+        self.time_format = "%Y-%m-%d %H:%M:%S"
+        self.logger=get_sfa_logger()
+
+    def getPLCShell(self):
+        self.plauth = {'Username': self.config.SFA_PLC_USER,
+                       'AuthMethod': 'password',
+                       'AuthString': self.config.SFA_PLC_PASSWORD}
+
+        self.plshell_type = 'xmlrpc' 
+        # connect via xmlrpc
+        url = self.config.SFA_PLC_URL
+        shell = xmlrpclib.Server(url, verbose = 0, allow_none = True)
+        return shell
+
+    def getCredential(self):
+        if self.interface in ['registry']:
+            return self.getCredentialFromLocalRegistry()
+        else:
+            return self.getCredentialFromRegistry()
+    
+    def getCredentialFromRegistry(self):
+        """ 
+        Get our credential from a remote registry 
+        """
+        type = 'authority'
+        path = self.config.SFA_DATA_DIR
+        filename = ".".join([self.interface, self.hrn, type, "cred"])
+        cred_filename = path + os.sep + filename
+        try:
+            credential = Credential(filename = cred_filename)
+            return credential.save_to_string(save_parents=True)
+        except IOError:
+            from sfa.server.registry import Registries
+            registries = Registries(self)
+            registry = registries[self.hrn]
+            cert_string=self.cert.save_to_string(save_parents=True)
+            # get self credential
+            self_cred = registry.get_self_credential(cert_string, type, self.hrn)
+            # get credential
+            cred = registry.get_credential(self_cred, type, self.hrn)
+            
+            # save cred to file
+            Credential(string=cred).save_to_file(cred_filename, save_parents=True)
+            return cred
+
+    def getCredentialFromLocalRegistry(self):
+        """
+        Get our current credential directly from the local registry.
+        """
+
+        hrn = self.hrn
+        auth_hrn = self.auth.get_authority(hrn)
+    
+        # is this a root or sub authority
+        if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN:
+            auth_hrn = hrn
+        auth_info = self.auth.get_auth_info(auth_hrn)
+        table = self.SfaTable()
+        records = table.findObjects(hrn)
+        if not records:
+            raise RecordNotFound
+        record = records[0]
+        type = record['type']
+        object_gid = record.get_gid_object()
+        new_cred = Credential(subject = object_gid.get_subject())
+        new_cred.set_gid_caller(object_gid)
+        new_cred.set_gid_object(object_gid)
+        new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
+        new_cred.set_pubkey(object_gid.get_pubkey())
+        r1 = determine_rights(type, hrn)
+        new_cred.set_privileges(r1)
+
+        auth_kind = "authority,ma,sa"
+
+        new_cred.set_parent(self.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
+
+        new_cred.encode()
+        new_cred.sign()
+
+        return new_cred.save_to_string(save_parents=True)
+   
+
+    def loadCredential (self):
+        """
+        Attempt to load credential from file if it exists. If it doesnt get
+        credential from registry.
+        """
+
+        # see if this file exists
+        # XX This is really the aggregate's credential. Using this is easier than getting
+        # the registry's credential from iteslf (ssl errors).   
+        ma_cred_filename = self.config.SFA_DATA_DIR + os.sep + self.interface + self.hrn + ".ma.cred"
+        try:
+            self.credential = Credential(filename = ma_cred_filename)
+        except IOError:
+            self.credential = self.getCredentialFromRegistry()
+
+    ##
+    # Convert SFA fields to PLC fields for use when registering up updating
+    # registry record in the PLC database
+    #
+    # @param type type of record (user, slice, ...)
+    # @param hrn human readable name
+    # @param sfa_fields dictionary of SFA fields
+    # @param pl_fields dictionary of PLC fields (output)
+
+    def sfa_fields_to_pl_fields(self, type, hrn, record):
+
+        def convert_ints(tmpdict, int_fields):
+            for field in int_fields:
+                if field in tmpdict:
+                    tmpdict[field] = int(tmpdict[field])
+
+        pl_record = {}
+        #for field in record:
+        #    pl_record[field] = record[field]
+        if type == "slice":
+            if not "instantiation" in pl_record:
+                pl_record["instantiation"] = "plc-instantiated"
+            pl_record["name"] = hrn_to_pl_slicename(hrn)
+           if "url" in record:
+               pl_record["url"] = record["url"]
+           if "description" in record:
+               pl_record["description"] = record["description"]
+           if "expires" in record:
+               pl_record["expires"] = int(record["expires"])
+
+        elif type == "node":
+            if not "hostname" in pl_record:
+                if not "hostname" in record:
+                    raise MissingSfaInfo("hostname")
+                pl_record["hostname"] = record["hostname"]
+            if not "model" in pl_record:
+                pl_record["model"] = "geni"
+
+        elif type == "authority":
+            pl_record["login_base"] = hrn_to_pl_login_base(hrn)
+
+            if not "name" in pl_record:
+                pl_record["name"] = hrn
+
+            if not "abbreviated_name" in pl_record:
+                pl_record["abbreviated_name"] = hrn
+
+            if not "enabled" in pl_record:
+                pl_record["enabled"] = True
+
+            if not "is_public" in pl_record:
+                pl_record["is_public"] = True
+
+        return pl_record
+
+
+    def fill_record_info(self, records):
+        """
+        Given a SFA record, fill in the PLC specific and SFA specific
+        fields in the record. 
+        """
+        if not isinstance(records, list):
+            records = [records]
+
+        record_info = RecordInfo(self, records)
+        return record_info.get_records()
+
+    def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
+        # get a list of the HRNs tht are members of the old and new records
+        if oldRecord:
+            oldList = oldRecord.get(listName, [])
+        else:
+            oldList = []     
+        newList = record.get(listName, [])
+
+        # if the lists are the same, then we don't have to update anything
+        if (oldList == newList):
+            return
+
+        # build a list of the new person ids, by looking up each person to get
+        # their pointer
+        newIdList = []
+        table = self.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(self.plauth, 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(self.plauth, personId, containerId)
+
+    def update_membership(self, oldRecord, record):
+        if record.type == "slice":
+            self.update_membership_list(oldRecord, record, 'researcher',
+                                        self.plshell.AddPersonToSlice,
+                                        self.plshell.DeletePersonFromSlice)
+        elif record.type == "authority":
+            # xxx TODO
+            pass
+
+
+
+class ComponentAPI(BaseAPI):
+
+    def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", methods='sfa.methods',
+                 peer_cert = None, interface = None, key_file = None, cert_file = None):
+
+        BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
+                         interface=interface, key_file=key_file, cert_file=cert_file)
+        self.encoding = encoding
+
+        # Better just be documenting the API
+        if config is None:
+            return
+
+        self.nodemanager = NodeManager(self.config)
+
+    def sliver_exists(self):
+        sliver_dict = self.nodemanager.GetXIDs()
+        if slicename in sliver_dict.keys():
+            return True
+        else:
+            return False
diff --git a/sfa/plc/api.py b/sfa/plc/api.py
new file mode 100644 (file)
index 0000000..897c664
--- /dev/null
@@ -0,0 +1,675 @@
+#
+# SFA XML-RPC and SOAP interfaces
+#
+### $Id: api.py 18650 2010-08-21 01:04:41Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/api.py $
+#
+
+import sys
+import os
+import traceback
+import string
+import xmlrpclib
+from sfa.trust.auth import Auth
+from sfa.util.config import *
+from sfa.util.faults import *
+from sfa.util.debug import *
+from sfa.trust.rights import *
+from sfa.trust.credential import *
+from sfa.trust.certificate import *
+from sfa.util.namespace import *
+from sfa.util.api import *
+from sfa.util.nodemanager import NodeManager
+from sfa.util.sfalogging import *
+try:
+    from collections import defaultdict
+except:
+    class defaultdict(dict):
+        def __init__(self, default_factory=None, *a, **kw):
+            if (default_factory is not None and
+                not hasattr(default_factory, '__call__')):
+                raise TypeError('first argument must be callable')
+            dict.__init__(self, *a, **kw)
+            self.default_factory = default_factory
+        def __getitem__(self, key):
+            try:
+                return dict.__getitem__(self, key)
+            except KeyError:
+                return self.__missing__(key)
+        def __missing__(self, key):
+            if self.default_factory is None:
+                raise KeyError(key)
+            self[key] = value = self.default_factory()
+            return value
+        def __reduce__(self):
+            if self.default_factory is None:
+                args = tuple()
+            else:
+                args = self.default_factory,
+            return type(self), args, None, None, self.items()
+        def copy(self):
+            return self.__copy__()
+        def __copy__(self):
+            return type(self)(self.default_factory, self)
+        def __deepcopy__(self, memo):
+            import copy
+            return type(self)(self.default_factory,
+                              copy.deepcopy(self.items()))
+        def __repr__(self):
+            return 'defaultdict(%s, %s)' % (self.default_factory,
+                                            dict.__repr__(self))
+## end of http://code.activestate.com/recipes/523034/ }}}
+
+def list_to_dict(recs, key):
+    """
+    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))
+
+class SfaAPI(BaseAPI):
+
+    # flat list of method names
+    import sfa.methods
+    methods = sfa.methods.all
+    
+    def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", 
+                 methods='sfa.methods', peer_cert = None, interface = None, 
+                key_file = None, cert_file = None, cache = None):
+        BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, \
+                         peer_cert=peer_cert, interface=interface, key_file=key_file, \
+                         cert_file=cert_file, cache=cache)
+        self.encoding = encoding
+        from sfa.util.table import SfaTable
+        self.SfaTable = SfaTable
+        # Better just be documenting the API
+        if config is None:
+            return
+
+        # Load configuration
+        self.config = Config(config)
+        self.auth = Auth(peer_cert)
+        self.interface = interface
+        self.key_file = key_file
+        self.key = Keypair(filename=self.key_file)
+        self.cert_file = cert_file
+        self.cert = Certificate(filename=self.cert_file)
+        self.credential = None
+        # Initialize the PLC shell only if SFA wraps a myPLC
+        rspec_type = self.config.get_aggregate_type()
+        if (rspec_type == 'pl' or rspec_type == 'vini' or rspec_type == 'eucalyptus'):
+            self.plshell = self.getPLCShell()
+            self.plshell_version = "4.3"
+
+        self.hrn = self.config.SFA_INTERFACE_HRN
+        self.time_format = "%Y-%m-%d %H:%M:%S"
+        self.logger=get_sfa_logger()
+
+    def getPLCShell(self):
+        self.plauth = {'Username': self.config.SFA_PLC_USER,
+                       'AuthMethod': 'password',
+                       'AuthString': self.config.SFA_PLC_PASSWORD}
+        try:
+            sys.path.append(os.path.dirname(os.path.realpath("/usr/bin/plcsh")))
+            self.plshell_type = 'direct'
+            import PLC.Shell
+            shell = PLC.Shell.Shell(globals = globals())
+        except:
+            self.plshell_type = 'xmlrpc' 
+            url = self.config.SFA_PLC_URL
+            shell = xmlrpclib.Server(url, verbose = 0, allow_none = True)
+        
+        return shell
+
+    def getCredential(self):
+        """
+        Retrun a valid credential for this interface. 
+        """
+        if self.interface in ['registry']:
+            return self.getCredentialFromLocalRegistry()
+        else:
+            return self.getCredentialFromRegistry()
+
+    def getDelegatedCredential(self, creds):
+        """
+        Attempt to find a credential delegated to us in
+        the specified list of creds.
+        """
+        if creds and not isinstance(creds, list): 
+            creds = [creds]
+        delegated_creds = filter_creds_by_caller(creds,self.hrn)
+        if not delegated_creds:
+            return None
+        return delegated_creds[0]
+    def getCredentialFromRegistry(self):
+        """ 
+        Get our credential from a remote registry 
+        """
+        type = 'authority'
+        path = self.config.SFA_DATA_DIR
+        filename = ".".join([self.interface, self.hrn, type, "cred"])
+        cred_filename = path + os.sep + filename
+        try:
+            credential = Credential(filename = cred_filename)
+            return credential.save_to_string(save_parents=True)
+        except IOError:
+            from sfa.server.registry import Registries
+            registries = Registries(self)
+            registry = registries[self.hrn]
+            cert_string=self.cert.save_to_string(save_parents=True)
+            # get self credential
+            self_cred = registry.get_self_credential(cert_string, type, self.hrn)
+            # get credential
+            cred = registry.get_credential(self_cred, type, self.hrn)
+            
+            # save cred to file
+            Credential(string=cred).save_to_file(cred_filename, save_parents=True)
+            return cred
+
+    def getCredentialFromLocalRegistry(self):
+        """
+        Get our current credential directly from the local registry.
+        """
+
+        hrn = self.hrn
+        auth_hrn = self.auth.get_authority(hrn)
+    
+        # is this a root or sub authority
+        if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN:
+            auth_hrn = hrn
+        auth_info = self.auth.get_auth_info(auth_hrn)
+        table = self.SfaTable()
+        records = table.findObjects(hrn)
+        if not records:
+            raise RecordNotFound
+        record = records[0]
+        type = record['type']
+        object_gid = record.get_gid_object()
+        new_cred = Credential(subject = object_gid.get_subject())
+        new_cred.set_gid_caller(object_gid)
+        new_cred.set_gid_object(object_gid)
+        new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
+        
+        r1 = determine_rights(type, hrn)
+        new_cred.set_privileges(r1)
+
+        auth_kind = "authority,ma,sa"
+
+        new_cred.set_parent(self.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
+
+        new_cred.encode()
+        new_cred.sign()
+
+        return new_cred.save_to_string(save_parents=True)
+   
+
+    def loadCredential (self):
+        """
+        Attempt to load credential from file if it exists. If it doesnt get
+        credential from registry.
+        """
+
+        # see if this file exists
+        # XX This is really the aggregate's credential. Using this is easier than getting
+        # the registry's credential from iteslf (ssl errors).   
+        ma_cred_filename = self.config.SFA_DATA_DIR + os.sep + self.interface + self.hrn + ".ma.cred"
+        try:
+            self.credential = Credential(filename = ma_cred_filename)
+        except IOError:
+            self.credential = self.getCredentialFromRegistry()
+
+    ##
+    # Convert SFA fields to PLC fields for use when registering up updating
+    # registry record in the PLC database
+    #
+    # @param type type of record (user, slice, ...)
+    # @param hrn human readable name
+    # @param sfa_fields dictionary of SFA fields
+    # @param pl_fields dictionary of PLC fields (output)
+
+    def sfa_fields_to_pl_fields(self, type, hrn, record):
+
+        def convert_ints(tmpdict, int_fields):
+            for field in int_fields:
+                if field in tmpdict:
+                    tmpdict[field] = int(tmpdict[field])
+
+        pl_record = {}
+        #for field in record:
+        #    pl_record[field] = record[field]
+        if type == "slice":
+            if not "instantiation" in pl_record:
+                pl_record["instantiation"] = "plc-instantiated"
+            pl_record["name"] = hrn_to_pl_slicename(hrn)
+           if "url" in record:
+               pl_record["url"] = record["url"]
+           if "description" in record:
+               pl_record["description"] = record["description"]
+           if "expires" in record:
+               pl_record["expires"] = int(record["expires"])
+
+        elif type == "node":
+            if not "hostname" in pl_record:
+                if not "hostname" in record:
+                    raise MissingSfaInfo("hostname")
+                pl_record["hostname"] = record["hostname"]
+            if not "model" in pl_record:
+                pl_record["model"] = "geni"
+
+        elif type == "authority":
+            pl_record["login_base"] = hrn_to_pl_login_base(hrn)
+
+            if not "name" in pl_record:
+                pl_record["name"] = hrn
+
+            if not "abbreviated_name" in pl_record:
+                pl_record["abbreviated_name"] = hrn
+
+            if not "enabled" in pl_record:
+                pl_record["enabled"] = True
+
+            if not "is_public" in pl_record:
+                pl_record["is_public"] = True
+
+        return pl_record
+
+    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
+        node_ids, site_ids, slice_ids = [], [], [] 
+        person_ids, key_ids = [], []
+        type_map = {'node': node_ids, 'authority': site_ids,
+                    'slice': slice_ids, 'user': person_ids}
+                  
+        for record in records:
+            for type in type_map:
+                if type == record['type']:
+                    type_map[type].append(record['pointer'])
+
+        # get pl records
+        nodes, sites, slices, persons, keys = {}, {}, {}, {}, {}
+        if node_ids:
+            node_list = self.plshell.GetNodes(self.plauth, node_ids)
+            nodes = list_to_dict(node_list, 'node_id')
+        if site_ids:
+            site_list = self.plshell.GetSites(self.plauth, site_ids)
+            sites = list_to_dict(site_list, 'site_id')
+        if slice_ids:
+            slice_list = self.plshell.GetSlices(self.plauth, slice_ids)
+            slices = list_to_dict(slice_list, 'slice_id')
+        if person_ids:
+            person_list = self.plshell.GetPersons(self.plauth, person_ids)
+            persons = list_to_dict(person_list, 'person_id')
+            for person in persons:
+                key_ids.extend(persons[person]['key_ids'])
+
+        pl_records = {'node': nodes, 'authority': sites,
+                      'slice': slices, 'user': persons}
+
+        if key_ids:
+            key_list = self.plshell.GetKeys(self.plauth, key_ids)
+            keys = list_to_dict(key_list, 'key_id')
+
+        # fill record info
+        for record in records:
+            # records with pointer==-1 do not have plc info.
+            # for example, the top level authority records which are
+            # authorities, but not PL "sites"
+            if record['pointer'] == -1:
+                continue
+           
+            for type in pl_records:
+                if record['type'] == type:
+                    if record['pointer'] in pl_records[type]:
+                        record.update(pl_records[type][record['pointer']])
+                        break
+            # fill in key info
+            if record['type'] == 'user':
+                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):
+        """
+        convert pl ids to hrns
+        """
+
+        # get ids
+        slice_ids, person_ids, site_ids, node_ids = [], [], [], []
+        for record in records:
+            if 'site_id' in record:
+                site_ids.append(record['site_id'])
+            if 'site_ids' in records:
+                site_ids.extend(record['site_ids'])
+            if 'person_ids' in record:
+                person_ids.extend(record['person_ids'])
+            if 'slice_ids' in record:
+                slice_ids.extend(record['slice_ids'])
+            if 'node_ids' in record:
+                node_ids.extend(record['node_ids'])
+
+        # get pl records
+        slices, persons, sites, nodes = {}, {}, {}, {}
+        if site_ids:
+            site_list = self.plshell.GetSites(self.plauth, site_ids, ['site_id', 'login_base'])
+            sites = list_to_dict(site_list, 'site_id')
+        if person_ids:
+            person_list = self.plshell.GetPersons(self.plauth, person_ids, ['person_id', 'email'])
+            persons = list_to_dict(person_list, 'person_id')
+        if slice_ids:
+            slice_list = self.plshell.GetSlices(self.plauth, slice_ids, ['slice_id', 'name'])
+            slices = list_to_dict(slice_list, 'slice_id')       
+        if node_ids:
+            node_list = self.plshell.GetNodes(self.plauth, node_ids, ['node_id', 'hostname'])
+            nodes = list_to_dict(node_list, 'node_id')
+       
+        # convert ids to hrns
+        for record in records:
+             
+            # get all relevant data
+            type = record['type']
+            pointer = record['pointer']
+            auth_hrn = self.hrn
+            login_base = ''
+            if pointer == -1:
+                continue
+
+            if 'site_id' in record:
+                site = sites[record['site_id']]
+                login_base = site['login_base']
+                record['site'] = ".".join([auth_hrn, login_base])
+            if 'person_ids' in record:
+                emails = [persons[person_id]['email'] for person_id in record['person_ids'] \
+                          if person_id in  persons]
+                usernames = [email.split('@')[0] for email in emails]
+                person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
+                record['persons'] = person_hrns 
+            if 'slice_ids' in record:
+                slicenames = [slices[slice_id]['name'] for slice_id in record['slice_ids'] \
+                              if slice_id in slices]
+                slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
+                record['slices'] = slice_hrns
+            if 'node_ids' in record:
+                hostnames = [nodes[node_id]['hostname'] for node_id in record['node_ids'] \
+                             if node_id in nodes]
+                node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
+                record['nodes'] = node_hrns
+            if 'site_ids' in record:
+                login_bases = [sites[site_id]['login_base'] for site_id in record['site_ids'] \
+                               if site_id in sites]
+                site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
+                record['sites'] = site_hrns
+
+        return records   
+
+    def fill_record_sfa_info(self, records):
+
+        def startswith(prefix, values):
+            return [value for value in values if value.startswith(prefix)]
+
+        # get person ids
+        person_ids = []
+        site_ids = []
+        for record in records:
+            person_ids.extend(record.get("person_ids", []))
+            site_ids.extend(record.get("site_ids", [])) 
+            if 'site_id' in record:
+                site_ids.append(record['site_id']) 
+        
+        # get all pis from the sites we've encountered
+        # and store them in a dictionary keyed on site_id 
+        site_pis = {}
+        if site_ids:
+            pi_filter = {'|roles': ['pi'], '|site_ids': site_ids} 
+            pi_list = self.plshell.GetPersons(self.plauth, pi_filter, ['person_id', 'site_ids'])
+            for pi in pi_list:
+                # we will need the pi's hrns also
+                person_ids.append(pi['person_id'])
+                
+                # we also need to keep track of the sites these pis
+                # belong to
+                for site_id in pi['site_ids']:
+                    if site_id in site_pis:
+                        site_pis[site_id].append(pi)
+                    else:
+                        site_pis[site_id] = [pi]
+                 
+        # get sfa records for all records associated with these records.   
+        # we'll replace pl ids (person_ids) with hrns from the sfa records
+        # we obtain
+        
+        # get the sfa records
+        table = self.SfaTable()
+        person_list, persons = [], {}
+        person_list = table.find({'type': 'user', 'pointer': person_ids})
+        # create a hrns keyed on the sfa record's pointer.
+        # Its possible for  multiple records to have the same pointer so
+        # the dict's value will be a list of hrns.
+        persons = defaultdict(list)
+        for person in person_list:
+            persons[person['pointer']].append(person)
+
+        # get the pl records
+        pl_person_list, pl_persons = [], {}
+        pl_person_list = self.plshell.GetPersons(self.plauth, person_ids, ['person_id', 'roles'])
+        pl_persons = list_to_dict(pl_person_list, 'person_id')
+
+        # fill sfa info
+        for record in records:
+            # skip records with no pl info (top level authorities)
+            if record['pointer'] == -1:
+                continue 
+            sfa_info = {}
+            type = record['type']
+            if (type == "slice"):
+                # all slice users are researchers
+                record['PI'] = []
+                record['researcher'] = []
+                for person_id in record['person_ids']:
+                    hrns = [person['hrn'] for person in persons[person_id]]
+                    record['researcher'].extend(hrns)                
+
+                # pis at the slice's site
+                pl_pis = site_pis[record['site_id']]
+                pi_ids = [pi['person_id'] for pi in pl_pis]
+                for person_id in pi_ids:
+                    hrns = [person['hrn'] for person in persons[person_id]]
+                    record['PI'].extend(hrns)
+                record['geni_urn'] = hrn_to_urn(record['hrn'], 'slice')
+                record['geni_creator'] = record['PI'] 
+                
+            elif (type == "authority"):
+                record['PI'] = []
+                record['operator'] = []
+                record['owner'] = []
+                for pointer in record['person_ids']:
+                    if pointer not in persons or pointer not in pl_persons:
+                        # this means there is not sfa or pl record for this user
+                        continue   
+                    hrns = [person['hrn'] for person in persons[pointer]] 
+                    roles = pl_persons[pointer]['roles']   
+                    if 'pi' in roles:
+                        record['PI'].extend(hrns)
+                    if 'tech' in roles:
+                        record['operator'].extend(hrns)
+                    if 'admin' in roles:
+                        record['owner'].extend(hrns)
+                    # xxx TODO: OrganizationName
+            elif (type == "node"):
+                sfa_info['dns'] = record.get("hostname", "")
+                # xxx TODO: URI, LatLong, IP, DNS
+    
+            elif (type == "user"):
+                sfa_info['email'] = record.get("email", "")
+                sfa_info['geni_urn'] = hrn_to_urn(record['hrn'], 'user')
+                sfa_info['geni_certificate'] = record['gid'] 
+                # xxx TODO: PostalAddress, Phone
+            record.update(sfa_info)
+
+    def fill_record_info(self, records):
+        """
+        Given a SFA record, fill in the PLC specific and SFA specific
+        fields in the record. 
+        """
+        if not isinstance(records, list):
+            records = [records]
+
+        self.fill_record_pl_info(records)
+        self.fill_record_sfa_info(records)
+
+    def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
+        # get a list of the HRNs tht are members of the old and new records
+        if oldRecord:
+            oldList = oldRecord.get(listName, [])
+        else:
+            oldList = []     
+        newList = record.get(listName, [])
+
+        # if the lists are the same, then we don't have to update anything
+        if (oldList == newList):
+            return
+
+        # build a list of the new person ids, by looking up each person to get
+        # their pointer
+        newIdList = []
+        table = self.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(self.plauth, 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(self.plauth, personId, containerId)
+
+    def update_membership(self, oldRecord, record):
+        if record.type == "slice":
+            self.update_membership_list(oldRecord, record, 'researcher',
+                                        self.plshell.AddPersonToSlice,
+                                        self.plshell.DeletePersonFromSlice)
+        elif record.type == "authority":
+            # xxx TODO
+            pass
+
+
+
+class ComponentAPI(BaseAPI):
+
+    def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", methods='sfa.methods',
+                 peer_cert = None, interface = None, key_file = None, cert_file = None):
+
+        BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
+                         interface=interface, key_file=key_file, cert_file=cert_file)
+        self.encoding = encoding
+
+        # Better just be documenting the API
+        if config is None:
+            return
+
+        self.nodemanager = NodeManager(self.config)
+
+    def sliver_exists(self):
+        sliver_dict = self.nodemanager.GetXIDs()
+        if slicename in sliver_dict.keys():
+            return True
+        else:
+            return False
+
+    def get_registry(self):
+        addr, port = self.config.SFA_REGISTRY_HOST, self.config.SFA_REGISTRY_PORT
+        url = "http://%(addr)s:%(port)s" % locals()
+        server = xmlrpcprotocol.get_server(url, self.key_file, self.cert_file)
+        return server
+
+    def get_node_key(self):
+        # this call requires no authentication,
+        # so we can generate a random keypair here
+        subject="component"
+        (kfd, keyfile) = tempfile.mkstemp()
+        (cfd, certfile) = tempfile.mkstemp()
+        key = Keypair(create=True)
+        key.save_to_file(keyfile)
+        cert = Certificate(subject=subject)
+        cert.set_issuer(key=key, subject=subject)
+        cert.set_pubkey(key)
+        cert.sign()
+        cert.save_to_file(certfile)
+        registry = self.get_registry()
+        # the registry will scp the key onto the node
+        registry.get_key()        
+
+    def getCredential(self):
+        """
+        Get our credential from a remote registry
+        """
+        path = self.config.SFA_DATA_DIR
+        config_dir = self.config.config_path
+        credfile = path + os.sep + 'node.cred'
+        try:
+            credential = Credential(filename = cred_filename)
+            return credential.save_to_string(save_parents=True)
+        except IOError:
+            node_pkey_file = config_dir + os.sep + "node.key"
+            node_gid_file = config_dir + os.sep + "node.gid"
+            if not os.path.exists(node_pkey_file) or \
+               not os.path.exists(node_gid_file):
+                self.get_node_key()
+
+            # get node's hrn
+            gid = GID(filename=node_gid_file)
+            hrn = gid.get_hrn()
+            # get credential from registry
+            registry = self.get_registry()
+            cred = registry.get_self_credential(cert_str, 'node', hrn)
+            Credential(string=cred).save_to_file(credfile, save_parents=True)            
+
+            return cred
+
+    def clean_key_cred(self):
+        """
+        remove the existing keypair and cred  and generate new ones
+        """
+        files = ["server.key", "server.cert", "node.cred"]
+        for f in files:
+            filepath = KEYDIR + os.sep + f
+            if os.path.isfile(filepath):
+                os.unlink(f)
+
+        # install the new key pair
+        # get_credential will take care of generating the new keypair
+        # and credential
+        self.get_node_key()
+        self.getCredential()
+
+    
diff --git a/sfa/plc/network.py b/sfa/plc/network.py
new file mode 100644 (file)
index 0000000..d1345bc
--- /dev/null
@@ -0,0 +1,581 @@
+from __future__ import with_statement
+import re
+import socket
+from sfa.util.namespace import *
+from sfa.util.faults import *
+from xmlbuilder import XMLBuilder
+from lxml import etree
+import sys
+from StringIO import StringIO
+
+
+class Sliver:
+    def __init__(self, node):
+        self.node = node
+        self.network = node.network
+        self.slice = node.network.slice
+        
+    def toxml(self, xml):
+        with xml.sliver:
+            self.slice.tags_to_xml(xml, self.node)
+
+        
+class Iface:
+    def __init__(self, network, iface):
+        self.network = network
+        self.id = iface['interface_id']
+        self.idtag = "i%s" % self.id
+        self.ipv4 = iface['ip']
+        self.bwlimit = iface['bwlimit']
+        self.hostname = iface['hostname']
+        self.primary = iface['is_primary']
+
+    """
+    Just print out bwlimit right now
+    """
+    def toxml(self, xml):
+        if self.bwlimit:
+            with xml.bw_limit(units="kbps"):
+                xml << str(self.bwlimit / 1000)
+
+
+class Node:
+    def __init__(self, network, node):
+        self.network = network
+        self.id = node['node_id']
+        self.idtag = "n%s" % self.id
+        self.hostname = node['hostname']
+        self.site_id = node['site_id']
+        self.iface_ids = node['interface_ids']
+        self.sliver = None
+        self.whitelist = node['slice_ids_whitelist']
+
+    def get_primary_iface(self):
+        for id in self.iface_ids:
+            iface = self.network.lookupIface(id)
+            if iface.primary:
+                return iface
+        return None
+        
+    def get_site(self):
+        return self.network.lookupSite(self.site_id)
+    
+    def add_sliver(self):
+        self.sliver = Sliver(self)
+
+    def toxml(self, xml):
+        slice = self.network.slice
+        if self.whitelist and not self.sliver:
+            if not slice or slice.id not in self.whitelist:
+                return
+
+        with xml.node(id = self.idtag):
+            with xml.hostname:
+                xml << self.hostname
+            iface = self.get_primary_iface()
+            if iface:
+                iface.toxml(xml)
+            if self.sliver:
+                self.sliver.toxml(xml)
+    
+
+class Site:
+    def __init__(self, network, site):
+        self.network = network
+        self.id = site['site_id']
+        self.idtag = "s%s" % self.id
+        self.node_ids = site['node_ids']
+        self.node_ids.sort()
+        self.name = site['abbreviated_name']
+        self.tag = site['login_base']
+        self.public = site['is_public']
+        self.enabled = site['enabled']
+        self.links = set()
+        self.whitelist = False
+
+    def get_sitenodes(self):
+        n = []
+        for i in self.node_ids:
+            n.append(self.network.lookupNode(i))
+        return n
+    
+    def toxml(self, xml):
+        if not (self.public and self.enabled and self.node_ids):
+            return
+        with xml.site(id = self.idtag):
+            with xml.name:
+                xml << self.name
+            for node in self.get_sitenodes():
+                node.toxml(xml)
+   
+    
+class Slice:
+    def __init__(self, network, hrn, slice):
+        self.hrn = hrn
+        self.network = network
+        self.id = slice['slice_id']
+        self.name = slice['name']
+        self.peer_id = slice['peer_id']
+        self.node_ids = set(slice['node_ids'])
+        self.slice_tag_ids = slice['slice_tag_ids']
+    
+    """
+    Use with tags that can have more than one instance
+    """
+    def get_multi_tag(self, tagname, node = None):
+        tags = []
+        for i in self.slice_tag_ids:
+            tag = self.network.lookupSliceTag(i)
+            if tag.tagname == tagname:
+                if not (node and node.id != tag.node_id):
+                    tags.append(tag)
+        return tags
+        
+    """
+    Use with tags that have only one instance
+    """
+    def get_tag(self, tagname, node = None):
+        for i in self.slice_tag_ids:
+            tag = self.network.lookupSliceTag(i)
+            if tag.tagname == tagname:
+                if (not node) or (node.id == tag.node_id):
+                    return tag
+        return None
+        
+    def get_nodes(self):
+        n = []
+        for id in self.node_ids:
+            if id in self.network.nodes:
+                n.append(self.network.nodes[id])
+        return n
+
+    # Add a new slice tag   
+    def add_tag(self, tagname, value, node = None, role_id = 40):
+        tt = self.network.lookupTagType(tagname)
+        if not tt.permit_update(role_id):
+            raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)
+        tag = Slicetag()
+        tag.initialize(tagname, value, node, self.network)
+        self.network.tags[tag.id] = tag
+        self.slice_tag_ids.append(tag.id)
+        return tag
+    
+    # Update a slice tag if it exists, else add it             
+    def update_tag(self, tagname, value, node = None, role_id = 40):
+        tag = self.get_tag(tagname, node)
+        if tag:
+            if not tag.permit_update(role_id, value):
+                raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)
+            tag.change(value)
+        else:
+            tag = self.add_tag(tagname, value, node, role_id)
+        return tag
+            
+    def update_multi_tag(self, tagname, value, node = None, role_id = 40):
+        tags = self.get_multi_tag(tagname, node)
+        for tag in tags:
+            if tag and tag.value == value:
+                break
+        else:
+            tag = self.add_tag(tagname, value, node, role_id)
+        return tag
+            
+    def tags_to_xml(self, xml, node = None):
+        tagtypes = self.network.getTagTypes()
+        for tt in tagtypes:
+            if tt.in_rspec:
+                if tt.multi:
+                    tags = self.get_multi_tag(tt.tagname, node)
+                    for tag in tags:
+                        if not tag.was_deleted():  ### Debugging
+                            xml << (tag.tagname, tag.value)
+                else:
+                    tag = self.get_tag(tt.tagname, node)
+                    if tag:
+                        if not tag.was_deleted():   ### Debugging
+                            xml << (tag.tagname, tag.value)
+
+    def toxml(self, xml):
+        with xml.sliver_defaults:
+            self.tags_to_xml(xml)
+
+
+class Slicetag:
+    newid = -1 
+    def __init__(self, tag = None):
+        if not tag:
+            return
+        self.id = tag['slice_tag_id']
+        self.slice_id = tag['slice_id']
+        self.tagname = tag['tagname']
+        self.value = tag['value']
+        self.node_id = tag['node_id']
+        self.category = tag['category']
+        self.min_role_id = tag['min_role_id']
+        self.status = None
+
+    # Create a new slicetag that will be written to the DB later
+    def initialize(self, tagname, value, node, network):
+        tt = network.lookupTagType(tagname)
+        self.id = Slicetag.newid
+        Slicetag.newid -=1
+        self.slice_id = network.slice.id
+        self.tagname = tagname
+        self.value = value
+        if node:
+            self.node_id = node.id
+        else:
+            self.node_id = None
+        self.category = tt.category
+        self.min_role_id = tt.min_role_id
+        self.status = "new"
+
+    def permit_update(self, role_id, value = None):
+        if value and self.value == value:
+            return True
+        if role_id > self.min_role_id:
+            return False
+        return True
+        
+    def change(self, value):
+        if self.value != value:
+            self.value = value
+            self.status = "change"
+        else:
+            self.status = "updated"
+        
+    # Mark a tag as deleted
+    def delete(self):
+        self.status = "delete"
+
+    def was_added(self):
+        return (self.id < 0)
+
+    def was_changed(self):
+        return (self.status == "change")
+
+    def was_deleted(self):
+        return (self.status == "delete")
+
+    def was_updated(self):
+        return (self.status != None)
+    
+    def write(self, api):
+        if self.was_added():
+            api.plshell.AddSliceTag(api.plauth, self.slice_id, 
+                                    self.tagname, self.value, self.node_id)
+        elif self.was_changed():
+            api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
+        elif self.was_deleted():
+            api.plshell.DeleteSliceTag(api.plauth, self.id)
+
+
+class TagType:
+    def __init__(self, tagtype):
+        self.id = tagtype['tag_type_id']
+        self.category = tagtype['category']
+        self.tagname = tagtype['tagname']
+        self.min_role_id = tagtype['min_role_id']
+        self.multi = False
+        self.in_rspec = False
+        if self.category == 'slice/rspec':
+            self.in_rspec = True
+        if self.tagname in ['codemux', 'ip_addresses', 'vsys']:
+            self.multi = True
+
+    def permit_update(self, role_id):
+        if role_id > self.min_role_id:
+            return False
+        return True
+        
+
+class Network:
+    """
+    A Network is a compound object consisting of:
+    * a dictionary mapping site IDs to Site objects
+    * a dictionary mapping node IDs to Node objects
+    * a dictionary mapping interface IDs to Iface objects
+    """
+    def __init__(self, api, type = "SFA"):
+        self.api = api
+        self.type = type
+        self.sites = self.get_sites(api)
+        self.nodes = self.get_nodes(api)
+        self.ifaces = self.get_ifaces(api)
+        self.tags = self.get_slice_tags(api)
+        self.tagtypes = self.get_tag_types(api)
+        self.slice = None
+    
+    def lookupSite(self, id):
+        """ Lookup site based on id or idtag value """
+        val = None
+        if isinstance(id, basestring):
+            id = int(id.lstrip('s'))
+        try:
+            val = self.sites[id]
+        except:
+            raise InvalidRSpec("site ID %s not found" % id)
+        return val
+    
+    def getSites(self):
+        sites = []
+        for s in self.sites:
+            sites.append(self.sites[s])
+        return sites
+        
+    def lookupNode(self, id):
+        """ Lookup node based on id or idtag value """
+        val = None
+        if isinstance(id, basestring):
+            id = int(id.lstrip('n'))
+        try:
+            val = self.nodes[id]
+        except:
+            raise InvalidRSpec("node ID %s not found" % id)
+        return val
+    
+    def getNodes(self):
+        nodes = []
+        for n in self.nodes:
+            nodes.append(self.nodes[n])
+        return nodes
+    
+    def lookupIface(self, id):
+        """ Lookup iface based on id or idtag value """
+        val = None
+        if isinstance(id, basestring):
+            id = int(id.lstrip('i'))
+        try:
+            val = self.ifaces[id]
+        except:
+            raise InvalidRSpec("interface ID %s not found" % id)
+        return val
+    
+    def getIfaces(self):
+        ifaces = []
+        for i in self.ifaces:
+            ifaces.append(self.ifaces[i])
+        return ifaces
+    
+    def nodesWithSlivers(self):
+        nodes = []
+        for n in self.nodes:
+            node = self.nodes[n]
+            if node.sliver:
+                nodes.append(node)
+        return nodes
+            
+    def lookupSliceTag(self, id):
+        val = None
+        try:
+            val = self.tags[id]
+        except:
+            raise InvalidRSpec("slicetag ID %s not found" % id)
+        return val
+    
+    def getSliceTags(self):
+        tags = []
+        for t in self.tags:
+            tags.append(self.tags[t])
+        return tags
+    
+    def lookupTagType(self, name):
+        val = None
+        try:
+            val = self.tagtypes[name]
+        except:
+            raise InvalidRSpec("tag %s not found" % name)
+        return val
+    
+    def getTagTypes(self):
+        tags = []
+        for t in self.tagtypes:
+            tags.append(self.tagtypes[t])
+        return tags
+    
+    def __process_attributes(self, element, node=None):
+        """
+        Process the elements under <sliver_defaults> or <sliver>
+        """
+        if element is None:
+            return 
+
+        tagtypes = self.getTagTypes()
+        for tt in tagtypes:
+            if tt.in_rspec:
+                if tt.multi:
+                    for e in element.iterfind("./" + tt.tagname):
+                        self.slice.update_multi_tag(tt.tagname, e.text, node)
+                else:
+                    e = element.find("./" + tt.tagname)
+                    if e is not None:
+                        self.slice.update_tag(tt.tagname, e.text, node)
+
+    def addRSpec(self, xml, schema=None):
+        """
+        Annotate the objects in the Network with information from the RSpec
+        """
+        try:
+            tree = etree.parse(StringIO(xml))
+        except etree.XMLSyntaxError:
+            message = str(sys.exc_info()[1])
+            raise InvalidRSpec(message)
+
+        # Filter out stuff that's not for us
+        rspec = tree.getroot()
+        for network in rspec.iterfind("./network"):
+            if network.get("name") != self.api.hrn:
+                rspec.remove(network)
+        for request in rspec.iterfind("./request"):
+            if request.get("name") != self.api.hrn:
+                rspec.remove(request)
+
+        if schema:
+            # Validate the incoming request against the RelaxNG schema
+            relaxng_doc = etree.parse(schema)
+            relaxng = etree.RelaxNG(relaxng_doc)
+        
+            if not relaxng(tree):
+                error = relaxng.error_log.last_error
+                message = "%s (line %s)" % (error.message, error.line)
+                raise InvalidRSpec(message)
+
+        self.rspec = rspec
+
+        defaults = rspec.find(".//sliver_defaults")
+        self.__process_attributes(defaults)
+
+        # Find slivers under node elements
+        for sliver in rspec.iterfind("./network/site/node/sliver"):
+            elem = sliver.getparent()
+            try:
+                node = self.lookupNode(elem.get("id"))
+            except:
+                # Don't worry about nodes from other aggregates
+                pass
+            else:
+                node.add_sliver()
+                self.__process_attributes(sliver, node)
+
+        # Find slivers that specify nodeid
+        for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
+            try:
+                node = self.lookupNode(sliver.get("nodeid"))
+            except:
+                # Don't worry about nodes from other aggregates
+                pass
+            else:
+                node.add_sliver()
+                self.__process_attributes(sliver, node)
+
+        return
+
+    def addSlice(self):
+        """
+        Annotate the objects in the Network with information from the slice
+        """
+        slice = self.slice
+        if not slice:
+            raise InvalidRSpec("no slice associated with network")
+
+        for node in slice.get_nodes():
+            node.add_sliver()
+
+    def updateSliceTags(self):
+        """
+        Write any slice tags that have been added or modified back to the DB
+        """
+        for tag in self.getSliceTags():
+            if tag.category == 'slice/rspec' and not tag.was_updated() and tag.permit_update(None, 40):
+                # The user wants to delete this tag
+                tag.delete()
+
+        # Update slice tags in database
+        for tag in self.getSliceTags():
+            if tag.slice_id == self.slice.id:
+                tag.write(self.api) 
+
+    def toxml(self):
+        """
+        Produce XML directly from the topology specification.
+        """
+        xml = XMLBuilder(format = True, tab_step = "  ")
+        with xml.RSpec(type=self.type):
+            if self.slice:
+                element = xml.network(name=self.api.hrn, slice=self.slice.hrn)
+            else:
+                element = xml.network(name=self.api.hrn)
+                
+            with element:
+                if self.slice:
+                    self.slice.toxml(xml)
+                for site in self.getSites():
+                    site.toxml(xml)
+
+        header = '<?xml version="1.0"?>\n'
+        return header + str(xml)
+
+    def get_sites(self, api):
+        """
+        Create a dictionary of site objects keyed by site ID
+        """
+        tmp = []
+        for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
+            t = site['site_id'], Site(self, site)
+            tmp.append(t)
+        return dict(tmp)
+
+
+    def get_nodes(self, api):
+        """
+        Create a dictionary of node objects keyed by node ID
+        """
+        tmp = []
+        for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
+            t = node['node_id'], Node(self, node)
+            tmp.append(t)
+        return dict(tmp)
+
+    def get_ifaces(self, api):
+        """
+        Create a dictionary of node objects keyed by node ID
+        """
+        tmp = []
+        for iface in api.plshell.GetInterfaces(api.plauth):
+            t = iface['interface_id'], Iface(self, iface)
+            tmp.append(t)
+        return dict(tmp)
+
+    def get_slice_tags(self, api):
+        """
+        Create a dictionary of slicetag objects keyed by slice tag ID
+        """
+        tmp = []
+        for tag in api.plshell.GetSliceTags(api.plauth):
+            t = tag['slice_tag_id'], Slicetag(tag)
+            tmp.append(t)
+        return dict(tmp)
+    
+    def get_tag_types(self, api):
+        """
+        Create a list of tagtype obects keyed by tag name
+        """
+        tmp = []
+        for tag in api.plshell.GetTagTypes(api.plauth):
+            t = tag['tagname'], TagType(tag)
+            tmp.append(t)
+        return dict(tmp)
+    
+    def get_slice(self, api, hrn):
+        """
+        Return a Slice object for a single slice
+        """
+        slicename = hrn_to_pl_slicename(hrn)
+        slice = api.plshell.GetSlices(api.plauth, [slicename])
+        if len(slice):
+            self.slice = Slice(self, slicename, slice[0])
+            return self.slice
+        else:
+            return None
+    
+
diff --git a/sfa/plc/peers.py b/sfa/plc/peers.py
new file mode 100644 (file)
index 0000000..455c682
--- /dev/null
@@ -0,0 +1,36 @@
+from sfa.util.namespace import *
+from types import StringTypes
+
+def get_peer(api, hrn):
+    # Becaues of myplc federation,  we first need to determine if this
+    # slice belongs to out local plc or a myplc peer. We will assume it
+    # is a local site, unless we find out otherwise
+    peer = None
+
+    # get this slice's authority (site)
+    slice_authority = get_authority(hrn)
+
+    # get this site's authority (sfa root authority or sub authority)
+    site_authority = get_authority(slice_authority).lower()
+    # check if we are already peered with this site_authority, if so
+    peers = api.plshell.GetPeers(api.plauth, {}, \
+                    ['peer_id', 'peername', 'shortname', 'hrn_root'])
+    for peer_record in peers:
+        names = [name.lower() for name in peer_record.values() if isinstance(name, StringTypes)]
+        if site_authority in names:
+            peer = peer_record['shortname']
+
+    return peer
+
+
+def get_sfa_peer(api, hrn):
+    # return the authority for this hrn or None if we are the authority
+    sfa_peer = None
+    slice_authority = get_authority(hrn)
+    site_authority = get_authority(slice_authority)
+
+    if site_authority != api.hrn:
+        sfa_peer = site_authority
+
+    return sfa_peer
+
diff --git a/sfa/plc/remoteshell.py b/sfa/plc/remoteshell.py
new file mode 100644 (file)
index 0000000..1241aef
--- /dev/null
@@ -0,0 +1,107 @@
+# remoteshell.py
+#
+# interface to the PLC api via xmlrpc
+#
+# RemoteShell() exports an API that looks identical to that exported by
+# PLC.Shell.Shell(). It's meant to be a drop in replacement for running
+# SFA on a different machine than PLC.
+
+### $Id: remoteshell.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/remoteshell.py $
+
+import xmlrpclib
+
+class RemoteShell:
+    def __init__(self):
+        self.servers = {}
+
+    def call(self, name, pl_auth, *args):
+
+        key = pl_auth["Url"] + "#" + pl_auth["Username"]
+
+        if not (key in self.servers):
+            server = xmlrpclib.Server(pl_auth["Url"], verbose = 0, allow_none=True)
+            #server.AdmAuthCheck(pl_auth)
+            server.AuthCheck(pl_auth)
+            self.servers[key] = server
+
+        server = self.servers[key]
+
+        arglist = ["pl_auth"]
+        for arg in args:
+            arglist.append(repr(arg))
+
+        str = "server." + name + "(" + ",".join(arglist) + ")"
+        result = eval(str)
+
+        return result
+
+    # TODO: there's probably an automatic way to import all these stubs
+
+    def AddInitScript(self, pl_auth, *args):
+        return self.call("AddInitScript", pl_auth, *args)
+
+    def AddNode(self, pl_auth, *args):
+        return self.call("AddNode", pl_auth, *args)
+
+    def AddPerson(self, pl_auth, *args):
+        return self.call("AddPerson", pl_auth, *args)
+
+    def AddPersonToSlice(self, pl_auth, *args):
+        return self.call("AddPersonToSlice", pl_auth, *args)
+
+    def AddSite(self, pl_auth, *args):
+        return self.call("AddSite", pl_auth, *args)
+
+    def AddSlice(self, pl_auth, *args):
+        return self.call("AddSlice", pl_auth, *args)
+
+    def DeleteNode(self, pl_auth, *args):
+        return self.call("DeleteNode", pl_auth, *args)
+
+    def DeletePerson(self, pl_auth, *args):
+        return self.call("DeletePerson", pl_auth, *args)
+
+    def DeletePersonFromSlice(self, pl_auth, *args):
+        return self.call("DeletePersonFromSlice", pl_auth, *args)
+
+    def DeleteSite(self, pl_auth, *args):
+        return self.call("DeleteSite", pl_auth, *args)
+
+    def DeleteSlice(self, pl_auth, *args):
+        return self.call("DeleteSlice", pl_auth, *args)
+
+    def GetInitScripts(self, pl_auth, *args):
+        return self.call("GetInitScripts", pl_auth, *args)
+
+    def GetKeys(self, pl_auth, *args):
+        return self.call("GetKeys", pl_auth, *args)
+
+    def GetNodes(self, pl_auth, *args):
+        return self.call("GetNodes", pl_auth, *args)
+
+    def GetPersons(self, pl_auth, *args):
+        return self.call("GetPersons", pl_auth, *args)
+
+    def GetSites(self, pl_auth, *args):
+        return self.call("GetSites", pl_auth, *args)
+
+    def GetSliceAttributes(self, pl_auth, *args):
+        return self.call("GetSliceAttributes", pl_auth, *args)
+
+    def GetSlices(self, pl_auth, *args):
+        return self.call("GetSlices", pl_auth, *args)
+
+    def UpdateNode(self, pl_auth, *args):
+        return self.call("UpdateNode", pl_auth, *args)
+
+    def UpdatePerson(self, pl_auth, *args):
+        return self.call("UpdatePerson", pl_auth, *args)
+
+    def UpdateSite(self, pl_auth, *args):
+        return self.call("UpdateSite", pl_auth, *args)
+
+    def UpdateSlice(self, pl_auth, *args):
+        return self.call("UpdateSlice", pl_auth, *args)
+
+
diff --git a/sfa/plc/sfa-import-plc.py b/sfa/plc/sfa-import-plc.py
new file mode 100755 (executable)
index 0000000..a89eefa
--- /dev/null
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+#
+### $Id: sfa-import-plc.py 18534 2010-07-27 16:49:53Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/sfa-import-plc.py $
+#
+##
+# Import PLC records into the SFA database. It is indended that this tool be
+# run once to create SFA records that reflect the current state of the
+# planetlab database.
+#
+# The import tool assumes that the existing PLC hierarchy should all be part
+# of "planetlab.us" (see the root_auth and level1_auth variables below).
+#
+# Public keys are extracted from the users' SSH keys automatically and used to
+# create GIDs. This is relatively experimental as a custom tool had to be
+# written to perform conversion from SSH to OpenSSL format. It only supports
+# RSA keys at this time, not DSA keys.
+##
+
+import getopt
+import sys
+import tempfile
+import logging.handlers
+import logging
+from sfa.util.record import *
+from sfa.util.table import SfaTable
+from sfa.util.namespace import *
+from sfa.util.config import Config
+from sfa.trust.certificate import convert_public_key, Keypair
+from sfa.trust.trustedroot import *
+from sfa.trust.hierarchy import *
+from sfa.plc.api import *
+from sfa.trust.gid import create_uuid
+from sfa.plc.sfaImport import *
+from sfa.util.report import trace, error
+
+def process_options():
+   global hrn
+
+   (options, args) = getopt.getopt(sys.argv[1:], '', [])
+   for opt in options:
+       name = opt[0]
+       val = opt[1]
+
+
+def load_keys(filename):
+    keys = {}
+    tmp_dict = {}
+    try:
+        execfile(filename, tmp_dict)
+        if 'keys' in tmp_dict:
+            keys = tmp_dict['keys']
+        return keys
+    except:
+        return keys
+
+def save_keys(filename, keys):
+    f = open(filename, 'w')
+    f.write("keys = %s" % str(keys))
+    f.close()
+
+def main():
+    # setup the logger
+    LOGFILE='/var/log/sfa_import_plc.log'
+    logging.basicConfig(level=logging.INFO,
+                        format='%(asctime)s - %(message)s',
+                        filename=LOGFILE)
+    rotate_handler = logging.handlers.RotatingFileHandler(LOGFILE, maxBytes=1000000, backupCount=5) 
+    logger = logging.getLogger()
+    logger.addHandler(rotate_handler)
+    
+    process_options()
+    config = Config()
+    if not config.SFA_REGISTRY_ENABLED:
+        sys.exit(0)
+    root_auth = config.SFA_REGISTRY_ROOT_AUTH
+    interface_hrn = config.SFA_INTERFACE_HRN
+    keys_filename = config.config_path + os.sep + 'person_keys.py' 
+    sfaImporter = sfaImport(logger)
+    shell = sfaImporter.shell
+    plc_auth = sfaImporter.plc_auth 
+    AuthHierarchy = sfaImporter.AuthHierarchy
+    TrustedRoots = sfaImporter.TrustedRoots
+    table = SfaTable()
+
+    if not table.exists():
+       table.create()
+
+    # create root authority 
+    sfaImporter.create_top_level_auth_records(root_auth)
+    if not root_auth == interface_hrn:
+        sfaImporter.create_top_level_auth_records(interface_hrn)
+
+    trace("Import: adding " + interface_hrn + " to trusted list", logger)
+    authority = AuthHierarchy.get_auth_info(interface_hrn)
+    TrustedRoots.add_gid(authority.get_gid_object())
+
+    if ".vini" in interface_hrn and interface_hrn.endswith('vini'):
+        # create a fake internet2 site first
+        i2site = {'name': 'Internet2', 'abbreviated_name': 'I2',
+                    'login_base': 'internet2', 'site_id': -1}
+        sfaImporter.import_site(interface_hrn, i2site)
+   
+    # create dict of all existing sfa records
+    existing_records = {}
+    existing_hrns = []
+    key_ids = []
+    person_keys = {} 
+    results = table.find()
+    for result in results:
+        existing_records[(result['hrn'], result['type'])] = result
+        existing_hrns.append(result['hrn']) 
+            
+    # Get all plc sites
+    sites = shell.GetSites(plc_auth, {'peer_id': None})
+    sites_dict = {}
+    for site in sites:
+        sites_dict[site['login_base']] = site 
+    
+    # Get all plc users
+    persons = shell.GetPersons(plc_auth, {'peer_id': None, 'enabled': True}, ['person_id', 'email', 'key_ids', 'site_ids'])
+    persons_dict = {}
+    for person in persons:
+        persons_dict[person['person_id']] = person
+        key_ids.extend(person['key_ids'])
+
+    # Get all public keys
+    keys = shell.GetKeys(plc_auth, {'peer_id': None, 'key_id': key_ids})
+    keys_dict = {}
+    for key in keys:
+        keys_dict[key['key_id']] = key['key']
+
+    # create a dict of person keys keyed on key_id 
+    old_person_keys = load_keys(keys_filename)
+    for person in persons:
+        pubkeys = []
+        for key_id in person['key_ids']:
+            pubkeys.append(keys_dict[key_id])
+        person_keys[person['person_id']] = pubkeys
+
+    # Get all plc nodes  
+    nodes = shell.GetNodes(plc_auth, {'peer_id': None}, ['node_id', 'hostname', 'site_id'])
+    nodes_dict = {}
+    for node in nodes:
+        nodes_dict[node['node_id']] = node
+
+    # Get all plc slices
+    slices = shell.GetSlices(plc_auth, {'peer_id': None}, ['slice_id', 'name'])
+    slices_dict = {}
+    for slice in slices:
+        slices_dict[slice['slice_id']] = slice
+    # start importing 
+    for site in sites:
+        site_hrn = interface_hrn + "." + site['login_base']
+        print "Importing site: %s" % site_hrn
+
+        # import if hrn is not in list of existing hrns or if the hrn exists
+        # but its not a site record
+        if site_hrn not in existing_hrns or \
+           (site_hrn, 'authority') not in existing_records:
+            site_hrn = sfaImporter.import_site(interface_hrn, site)
+             
+        # import node records
+        for node_id in site['node_ids']:
+            if node_id not in nodes_dict:
+                continue 
+            node = nodes_dict[node_id]
+            hrn =  hostname_to_hrn(interface_hrn, site['login_base'], node['hostname'])
+            if hrn not in existing_hrns or \
+               (hrn, 'node') not in existing_records:
+                sfaImporter.import_node(site_hrn, node)
+
+        # import slices
+        for slice_id in site['slice_ids']:
+            if slice_id not in slices_dict:
+                continue 
+            slice = slices_dict[slice_id]
+            hrn = slicename_to_hrn(interface_hrn, slice['name'])
+            if hrn not in existing_hrns or \
+               (hrn, 'slice') not in existing_records:
+                sfaImporter.import_slice(site_hrn, slice)      
+
+        # import persons
+        for person_id in site['person_ids']:
+            if person_id not in persons_dict:
+                continue 
+            person = persons_dict[person_id]
+            hrn = email_to_hrn(site_hrn, person['email'])
+            old_keys = []
+            new_keys = []
+            if person_id in old_person_keys:
+                old_keys = old_person_keys[person_id]
+            if person_id in person_keys:
+                new_keys = person_keys[person_id]
+            update_record = False
+            for key in new_keys:
+                if key not in old_keys:
+                    update_record = True 
+
+            if hrn not in existing_hrns or \
+               (hrn, 'user') not in existing_records or update_record:
+                sfaImporter.import_person(site_hrn, person)
+
+    # remove stale records    
+    for (record_hrn, type) in existing_records.keys():
+        record = existing_records[(record_hrn, type)]
+        # if this is the interface name dont do anything
+        if record_hrn == interface_hrn or \
+           record_hrn == root_auth or \
+           record['peer_authority']:
+            continue
+        # dont delete vini's internet2 placeholdder record
+        # normally this would be deleted becuase it does not have a plc record 
+        if ".vini" in interface_hrn and interface_hrn.endswith('vini') and \
+           record_hrn.endswith("internet2"):     
+            continue
+
+        found = False
+        
+        if type == 'authority':    
+            for site in sites:
+                site_hrn = interface_hrn + "." + site['login_base']
+                if site_hrn == record_hrn and site['site_id'] == record['pointer']:
+                    found = True
+                    break
+
+        elif type == 'user':
+            login_base = get_leaf(get_authority(record_hrn))
+            username = get_leaf(record_hrn)
+            if login_base in sites_dict:
+                site = sites_dict[login_base]
+                for person in persons:
+                    tmp_username = person['email'].split("@")[0]
+                    alt_username = person['email'].split("@")[0].replace(".", "_")
+                    if username in [tmp_username, alt_username] and \
+                       site['site_id'] in person['site_ids'] and \
+                       person['person_id'] == record['pointer']:
+                        found = True
+                        break
+        
+        elif type == 'slice':
+            slicename = hrn_to_pl_slicename(record_hrn)
+            for slice in slices:
+                if slicename == slice['name'] and \
+                   slice['slice_id'] == record['pointer']:
+                    found = True
+                    break    
+        elif type == 'node':
+            login_base = get_leaf(get_authority(record_hrn))
+            nodename = get_leaf(record_hrn)
+            if login_base in sites_dict:
+                site = sites_dict[login_base]
+                for node in nodes:
+                    tmp_nodename = node['hostname'].split(".")[0]
+                    if tmp_nodename == nodename and \
+                       node['site_id'] == site['site_id'] and \
+                       node['node_id'] == record['pointer']:
+                        found = True
+                        break  
+        else:
+            continue 
+        
+        if not found:
+            record_object = existing_records[(record_hrn, type)]
+            sfaImporter.delete_record(record_hrn, type) 
+                                   
+    # save pub keys
+    trace('Import: saving current pub keys', logger)
+    save_keys(keys_filename, person_keys)                
+        
+if __name__ == "__main__":
+    main()
diff --git a/sfa/plc/sfa-nuke-plc.py b/sfa/plc/sfa-nuke-plc.py
new file mode 100755 (executable)
index 0000000..2b1b41f
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+##
+# Delete all the database records for SFA. This tool is used to clean out SFA
+# records during testing.
+#
+# Authority info (maintained by the hierarchy module in a subdirectory tree)
+# is not purged by this tool and may be deleted by a command like 'rm'.
+##
+
+import getopt
+import sys
+
+from sfa.trust.hierarchy import *
+from sfa.util.record import *
+from sfa.util.table import SfaTable
+
+def process_options():
+
+   (options, args) = getopt.getopt(sys.argv[1:], '', [])
+   for opt in options:
+       name = opt[0]
+       val = opt[1]
+
+def main():
+    process_options()
+
+    print "Purging SFA records from database"
+    table = SfaTable()
+    table.sfa_records_purge()
+
+if __name__ == "__main__":
+    main()
diff --git a/sfa/plc/sfaImport.py b/sfa/plc/sfaImport.py
new file mode 100644 (file)
index 0000000..b95bc5a
--- /dev/null
@@ -0,0 +1,246 @@
+#
+# The import tool assumes that the existing PLC hierarchy should all be part
+# of "planetlab.us" (see the root_auth and level1_auth variables below).
+#
+# Public keys are extracted from the users' SSH keys automatically and used to
+# create GIDs. This is relatively experimental as a custom tool had to be
+# written to perform conversion from SSH to OpenSSL format. It only supports
+# RSA keys at this time, not DSA keys.
+##
+
+import getopt
+import sys
+import tempfile
+
+from sfa.util.record import *
+from sfa.util.table import SfaTable
+from sfa.util.namespace import *
+from sfa.util.config import Config
+from sfa.util.report import trace, error
+from sfa.trust.certificate import convert_public_key, Keypair
+from sfa.trust.trustedroot import *
+from sfa.trust.hierarchy import *
+from sfa.trust.gid import create_uuid
+
+
+def un_unicode(str):
+   if isinstance(str, unicode):
+       return str.encode("ascii", "ignore")
+   else:
+       return str
+
+def cleanup_string(str):
+    # pgsql has a fit with strings that have high ascii in them, so filter it
+    # out when generating the hrns.
+    tmp = ""
+    for c in str:
+        if ord(c) < 128:
+            tmp = tmp + c
+    str = tmp
+
+    str = un_unicode(str)
+    str = str.replace(" ", "_")
+    str = str.replace(".", "_")
+    str = str.replace("(", "_")
+    str = str.replace("'", "_")
+    str = str.replace(")", "_")
+    str = str.replace('"', "_")
+    return str
+
+class sfaImport:
+
+    def __init__(self, logger=None):
+        self.logger = logger
+        self.AuthHierarchy = Hierarchy()
+        self.config = Config()
+        self.TrustedRoots = TrustedRootList(Config.get_trustedroots_dir(self.config))
+        self.plc_auth = self.config.get_plc_auth()
+        self.root_auth = self.config.SFA_REGISTRY_ROOT_AUTH
+        
+        # connect to planetlab
+        self.shell = None
+        if "Url" in self.plc_auth:
+            from sfa.plc.remoteshell import RemoteShell
+            self.shell = RemoteShell()
+        else:
+            import PLC.Shell
+            self.shell = PLC.Shell.Shell(globals = globals())        
+
+
+    def create_top_level_auth_records(self, hrn):
+        AuthHierarchy = self.AuthHierarchy
+        urn = hrn_to_urn(hrn, 'authority')
+        # make sure parent exists
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn:
+            parent_hrn = hrn
+        if not parent_hrn == hrn:
+            self.create_top_level_auth_records(parent_hrn)
+
+        # create the authority if it doesnt already exist 
+        if not AuthHierarchy.auth_exists(urn):
+            trace("Import: creating top level authorites", self.logger)
+            AuthHierarchy.create_auth(urn)
+        
+        # create the db record if it doesnt already exist    
+        auth_info = AuthHierarchy.get_auth_info(hrn)
+        table = SfaTable()
+        auth_record = table.find({'type': 'authority', 'hrn': hrn})
+
+        if not auth_record:
+            auth_record = SfaRecord(hrn=hrn, gid=auth_info.get_gid_object(), type="authority", pointer=-1)
+            auth_record['authority'] = get_authority(auth_record['hrn'])
+            trace("Import: inserting authority record for " + hrn, self.logger)
+            table.insert(auth_record)
+
+
+    def import_person(self, parent_hrn, person):
+        AuthHierarchy = self.AuthHierarchy
+        hrn = email_to_hrn(parent_hrn, person['email'])
+
+        # ASN.1 will have problems with hrn's longer than 64 characters
+        if len(hrn) > 64:
+            hrn = hrn[:64]
+
+        trace("Import: importing person " + hrn, self.logger)
+        key_ids = []
+        if 'key_ids' in person and person['key_ids']:
+            key_ids = person["key_ids"]
+            # get the user's private key from the SSH keys they have uploaded
+            # to planetlab
+            keys = self.shell.GetKeys(self.plc_auth, key_ids)
+            key = keys[0]['key']
+            pkey = convert_public_key(key)
+            if not pkey:
+                pkey = Keypair(create=True)
+        else:
+            # the user has no keys
+            trace("   person " + hrn + " does not have a PL public key", self.logger)
+            # if a key is unavailable, then we still need to put something in the
+            # user's GID. So make one up.
+            pkey = Keypair(create=True)
+
+        # create the gid
+        urn = hrn_to_urn(hrn, 'user')
+        person_gid = AuthHierarchy.create_gid(urn, create_uuid(), pkey)
+        table = SfaTable()
+        person_record = SfaRecord(hrn=hrn, gid=person_gid, type="user", pointer=person['person_id'])
+        person_record['authority'] = get_authority(person_record['hrn'])
+        existing_records = table.find({'hrn': hrn, 'type': 'user', 'pointer': person['person_id']})
+        if not existing_records:
+            table.insert(person_record)
+        else:
+            trace("Import: %s exists, updating " % hrn, self.logger)
+            existing_record = existing_records[0]
+            person_record['record_id'] = existing_record['record_id']
+            table.update(person_record)
+
+    def import_slice(self, parent_hrn, slice):
+        AuthHierarchy = self.AuthHierarchy
+        slicename = slice['name'].split("_",1)[-1]
+        slicename = cleanup_string(slicename)
+
+        if not slicename:
+            error("Import_Slice: failed to parse slice name " + slice['name'], self.logger)
+            return
+
+        hrn = parent_hrn + "." + slicename
+        trace("Import: importing slice " + hrn, self.logger)
+
+        pkey = Keypair(create=True)
+        urn = hrn_to_urn(hrn, 'slice')
+        slice_gid = AuthHierarchy.create_gid(urn, create_uuid(), pkey)
+        slice_record = SfaRecord(hrn=hrn, gid=slice_gid, type="slice", pointer=slice['slice_id'])
+        slice_record['authority'] = get_authority(slice_record['hrn'])
+        table = SfaTable()
+        existing_records = table.find({'hrn': hrn, 'type': 'slice', 'pointer': slice['slice_id']})
+        if not existing_records:
+            table.insert(slice_record)
+        else:
+            trace("Import: %s exists, updating " % hrn, self.logger)
+            existing_record = existing_records[0]
+            slice_record['record_id'] = existing_record['record_id']
+            table.update(slice_record)
+
+    def import_node(self, parent_hrn, node):
+        AuthHierarchy = self.AuthHierarchy
+        nodename = node['hostname'].split(".")[0]
+        nodename = cleanup_string(nodename)
+        
+        if not nodename:
+            error("Import_node: failed to parse node name " + node['hostname'], self.logger)
+            return
+
+        hrn = parent_hrn + "." + nodename
+        trace("Import: importing node " + hrn, self.logger)
+        # ASN.1 will have problems with hrn's longer than 64 characters
+        if len(hrn) > 64:
+            hrn = hrn[:64]
+
+        table = SfaTable()
+        node_record = table.find({'type': 'node', 'hrn': hrn})
+        pkey = Keypair(create=True)
+        urn = hrn_to_urn(hrn, 'node')
+        node_gid = AuthHierarchy.create_gid(urn, create_uuid(), pkey)
+        node_record = SfaRecord(hrn=hrn, gid=node_gid, type="node", pointer=node['node_id'])
+        node_record['authority'] = get_authority(node_record['hrn'])
+        existing_records = table.find({'hrn': hrn, 'type': 'node', 'pointer': node['node_id']})
+        if not existing_records:
+            table.insert(node_record)
+        else:
+            trace("Import: %s exists, updating " % hrn, self.logger)
+            existing_record = existing_records[0]
+            node_record['record_id'] = existing_record['record_id']
+            table.update(node_record)
+
+    
+    def import_site(self, parent_hrn, site):
+        AuthHierarchy = self.AuthHierarchy
+        shell = self.shell
+        plc_auth = self.plc_auth
+        sitename = site['login_base']
+        sitename = cleanup_string(sitename)
+        print 'importing site %s' % sitename
+        hrn = parent_hrn + "." + sitename
+        urn = hrn_to_urn(hrn, 'authority')
+        # Hardcode 'internet2' into the hrn for sites hosting
+        # internet2 nodes. This is a special operation for some vini
+        # sites only
+        if ".vini" in parent_hrn and parent_hrn.endswith('vini'):
+            if sitename.startswith("i2"):
+                #sitename = sitename.replace("ii", "")
+                hrn = ".".join([parent_hrn, "internet2", sitename])
+            elif sitename.startswith("nlr"):
+                #sitename = sitename.replace("nlr", "")
+                hrn = ".".join([parent_hrn, "internet2", sitename])
+
+        trace("Import: importing site " + hrn, self.logger)
+
+        # create the authority
+        if not AuthHierarchy.auth_exists(urn):
+            AuthHierarchy.create_auth(urn)
+
+        auth_info = AuthHierarchy.get_auth_info(urn)
+
+        table = SfaTable()
+        auth_record = SfaRecord(hrn=hrn, gid=auth_info.get_gid_object(), type="authority", pointer=site['site_id'])
+        auth_record['authority'] = get_authority(auth_record['hrn'])
+        existing_records = table.find({'hrn': hrn, 'type': 'authority', 'pointer': site['site_id']})
+        if not existing_records:
+            table.insert(auth_record)
+        else:
+            trace("Import: %s exists, updating " % hrn, self.logger)
+            existing_record = existing_records[0]
+            auth_record['record_id'] = existing_record['record_id']
+            table.update(auth_record)
+
+        return hrn
+
+
+    def delete_record(self, hrn, type):
+        # delete the record
+        table = SfaTable()
+        record_list = table.find({'type': type, 'hrn': hrn})
+        for record in record_list:
+            trace("Import: Removing record %s %s" % (type, hrn), self.logger)
+            table.remove(record)        
diff --git a/sfa/plc/slices.py b/sfa/plc/slices.py
new file mode 100644 (file)
index 0000000..2344bc8
--- /dev/null
@@ -0,0 +1,443 @@
+### $Id: slices.py 18613 2010-08-09 17:02:53Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/slices.py $
+
+import datetime
+import time
+import traceback
+import sys
+
+from types import StringTypes
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.record import SfaRecord
+from sfa.util.policy import Policy
+from sfa.util.prefixTree import prefixTree
+from sfa.util.debug import log
+
+MAXINT =  2L**31-1
+
+class Slices:
+
+    rspec_to_slice_tag = {'max_rate':'net_max_rate'}
+
+    def __init__(self, api, ttl = .5, origin_hrn=None):
+        self.api = api
+        #filepath = path + os.sep + filename
+        self.policy = Policy(self.api)    
+        self.origin_hrn = origin_hrn
+
+    def get_slivers(self, xrn, node=None):
+        hrn, type = urn_to_hrn(xrn)
+         
+        slice_name = hrn_to_pl_slicename(hrn)
+        # XX Should we just call PLCAPI.GetSliceTicket(slice_name) instead
+        # of doing all of this?
+        #return self.api.GetSliceTicket(self.auth, slice_name) 
+        
+        # from PLCAPI.GetSlivers.get_slivers()
+        slice_fields = ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids']
+        slices = self.api.plshell.GetSlices(self.api.plauth, slice_name, slice_fields)
+        # Build up list of users and slice attributes
+        person_ids = set()
+        all_slice_tag_ids = set()
+        for slice in slices:
+            person_ids.update(slice['person_ids'])
+            all_slice_tag_ids.update(slice['slice_tag_ids'])
+        person_ids = list(person_ids)
+        all_slice_tag_ids = list(all_slice_tag_ids)
+        # Get user information
+        all_persons_list = self.api.plshell.GetPersons(self.api.plauth, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids'])
+        all_persons = {}
+        for person in all_persons_list:
+            all_persons[person['person_id']] = person        
+
+        # Build up list of keys
+        key_ids = set()
+        for person in all_persons.values():
+            key_ids.update(person['key_ids'])
+        key_ids = list(key_ids)
+        # Get user account keys
+        all_keys_list = self.api.plshell.GetKeys(self.api.plauth, key_ids, ['key_id', 'key', 'key_type'])
+        all_keys = {}
+        for key in all_keys_list:
+            all_keys[key['key_id']] = key
+        # Get slice attributes
+        all_slice_tags_list = self.api.plshell.GetSliceTags(self.api.plauth, all_slice_tag_ids)
+        all_slice_tags = {}
+        for slice_tag in all_slice_tags_list:
+            all_slice_tags[slice_tag['slice_tag_id']] = slice_tag
+           
+        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_tags = []
+            for slice_tag_id in slice['slice_tag_ids']:
+                if slice_tag_id in all_slice_tags:
+                    slice_tags.append(all_slice_tags[slice_tag_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_tags):
+                    sliver_attributes.append(sliver_attribute['tagname'])
+                    attributes.append({'tagname': sliver_attribute['tagname'],
+                                    'value': sliver_attribute['value']})
+
+            # set nodegroup slice attributes
+            for slice_tag in filter(lambda a: a['nodegroup_id'] in node['nodegroup_ids'], slice_tags):
+                # Do not set any nodegroup slice attributes for
+                # which there is at least one sliver attribute
+                # already set.
+                if slice_tag not in slice_tags:
+                    attributes.append({'tagname': slice_tag['tagname'],
+                        'value': slice_tag['value']})
+
+            for slice_tag in filter(lambda a: a['node_id'] is None, slice_tags):
+                # Do not set any global slice attributes for
+                # which there is at least one sliver attribute
+                # already set.
+                if slice_tag['tagname'] not in sliver_attributes:
+                    attributes.append({'tagname': slice_tag['tagname'],
+                                   'value': slice_tag['value']})
+
+            # XXX Sanity check; though technically this should be a system invariant
+            # checked with an assertion
+            if slice['expires'] > MAXINT:  slice['expires']= MAXINT
+            
+            slivers.append({
+                'hrn': hrn,
+                'name': slice['name'],
+                'slice_id': slice['slice_id'],
+                'instantiation': slice['instantiation'],
+                'expires': slice['expires'],
+                'keys': keys,
+                'attributes': attributes
+            })
+
+        return slivers
+    def get_peer(self, xrn):
+        hrn, type = urn_to_hrn(xrn)
+        # Becaues of myplc federation,  we first need to determine if this
+        # slice belongs to out local plc or a myplc peer. We will assume it 
+        # is a local site, unless we find out otherwise  
+        peer = None
+
+        # get this slice's authority (site)
+        slice_authority = get_authority(hrn)
+
+        # get this site's authority (sfa root authority or sub authority)
+        site_authority = get_authority(slice_authority).lower()
+
+        # check if we are already peered with this site_authority, if so
+        peers = self.api.plshell.GetPeers(self.api.plauth, {}, ['peer_id', 'peername', 'shortname', 'hrn_root'])
+        for peer_record in peers:
+            names = [name.lower() for name in peer_record.values() if isinstance(name, StringTypes)]
+            if site_authority in names:
+                peer = peer_record['shortname']
+
+        return peer
+
+    def get_sfa_peer(self, xrn):
+        hrn, type = urn_to_hrn(xrn)
+
+        # return the authority for this hrn or None if we are the authority
+        sfa_peer = None
+        slice_authority = get_authority(hrn)
+        site_authority = get_authority(slice_authority)
+
+        if site_authority != self.api.hrn:
+            sfa_peer = site_authority
+
+        return sfa_peer 
+
+    def verify_site(self, registry, credential, slice_hrn, peer, sfa_peer, reg_objects=None):
+        authority = get_authority(slice_hrn)
+        authority_urn = hrn_to_urn(authority, 'authority')
+        
+        if reg_objects:
+            site = reg_objects['site']
+        else:
+            site_records = registry.Resolve(authority_urn, [credential])
+            site = {}            
+            for site_record in site_records:            
+                if site_record['type'] == 'authority':
+                    site = site_record
+            if not site:
+                raise RecordNotFound(authority)
+            
+        remote_site_id = site.pop('site_id')    
+                
+        login_base = get_leaf(authority)
+        sites = self.api.plshell.GetSites(self.api.plauth, login_base)
+
+        if not sites:
+            site_id = self.api.plshell.AddSite(self.api.plauth, site)
+            if peer:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'site', site_id, peer, remote_site_id)   
+            # mark this site as an sfa peer record
+            if sfa_peer and not reg_objects:
+                peer_dict = {'type': 'authority', 'hrn': authority, 'peer_authority': sfa_peer, 'pointer': site_id}
+                registry.register_peer_object(credential, peer_dict)
+        else:
+            site_id = sites[0]['site_id']
+            remote_site_id = sites[0]['peer_site_id']
+            
+           old_site = sites[0]
+           #the site is already on the remote agg. Let us update(e.g. max_slices field) it with the latest info.
+            self.sync_site(old_site, site, peer)
+
+
+        return (site_id, remote_site_id) 
+
+    def verify_slice(self, registry, credential, slice_hrn, site_id, remote_site_id, peer, sfa_peer, reg_objects=None):
+        slice = {}
+        slice_record = None
+        authority = get_authority(slice_hrn)
+
+        if reg_objects:
+            slice_record = reg_objects['slice_record']
+        else:
+            slice_records = registry.Resolve(slice_hrn, [credential])
+    
+            for record in slice_records:
+                if record['type'] in ['slice']:
+                    slice_record = record
+            if not slice_record:
+                raise RecordNotFound(hrn)
+            
+        
+        slicename = hrn_to_pl_slicename(slice_hrn)
+        parts = slicename.split("_")
+        login_base = parts[0]
+        slices = self.api.plshell.GetSlices(self.api.plauth, [slicename]) 
+        if not slices:
+            slice_fields = {}
+            slice_keys = ['name', 'url', 'description']
+            for key in slice_keys:
+                if key in slice_record and slice_record[key]:
+                    slice_fields[key] = slice_record[key]
+
+            # add the slice  
+            slice_id = self.api.plshell.AddSlice(self.api.plauth, slice_fields)
+            slice = slice_fields
+            slice['slice_id'] = slice_id
+
+            # mark this slice as an sfa peer record
+            if sfa_peer:
+                peer_dict = {'type': 'slice', 'hrn': slice_hrn, 'peer_authority': sfa_peer, 'pointer': slice_id}
+                registry.register_peer_object(credential, peer_dict)
+
+            #this belongs to a peer
+            if peer:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'slice', slice_id, peer, slice_record['pointer'])
+            slice['node_ids'] = []
+        else:
+            slice = slices[0]
+            slice_id = slice['slice_id']
+            site_id = slice['site_id']
+           #the slice is alredy on the remote agg. Let us update(e.g. expires field) it with the latest info.
+           self.sync_slice(slice, slice_record, peer)
+
+        slice['peer_slice_id'] = slice_record['pointer']
+        self.verify_persons(registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer, reg_objects)
+    
+        return slice        
+
+    def verify_persons(self, registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer, reg_objects=None):
+        # get the list of valid slice users from the registry and make 
+        # sure they are added to the slice 
+        slicename = hrn_to_pl_slicename(slice_record['hrn'])
+        if reg_objects:
+            researchers = reg_objects['users'].keys()
+        else:
+            researchers = slice_record.get('researcher', [])
+        for researcher in researchers:
+            if reg_objects:
+                person_dict = reg_objects['users'][researcher]
+            else:
+                person_records = registry.Resolve(researcher, [credential])
+                for record in person_records:
+                    if record['type'] in ['user'] and record['enabled']:
+                        person_record = record
+                if not person_record:
+                    return 1
+                person_dict = person_record
+
+            local_person=False
+            if peer:
+                peer_id = self.api.plshell.GetPeers(self.api.plauth, {'shortname': peer}, ['peer_id'])[0]['peer_id']
+                persons = self.api.plshell.GetPersons(self.api.plauth, {'email': [person_dict['email']], 'peer_id': peer_id}, ['person_id', 'key_ids'])
+                if not persons:
+                    persons = self.api.plshell.GetPersons(self.api.plauth, [person_dict['email']], ['person_id', 'key_ids'])
+                    if persons:
+                        local_person=True
+                        
+            else:
+                persons = self.api.plshell.GetPersons(self.api.plauth, [person_dict['email']], ['person_id', 'key_ids'])   
+        
+            if not persons:
+                person_id=self.api.plshell.AddPerson(self.api.plauth, person_dict)
+                self.api.plshell.UpdatePerson(self.api.plauth, person_id, {'enabled' : True})
+                
+                # mark this person as an sfa peer record
+                if sfa_peer:
+                    peer_dict = {'type': 'user', 'hrn': researcher, 'peer_authority': sfa_peer, 'pointer': person_id}
+                    registry.register_peer_object(credential, peer_dict)
+
+                if peer:
+                    self.api.plshell.BindObjectToPeer(self.api.plauth, 'person', person_id, peer, person_dict['pointer'])
+                key_ids = []
+            else:
+                person_id = persons[0]['person_id']
+                key_ids = persons[0]['key_ids']
+
+
+            # if this is a peer person, we must unbind them from the peer or PLCAPI will throw
+            # an error
+            if peer:
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'person', person_id, peer)
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'site', site_id,  peer)
+
+            self.api.plshell.AddPersonToSlice(self.api.plauth, person_dict['email'], slicename)
+            self.api.plshell.AddPersonToSite(self.api.plauth, person_dict['email'], site_id)
+            if peer and not local_person:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'person', person_id, peer, person_dict['pointer'])
+            if peer:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'site', site_id, peer, remote_site_id)
+            
+            self.verify_keys(registry, credential, person_dict, key_ids, person_id, peer, local_person)
+
+    def verify_keys(self, registry, credential, person_dict, key_ids, person_id,  peer, local_person):
+        keylist = self.api.plshell.GetKeys(self.api.plauth, key_ids, ['key'])
+        keys = [key['key'] for key in keylist]
+        
+        #add keys that arent already there
+        key_ids = person_dict['key_ids']
+        for personkey in person_dict['keys']:
+            if personkey not in keys:
+                key = {'key_type': 'ssh', 'key': personkey}
+                if peer:
+                    self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'person', person_id, peer)
+                key_id = self.api.plshell.AddPersonKey(self.api.plauth, person_dict['email'], key)
+                if peer and not local_person:
+                    self.api.plshell.BindObjectToPeer(self.api.plauth, 'person', person_id, peer, person_dict['pointer'])
+                if peer:
+                    try: self.api.plshell.BindObjectToPeer(self.api.plauth, 'key', key_id, peer, key_ids.pop(0))
+
+                    except: pass   
+
+    def create_slice_aggregate(self, xrn, rspec):
+        hrn, type = urn_to_hrn(xrn)
+        # Determine if this is a peer slice
+        peer = self.get_peer(hrn)
+        sfa_peer = self.get_sfa_peer(hrn)
+
+        spec = RSpec(rspec)
+        # Get the slice record from sfa
+        slicename = hrn_to_pl_slicename(hrn) 
+        slice = {}
+        slice_record = None
+        registry = self.api.registries[self.api.hrn]
+        credential = self.api.getCredential()
+
+        site_id, remote_site_id = self.verify_site(registry, credential, hrn, peer, sfa_peer)
+        slice = self.verify_slice(registry, credential, hrn, site_id, remote_site_id, peer, sfa_peer)
+
+        # find out where this slice is currently running
+        nodelist = self.api.plshell.GetNodes(self.api.plauth, slice['node_ids'], ['hostname'])
+        hostnames = [node['hostname'] for node in nodelist]
+
+        # get netspec details
+        nodespecs = spec.getDictsByTagName('NodeSpec')
+
+        # dict in which to store slice attributes to set for the nodes
+        nodes = {}
+        for nodespec in nodespecs:
+            if isinstance(nodespec['name'], list):
+                for nodename in nodespec['name']:
+                    nodes[nodename] = {}
+                    for k in nodespec.keys():
+                        rspec_attribute_value = nodespec[k]
+                        if (self.rspec_to_slice_tag.has_key(k)):
+                            slice_tag_name = self.rspec_to_slice_tag[k]
+                            nodes[nodename][slice_tag_name] = rspec_attribute_value
+            elif isinstance(nodespec['name'], StringTypes):
+                nodename = nodespec['name']
+                nodes[nodename] = {}
+                for k in nodespec.keys():
+                    rspec_attribute_value = nodespec[k]
+                    if (self.rspec_to_slice_tag.has_key(k)):
+                        slice_tag_name = self.rspec_to_slice_tag[k]
+                        nodes[nodename][slice_tag_name] = rspec_attribute_value
+
+                for k in nodespec.keys():
+                    rspec_attribute_value = nodespec[k]
+                    if (self.rspec_to_slice_tag.has_key(k)):
+                        slice_tag_name = self.rspec_to_slice_tag[k]
+                        nodes[nodename][slice_tag_name] = rspec_attribute_value
+
+        node_names = nodes.keys()
+        # remove nodes not in rspec
+        deleted_nodes = list(set(hostnames).difference(node_names))
+        # add nodes from rspec
+        added_nodes = list(set(node_names).difference(hostnames))
+
+        if peer:
+            self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'slice', slice['slice_id'], peer)
+
+        self.api.plshell.AddSliceToNodes(self.api.plauth, slicename, added_nodes) 
+
+        # Add recognized slice tags
+        for node_name in node_names:
+            node = nodes[node_name]
+            for slice_tag in node.keys():
+                value = node[slice_tag]
+                if (isinstance(value, list)):
+                    value = value[0]
+
+                self.api.plshell.AddSliceTag(self.api.plauth, slicename, slice_tag, value, node_name)
+
+        self.api.plshell.DeleteSliceFromNodes(self.api.plauth, slicename, deleted_nodes)
+        if peer:
+            self.api.plshell.BindObjectToPeer(self.api.plauth, 'slice', slice['slice_id'], peer, slice['peer_slice_id'])
+
+        return 1
+
+    def sync_site(self, old_record, new_record, peer):
+        if old_record['max_slices'] != new_record['max_slices'] or old_record['max_slivers'] != new_record['max_slivers']:
+            if peer:
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'site', old_record['site_id'], peer)
+           if old_record['max_slices'] != new_record['max_slices']:
+                self.api.plshell.UpdateSite(self.api.plauth, old_record['site_id'], {'max_slices' : new_record['max_slices']})
+           if old_record['max_slivers'] != new_record['max_slivers']:
+               self.api.plshell.UpdateSite(self.api.plauth, old_record['site_id'], {'max_slivers' : new_record['max_slivers']})
+           if peer:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'site', old_record['site_id'], peer, old_record['peer_site_id'])
+       return 1
+
+    def sync_slice(self, old_record, new_record, peer):
+        if old_record['expires'] != new_record['expires']:
+            if peer:
+                self.api.plshell.UnBindObjectFromPeer(self.api.plauth, 'slice', old_record['slice_id'], peer)
+            self.api.plshell.UpdateSlice(self.api.plauth, old_record['slice_id'], {'expires' : new_record['expires']})
+           if peer:
+                self.api.plshell.BindObjectToPeer(self.api.plauth, 'slice', old_record['slice_id'], peer, old_record['peer_slice_id'])
+       return 1
diff --git a/sfa/rspecs/__init__.py b/sfa/rspecs/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/rspecs/aggregates/__init__.py b/sfa/rspecs/aggregates/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/rspecs/aggregates/max.xml b/sfa/rspecs/aggregates/max.xml
new file mode 100644 (file)
index 0000000..f549ab7
--- /dev/null
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rspec id="max_rspec_slice1" xmlns="http://geni.maxgigapop.net/aggregate/rspec/20100412/" schemaLocation="http://geni.dragon.maxgigapop.net/max-rspec.xsd" 
+  xmlns:CtrlPlane="http://ogf.org/schema/network/topology/ctrlPlane/20080828/" CtrlPlane:schemaLocation="http://www.controlplane.net/idcp-v1.1/nmtopo-ctrlp.xsd">
+    <aggregate>geni.maxgigapop.net</aggregate>
+    <description>Example MAX RSpec</description>
+    <lifetime id="time-1271533930-1271563981">
+        <CtrlPlane:start type="CtrlPlane:TimeContent">1279848020</CtrlPlane:start>
+        <CtrlPlane:end type="CtrlPlane:TimeContent">1280712039</CtrlPlane:end>
+    </lifetime>
+    <computeResource id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1">
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3">
+            <address>206.196.176.55</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_1">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.10.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_1</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_2">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.30.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_2</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5">
+            <address>206.196.176.138</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_3">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.20.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_3</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_2">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.30.2/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_2</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+        <planetlabNodeSliver id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2">
+            <address>206.196.176.133</address>
+            <computeCapacity>
+                <cpuType>generic</cpuType>
+                <cpuSpeed>2.0GHz</cpuSpeed>
+                <numCpuCores>1</numCpuCores>
+                <memorySize>256MB</memorySize>
+                <diskSize>16GB</diskSize>
+            </computeCapacity>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_1">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.10.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab3:interface=eth1.any_1</peerNetworkInterface>
+            </networkInterface>
+            <networkInterface id="urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab2:interface=eth1.any_3">
+                <deviceType>Ethernet</deviceType>
+                <deviceName>eth1</deviceName>
+                <capacity>100Mbps</capacity>
+                <ipAddress>10.10.20.1/24</ipAddress>
+                <vlanRange>any</vlanRange>
+                <peerNetworkInterface>urn:aggregate=geni.maxgigapop.net:rspec=my-test-max-rspec-slice1:domain=dragon.maxgigapop.net:node=planetlab5:interface=eth1.any_3</peerNetworkInterface>
+            </networkInterface>
+        </planetlabNodeSliver>
+    </computeResource>
+</rspec>
diff --git a/sfa/rspecs/aggregates/openflow.xml b/sfa/rspecs/aggregates/openflow.xml
new file mode 100755 (executable)
index 0000000..791baab
--- /dev/null
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<tns:RSpec xmlns:tns="http://yuba.stanford.edu/egeni/rspec" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://yuba.stanford.edu/egeni/rspec stanford-rspec.xsd ">\r
+  <tns:version>tns:version</tns:version>\r
+  <tns:switchEntry>\r
+    <tns:node>\r
+      <tns:nodeId>tns:nodeId</tns:nodeId>\r
+      <tns:type>0</tns:type>\r
+      <tns:interfaceEntry>\r
+        <tns:port>0</tns:port>\r
+        <tns:remoteNodeId>tns:remoteNodeId</tns:remoteNodeId>\r
+        <tns:remotePort>0</tns:remotePort>\r
+        <tns:flowSpaceEntry>\r
+          <tns:policy>readonly</tns:policy>\r
+          <tns:dl_src>0F00</tns:dl_src>\r
+          <tns:dl_dst>0F00</tns:dl_dst>\r
+          <tns:dl_type>0</tns:dl_type>\r
+          <tns:vlan_id>0</tns:vlan_id>\r
+          <tns:ip_src>0F00</tns:ip_src>\r
+          <tns:ip_dst>0F00</tns:ip_dst>\r
+          <tns:ip_proto>0</tns:ip_proto>\r
+          <tns:tp_src>0</tns:tp_src>\r
+          <tns:tp_dst>0</tns:tp_dst>\r
+        </tns:flowSpaceEntry>\r
+        <tns:bandwidth>0.0</tns:bandwidth>\r
+        <tns:latency>0.0</tns:latency>\r
+        <tns:lossRate>0.0</tns:lossRate>\r
+      </tns:interfaceEntry>\r
+    </tns:node>\r
+    <tns:switchFeatures>tns:switchFeatures</tns:switchFeatures>\r
+    <tns:controllerUrl>tns:controllerUrl</tns:controllerUrl>\r
+  </tns:switchEntry>\r
+  <tns:hostEntry>\r
+    <tns:node>\r
+      <tns:nodeId>tns:nodeId</tns:nodeId>\r
+      <tns:type>0</tns:type>\r
+      <tns:interfaceEntry>\r
+        <tns:port>0</tns:port>\r
+        <tns:remoteNodeId>tns:remoteNodeId</tns:remoteNodeId>\r
+        <tns:remotePort>0</tns:remotePort>\r
+        <tns:flowSpaceEntry>\r
+          <tns:policy>readonly</tns:policy>\r
+          <tns:dl_src>0F00</tns:dl_src>\r
+          <tns:dl_dst>0F00</tns:dl_dst>\r
+          <tns:dl_type>0</tns:dl_type>\r
+          <tns:vlan_id>0</tns:vlan_id>\r
+          <tns:ip_src>0F00</tns:ip_src>\r
+          <tns:ip_dst>0F00</tns:ip_dst>\r
+          <tns:ip_proto>0</tns:ip_proto>\r
+          <tns:tp_src>0</tns:tp_src>\r
+          <tns:tp_dst>0</tns:tp_dst>\r
+        </tns:flowSpaceEntry>\r
+        <tns:bandwidth>0.0</tns:bandwidth>\r
+        <tns:latency>0.0</tns:latency>\r
+        <tns:lossRate>0.0</tns:lossRate>\r
+      </tns:interfaceEntry>\r
+    </tns:node>\r
+    <tns:cpuUtil>0.0</tns:cpuUtil>\r
+    <tns:memUtil>0.0</tns:memUtil>\r
+  </tns:hostEntry>\r
+  <tns:remoteNodeEntry>\r
+    <tns:remoteURL>tns:remoteURL</tns:remoteURL>\r
+    <tns:remoteType>tns:remoteType</tns:remoteType>\r
+    <tns:node>\r
+      <tns:nodeId>tns:nodeId</tns:nodeId>\r
+      <tns:type>0</tns:type>\r
+      <tns:interfaceEntry>\r
+        <tns:port>0</tns:port>\r
+        <tns:remoteNodeId>tns:remoteNodeId</tns:remoteNodeId>\r
+        <tns:remotePort>0</tns:remotePort>\r
+        <tns:flowSpaceEntry>\r
+          <tns:policy>readonly</tns:policy>\r
+          <tns:dl_src>0F00</tns:dl_src>\r
+          <tns:dl_dst>0F00</tns:dl_dst>\r
+          <tns:dl_type>0</tns:dl_type>\r
+          <tns:vlan_id>0</tns:vlan_id>\r
+          <tns:ip_src>0F00</tns:ip_src>\r
+          <tns:ip_dst>0F00</tns:ip_dst>\r
+          <tns:ip_proto>0</tns:ip_proto>\r
+          <tns:tp_src>0</tns:tp_src>\r
+          <tns:tp_dst>0</tns:tp_dst>\r
+        </tns:flowSpaceEntry>\r
+        <tns:bandwidth>0.0</tns:bandwidth>\r
+        <tns:latency>0.0</tns:latency>\r
+        <tns:lossRate>0.0</tns:lossRate>\r
+      </tns:interfaceEntry>\r
+    </tns:node>\r
+  </tns:remoteNodeEntry>\r
+  <tns:flowSpaceEntry>\r
+    <tns:policy>readonly</tns:policy>\r
+    <tns:dl_src>0F00</tns:dl_src>\r
+    <tns:dl_dst>0F00</tns:dl_dst>\r
+    <tns:dl_type>0</tns:dl_type>\r
+    <tns:vlan_id>0</tns:vlan_id>\r
+    <tns:ip_src>0F00</tns:ip_src>\r
+    <tns:ip_dst>0F00</tns:ip_dst>\r
+    <tns:ip_proto>0</tns:ip_proto>\r
+    <tns:tp_src>0</tns:tp_src>\r
+    <tns:tp_dst>0</tns:tp_dst>\r
+  </tns:flowSpaceEntry>\r
+</tns:RSpec>\r
+\r
diff --git a/sfa/rspecs/aggregates/rspec_manager_max.py b/sfa/rspecs/aggregates/rspec_manager_max.py
new file mode 100644 (file)
index 0000000..c6504b9
--- /dev/null
@@ -0,0 +1,331 @@
+#!/usr/bin/python
+
+from sfa.util.rspec import RSpec
+import sys
+import pdb
+from sfa.util.namespace import *
+from sfa.util.rspec import *
+from sfa.util.specdict import *
+from sfa.util.faults import *
+from sfa.util.storage import *
+from sfa.util.policy import Policy
+from sfa.util.debug import log
+from sfa.server.aggregate import Aggregates
+from sfa.server.registry import Registries
+from sfa.util.faults import *
+
+import xml.dom.minidom
+
+SFA_MAX_CONF_FILE = '/etc/sfa/max_allocations'
+SFA_MAX_DEFAULT_RSPEC = '/etc/sfa/max_physical.xml'
+SFA_MAX_CANNED_RSPEC = '/etc/sfa/max_physical_canned.xml'
+
+topology = {}
+
+class SfaOutOfResource(SfaFault):
+    def __init__(self, interface):
+        faultString = "Interface " + interface + " not available"
+        SfaFault.__init__(self, 100, faultString, '')
+
+class SfaNoPairRSpec(SfaFault):
+    def __init__(self, interface, interface2):
+        faultString = "Interface " + interface + " should be paired with " + interface2
+        SfaFault.__init__(self, 100, faultString, '')
+
+# Returns a mapping from interfaces to the nodes they lie on and their peer interfaces
+# i -> node,i_peer
+
+def get_interface_map():
+    r = RSpec()
+    r.parseFile(SFA_MAX_DEFAULT_RSPEC)
+    rspec = r.toDict()
+    capacity = rspec['rspec']['capacity']
+    netspec = capacity[0]['netspec'][0]
+    linkdefs = {}
+    for n in netspec['nodespec']:
+        ifspecs = n['ifspec']
+        nodename = n['node']
+        for i in ifspecs:
+            ifname = i['name']
+            linkid = i['linkid']
+
+            if (linkdefs.has_key(linkid)):
+                linkdefs[linkid].extend([(nodename,ifname)])
+            else:
+                linkdefs[linkid]=[(nodename,ifname)]
+    
+    # topology maps interface x interface -> link,node1,node2
+    topology={}
+
+    for k in linkdefs.keys():
+        (n1,i1) = linkdefs[k][0]
+        (n2,i2) = linkdefs[k][1]
+
+        topology[i1] = (n1, i2)
+        topology[i2] = (n2, i1)
+        
+
+    return topology    
+
+    
+def allocations_to_rspec(allocations):
+    rspec = xml.dom.minidom.parse(SFA_MAX_DEFAULT_RSPEC)
+    req = rspec.firstChild.appendChild(rspec.createElement("request"))
+    for (iname,ip) in allocations:
+        ifspec = req.appendChild(rspec.createElement("ifspec"))
+        ifspec.setAttribute("name","tns:"+iname)
+        ifspec.setAttribute("ip",ip)
+
+    return rspec.toxml()
+        
+    
+def if_endpoints(ifs):
+    nodes=[]
+    for l in ifs:
+        nodes.extend(topology[l][0])
+    return nodes
+
+def lock_state_file():
+    # Noop for demo
+    return True
+
+def unlock_state_file():
+    return True
+    # Noop for demo
+
+def read_alloc_dict():
+    alloc_dict={}
+    rows = open(SFA_MAX_CONF_FILE).read().split('\n')
+    for r in rows:
+        columns = r.split(' ')
+        if (len(columns)==2):
+            hrn = columns[0]
+            allocs = columns[1].split(',')
+            ipallocs = map(lambda alloc:alloc.split('/'), allocs)
+            alloc_dict[hrn]=ipallocs
+    return alloc_dict
+
+def commit_alloc_dict(d):
+    f = open(SFA_MAX_CONF_FILE, 'w')
+    for hrn in d.keys():
+        columns = d[hrn]
+        ipcolumns = map(lambda x:"/".join(x), columns)
+        row = hrn+' '+','.join(ipcolumns)+'\n'
+        f.write(row)
+    f.close()
+
+def collapse_alloc_dict(d):
+    ret = []
+    for k in d.keys():
+        ret.extend(d[k])
+    return ret
+
+
+def alloc_links(api, hrn, links_to_add, links_to_drop):
+    slicename=hrn_to_pl_slicename(hrn)
+    for (iface,ip) in links_to_add:
+        node = topology[iface][0][0]
+        try:
+            api.plshell.AddSliceTag(api.plauth, slicename, "ip_addresses", ip, node)
+            api.plshell.AddSliceTag(api.plauth, slicename, "vsys", "getvlan", node)
+        except Exception: 
+            # Probably a duplicate tag. XXX July 21
+            pass
+    return True
+
+def alloc_nodes(api,hrn, requested_ifs):
+    requested_nodes = if_endpoints(requested_ifs)
+    create_slice_max_aggregate(api, hrn, requested_nodes)
+
+# Taken from slices.py
+
+def create_slice_max_aggregate(api, hrn, nodes):
+    # Get the slice record from SFA
+    global topology
+    topology = get_interface_map()
+    slice = {}
+    registries = Registries(api)
+    registry = registries[api.hrn]
+    credential = api.getCredential()
+    records = registry.resolve(credential, hrn)
+    for record in records:
+        if record.get_type() in ['slice']:
+            slice = record.as_dict()
+    if not slice:
+        raise RecordNotFound(hrn)   
+
+    # Make sure slice exists at plc, if it doesnt add it
+    slicename = hrn_to_pl_slicename(hrn)
+    slices = api.plshell.GetSlices(api.plauth, [slicename], ['node_ids'])
+    if not slices:
+        parts = slicename.split("_")
+        login_base = parts[0]
+        # if site doesnt exist add it
+        sites = api.plshell.GetSites(api.plauth, [login_base])
+        if not sites:
+            authority = get_authority(hrn)
+            site_records = registry.resolve(credential, authority)
+            site_record = {}
+            if not site_records:
+                raise RecordNotFound(authority)
+            site_record = site_records[0]
+            site = site_record.as_dict()
+                
+            # add the site
+            site.pop('site_id')
+            site_id = api.plshell.AddSite(api.plauth, site)
+        else:
+            site = sites[0]
+            
+        slice_fields = {}
+        slice_keys = ['name', 'url', 'description']
+        for key in slice_keys:
+            if key in slice and slice[key]:
+                slice_fields[key] = slice[key]  
+        api.plshell.AddSlice(api.plauth, slice_fields)
+        slice = slice_fields
+        slice['node_ids'] = 0
+    else:
+        slice = slices[0]    
+
+    # get the list of valid slice users from the registry and make 
+    # they are added to the slice 
+    researchers = record.get('researcher', [])
+    for researcher in researchers:
+        person_record = {}
+        person_records = registry.resolve(credential, researcher)
+        for record in person_records:
+            if record.get_type() in ['user']:
+                person_record = record
+        if not person_record:
+            pass
+        person_dict = person_record.as_dict()
+        persons = api.plshell.GetPersons(api.plauth, [person_dict['email']],
+                                         ['person_id', 'key_ids'])
+
+        # Create the person record 
+        if not persons:
+            person_id=api.plshell.AddPerson(api.plauth, person_dict)
+
+            # The line below enables the user account on the remote aggregate
+            # soon after it is created.
+            # without this the user key is not transfered to the slice
+            # (as GetSlivers returns key of only enabled users),
+            # which prevents the user from login to the slice.
+            # We may do additional checks before enabling the user.
+
+            api.plshell.UpdatePerson(api.plauth, person_id, {'enabled' : True})
+            key_ids = []
+        else:
+            key_ids = persons[0]['key_ids']
+
+        api.plshell.AddPersonToSlice(api.plauth, person_dict['email'],
+                                     slicename)        
+
+        # Get this users local keys
+        keylist = api.plshell.GetKeys(api.plauth, key_ids, ['key'])
+        keys = [key['key'] for key in keylist]
+
+        # add keys that arent already there 
+        for personkey in person_dict['keys']:
+            if personkey not in keys:
+                key = {'key_type': 'ssh', 'key': personkey}
+                api.plshell.AddPersonKey(api.plauth, person_dict['email'], key)
+
+    # find out where this slice is currently running
+    nodelist = api.plshell.GetNodes(api.plauth, slice['node_ids'],
+                                    ['hostname'])
+    hostnames = [node['hostname'] for node in nodelist]
+
+    # remove nodes not in rspec
+    deleted_nodes = list(set(hostnames).difference(nodes))
+    # add nodes from rspec
+    added_nodes = list(set(nodes).difference(hostnames))
+
+    api.plshell.AddSliceToNodes(api.plauth, slicename, added_nodes) 
+    api.plshell.DeleteSliceFromNodes(api.plauth, slicename, deleted_nodes)
+
+    return 1
+
+
+def get_rspec(api, hrn):
+    # Eg. config line:
+    # plc.princeton.sapan vlan23,vlan45
+
+    allocations = read_alloc_dict()
+    if (hrn and allocations.has_key(hrn)):
+            ret_rspec = allocations_to_rspec(allocations[hrn])
+    else:
+        ret_rspec = open(SFA_MAX_CANNED_RSPEC).read()
+
+    return (ret_rspec)
+
+
+def create_slice(api, hrn, rspec_xml):
+    global topology
+    topology = get_interface_map()
+
+    # Check if everything in rspec is either allocated by hrn
+    # or not allocated at all.
+    r = RSpec()
+    r.parseString(rspec_xml)
+    rspec = r.toDict()
+
+    lock_state_file()
+
+    allocations = read_alloc_dict()
+    requested_allocations = rspec_to_allocations (rspec)
+    current_allocations = collapse_alloc_dict(allocations)
+    try:
+        current_hrn_allocations=allocations[hrn]
+    except KeyError:
+        current_hrn_allocations=[]
+
+    # Check request against current allocations
+    requested_interfaces = map(lambda(elt):elt[0], requested_allocations)
+    current_interfaces = map(lambda(elt):elt[0], current_allocations)
+    current_hrn_interfaces = map(lambda(elt):elt[0], current_hrn_allocations)
+
+    for a in requested_interfaces:
+        if (a not in current_hrn_interfaces and a in current_interfaces):
+            raise SfaOutOfResource(a)
+        if (topology[a][1] not in requested_interfaces):
+            raise SfaNoPairRSpec(a,topology[a][1])
+    # Request OK
+
+    # Allocations to delete
+    allocations_to_delete = []
+    for a in current_hrn_allocations:
+        if (a not in requested_allocations):
+            allocations_to_delete.extend([a])
+
+    # Ok, let's do our thing
+    alloc_nodes(api, hrn, requested_interfaces)
+    alloc_links(api, hrn, requested_allocations, allocations_to_delete)
+    allocations[hrn] = requested_allocations
+    commit_alloc_dict(allocations)
+
+    unlock_state_file()
+
+    return True
+
+def rspec_to_allocations(rspec):
+    ifs = []
+    try:
+        ifspecs = rspec['rspec']['request'][0]['ifspec']
+        for l in ifspecs:
+            ifs.extend([(l['name'].replace('tns:',''),l['ip'])])
+    except KeyError:
+        # Bad RSpec
+        pass
+    return ifs
+
+def main():
+    t = get_interface_map()
+    r = RSpec()
+    rspec_xml = open(sys.argv[1]).read()
+    #get_rspec(None,'foo')
+    create_slice(None, "plc.princeton.sap0", rspec_xml)
+    
+if __name__ == "__main__":
+    main()
diff --git a/sfa/rspecs/aggregates/rspec_manager_openflow.py b/sfa/rspecs/aggregates/rspec_manager_openflow.py
new file mode 100755 (executable)
index 0000000..a745b6e
--- /dev/null
@@ -0,0 +1,166 @@
+from sfa.util.faults import *
+from sfa.util.namespace import *
+from sfa.util.rspec import RSpec
+from sfa.server.registry import Registries
+from sfa.util.config import Config
+from sfa.plc.nodes import *
+import sys
+
+#The following is not essential
+#from soaplib.wsgi_soap import SimpleWSGISoapApp
+#from soaplib.serializers.primitive import *
+#from soaplib.serializers.clazz import *
+
+import socket
+import struct
+
+# Message IDs for all the SFA light calls
+# This will be used by the aggrMgr controller
+SFA_GET_RESOURCES = 101
+SFA_CREATE_SLICE = 102
+SFA_START_SLICE = 103
+SFA_STOP_SLICE = 104
+SFA_DELETE_SLICE = 105
+SFA_GET_SLICES = 106
+SFA_RESET_SLICES = 107
+
+DEBUG = 1
+
+def print_buffer(buf):
+    for i in range(0,len(buf)):
+        print('%x' % buf[i])
+
+def extract(sock):
+    # Shud we first obtain the message length?
+    # msg_len = socket.ntohs(sock.recv(2))
+    msg = ""
+
+    while (1):
+        try:
+            chunk = sock.recv(1)
+        except socket.error, message:
+            if 'timed out' in message:
+                break
+            else:
+                sys.exit("Socket error: " + message)
+
+        if len(chunk) == 0:
+            break
+        msg += chunk
+
+    print 'Done extracting %d bytes of response from aggrMgr' % len(msg)
+    return msg
+   
+def connect(server, port):
+    '''Connect to the Aggregate Manager module'''
+    sock = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
+    sock.connect ( ( server, port) )
+    sock.settimeout(1)
+    if DEBUG: print 'Connected!'
+    return sock
+    
+def connect_aggrMgr():
+    (aggr_mgr_ip, aggr_mgr_port) = Config().get_openflow_aggrMgr_info()
+    if DEBUG: print """Connecting to port %d of %s""" % (aggr_mgr_port, aggr_mgr_ip)
+    return connect(aggr_mgr_ip, aggr_mgr_port)
+
+def generate_slide_id(cred, hrn):
+    if cred == None:
+        cred = ""
+    if hrn == None:
+        hrn = ""
+    #return cred + '_' + hrn
+    return str(hrn)
+
+def msg_aggrMgr(cred, hrn, msg_id):
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds' % len(slice_id), msg_id, slice_id)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        aggrMgr_sock.close()
+        return 1
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return 0
+
+def start_slice(cred, hrn):
+    if DEBUG: print "Received start_slice call"
+    return msg_aggrMgr(SFA_START_SLICE)
+
+def stop_slice(cred, hrn):
+    if DEBUG: print "Received stop_slice call"
+    return msg_aggrMgr(SFA_STOP_SLICE)
+
+def delete_slice(cred, hrn):
+    if DEBUG: print "Received delete_slice call"
+    return msg_aggrMgr(SFA_DELETE_SLICE)
+
+def reset_slices(cred, hrn):
+    if DEBUG: print "Received reset_slices call"
+    return msg_aggrMgr(SFA_RESET_SLICES)
+
+def create_slice(cred, hrn, rspec):
+    if DEBUG: print "Received create_slice call"
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds%ds' % (len(slice_id)+1, len(rspec)), SFA_CREATE_SLICE, slice_id, rspec)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        if DEBUG: print "Sent %d bytes and closing connection" % len(buf)
+        aggrMgr_sock.close()
+
+        if DEBUG: print "----------------"
+        return 1
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return 0
+
+def get_rspec(cred, hrn=None):
+    if DEBUG: print "Received get_rspec call"
+    slice_id = generate_slide_id(cred, hrn)
+
+    msg = struct.pack('> B%ds' % len(slice_id), SFA_GET_RESOURCES, slice_id)
+    buf = struct.pack('> H', len(msg)+2) + msg
+
+    try:
+        aggrMgr_sock = connect_aggrMgr()
+        aggrMgr_sock.send(buf)
+        resource_list = extract(aggrMgr_sock);
+        aggrMgr_sock.close()
+
+        if DEBUG: print "----------------"
+        return resource_list 
+    except socket.error, message:
+        print "Socket error"
+    except IOerror, message:
+        print "IO error"
+    return None
+
+"""
+Returns the request context required by sfatables. At some point, this mechanism should be changed
+to refer to "contexts", which is the information that sfatables is requesting. But for now, we just
+return the basic information needed in a dict.
+"""
+def fetch_context(slice_hrn, user_hrn, contexts):
+    base_context = {'sfa':{'user':{'hrn':user_hrn}}}
+    return base_context
+
+def main():
+    r = RSpec()
+    r.parseFile(sys.argv[1])
+    rspec = r.toDict()
+    create_slice(None,'plc',rspec)
+    
+if __name__ == "__main__":
+    main()
diff --git a/sfa/rspecs/aggregates/rspec_manager_pl.py b/sfa/rspecs/aggregates/rspec_manager_pl.py
new file mode 100644 (file)
index 0000000..ffc0a2b
--- /dev/null
@@ -0,0 +1,8 @@
+"""
+Returns the request context required by sfatables. At some point, this mechanism should be changed
+to refer to "contexts", which is the information that sfatables is requesting. But for now, we just
+return the basic information needed in a dict.
+"""
+def fetch_context(slice_hrn, user_hrn, contexts):
+    base_context = {'sfa':{'user':{'hrn':user_hrn}}}
+    return base_context
diff --git a/sfa/server/__init__.py b/sfa/server/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/server/aggregate.py b/sfa/server/aggregate.py
new file mode 100644 (file)
index 0000000..07a8d20
--- /dev/null
@@ -0,0 +1,47 @@
+### $Id: aggregate.py 18632 2010-08-16 21:44:19Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/server/aggregate.py $
+
+
+from sfa.util.server import SfaServer
+from sfa.util.faults import *
+from sfa.util.namespace import hrn_to_urn
+from sfa.server.interface import Interfaces
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol
+import sfa.util.soapprotocol as soapprotocol
+
+
+class Aggregate(SfaServer):
+
+    ##
+    # Create a new aggregate object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key (could be a GID file)     
+    def __init__(self, ip, port, key_file, cert_file):
+        SfaServer.__init__(self, ip, port, key_file, cert_file)
+        self.server.interface = 'aggregate'
+
+##
+# Aggregates is a dictionary of aggregate connections keyed on the aggregate hrn
+
+class Aggregates(Interfaces):
+
+    default_dict = {'aggregates': {'aggregate': [Interfaces.default_fields]}}
+    def __init__(self, api, conf_file = "/etc/sfa/aggregates.xml"):
+        Interfaces.__init__(self, api, conf_file)
+        # set up a connection to the local registry
+        address = self.api.config.SFA_AGGREGATE_HOST
+        port = self.api.config.SFA_AGGREGATE_PORT
+        url = 'http://%(address)s:%(port)s' % locals()
+        local_aggregate = {'hrn': self.api.hrn,
+                           'urn': hrn_to_urn(self.api.hrn, 'authority'),
+                           'addr': address,
+                           'port': port,
+                           'url': url}
+        self.interfaces[self.api.hrn] = local_aggregate
+
+        # get connections
+        self.update(self.get_connections())
diff --git a/sfa/server/component.py b/sfa/server/component.py
new file mode 100644 (file)
index 0000000..c83ac4a
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# Component is a SfaServer that implements the Component interface
+#
+### $Id: 
+### $URL: 
+#
+
+import tempfile
+import os
+import time
+import sys
+
+from sfa.util.componentserver import ComponentServer
+# GeniLight client support is optional
+try:
+    from egeni.geniLight_client import *
+except ImportError:
+    GeniClientLight = None            
+
+##
+# Component is a SfaServer that serves component operations.
+
+class Component(ComponentServer):
+    ##
+    # Create a new registry object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key (could be a GID file)
+
+    def __init__(self, ip, port, key_file, cert_file):
+        ComponentServer.__init__(self, ip, port, key_file, cert_file)
+        self.server.interface = 'component'
diff --git a/sfa/server/interface.py b/sfa/server/interface.py
new file mode 100644 (file)
index 0000000..6492e70
--- /dev/null
@@ -0,0 +1,206 @@
+#
+### $Id: interface.py 17583 2010-04-06 15:01:08Z tmack $
+### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/server/interface.py $
+#
+
+from sfa.util.faults import *
+from sfa.util.storage import *
+from sfa.util.namespace import *
+from sfa.trust.gid import GID
+from sfa.util.table import SfaTable
+from sfa.util.record import SfaRecord
+import traceback
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol
+import sfa.util.soapprotocol as soapprotocol
+
+
+# GeniLight client support is optional
+try:
+    from egeni.geniLight_client import *
+except ImportError:
+    GeniClientLight = None            
+
+
+##
+# In is a dictionary of registry connections keyed on the registry
+# hrn
+
+class Interfaces(dict):
+    """
+    Interfaces is a base class for managing information on the
+    peers we are federated with. It is responsible for the following:
+
+    1) Makes sure a record exist in the local registry for the each 
+       fedeated peer   
+    2) Attepts to fetch and install trusted gids   
+    3) Provides connections (xmlrpc or soap) to federated peers
+    """
+
+    # fields that must be specified in the config file
+    default_fields = {
+        'hrn': '',
+        'addr': '', 
+        'port': '', 
+    }
+
+    # defined by the class 
+    default_dict = {}
+
+    types = ['authority']
+
+    def __init__(self, api, conf_file, type='authority'):
+        if type not in self.types:
+            raise SfaInfaildArgument('Invalid type %s: must be in %s' % (type, self.types))    
+        dict.__init__(self, {})
+        self.api = api
+        self.type = type  
+        # load config file
+        self.interface_info = XmlStorage(conf_file, self.default_dict)
+        self.interface_info.load()
+        interfaces = self.interface_info.values()[0].values()[0]
+        if not isinstance(interfaces, list):
+            interfaces = [self.interfaces]
+        # set the url and urn 
+        for interface in interfaces:
+            hrn, address, port = interface['hrn'], interface['addr'], interface['port']
+            url = 'http://%(address)s:%(port)s' % locals()
+            interface['url'] = url
+            interface['urn'] = hrn_to_urn(hrn, 'authority')
+    
+        self.interfaces = {}
+        required_fields = self.default_fields.keys()
+        for interface in interfaces:
+            valid = True
+            # skp any interface definition that has a null hrn, 
+            # address or port
+            for field in required_fields:
+                if field not in interface or not interface[field]:
+                    valid = False
+                    break
+            if valid:     
+                self.interfaces[interface['hrn']] = interface
+
+
+    def sync_interfaces(self):
+        """
+        Install missing trusted gids and db records for our federated
+        interfaces
+        """     
+        # Attempt to get any missing peer gids
+        # There should be a gid file in /etc/sfa/trusted_roots for every
+        # peer registry found in in the registries.xml config file. If there
+        # are any missing gids, request a new one from the peer registry.
+        gids_current = self.api.auth.trusted_cert_list
+        hrns_current = [gid.get_hrn() for gid in gids_current] 
+        hrns_expected = self.interfaces.keys() 
+        new_hrns = set(hrns_expected).difference(hrns_current)
+        gids = self.get_peer_gids(new_hrns) + gids_current
+        # make sure there is a record for every gid
+        self.update_db_records(self.type, gids)
+        
+    def get_peer_gids(self, new_hrns):
+        """
+        Install trusted gids from the specified interfaces.  
+        """
+        peer_gids = []
+        if not new_hrns:
+            return peer_gids
+        trusted_certs_dir = self.api.config.get_trustedroots_dir()
+        for new_hrn in new_hrns:
+            if not new_hrn:
+                continue
+            # the gid for this interface should already be installed  
+            if new_hrn == self.api.config.SFA_INTERFACE_HRN:
+                continue
+            try:
+                # get gid from the registry
+                interface_info =  self.interfaces[new_hrn]
+                interface = self[new_hrn]
+                trusted_gids = interface.get_trusted_certs()
+                if trusted_gids:
+                    # the gid we want shoudl be the first one in the list, 
+                    # but lets make sure
+                    for trusted_gid in trusted_gids:
+                        # default message
+                        message = "interface: %s\t" % (self.api.interface)
+                        message += "unable to install trusted gid for %s" % \
+                                   (new_hrn) 
+                        gid = GID(string=trusted_gids[0])
+                        peer_gids.append(gid) 
+                        if gid.get_hrn() == new_hrn:
+                            gid_filename = os.path.join(trusted_certs_dir, '%s.gid' % new_hrn)
+                            gid.save_to_file(gid_filename, save_parents=True)
+                            message = "interface: %s\tinstalled trusted gid for %s" % \
+                                (self.api.interface, new_hrn)
+                        # log the message
+                        self.api.logger.info(message)
+            except:
+                message = "interface: %s\tunable to install trusted gid for %s" % \
+                            (self.api.interface, new_hrn) 
+                self.api.logger.info(message)
+                traceback.print_exc()
+        
+        # reload the trusted certs list
+        self.api.auth.load_trusted_certs()
+        return peer_gids
+
+    def update_db_records(self, type, gids):
+        """
+        Make sure there is a record in the local db for allowed registries
+        defined in the config file (registries.xml). Removes old records from
+        the db.         
+        """
+        if not gids: 
+            return
+        
+        # hrns that should have a record
+        hrns_expected = [gid.get_hrn() for gid in gids]
+
+        # get hrns that actually exist in the db
+        table = SfaTable()
+        records = table.find({'type': type, 'pointer': -1})
+        hrns_found = [record['hrn'] for record in records]
+      
+        # remove old records
+        for record in records:
+            if record['hrn'] not in hrns_expected and \
+                record['hrn'] != self.api.config.SFA_INTERFACE_HRN:
+                table.remove(record)
+
+        # add new records
+        for gid in gids:
+            hrn = gid.get_hrn()
+            if hrn not in hrns_found:
+                record = {
+                    'hrn': hrn,
+                    'type': type,
+                    'pointer': -1, 
+                    'authority': get_authority(hrn),
+                    'gid': gid.save_to_string(save_parents=True),
+                }
+                record = SfaRecord(dict=record)
+                table.insert(record)
+                        
+    def get_connections(self):
+        """
+        read connection details for the trusted peer registries from file return 
+        a dictionary of connections keyed on interface hrn. 
+        """
+        connections = {}
+        required_fields = self.default_fields.keys()
+        for interface in self.interfaces.values():
+            # make sure the required fields are present and not null
+            
+            url = interface['url']
+            # check which client we should use
+            # sfa.util.xmlrpcprotocol is default
+            client_type = 'xmlrpcprotocol'
+            if interface.has_key('client') and \
+               interface['client'] in ['geniclientlight'] and \
+               GeniClientLight:
+                client_type = 'geniclientlight'
+                connections[hrn] = GeniClientLight(url, self.api.key_file, self.api.cert_file) 
+            else:
+                connections[interface['hrn']] = xmlrpcprotocol.get_server(url, self.api.key_file, self.api.cert_file)
+
+        return connections 
diff --git a/sfa/server/modpython/SfaAggregateModPython.py b/sfa/server/modpython/SfaAggregateModPython.py
new file mode 100755 (executable)
index 0000000..e1ab86d
--- /dev/null
@@ -0,0 +1,70 @@
+#
+# Apache mod_python interface
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+
+import sys
+import traceback
+import xmlrpclib
+from mod_python import apache
+
+from sfa.plc.api import SfaAPI
+from sfa.util.debug import log
+
+api = SfaAPI(interface='aggregate')
+
+class unbuffered:
+    """\r
+    Write to /var/log/httpd/error_log. See\r
+\r
+    http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp\r
+    """\r
+\r
+    def write(self, data):\r
+        sys.stderr.write(data)\r
+        sys.stderr.flush()\r
+\r
+#log = unbuffered()
+
+def handler(req):
+    try:
+        if req.method != "POST":
+            req.content_type = "text/html"
+            req.send_http_header()
+            req.write("""
+<html><head>
+<title>SFA Aggregate API XML-RPC/SOAP Interface</title>
+</head><body>
+<h1>SFA Aggregate API XML-RPC/SOAP Interface</h1>
+<p>Please use XML-RPC or SOAP to access the SFA API.</p>
+</body></html>
+""")
+            return apache.OK
+
+        # Read request
+        request = req.read(int(req.headers_in['content-length']))
+
+        # mod_python < 3.2: The IP address portion of remote_addr is
+        # incorrect (always 0.0.0.0) when IPv6 is enabled.
+        # http://issues.apache.org/jira/browse/MODPYTHON-64?page=all
+        (remote_ip, remote_port) = req.connection.remote_addr
+        remote_addr = (req.connection.remote_ip, remote_port)
+
+        # Handle request
+        response = api.handle(remote_addr, request)
+
+        # Write response
+        req.content_type = "text/xml; charset=" + api.encoding
+        req.send_http_header()
+        req.write(response)
+
+        return apache.OK
+
+    except Exception, err:
+        # Log error in /var/log/httpd/(ssl_)?error_log
+        print >> log, err, traceback.format_exc()
+        return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpython/SfaRegistryModPython.py b/sfa/server/modpython/SfaRegistryModPython.py
new file mode 100755 (executable)
index 0000000..a9044eb
--- /dev/null
@@ -0,0 +1,69 @@
+#
+# Apache mod_python interface
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+
+import sys
+import traceback
+import xmlrpclib
+from mod_python import apache
+from sfa.util.debug import log
+from sfa.plc.api import SfaAPI
+
+api = SfaAPI(interface='registry')
+
+class unbuffered:
+    """\r
+    Write to /var/log/httpd/error_log. See\r
+\r
+    http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp\r
+    """\r
+\r
+    def write(self, data):\r
+        sys.stderr.write(data)\r
+        sys.stderr.flush()\r
+\r
+#log = unbuffered()
+
+def handler(req):
+    try:
+        if req.method != "POST":
+            req.content_type = "text/html"
+            req.send_http_header()
+            req.write("""
+<html><head>
+<title>SFA Registry API XML-RPC/SOAP Interface</title>
+</head><body>
+<h1>SFA Registry API XML-RPC/SOAP Interface</h1>
+<p>Please use XML-RPC or SOAP to access the SFA API.</p>
+</body></html>
+""")
+            return apache.OK
+
+        # Read request
+        request = req.read(int(req.headers_in['content-length']))
+
+        # mod_python < 3.2: The IP address portion of remote_addr is
+        # incorrect (always 0.0.0.0) when IPv6 is enabled.
+        # http://issues.apache.org/jira/browse/MODPYTHON-64?page=all
+        (remote_ip, remote_port) = req.connection.remote_addr
+        remote_addr = (req.connection.remote_ip, remote_port)
+
+        # Handle request
+        response = api.handle(remote_addr, request)
+
+        # Write response
+        req.content_type = "text/xml; charset=" + api.encoding
+        req.send_http_header()
+        req.write(response)
+
+        return apache.OK
+
+    except Exception, err:
+        # Log error in /var/log/httpd/(ssl_)?error_log
+        print >> log, err, traceback.format_exc()
+        return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpython/SfaSliceMgrModPython.py b/sfa/server/modpython/SfaSliceMgrModPython.py
new file mode 100755 (executable)
index 0000000..7ad8e0d
--- /dev/null
@@ -0,0 +1,70 @@
+#
+# Apache mod_python interface
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+
+import sys
+import traceback
+import xmlrpclib
+from mod_python import apache
+
+from sfa.plc.api import SfaAPI
+from sfa.util.debug import log
+
+api = SfaAPI(interface='slicemgr')
+
+class unbuffered:
+    """\r
+    Write to /var/log/httpd/error_log. See\r
+\r
+    http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp\r
+    """\r
+\r
+    def write(self, data):\r
+        sys.stderr.write(data)\r
+        sys.stderr.flush()\r
+\r
+#log = unbuffered()
+
+def handler(req):
+    try:
+        if req.method != "POST":
+            req.content_type = "text/html"
+            req.send_http_header()
+            req.write("""
+<html><head>
+<title>SFA SliceMgr API XML-RPC/SOAP Interface</title>
+</head><body>
+<h1>SFA SliceMgr API XML-RPC/SOAP Interface</h1>
+<p>Please use XML-RPC or SOAP to access the SFA API.</p>
+</body></html>
+""")
+            return apache.OK
+
+        # Read request
+        request = req.read(int(req.headers_in['content-length']))
+
+        # mod_python < 3.2: The IP address portion of remote_addr is
+        # incorrect (always 0.0.0.0) when IPv6 is enabled.
+        # http://issues.apache.org/jira/browse/MODPYTHON-64?page=all
+        (remote_ip, remote_port) = req.connection.remote_addr
+        remote_addr = (req.connection.remote_ip, remote_port)
+
+        # Handle request
+        response = api.handle(remote_addr, request)
+
+        # Write response
+        req.content_type = "text/xml; charset=" + api.encoding
+        req.send_http_header()
+        req.write(response)
+
+        return apache.OK
+
+    except Exception, err:
+        # Log error in /var/log/httpd/(ssl_)?error_log
+        print >> log, err, traceback.format_exc()
+        return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpython/sfa.aggregate.httpd.conf b/sfa/server/modpython/sfa.aggregate.httpd.conf
new file mode 100644 (file)
index 0000000..2e6f4f4
--- /dev/null
@@ -0,0 +1,24 @@
+Listen 12346
+
+<VirtualHost *:12346>
+    ErrorLog logs/sfa_ssl_error_log
+    TransferLog logs/sfa_ssl_access_log
+    CustomLog logs/ssl_request_log \
+          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b
+    LogLevel warn
+
+    SSLEngine on
+    SSLProtocol all -SSLv2
+
+    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
+    SSLCertificateFile /etc/sfa/authorities/server.cert
+    SSLCertificateKeyFile /etc/sfa/authorities/server.key
+    SetEnvIf User-Agent ".*MSIE.*" \
+         nokeepalive ssl-unclean-shutdown \
+         downgrade-1.0 force-response-1.0
+
+    SetHandler mod_python
+    PythonPath "sys.path + ['/usr/lib/python2.5/site-packages/sfa/', '/usr/lib/python2.5/site-packages/sfa/server/']"
+    PythonHandler SfaAggregateModPython
+
+</VirtualHost> 
diff --git a/sfa/server/modpython/sfa.registry.httpd.conf b/sfa/server/modpython/sfa.registry.httpd.conf
new file mode 100644 (file)
index 0000000..714fe55
--- /dev/null
@@ -0,0 +1,24 @@
+Listen 12345
+
+<VirtualHost *:12345>
+    ErrorLog logs/sfa_ssl_error_log
+    TransferLog logs/sfa_ssl_access_log
+    CustomLog logs/ssl_request_log \
+          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b
+    LogLevel warn
+
+    SSLEngine on
+    SSLProtocol all -SSLv2
+
+    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
+    SSLCertificateFile /etc/sfa/authorities/server.cert
+    SSLCertificateKeyFile /etc/sfa/authorities/server.key
+    SetEnvIf User-Agent ".*MSIE.*" \
+         nokeepalive ssl-unclean-shutdown \
+         downgrade-1.0 force-response-1.0
+
+    SetHandler mod_python
+    PythonPath "sys.path + ['/usr/lib/python2.5/site-packages/sfa/', '/usr/lib/python2.5/site-packages/sfa/server/']"
+    PythonHandler SfaRegistryModPython
+
+</VirtualHost> 
diff --git a/sfa/server/modpython/sfa.slicemgr.httpd.conf b/sfa/server/modpython/sfa.slicemgr.httpd.conf
new file mode 100644 (file)
index 0000000..6e3a8e8
--- /dev/null
@@ -0,0 +1,24 @@
+Listen 12347
+
+<VirtualHost *:12347>
+    ErrorLog logs/sfa_ssl_error_log
+    TransferLog logs/sfa_ssl_access_log
+    CustomLog logs/ssl_request_log \
+          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b
+    LogLevel warn
+
+    SSLEngine on
+    SSLProtocol all -SSLv2
+
+    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
+    SSLCertificateFile /etc/sfa/authorities/server.cert
+    SSLCertificateKeyFile /etc/sfa/authorities/server.key
+    SetEnvIf User-Agent ".*MSIE.*" \
+         nokeepalive ssl-unclean-shutdown \
+         downgrade-1.0 force-response-1.0
+
+    SetHandler mod_python
+    PythonPath "sys.path + ['/usr/lib/python2.5/site-packages/sfa/', '/usr/lib/python2.5/site-packages/sfa/server/']"
+    PythonHandler SfaSliceMgrModPython
+
+</VirtualHost> 
diff --git a/sfa/server/modpythonapi/ApiExceptionCodes.py b/sfa/server/modpythonapi/ApiExceptionCodes.py
new file mode 100644 (file)
index 0000000..cd811d2
--- /dev/null
@@ -0,0 +1,2 @@
+FAULT_UNHANDLEDSERVEREXCEPTION = 901
+FAULT_BADREQUESTHASH = 902
diff --git a/sfa/server/modpythonapi/ApiExceptions.py b/sfa/server/modpythonapi/ApiExceptions.py
new file mode 100644 (file)
index 0000000..834afd2
--- /dev/null
@@ -0,0 +1,15 @@
+import traceback
+import xmlrpclib
+
+FAULTCODE = 900
+
+class UnhandledServerException(xmlrpclib.Fault):
+    def __init__(self, type, value, tb):
+        exc_str = ''.join(traceback.format_exception(type, value, tb))
+        faultString = exc_str # "Unhandled exception: " + str(type) + "\n" + exc_str
+        xmlrpclib.Fault.__init__(self, FAULTCODE + 1, faultString)
+
+class BadRequestHash(xmlrpclib.Fault):    
+   def __init__(self, hash = None):
+        faultString = "bad request hash: " + str(hash)
+        xmlrpclib.Fault.__init__(self, FAULTCODE + 2, faultString)
diff --git a/sfa/server/modpythonapi/AuthenticatedApi.py b/sfa/server/modpythonapi/AuthenticatedApi.py
new file mode 100755 (executable)
index 0000000..e86781f
--- /dev/null
@@ -0,0 +1,66 @@
+import xmlrpclib
+
+from BaseApi import BaseApi
+
+from sfa.trust.credential import Credential
+from sfa.trust.gid import GID
+from sfa.trust.trustedroot import TrustedRootList
+
+from ApiExceptionCodes import *
+
+class BadRequestHash(xmlrpclib.Fault):
+   def __init__(self, hash = None):
+        faultString = "bad request hash: " + str(hash)
+        xmlrpclib.Fault.__init__(self, FAULT_BADREQUESTHASH, faultString)
+
+class AuthenticatedApi(BaseApi):
+    def __init__(self, encoding = "utf-8", trustedRootsDir=None):
+        BaseApi.__init__(self, encoding)
+        if trustedRootsDir:
+            self.trusted_cert_list = TrustedRootList(trustedRootsDir).get_list()
+            self.trusted_cert_file_list = TrustedRootList(trustedRootsDir).get_file_list()
+        else:
+            self.trusted_cert_list = None
+
+    def register_functions(self):
+        BaseApi.register_functions(self)
+        self.register_function(self.gidNoop)
+
+    def verifyGidRequestHash(self, gid, hash, arglist):
+        key = gid.get_pubkey()
+        if not key.verify_string(str(arglist), hash):
+            raise BadRequestHash(hash)
+
+    def verifyCredRequestHash(self, cred, hash, arglist):
+        gid = cred.get_gid_caller()
+        self.verifyGidRequestHash(gid, hash, arglist)
+
+    def validateGid(self, gid):
+        if self.trusted_cert_list:
+            gid.verify_chain(self.trusted_cert_list)
+
+    def validateCred(self, cred):
+        if self.trusted_cert_list:
+            cred.verify(self.trusted_cert_file_list)
+
+    def authenticateGid(self, gidStr, argList, requestHash):
+        gid = GID(string = gidStr)
+        self.validateGid(gid)
+        self.verifyGidRequestHash(gid, requestHash, argList)
+        return gid
+
+    def authenticateCred(self, credStr, argList, requestHash):
+        cred = Credential(string = credStr)
+        self.validateCred(cred)
+        self.verifyCredRequestHash(cred, requestHash, argList)
+        return cred
+
+    def gidNoop(self, gidStr, value, requestHash):
+        self.authenticateGid(gidStr, [gidStr, value], requestHash)
+        return value
+
+    def credNoop(self, credStr, value, requestHash):
+        self.authenticateCred(credStr, [credStr, value], requestHash)
+        return value
+
+
diff --git a/sfa/server/modpythonapi/AuthenticatedClient.py b/sfa/server/modpythonapi/AuthenticatedClient.py
new file mode 100755 (executable)
index 0000000..6b705fc
--- /dev/null
@@ -0,0 +1,24 @@
+from sfa.trust.certificate import Keypair
+from sfa.trust.gid import GID
+
+from BaseClient import BaseClient
+
+class AuthenticatedClient(BaseClient):
+    def __init__(self, url, private_key_file, gid_file=None, cred_file=None):
+        BaseClient.__init__(self, url)
+        self.private_key_file = private_key_file
+        self.gid_file = gid_file
+        self.cred_file = cred_file
+        self.private_key = Keypair(filename = self.private_key_file)
+        if gid_file:
+            self.gid = GID(filename = self.gid_file)
+        if cred_file:
+            self.cred = Credential(filename = self.cred_file)
+
+    def computeRequestHash(self, argList):
+        return self.private_key.sign_string(str(argList))
+
+    def gidNoop(self, value):
+        gidStr = self.gid.save_to_string(True)
+        reqHash = self.computeRequestHash([gidStr, value])
+        return self.server.gidNoop(gidStr, value, reqHash)
diff --git a/sfa/server/modpythonapi/BaseApi.py b/sfa/server/modpythonapi/BaseApi.py
new file mode 100755 (executable)
index 0000000..95538ad
--- /dev/null
@@ -0,0 +1,203 @@
+#
+# 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 14587 2009-07-19 13:18:50Z thierry $
+# $URL: https://svn.planet-lab.org/svn/PLCAPI/trunk/PLC/API.py $
+#
+
+import sys
+import traceback
+import string
+
+import xmlrpclib
+import logging
+import logging.handlers
+
+from ApiExceptionCodes import *
+
+# Wrapper around xmlrpc fault to include a traceback of the server to the
+# client. This is done to aid in debugging from a client perspective.
+
+class FaultWithTraceback(xmlrpclib.Fault):
+    def __init__(self, code, faultString, exc_info):
+        type, value, tb = exc_info
+        exc_str = ''.join(traceback.format_exception(type, value, tb))
+        faultString = faultString + "\nFAULT_TRACEBACK:" + exc_str
+        xmlrpclib.Fault.__init__(self, code, faultString)
+
+# Exception to report to the caller when some non-XMLRPC fault occurs on the
+# server. For example a TypeError.
+
+class UnhandledServerException(FaultWithTraceback):
+    def __init__(self, exc_info):
+        type, value, tb = exc_info
+        faultString = "Unhandled exception: " + str(type)
+        FaultWithTraceback.__init__(self, FAULT_UNHANDLEDSERVEREXCEPTION, faultString, exc_info)
+
+# 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, "&", "&amp;")
+    s = replace(s, "<", "&lt;")
+    s = replace(s, ">", "&gt;",)
+
+    # 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
+
+def import_deep(name):
+    mod = __import__(name)
+    components = name.split('.')
+    for comp in components[1:]:
+        mod = getattr(mod, comp)
+    return mod
+
+class BaseApi:
+    def __init__(self, encoding = "utf-8"):
+        self.encoding = encoding
+        self.init_logger()
+        self.funcs = {}
+        self.register_functions()
+
+    def init_logger(self):
+        self.logger = logging.getLogger("ApiLogger")
+        self.logger.setLevel(logging.INFO)
+        self.logger.addHandler(logging.handlers.RotatingFileHandler(self.get_log_name(), maxBytes=100000, backupCount=5))
+
+    def get_log_name(self):
+        return "/tmp/apilogfile.txt"
+
+    def register_functions(self):
+        self.register_function(self.noop)
+
+    def register_function(self, function, name = None):
+        if name is None:
+            name = function.__name__
+        self.funcs[name] = function
+
+    def call(self, source, method, *args):
+        """
+        Call the named method from the specified source with the
+        specified arguments.
+        """
+
+        if not method in self.funcs:
+            raise "Unknown method: " + method
+
+        return self.funcs[method](*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
+
+        self.logger.debug("OP:" + str(method) + " from " + str(source))
+
+        try:
+            result = self.call(source, method, *args)
+        except xmlrpclib.Fault, fault:
+            self.logger.warning("FAULT: " + str(fault.faultCode) + " " + str(fault.faultString))
+            self.logger.info(traceback.format_exc())
+            # Handle expected faults
+            if interface == xmlrpclib:
+                result = FaultWithTraceback(fault.faultCode, fault.faultString, sys.exc_info())
+                methodresponse = None
+            elif interface == SOAPpy:
+                result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
+                result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
+                self.logger.debug
+        except:
+            self.logger.warning("EXCEPTION: " + str(sys.exc_info()[0]))
+            self.logger.info(traceback.format_exc())
+            result = UnhandledServerException(sys.exc_info())
+            methodresponse = None
+
+        # Return result
+        if interface == xmlrpclib:
+            if not isinstance(result, xmlrpclib.Fault):
+                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
+
+    def noop(self, value):
+        return value
+
+
+
diff --git a/sfa/server/modpythonapi/BaseClient.py b/sfa/server/modpythonapi/BaseClient.py
new file mode 100755 (executable)
index 0000000..448f934
--- /dev/null
@@ -0,0 +1,46 @@
+import xmlrpclib
+
+from ApiExceptionCodes import *
+
+VerboseExceptions = False
+
+def EnableVerboseExceptions(x=True):
+    global VerboseExceptions
+    VerboseExceptions = x
+
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+    def close(self):
+        try:\r
+            return xmlrpclib.Unmarshaller.close(self)\r
+        except xmlrpclib.Fault, e:\r
+            # if the server tagged some traceback info onto the end of the\r
+            # exception text, then print it out on the client.\r
+\r
+            if "\nFAULT_TRACEBACK:" in e.faultString:\r
+                parts = e.faultString.split("\nFAULT_TRACEBACK:")\r
+                e.faultString = parts[0]\r
+                if VerboseExceptions:\r
+                    print "\n|Server Traceback:", "\n|".join(parts[1].split("\n"))\r
+\r
+            raise e\r
+\r
+class ExceptionReportingTransport(xmlrpclib.Transport):
+    def make_connection(self, host):
+        import httplib\r
+        if host.startswith("https:"):\r
+           return httplib.HTTPS(host)\r
+        else:\r
+           return httplib.HTTP(host)\r
+\r
+    def getparser(self):\r
+        unmarshaller = ExceptionUnmarshaller()\r
+        parser = xmlrpclib.ExpatParser(unmarshaller)\r
+        return parser, unmarshaller
+
+class BaseClient():
+    def __init__(self, url):
+        self.url = url
+        self.server = xmlrpclib.ServerProxy(self.url, ExceptionReportingTransport())
+
+    def noop(self, value):
+        return self.server.noop(value)
diff --git a/sfa/server/modpythonapi/ModPython.py b/sfa/server/modpythonapi/ModPython.py
new file mode 100755 (executable)
index 0000000..64ceb99
--- /dev/null
@@ -0,0 +1,68 @@
+#
+# Apache mod_python interface
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+
+import sys
+import traceback
+import xmlrpclib
+from mod_python import apache
+
+from API import RemoteApi
+api = RemoteApi()
+
+class unbuffered:
+    """\r
+    Write to /var/log/httpd/error_log. See\r
+\r
+    http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp\r
+    """\r
+\r
+    def write(self, data):\r
+        sys.stderr.write(data)\r
+        sys.stderr.flush()\r
+\r
+#log = unbuffered()
+
+def handler(req):
+    try:
+        if req.method != "POST":
+            req.content_type = "text/html"
+            req.send_http_header()
+            req.write("""
+<html><head>
+<title>PLCAPI XML-RPC/SOAP Interface</title>
+</head><body>
+<h1>PLCAPI XML-RPC/SOAP Interface</h1>
+<p>Please use XML-RPC or SOAP to access the PLCAPI.</p>
+</body></html>
+""")
+            return apache.OK
+
+        # Read request
+        request = req.read(int(req.headers_in['content-length']))
+
+        # mod_python < 3.2: The IP address portion of remote_addr is
+        # incorrect (always 0.0.0.0) when IPv6 is enabled.
+        # http://issues.apache.org/jira/browse/MODPYTHON-64?page=all
+        (remote_ip, remote_port) = req.connection.remote_addr
+        remote_addr = (req.connection.remote_ip, remote_port)
+
+        # Handle request
+        response = api.handle(remote_addr, request)
+
+        # Write response
+        req.content_type = "text/xml; charset=" + api.encoding
+        req.send_http_header()
+        req.write(response)
+
+        return apache.OK
+
+    except Exception, err:
+        # Log error in /var/log/httpd/(ssl_)?error_log
+        print >> log, err, traceback.format_exc()
+        return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpythonapi/TestApi.py b/sfa/server/modpythonapi/TestApi.py
new file mode 100755 (executable)
index 0000000..11daed5
--- /dev/null
@@ -0,0 +1,19 @@
+from AuthenticatedApi import AuthenticatedApi, BadRequestHash
+
+class RemoteApi(AuthenticatedApi):
+    def __init__(self, encoding="utf-8", trustedRootsDir="/usr/local/testapi/var/trusted_roots"):
+        return AuthenticatedApi.__init__(self, encoding)
+
+    def get_log_name(self):
+        return "/usr/local/testapi/var/logfile.txt"
+
+    def register_functions(self):
+        AuthenticatedApi.register_functions(self)
+        self.register_function(self.typeError)
+        self.register_function(self.badRequestHash)
+
+    def typeError(self):
+        raise TypeError()
+
+    def badRequestHash(self):
+        raise BadRequestHash("somehashvalue")
diff --git a/sfa/server/modpythonapi/installTest.sh b/sfa/server/modpythonapi/installTest.sh
new file mode 100755 (executable)
index 0000000..1a6514e
--- /dev/null
@@ -0,0 +1,37 @@
+SFA_SRC_DIR=/home/smbaker/projects/sfa/trunk
+
+mkdir -p /usr/local/testapi/bin
+mkdir -p /usr/local/testapi/bin/sfa/trust
+mkdir -p /usr/local/testapi/bin/sfa/util
+mkdir -p /usr/local/testapi/var/trusted_roots
+mkdir -p /repository/testapi
+
+# source code for the API
+cp BaseApi.py /usr/local/testapi/bin/
+cp AuthenticatedApi.py /usr/local/testapi/bin/
+cp TestApi.py /usr/local/testapi/bin/API.py
+cp ModPython.py /usr/local/testapi/bin/
+cp ApiExceptionCodes.py /usr/local/testapi/bin/
+
+# trusted root certificates that match gackstestuser.*
+cp trusted_roots/*.gid /usr/local/testapi/var/trusted_roots/
+
+# apache config file to enable the api
+cp testapi.conf /etc/httpd/conf.d/
+
+# copy over sfa stuff that we need
+echo > /usr/local/testapi/bin/sfa/__init__.py
+echo > /usr/local/testapi/bin/sfa/trust/__init__.py
+echo > /usr/local/testapi/bin/sfa/util/__init__.py
+cp $SFA_SRC_DIR/sfa/trust/gid.py /usr/local/testapi/bin/sfa/trust/
+cp $SFA_SRC_DIR/sfa/trust/certificate.py /usr/local/testapi/bin/sfa/trust/
+cp $SFA_SRC_DIR/sfa/trust/trustedroot.py /usr/local/testapi/bin/sfa/trust/
+cp $SFA_SRC_DIR/sfa/trust/credential.py /usr/local/testapi/bin/sfa/trust/
+cp $SFA_SRC_DIR/sfa/trust/rights.py /usr/local/testapi/bin/sfa/trust/
+cp $SFA_SRC_DIR/sfa/util/faults.py /usr/local/testapi/bin/sfa/util/ 
+
+# make everything owned by apache
+chown -R apache /usr/local/testapi
+chown apache /etc/httpd/conf.d/testapi.conf
+
+/etc/init.d/httpd restart
diff --git a/sfa/server/modpythonapi/test.py b/sfa/server/modpythonapi/test.py
new file mode 100755 (executable)
index 0000000..d3fafed
--- /dev/null
@@ -0,0 +1,44 @@
+import sys
+import traceback
+
+from BaseClient import BaseClient, EnableVerboseExceptions
+from AuthenticatedClient import AuthenticatedClient
+
+EnableVerboseExceptions(True)
+
+HOST = "localhost"
+URL = "http://" + HOST + "/TESTAPI/"
+SURL = "https://" + HOST + "/TESTAPI/"
+
+print "*** testing some valid ops; these should print \"Hello, World\" ***"
+
+bc = BaseClient(URL)
+print "HTTP noop:", bc.noop("Hello, World")
+
+ac = AuthenticatedClient(URL, "gackstestuser.pkey", "gackstestuser.gid")
+print "HTTP gidNoop:", ac.gidNoop("Hello, World")
+
+bc = BaseClient(SURL)
+print "HTTPS noop:", bc.noop("Hello, World")
+
+ac = AuthenticatedClient(URL, "gackstestuser.pkey", "gackstestuser.gid")
+print "HTTPS gidNoop:", ac.gidNoop("Hello, World")
+
+print
+print "*** testing some exception handling: ***"
+
+bc = BaseClient(URL)
+print "HTTP typeError:",
+try:
+    result = bc.server.typeError()
+    print result
+except Exception, e:
+    print ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
+
+print "HTTP badrequesthash:",
+try:
+    result = bc.server.badRequestHash()
+    print result
+except:
+    print ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
+
diff --git a/sfa/server/modpythonapi/test.sh b/sfa/server/modpythonapi/test.sh
new file mode 100755 (executable)
index 0000000..cafa0fb
--- /dev/null
@@ -0,0 +1,3 @@
+export PYTHONPATH=/home/smbaker/projects/sfa/trunk
+
+python ./test.py
diff --git a/sfa/server/modpythonapi/testapi.conf b/sfa/server/modpythonapi/testapi.conf
new file mode 100644 (file)
index 0000000..5495fd2
--- /dev/null
@@ -0,0 +1,5 @@
+<Location /TESTAPI/>
+    SetHandler mod_python
+    PythonPath "sys.path + ['/usr/local/testapi/bin/']"
+    PythonHandler ModPython
+</Location>
\ No newline at end of file
diff --git a/sfa/server/registry.py b/sfa/server/registry.py
new file mode 100644 (file)
index 0000000..020d8f5
--- /dev/null
@@ -0,0 +1,53 @@
+#
+# Registry is a SfaServer that implements the Registry interface
+#
+### $Id: registry.py 18632 2010-08-16 21:44:19Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/server/registry.py $
+#
+
+from sfa.util.server import SfaServer
+from sfa.util.faults import *
+from sfa.util.namespace import hrn_to_urn
+from sfa.server.interface import Interfaces
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol
+import sfa.util.soapprotocol as soapprotocol
+
+##
+# Registry is a SfaServer that serves registry and slice operations at PLC.
+class Registry(SfaServer):
+    ##
+    # Create a new registry object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key (could be a GID file)
+    
+    def __init__(self, ip, port, key_file, cert_file):
+        SfaServer.__init__(self, ip, port, key_file, cert_file)
+        self.server.interface = 'registry' 
+
+
+##
+# Registries is a dictionary of registry connections keyed on the registry
+# hrn
+
+class Registries(Interfaces):
+    
+    default_dict = {'registries': {'registry': [Interfaces.default_fields]}}
+
+    def __init__(self, api, conf_file = "/etc/sfa/registries.xml"):
+        Interfaces.__init__(self, api, conf_file) 
+        address = self.api.config.SFA_REGISTRY_HOST
+        port = self.api.config.SFA_REGISTRY_PORT
+        url = 'http://%(address)s:%(port)s' % locals()
+        local_registry = {'hrn': self.api.hrn,
+                           'urn': hrn_to_urn(self.api.hrn, 'authority'),
+                           'addr': address,
+                           'port': port,
+                           'url': url}
+        self.interfaces[self.api.hrn] = local_registry
+       
+        # get connections
+        self.update(self.get_connections()) 
diff --git a/sfa/server/sfa-ca.py b/sfa/server/sfa-ca.py
new file mode 100755 (executable)
index 0000000..c76b985
--- /dev/null
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+
+#
+# SFA Certificate Signing and management 
+#   
+
+import os
+import sys
+from optparse import OptionParser
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.gid import GID, create_uuid
+from sfa.trust.hierarchy import Hierarchy
+from sfa.util.config import Config
+from collections import defaultdict
+
+def main():
+    args = sys.argv
+    script_name = args[0]
+    parser = OptionParser(usage="%(script_name)s [options]" % locals())
+    parser.add_option("-d", "--display", dest="display", default=None,
+                      help="print contents of specified gid")           
+    parser.add_option("-s", "--sign", dest="sign", default=None, 
+                      help="gid to sign" )
+    parser.add_option("-k", "--key", dest="key", default=None, 
+                      help="keyfile to use for signing")
+    parser.add_option("-a", "--authority", dest="authority", default=None, 
+                      help="sign the gid using the specified authority ")
+    parser.add_option("-i", "--import", dest="importgid", default=None,
+                      help="gid file to import into the registry")
+    parser.add_option("-e", "--export", dest="export", 
+                      help="name of gid to export from registry")
+    parser.add_option("-o", "--outfile", dest="outfile",
+                      help="where to write the exprted gid") 
+    parser.add_option("-v", "--verbose", dest="verbose", default=False, 
+                      action="store_true", help="be verbose")           
+                
+    (options, args) = parser.parse_args()
+
+
+    if options.display:
+        display(options)
+    elif options.sign:
+        sign(options)
+    elif options.importgid:
+        import_gid(options) 
+    elif options.export:
+        export_gid(options)  
+    else:
+        parser.print_help()
+        sys.exit(1)        
+
+
+def display(options):
+    """
+    Display the sepcified GID
+    """
+    gidfile = os.path.abspath(options.display)
+    if not gidfile or not os.path.isfile(gidfile):
+        print "No such gid: %s" % gidfile
+        sys.exit(1)
+    gid = GID(filename=gidfile)
+    gid.dump(dump_parents=True)
+
+def sign_gid(gid, parent_key, parent_gid):
+    gid.set_issuer(parent_key, parent_gid.get_hrn())
+    gid.set_parent(parent_gid)
+    gid.sign()
+    return gid 
+
+def sign(options):
+    """
+    Sign the specified gid
+    """
+    hierarchy = Hierarchy()
+    config = Config()
+    default_authority = config.SFA_INTERFACE_HRN
+    auth_info = hierarchy.get_auth_info(default_authority)
+
+    # load the gid
+    gidfile = os.path.abspath(options.sign)
+    if not os.path.isfile(gidfile):
+        print "no such gid: %s" % gidfile
+        sys.exit(1)
+    gid = GID(filename=gidfile)
+
+    # remove previous parent
+    gid = GID(string=gid.save_to_string(save_parents=False))
+
+    # load the parent private info
+    authority = options.authority    
+    # if no pkey was specified, then use the this authority's key
+    if not authority:
+        authority = default_authority 
+    
+    if not hierarchy.auth_exists(authority):
+        print "no such authority: %s" % authority    
+
+    # load the parent gid and key 
+    auth_info = hierarchy.get_auth_info(authority)
+    pkeyfile = auth_info.privkey_filename
+    parent_key = Keypair(filename=pkeyfile)
+    parent_gid = auth_info.gid_object
+
+    # get the outfile
+    outfile = options.outfile
+    if not outfile:
+        outfile = os.path.abspath('./signed-%s.gid' % gid.get_hrn())
+   
+    # check if gid already has a parent
+    # sign the gid
+    if options.verbose:
+        print "Signing %s gid with parent %s" % \
+              (gid.get_hrn(), parent_gid.get_hrn())
+    gid = sign_gid(gid, parent_key, parent_gid)
+    # save the signed gid
+    if options.verbose:
+        print "Writing signed gid %s" % outfile  
+    gid.save_to_file(outfile, save_parents=True)
+    
+
+def export_gid(options):
+    from sfa.util.table import SfaTable
+    # lookup the record for the specified hrn 
+    hrn = options.export
+
+    # check sfa table first    
+    table = SfaTable()
+    records = table.find({'hrn': hrn, type: 'authority'})
+    if not records:
+        # check the authorities hierarchy 
+        hierarchy = Hierarchy()
+        try:
+            auth_info = hierarchy.get_auth_info()
+            gid = auth_info.gid_object 
+        except:
+            print "Record: %s not found" % hrn
+            sys.exit(1)
+    else:
+        record = records[0]
+        gid = GID(string=record['gid'])
+        
+    # get the outfile
+    outfile = options.outfile
+    if not outfile:
+        outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
+
+    # save it
+    if options.verbose:
+        print "Writing %s gid to %s" % (gid.get_hrn(), outfile)
+    gid.save_to_file(outfile, save_parents=True)
+
+def import_gid(options):
+    """
+    Import the specified gid into the registry (db and authorities 
+    hierarchy) overwriting any previous gid.
+    """
+    from sfa.util.table import SfaTable
+    from sfa.util.record import SfaRecord
+    # load the gid
+    gidfile = os.path.abspath(options.importgid)
+    if not gidfile or not os.path.isfile(gidfile):
+        print "No such gid: %s" % gidfile
+        sys.exit(1)
+    gid = GID(filename=gidfile)
+    
+    # check if it exists within the hierarchy
+    hierarchy = Hierarchy()
+    if not hierarchy.auth_exists(gid.get_hrn()):
+        print "%s not found in hierarchy" % gid.get_hrn()
+        sys.exit(1)
+
+    # check if record exists in db
+    table = SfaTable()
+    records = table.find({'hrn': gid.get_hrn(), 'type': 'authority'})
+    if not records:
+        print "%s not found in record database" % get.get_hrn()  
+        sys.exit(1)
+
+    # update the database record
+    record = records[0]
+    record['gid'] = gid.save_to_string(save_parents=True)
+    table.update(record)
+    if options.verbose:
+        print "Imported %s gid into db" % record['hrn']
+
+    # update the hierarchy
+    auth_info = hierarchy.get_auth_info(gid.get_hrn())  
+    filename = auth_info.gid_filename
+    gid.save_to_file(filename, save_parents=True)
+    if options.verbose:
+        print "Writing %s gid to %s" % (gid.get_hrn(), filename)
+
+    # re-sign all existing gids signed by this authority  
+    # create a dictionary of records keyed on the record's authority
+    record_dict = defaultdict(list)
+    # only get regords that belong to this authority 
+    # or any of its sub authorities   
+    child_records = table.find({'hrn': '%s*' % gid.get_hrn()})
+    if not child_records:
+        return
+  
+    for record in child_records:
+        record_dict[record['authority']].append(record) 
+
+    # start with the authority we just imported       
+    authorities = [gid.get_hrn()]
+    while authorities:
+        next_authorities = []
+        for authority in authorities:
+            # create a new signed gid for each record at this authority 
+            # and update the registry
+            auth_info = hierarchy.get_auth_info(authority)
+            records = record_dict[authority]
+            for record in records:
+                record_gid = GID(string=record['gid'])
+                parent_pkey = Keypair(filename=auth_info.privkey_filename)
+                parent_gid = GID(filename=auth_info.gid_filename)
+                if options.verbose:
+                    print "re-signing %s gid with parent %s" % \
+                           (record['hrn'], parent_gid.get_hrn())  
+                signed_gid = sign_gid(record_gid, parent_pkey, parent_gid)
+                record['gid'] = signed_gid.save_to_string(save_parents=True)
+                table.update(record)
+                
+                # if this is an authority then update the hierarchy
+                if record['type'] == 'authority':
+                    record_info = hierarchy.get_auth_info(record['hrn'])
+                    if options.verbose:
+                        print "Writing %s gid to %s" % (record['hrn'], record_info.gid_filename) 
+                    signed_gid.save_to_file(filename=record_info.gid_filename, save_parents=True)
+
+             # update list of next authorities
+            tmp_authorities = set([record['hrn'] for record in records \
+                                   if record['type'] == 'authority'])
+            next_authorities.extend(tmp_authorities)
+
+        # move on to next set of authorities
+        authorities = next_authorities     
+
+if __name__ == '__main__':
+    main()
diff --git a/sfa/server/sfa-clean-peer-records.py b/sfa/server/sfa-clean-peer-records.py
new file mode 100644 (file)
index 0000000..5edb3f3
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+
+import sys
+import os
+import traceback
+from sfa.util.namespace import *
+from sfa.util.table import SfaTable
+from sfa.util.prefixTree import prefixTree
+from sfa.plc.api import SfaAPI
+from sfa.util.config import Config
+from sfa.trust.certificate import Keypair
+from sfa.trust.hierarchy import Hierarchy
+from sfa.util.report import trace, error
+from sfa.server.registry import Registries
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol 
+import socket
+
+def main():
+    config = Config()
+    if not config.SFA_REGISTRY_ENABLED:
+        sys.exit(0)
+
+    # Get the path to the sfa server key/cert files from 
+    # the sfa hierarchy object
+    sfa_hierarchy = Hierarchy()
+    sfa_key_path = sfa_hierarchy.basedir
+    key_file = os.path.join(sfa_key_path, "server.key")
+    cert_file = os.path.join(sfa_key_path, "server.cert")
+    key = Keypair(filename=key_file) 
+
+    # get a connection to our local sfa registry
+    # and a valid credential
+    authority = config.SFA_INTERFACE_HRN
+    url = 'http://%s:%s/' %(config.SFA_REGISTRY_HOST, config.SFA_REGISTRY_PORT)
+    registry = xmlrpcprotocol.get_server(url, key_file, cert_file)
+    sfa_api = SfaAPI(key_file = key_file, cert_file = cert_file, interface='registry')
+    credential = sfa_api.getCredential()
+
+    # get peer registries
+    registries = Registries(sfa_api)
+    tree = prefixTree()
+    tree.load(registries.keys())
+    
+    # get local peer records
+    table = SfaTable()
+    peer_records = table.find({'~peer_authority': None})
+    found_records = []
+    hrn_dict = {}
+    for record in peer_records:
+        registry_hrn = tree.best_match(record['hrn'])
+        if registry_hrn not in hrn_dict:
+            hrn_dict[registry_hrn] = []
+        hrn_dict[registry_hrn].append(record['hrn'])
+
+    # attempt to resolve the record at the authoritative interface 
+    for registry_hrn in hrn_dict:
+        if registry_hrn in registries:
+            records = []
+            target_hrns = hrn_dict[registry_hrn]    
+            try:
+                records = registries[registry_hrn].Resolve(target_hrns, credential)
+                found_records.extend([record['hrn'] for record in records])
+            except ServerException:
+                # an exception will be thrown if the record doenst exist
+                # if so remove the record from the local registry
+                continue
+            except:
+                # this deosnt necessarily mean the records dont exist
+                # lets give them the benefit of the doubt here (for now)
+                found_records.extend(target_hrns)
+                traceback.print_exc()
+
+    # remove what wasnt found 
+    for peer_record in peer_records:
+        if peer_record['hrn'] not in found_records:
+            registries[sfa_api.hrn].Remove(peer_record['hrn'], credential, peer_record['type'])
+                
+if __name__ == '__main__':
+    main()
diff --git a/sfa/server/sfa-server.py b/sfa/server/sfa-server.py
new file mode 100755 (executable)
index 0000000..a9e9b45
--- /dev/null
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+#
+### $Id: sfa-server.py 18662 2010-08-24 16:53:40Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/server/sfa-server.py $
+#
+# SFA PLC Wrapper
+#
+# This wrapper implements the SFA Registry and Slice Interfaces on PLC.
+# Depending on command line options, it starts some combination of a
+# Registry, an Aggregate Manager, and a Slice Manager.
+#
+# There are several items that need to be done before starting the wrapper
+# server.
+#
+# NOTE:  Many configuration settings, including the PLC maintenance account
+# credentials, URI of the PLCAPI, and PLC DB URI and admin credentials are initialized
+# from your MyPLC configuration (/etc/planetlab/plc_config*).  Please make sure this information
+# is up to date and accurate.
+#
+# 1) Import the existing planetlab database, creating the
+#    appropriate SFA records. This is done by running the "sfa-import-plc.py" tool.
+#
+# 2) Create a "trusted_roots" directory and place the certificate of the root
+#    authority in that directory. Given the defaults in sfa-import-plc.py, this
+#    certificate would be named "planetlab.gid". For example,
+#
+#    mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/
+#
+# TODO: Can all three servers use the same "registry" certificate?
+##
+
+# TCP ports for the three servers
+registry_port=12345
+aggregate_port=12346
+slicemgr_port=12347
+component_port=12346
+import os, os.path
+import sys
+from optparse import OptionParser
+from sfa.trust.trustedroot import TrustedRootList
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.hierarchy import Hierarchy
+from sfa.util.config import Config
+from sfa.util.report import trace
+from sfa.plc.api import SfaAPI
+from sfa.server.registry import Registries
+from sfa.server.aggregate import Aggregates
+
+# after http://www.erlenstar.demon.co.uk/unix/faq_2.html
+def daemon():
+    """Daemonize the current process."""
+    if os.fork() != 0: os._exit(0)
+    os.setsid()
+    if os.fork() != 0: os._exit(0)
+    os.umask(0)
+    devnull = os.open(os.devnull, os.O_RDWR)
+    os.dup2(devnull, 0)
+    # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
+    crashlog = os.open('/var/log/sfa.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
+    os.dup2(crashlog, 1)
+    os.dup2(crashlog, 2)
+
+def init_server_key(server_key_file, server_cert_file, config, hierarchy):
+
+    subject = config.SFA_INTERFACE_HRN
+    # check if the server's private key exists. If it doesnt,
+    # get the right one from the authorities directory. If it cant be
+    # found in the authorities directory, generate a random one
+    if not os.path.exists(server_key_file):
+        hrn = config.SFA_INTERFACE_HRN.lower()
+        hrn_parts = hrn.split(".")
+        rel_key_path = hrn
+        pkey_filename = hrn+".pkey"
+
+        # sub authority's have "." in their hrn. This must
+        # be converted to os.path separator
+        if len(hrn_parts) > 0:
+            rel_key_path = hrn.replace(".", os.sep)
+            pkey_filename= hrn_parts[-1]+".pkey"
+
+        key_file = os.sep.join([hierarchy.basedir, rel_key_path, pkey_filename])
+        if not os.path.exists(key_file):
+            # if it doesnt exist then this is probably a fresh interface
+            # with no records. Generate a random keypair for now
+            trace("server's public key not found in %s" % key_file)
+            trace("generating a random server key pair")
+            key = Keypair(create=True)
+            key.save_to_file(server_key_file)
+            cert = Certificate(subject=subject)
+            cert.set_issuer(key=key, subject=subject)
+            cert.set_pubkey(key)
+            cert.sign()
+            cert.save_to_file(server_cert_file, save_parents=True)
+
+        else:
+            # the pkey was found in the authorites directory. lets 
+            # copy it to where the server key should be and generate
+            # the cert
+            key = Keypair(filename=key_file)
+            key.save_to_file(server_key_file)
+            cert = Certificate(subject=subject)
+            cert.set_issuer(key=key, subject=subject)
+            cert.set_pubkey(key)
+            cert.sign()
+            cert.save_to_file(server_cert_file, save_parents=True)
+
+
+    # If private key exists and cert doesnt, recreate cert
+    if (os.path.exists(server_key_file)) and (not os.path.exists(server_cert_file)):
+        key = Keypair(filename=server_key_file)
+        cert = Certificate(subject=subject)
+        cert.set_issuer(key=key, subject=subject)
+        cert.set_pubkey(key)
+        cert.sign()
+        cert.save_to_file(server_cert_file)
+
+def init_server(options, config):
+    """
+    Execute the init method defined in the manager file 
+    """
+    manager_base = 'sfa.managers'
+    if options.registry:
+        mgr_type = config.SFA_REGISTRY_TYPE
+        manager_module = manager_base + ".registry_manager_%s" % mgr_type
+        try: manager = __import__(manager_module, fromlist=[manager_base])
+        except: manager = None
+        if manager and hasattr(manager, 'init_server'): 
+            manager.init_server()    
+    if options.am:
+        mgr_type = config.SFA_AGGREGATE_TYPE
+        manager_module = manager_base + ".aggregate_manager_%s" % mgr_type
+        try: manager = __import__(manager_module, fromlist=[manager_base])
+        except: manager = None
+        if manager and hasattr(manager, 'init_server'):
+            manager.init_server()    
+    if options.sm:
+        mgr_type = config.SFA_SM_TYPE
+        manager_module = manager_base + ".slice_manager_%s" % mgr_type
+        try: manager = __import__(manager_module, fromlist=[manager_base])
+        except: manager = None
+        if manager and hasattr(manager, 'init_server'):
+            manager.init_server()    
+    if options.cm:
+        mgr_type = config.SFA_CM_TYPE
+        manager_module = manager_base + ".component_manager_%s" % mgr_type
+        try: manager = __import__(manager_module, fromlist=[manager_base])
+        except: manager = None
+        if manager and hasattr(manager, 'init_server'):
+            manager.init_server()
+
+def sync_interfaces(server_key_file, server_cert_file):
+    """
+    Attempt to install missing trusted gids and db records for 
+    our federated interfaces
+    """
+    api = SfaAPI(key_file = server_key_file, cert_file = server_cert_file)
+    registries = Registries(api)
+    aggregates = Aggregates(api)
+    registries.sync_interfaces()
+    aggregates.sync_interfaces()
+
+def main():
+    # xxx get rid of globals - name consistently CamelCase or under_score
+    global AuthHierarchy
+    global TrustedRoots
+    global registry_port
+    global aggregate_port
+    global slicemgr_port
+
+    # Generate command line parser
+    parser = OptionParser(usage="sfa-server [options]")
+    parser.add_option("-r", "--registry", dest="registry", action="store_true",
+         help="run registry server", default=False)
+    parser.add_option("-s", "--slicemgr", dest="sm", action="store_true",
+         help="run slice manager", default=False)
+    parser.add_option("-a", "--aggregate", dest="am", action="store_true",
+         help="run aggregate manager", default=False)
+    parser.add_option("-c", "--component", dest="cm", action="store_true",
+         help="run component server", default=False)
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true", 
+         help="verbose mode", default=False)
+    parser.add_option("-d", "--daemon", dest="daemon", action="store_true",
+         help="Run as daemon.", default=False)
+    (options, args) = parser.parse_args()
+
+
+    config = Config()
+    hierarchy = Hierarchy()
+    server_key_file = os.path.join(hierarchy.basedir, "server.key")
+    server_cert_file = os.path.join(hierarchy.basedir, "server.cert")
+
+    init_server_key(server_key_file, server_cert_file, config, hierarchy)
+    init_server(options, config)
+    sync_interfaces(server_key_file, server_cert_file)   
+    if (options.daemon):  daemon()
+    # start registry server
+    if (options.registry):
+        from sfa.server.registry import Registry
+        r = Registry("", registry_port, server_key_file, server_cert_file)
+        r.start()
+
+    # start aggregate manager
+    if (options.am):
+        from sfa.server.aggregate import Aggregate
+        a = Aggregate("", aggregate_port, server_key_file, server_cert_file)
+        a.start()
+
+    # start slice manager
+    if (options.sm):
+        from sfa.server.slicemgr import SliceMgr
+        s = SliceMgr("", slicemgr_port, server_key_file, server_cert_file)
+        s.start()
+
+    if (options.cm):
+        from sfa.server.component import Component
+        c = Component("", component_port, server_key_file, server_cert_file)
+        c.start()
+
+if __name__ == "__main__":
+    main()
diff --git a/sfa/server/sfa_component_setup.py b/sfa/server/sfa_component_setup.py
new file mode 100755 (executable)
index 0000000..628ae7a
--- /dev/null
@@ -0,0 +1,311 @@
+#!/usr/bin/python
+import sys
+import os
+import tempfile
+from optparse import OptionParser
+from sfa.util.config import Config
+import sfa.util.xmlrpcprotocol as xmlrpcprotocol
+from sfa.util.namespace import *
+from sfa.util.faults import *
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.credential import Credential
+from sfa.trust.gid import GID
+from sfa.trust.hierarchy import Hierarchy
+
+KEYDIR = "/var/lib/sfa/"
+CONFDIR = "/etc/sfa/"
+
+def handle_gid_mismatch_exception(f):
+    def wrapper(*args, **kwds):
+        try: return f(*args, **kwds)
+        except ConnectionKeyGIDMismatch:
+            # clean regen server keypair and try again
+            print "cleaning keys and trying again"
+            clean_key_cred()
+            return f(args, kwds)
+
+    return wrapper
+
+def get_server(url=None, port=None, keyfile=None, certfile=None,verbose=False):
+    """
+    returns an xmlrpc connection to the service a the specified 
+    address
+    """
+    if url:
+        url_parts = url.split(":")
+        if len(url_parts) >1:
+            pass
+        else:
+            url = "http://%(url)s:%(port)s" % locals()
+    else:
+        # connect to registry by default
+        config = Config()
+        addr, port = config.SFA_REGISTRY_HOST, config.SFA_REGISTRY_PORT
+        url = "http://%(addr)s:%(port)s" % locals()
+
+    if verbose:
+        print "Contacting registry at: %(url)s" % locals()
+
+    server = xmlrpcprotocol.get_server(url, keyfile, certfile)
+    return server    
+    
+
+def create_default_dirs():
+    config = Config()
+    hierarchy = Hierarchy()
+    config_dir = config.config_path
+    trusted_certs_dir = config.get_trustedroots_dir()
+    authorities_dir = hierarchy.basedir
+    all_dirs = [config_dir, trusted_certs_dir, authorities_dir]
+    for dir in all_dirs:
+        if not os.path.exists(dir):
+            os.makedirs(dir)
+
+def has_node_key():
+    key_file = KEYDIR + os.sep + 'server.key'
+    return os.path.exists(key_file) 
+
+def clean_key_cred():
+    """
+    remove the existing keypair and cred  and generate new ones
+    """
+    files = ["server.key", "server.cert", "node.cred"]
+    for f in files:
+        filepath = KEYDIR + os.sep + f
+        if os.path.isfile(filepath):
+            os.unlink(f)
+   
+    # install the new key pair
+    # get_credential will take care of generating the new keypair
+    # and credential 
+    get_credential()
+    
+             
+def get_node_key(registry=None, verbose=False):
+    # this call requires no authentication, 
+    # so we can generate a random keypair here
+    subject="component"
+    (kfd, keyfile) = tempfile.mkstemp()
+    (cfd, certfile) = tempfile.mkstemp()
+    key = Keypair(create=True)
+    key.save_to_file(keyfile)
+    cert = Certificate(subject=subject)
+    cert.set_issuer(key=key, subject=subject)
+    cert.set_pubkey(key)
+    cert.sign()
+    cert.save_to_file(certfile)
+    
+    registry = get_server(url = registry, keyfile=keyfile, certfile=certfile)    
+    registry.get_key()
+
+def create_server_keypair(keyfile=None, certfile=None, hrn="component", verbose=False):
+    """
+    create the server key/cert pair in the right place
+    """
+    key = Keypair(filename=keyfile)
+    key.save_to_file(keyfile)
+    cert = Certificate(subject=hrn)
+    cert.set_issuer(key=key, subject=hrn)
+    cert.set_pubkey(key)
+    cert.sign()
+    cert.save_to_file(certfile, save_parents=True)       
+
+@handle_gid_mismatch_exception
+def get_credential(registry=None, force=False, verbose=False):
+    config = Config()
+    hierarchy = Hierarchy()
+    key_dir= hierarchy.basedir
+    data_dir = config.data_path
+    config_dir = config.config_path
+    credfile = data_dir + os.sep + 'node.cred'
+    # check for existing credential
+    if not force and os.path.exists(credfile):
+        if verbose:
+            print "Loading Credential from %(credfile)s " % locals()  
+        cred = Credential(filename=credfile).save_to_string(save_parents=True)
+    else:
+        if verbose:
+            print "Getting credential from registry" 
+        # make sure node private key exists
+        node_pkey_file = config_dir + os.sep + "node.key"
+        node_gid_file = config_dir + os.sep + "node.gid"
+        if not os.path.exists(node_pkey_file) or \
+           not os.path.exists(node_gid_file):
+            get_node_key(registry=registry, verbose=verbose)
+        
+        gid = GID(filename=node_gid_file)
+        hrn = gid.get_hrn()
+        # create server key and certificate
+        keyfile =data_dir + os.sep + "server.key"
+        certfile = data_dir + os.sep + "server.cert"
+        key = Keypair(filename=node_pkey_file)
+        key.save_to_file(keyfile)
+        create_server_keypair(keyfile, certfile, hrn, verbose)
+
+        # get credential from registry 
+        registry = get_server(url=registry, keyfile=keyfile, certfile=certfile)
+        cert = Certificate(filename=certfile)
+        cert_str = cert.save_to_string(save_parents=True)
+        cred = registry.GetSelfCredential(cert_str, hrn, 'node')    
+        Credential(string=cred).save_to_file(credfile, save_parents=True)
+    
+    return cred
+
+@handle_gid_mismatch_exception
+def get_trusted_certs(registry=None, verbose=False):
+    """
+    refresh our list of trusted certs.
+    """
+    # define useful variables
+    config = Config()
+    data_dir = config.SFA_DATA_DIR
+    config_dir = config.SFA_CONFIG_DIR
+    trusted_certs_dir = config.get_trustedroots_dir()
+    keyfile = data_dir + os.sep + "server.key"
+    certfile = data_dir + os.sep + "server.cert"
+    node_gid_file = config_dir + os.sep + "node.gid"
+    node_gid = GID(filename=node_gid_file)
+    hrn = node_gid.get_hrn()
+    # get credential
+    cred = get_credential(registry=registry, verbose=verbose)
+    # make sure server key cert pair exists
+    create_server_keypair(keyfile=keyfile, certfile=certfile, hrn=hrn, verbose=verbose)
+    registry = get_server(url=registry, keyfile=keyfile, certfile=certfile)
+    # get the trusted certs and save them in the right place
+    if verbose:
+        print "Getting trusted certs from registry"
+    trusted_certs = registry.get_trusted_certs(cred)
+    trusted_gid_names = [] 
+    for gid_str in trusted_certs:
+        gid = GID(string=gid_str)
+        gid.decode()
+        relative_filename = gid.get_hrn() + ".gid"
+        trusted_gid_names.append(relative_filename)
+        gid_filename = trusted_certs_dir + os.sep + relative_filename
+        if verbose:
+            print "Writing GID for %s as %s" % (gid.get_hrn(), gid_filename) 
+        gid.save_to_file(gid_filename, save_parents=True)
+
+    # remove old certs
+    all_gids_names = os.listdir(trusted_certs_dir)
+    for gid_name in all_gids_names:
+        if gid_name not in trusted_gid_names:
+            if verbose:
+                print "Removing old gid ", gid_name
+            os.unlink(trusted_certs_dir + os.sep + gid_name)                     
+
+@handle_gid_mismatch_exception
+def get_gids(registry=None, verbose=False):
+    """
+    Get the gid for all instantiated slices on this node and store it
+    in /etc/sfa/slice.gid in the slice's filesystem
+    """
+    # define useful variables
+    config = Config()
+    data_dir = config.data_path
+    config_dir = config.SFA_CONFIG_DIR
+    trusted_certs_dir = config.get_trustedroots_dir()
+    keyfile = data_dir + os.sep + "server.key"
+    certfile = data_dir + os.sep + "server.cert"
+    node_gid_file = config_dir + os.sep + "node.gid"
+    node_gid = GID(filename=node_gid_file)
+    hrn = node_gid.get_hrn()
+    interface_hrn = config.SFA_INTERFACE_HRN
+    # get credential
+    cred = get_credential(registry=registry, verbose=verbose)
+    # make sure server key cert pair exists
+    create_server_keypair(keyfile=keyfile, certfile=certfile, hrn=hrn, verbose=verbose)
+    registry = get_server(url=registry, keyfile=keyfile, certfile=certfile)
+            
+    if verbose:
+        print "Getting current slices on this node"
+    # get a list of slices on this node
+    from sfa.plc.api import ComponentAPI
+    api = ComponentAPI()
+    xids_tuple = api.nodemanager.GetXIDs()
+    slices = eval(xids_tuple[1])
+    slicenames = slices.keys()
+
+    # generate a list of slices that dont have gids installed
+    slices_without_gids = []
+    for slicename in slicenames:
+        if not os.path.isfile("/vservers/%s/etc/slice.gid" % slicename) \
+        or not os.path.isfile("/vservers/%s/etc/node.gid" % slicename):
+            slices_without_gids.append(slicename) 
+    
+    # convert slicenames to hrns
+    hrns = [slicename_to_hrn(interface_hrn, slicename) \
+            for slicename in slices_without_gids]
+    
+    # exit if there are no gids to install
+    if not hrns:
+        return
+        
+    if verbose:
+        print "Getting gids for slices on this node from registry"  
+    # get the gids
+    # and save them in the right palce
+    records = registry.GetGids(hrns, cred)
+    for record in records:
+        # if this isnt a slice record skip it
+        if not record['type'] == 'slice':
+            continue
+        slicename = hrn_to_pl_slicename(record['hrn'])
+        # if this slice isnt really instatiated skip it
+        if not os.path.exists("/vservers/%(slicename)s" % locals()):
+            continue
+       
+        # save the slice gid in /etc/sfa/ in the vservers filesystem
+        vserver_path = "/vservers/%(slicename)s" % locals()
+        gid = record['gid']
+        slice_gid_filename = os.sep.join([vserver_path, "etc", "slice.gid"])
+        if verbose:
+            print "Saving GID for %(slicename)s as %(slice_gid_filename)s" % locals()
+        GID(string=gid).save_to_file(slice_gid_filename, save_parents=True)
+        # save the node gid in /etc/sfa
+        node_gid_filename = os.sep.join([vserver_path, "etc", "node.gid"])
+        if verbose:
+            print "Saving node GID for %(slicename)s as %(node_gid_filename)s" % locals()
+        node_gid.save_to_file(node_gid_filename, save_parents=True) 
+                
+
+def dispatch(options, args):
+
+    create_default_dirs()
+    if options.key:
+        if options.verbose:
+            print "Getting the component's pkey"
+        get_node_key(registry=options.registry, verbose=options.verbose)
+    if options.certs:
+        if options.verbose:
+            print "Getting the component's trusted certs"
+        get_trusted_certs(verbose=options.verbose)
+    if options.gids:        
+        if options.verbose:
+            print "Geting the component's GIDs"
+        get_gids(verbose=options.verbose)
+
+def main():
+    args = sys.argv
+    prog_name = args[0]
+    parser = OptionParser(usage="%(prog_name)s [options]" % locals())
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      default=False, help="Be verbose") 
+    parser.add_option("-r", "--registry", dest="registry", default=None,
+                      help="Url of registry to contact")  
+    parser.add_option("-k", "--key", dest="key", action="store_true", 
+                     default=False,  
+                     help="Get the node's pkey from the registry")
+    parser.add_option("-c", "--certs", dest="certs", action="store_true",
+                      default=False,
+                      help="Get the trusted certs from the registry")
+    parser.add_option("-g", "--gids", dest="gids", action="store_true",       
+                      default=False,
+                      help="Get gids for all the slices on the component")
+
+    (options, args) = parser.parse_args()
+
+    dispatch(options, args)
+
+if __name__ == '__main__':
+    main()    
diff --git a/sfa/server/slicemgr.py b/sfa/server/slicemgr.py
new file mode 100644 (file)
index 0000000..2f70627
--- /dev/null
@@ -0,0 +1,23 @@
+### $Id: slicemgr.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/server/slicemgr.py $
+
+import os
+import sys
+import datetime
+import time
+from sfa.util.server import *
+
+class SliceMgr(SfaServer):
+
+  
+    ##
+    # Create a new slice manager object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key (could be a GID file)     
+
+    def __init__(self, ip, port, key_file, cert_file, config = "/etc/sfa/sfa_config"):
+        SfaServer.__init__(self, ip, port, key_file, cert_file)
+        self.server.interface = 'slicemgr'      
diff --git a/sfa/trust/__init__.py b/sfa/trust/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/trust/auth.py b/sfa/trust/auth.py
new file mode 100644 (file)
index 0000000..17f3ec8
--- /dev/null
@@ -0,0 +1,321 @@
+#
+# SfaAPI authentication 
+#
+### $Id: auth.py 18648 2010-08-20 22:29:27Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/auth.py $
+#
+
+
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.credential import Credential
+from sfa.trust.trustedroot import TrustedRootList
+from sfa.util.faults import *
+from sfa.trust.hierarchy import Hierarchy
+from sfa.util.config import *
+from sfa.util.namespace import *
+from sfa.util.sfaticket import *
+from sfa.util.sfalogging import logger
+import sys
+
+class Auth:
+    """
+    Credential based authentication
+    """
+
+    def __init__(self, peer_cert = None, config = None ):
+        self.peer_cert = peer_cert
+        self.hierarchy = Hierarchy()
+        if not config:
+            self.config = Config()
+        self.load_trusted_certs()
+
+    def load_trusted_certs(self):
+        self.trusted_cert_list = TrustedRootList(self.config.get_trustedroots_dir()).get_list()
+        self.trusted_cert_file_list = TrustedRootList(self.config.get_trustedroots_dir()).get_file_list()
+
+        
+        
+    def checkCredentials(self, creds, operation, hrn = None):
+        valid = []
+        if not isinstance(creds, list):
+            creds = [creds]
+        for cred in creds:
+            try:
+                self.check(cred, operation, hrn)
+                valid.append(cred)
+            except:
+                error = sys.exc_info()[:2]
+                continue
+            
+        if not len(valid):
+            raise InsufficientRights('Access denied: %s -- %s' % (error[0],error[1]))
+        
+        return valid
+        
+        
+    def check(self, cred, operation, hrn = None):
+        """
+        Check the credential against the peer cert (callerGID included 
+        in the credential matches the caller that is connected to the 
+        HTTPS connection, check if the credential was signed by a 
+        trusted cert and check if the credential is allowd to perform 
+        the specified operation.    
+        """
+        self.client_cred = Credential(string = cred)
+        self.client_gid = self.client_cred.get_gid_caller()
+        self.object_gid = self.client_cred.get_gid_object()
+        
+        # make sure the client_gid is not blank
+        if not self.client_gid:
+            raise MissingCallerGID(self.client_cred.get_subject())
+       
+        # validate the client cert if it exists
+        if self.peer_cert:
+            self.verifyPeerCert(self.peer_cert, self.client_gid)                   
+
+        # make sure the client is allowed to perform the operation
+        if operation:
+            if not self.client_cred.can_perform(operation):
+                raise InsufficientRights(operation)
+
+        if self.trusted_cert_list:
+            self.client_cred.verify(self.trusted_cert_file_list)
+        else:
+           raise MissingTrustedRoots(self.config.get_trustedroots_dir())
+       
+        # Make sure the credential's target matches the specified hrn. 
+        # This check does not apply to trusted peers 
+        trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
+        if hrn and self.client_gid.get_hrn() not in trusted_peers:
+            target_hrn = self.object_gid.get_hrn()
+            if not hrn == target_hrn:
+                raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
+                                       (target_hrn, hrn) )       
+        return True
+
+    def check_ticket(self, ticket):
+        """
+        Check if the tickt was signed by a trusted cert
+        """
+        if self.trusted_cert_list:
+            client_ticket = SfaTicket(string=ticket)
+            client_ticket.verify_chain(self.trusted_cert_list)
+        else:
+           raise MissingTrustedRoots(self.config.get_trustedroots_dir())
+
+        return True 
+
+    def verifyPeerCert(self, cert, gid):
+        # make sure the client_gid matches client's certificate
+        if not cert.is_pubkey(gid.get_pubkey()):
+            raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())            
+
+    def verifyGidRequestHash(self, gid, hash, arglist):
+        key = gid.get_pubkey()
+        if not key.verify_string(str(arglist), hash):
+            raise BadRequestHash(hash)
+
+    def verifyCredRequestHash(self, cred, hash, arglist):
+        gid = cred.get_gid_caller()
+        self.verifyGidRequestHash(gid, hash, arglist)
+
+    def validateGid(self, gid):
+        if self.trusted_cert_list:
+            gid.verify_chain(self.trusted_cert_list)
+
+    def validateCred(self, cred):
+        if self.trusted_cert_list:
+            cred.verify(self.trusted_cert_file_list)
+
+    def authenticateGid(self, gidStr, argList, requestHash=None):
+        gid = GID(string = gidStr)
+        self.validateGid(gid)
+        # request_hash is optional
+        if requestHash:
+            self.verifyGidRequestHash(gid, requestHash, argList)
+        return gid
+
+    def authenticateCred(self, credStr, argList, requestHash=None):
+        cred = Credential(string = credStr)
+        self.validateCred(cred)
+        # request hash is optional
+        if requestHash:
+            self.verifyCredRequestHash(cred, requestHash, argList)
+        return cred
+
+    def authenticateCert(self, certStr, requestHash):
+        cert = Certificate(string=certStr)
+        self.validateCert(self, cert)   
+
+    def gidNoop(self, gidStr, value, requestHash):
+        self.authenticateGid(gidStr, [gidStr, value], requestHash)
+        return value
+
+    def credNoop(self, credStr, value, requestHash):
+        self.authenticateCred(credStr, [credStr, value], requestHash)
+        return value
+
+    def verify_cred_is_me(self, credential):
+        is_me = False 
+        cred = Credential(string=credential)
+        caller_gid = cred.get_gid_caller()
+        caller_hrn = caller_gid.get_hrn()
+        if caller_hrn != self.config.SFA_INTERFACE_HRN:
+            raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
+
+        return   
+        
+    def get_auth_info(self, auth_hrn):
+        """
+        Given an authority name, return the information for that authority.
+        This is basically a stub that calls the hierarchy module.
+        
+        @param auth_hrn human readable name of authority  
+        """
+
+        return self.hierarchy.get_auth_info(auth_hrn)
+
+
+    def veriry_auth_belongs_to_me(self, name):
+        """
+        Verify that an authority belongs to our hierarchy. 
+        This is basically left up to the implementation of the hierarchy
+        module. If the specified name does not belong, ane exception is 
+        thrown indicating the caller should contact someone else.
+
+        @param auth_name human readable name of authority
+        """
+
+        # get auth info will throw an exception if the authority doesnt exist
+        self.get_auth_info(name)
+
+
+    def verify_object_belongs_to_me(self, name):
+        """
+        Verify that an object belongs to our hierarchy. By extension,
+        this implies that the authority that owns the object belongs
+        to our hierarchy. If it does not an exception is thrown.
+    
+        @param name human readable name of object        
+        """
+        auth_name = self.get_authority(name)
+        if not auth_name:
+            auth_name = name 
+        if name == self.config.SFA_INTERFACE_HRN:
+            return
+        self.verify_auth_belongs_to_me(auth_name) 
+             
+    def verify_auth_belongs_to_me(self, name):
+        # get auth info will throw an exception if the authority doesnt exist
+        self.get_auth_info(name) 
+
+
+    def verify_object_permission(self, name):
+        """
+        Verify that the object gid that was specified in the credential
+        allows permission to the object 'name'. This is done by a simple
+        prefix test. For example, an object_gid for plc.arizona would 
+        match the objects plc.arizona.slice1 and plc.arizona.
+    
+        @param name human readable name to test  
+        """
+        object_hrn = self.object_gid.get_hrn()
+        if object_hrn == name:
+            return
+        if name.startswith(object_hrn + "."):
+            return
+        #if name.startswith(get_authority(name)):
+            #return
+    
+        raise PermissionError(name)
+
+    def determine_user_rights(self, caller_hrn, record):
+        """
+        Given a user credential and a record, determine what set of rights the
+        user should have to that record.
+        
+        This is intended to replace determine_rights() and
+        verify_cancreate_credential()
+        """
+
+        rl = RightList()
+        type = record['type']
+
+
+        if type=="slice":
+            researchers = record.get("researcher", [])
+            pis = record.get("PI", [])
+            if (caller_hrn in researchers + pis):
+                rl.add("refresh")
+                rl.add("embed")
+                rl.add("bind")
+                rl.add("control")
+                rl.add("info")
+
+        elif type == "authority":
+            pis = record.get("PI", [])
+            operators = record.get("operator", [])
+            if (caller_hrn == self.config.SFA_INTERFACE_HRN):
+                rl.add("authority")
+                rl.add("sa")
+                rl.add("ma")
+            if (caller_hrn in pis):
+                rl.add("authority")
+                rl.add("sa")
+            if (caller_hrn in operators):
+                rl.add("authority")
+                rl.add("ma")
+
+        elif type == "user":
+            rl.add("refresh")
+            rl.add("resolve")
+            rl.add("info")
+
+        elif type == "node":
+            rl.add("operator")
+
+        return rl
+
+    def verify_cancreate_credential(self, src_cred, record):
+        """
+        Verify that a user can retrive a particular type of credential.
+        For slices, the user must be on the researcher list. For SA and
+        MA the user must be on the pi and operator lists respectively
+        """
+
+        type = record.get_type()
+        cred_object_hrn = src_cred.get_gid_object().get_hrn()
+        if cred_object_hrn in [self.config.SFA_REGISTRY_ROOT_AUTH]:
+            return
+        if type=="slice":
+            researchers = record.get("researcher", [])
+            if not (cred_object_hrn in researchers):
+                raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
+        elif type == "sa":
+            pis = record.get("pi", [])
+            if not (cred_object_hrn in pis):
+                raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
+        elif type == "ma":
+            operators = record.get("operator", [])
+            if not (cred_object_hrn in operators):
+                raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
+
+    def get_authority(self, hrn):
+        return get_authority(hrn)
+
+    def filter_creds_by_caller(self, creds, caller_hrn):
+        """
+        Returns a list of creds who's gid caller matches the 
+        specified caller hrn
+        """
+        if not isinstance(creds, list):
+            creds = [creds]
+        creds = []
+        for cred in creds:
+            try:
+                tmp_cred = Credential(string=cred)
+                if tmp_cred.get_gid_caller().get_hrn() == caller_hrn:
+                    creds.append(cred)
+            except: pass
+        return creds
+
diff --git a/sfa/trust/certificate.py b/sfa/trust/certificate.py
new file mode 100644 (file)
index 0000000..2d33cff
--- /dev/null
@@ -0,0 +1,604 @@
+#----------------------------------------------------------------------
+# Copyright (c) 2008 Board of Trustees, Princeton University
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
+# IN THE WORK.
+#----------------------------------------------------------------------
+
+##
+# SFA uses two crypto libraries: pyOpenSSL and M2Crypto to implement
+# the necessary crypto functionality. Ideally just one of these libraries
+# would be used, but unfortunately each of these libraries is independently
+# lacking. The pyOpenSSL library is missing many necessary functions, and
+# the M2Crypto library has crashed inside of some of the functions. The
+# design decision is to use pyOpenSSL whenever possible as it seems more
+# stable, and only use M2Crypto for those functions that are not possible
+# in pyOpenSSL.
+#
+# This module exports two classes: Keypair and Certificate.
+##
+#
+### $Id: certificate.py 18513 2010-07-14 17:00:23Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/certificate.py $
+#
+
+import os
+import tempfile
+import base64
+import traceback
+from OpenSSL import crypto
+import M2Crypto
+from M2Crypto import X509
+from tempfile import mkstemp
+from sfa.util.sfalogging import logger
+from sfa.util.namespace import urn_to_hrn
+from sfa.util.faults import *
+
+def convert_public_key(key):
+    keyconvert_path = "/usr/bin/keyconvert.py"
+    if not os.path.isfile(keyconvert_path):
+        raise IOError, "Could not find keyconvert in %s" % keyconvert_path
+
+    # we can only convert rsa keys
+    if "ssh-dss" in key:
+        return None
+
+    (ssh_f, ssh_fn) = tempfile.mkstemp()
+    ssl_fn = tempfile.mktemp()
+    os.write(ssh_f, key)
+    os.close(ssh_f)
+
+    cmd = keyconvert_path + " " + ssh_fn + " " + ssl_fn
+    os.system(cmd)
+
+    # this check leaves the temporary file containing the public key so
+    # that it can be expected to see why it failed.
+    # TODO: for production, cleanup the temporary files
+    if not os.path.exists(ssl_fn):
+        return None
+
+    k = Keypair()
+    try:
+        k.load_pubkey_from_file(ssl_fn)
+    except:
+        traceback.print_exc()
+        k = None
+
+    # remove the temporary files
+    os.remove(ssh_fn)
+    os.remove(ssl_fn)
+
+    return k
+
+##
+# Public-private key pairs are implemented by the Keypair class.
+# A Keypair object may represent both a public and private key pair, or it
+# may represent only a public key (this usage is consistent with OpenSSL).
+
+class Keypair:
+    key = None       # public/private keypair
+    m2key = None     # public key (m2crypto format)
+
+    ##
+    # Creates a Keypair object
+    # @param create If create==True, creates a new public/private key and
+    #     stores it in the object
+    # @param string If string!=None, load the keypair from the string (PEM)
+    # @param filename If filename!=None, load the keypair from the file
+
+    def __init__(self, create=False, string=None, filename=None):
+        if create:
+            self.create()
+        if string:
+            self.load_from_string(string)
+        if filename:
+            self.load_from_file(filename)
+
+    ##
+    # Create a RSA public/private key pair and store it inside the keypair object
+
+    def create(self):
+        self.key = crypto.PKey()
+        self.key.generate_key(crypto.TYPE_RSA, 1024)
+
+    ##
+    # Save the private key to a file
+    # @param filename name of file to store the keypair in
+
+    def save_to_file(self, filename):
+        open(filename, 'w').write(self.as_pem())
+
+    ##
+    # Load the private key from a file. Implicity the private key includes the public key.
+
+    def load_from_file(self, filename):
+        buffer = open(filename, 'r').read()
+        self.load_from_string(buffer)
+
+    ##
+    # Load the private key from a string. Implicitly the private key includes the public key.
+
+    def load_from_string(self, string):
+        self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
+        self.m2key = M2Crypto.EVP.load_key_string(string)
+
+    ##
+    #  Load the public key from a string. No private key is loaded.
+
+    def load_pubkey_from_file(self, filename):
+        # load the m2 public key
+        m2rsakey = M2Crypto.RSA.load_pub_key(filename)
+        self.m2key = M2Crypto.EVP.PKey()
+        self.m2key.assign_rsa(m2rsakey)
+
+        # create an m2 x509 cert
+        m2name = M2Crypto.X509.X509_Name()
+        m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0)
+        m2x509 = M2Crypto.X509.X509()
+        m2x509.set_pubkey(self.m2key)
+        m2x509.set_serial_number(0)
+        m2x509.set_issuer_name(m2name)
+        m2x509.set_subject_name(m2name)
+        ASN1 = M2Crypto.ASN1.ASN1_UTCTIME()
+        ASN1.set_time(500)
+        m2x509.set_not_before(ASN1)
+        m2x509.set_not_after(ASN1)
+        junk_key = Keypair(create=True)
+        m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1")
+
+        # convert the m2 x509 cert to a pyopenssl x509
+        m2pem = m2x509.as_pem()
+        pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem)
+
+        # get the pyopenssl pkey from the pyopenssl x509
+        self.key = pyx509.get_pubkey()
+
+    ##
+    # Load the public key from a string. No private key is loaded.
+
+    def load_pubkey_from_string(self, string):
+        (f, fn) = tempfile.mkstemp()
+        os.write(f, string)
+        os.close(f)
+        self.load_pubkey_from_file(fn)
+        os.remove(fn)
+
+    ##
+    # Return the private key in PEM format.
+
+    def as_pem(self):
+        return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key)
+
+    ##
+    # Return an M2Crypto key object
+
+    def get_m2_pkey(self):
+        if not self.m2key:
+            self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
+        return self.m2key
+
+    ##
+    # Returns a string containing the public key represented by this object.
+
+    def get_pubkey_string(self):
+        m2pkey = self.get_m2_pkey()
+        return base64.b64encode(m2pkey.as_der())
+
+    ##
+    # Return an OpenSSL pkey object
+
+    def get_openssl_pkey(self):
+        return self.key
+
+
+    ##
+    # Given another Keypair object, return TRUE if the two keys are the same.
+
+    def is_same(self, pkey):
+        return self.as_pem() == pkey.as_pem()
+
+    def sign_string(self, data):
+        k = self.get_m2_pkey()
+        k.sign_init()
+        k.sign_update(data)
+        return base64.b64encode(k.sign_final())
+
+    def verify_string(self, data, sig):
+        k = self.get_m2_pkey()
+        k.verify_init()
+        k.verify_update(data)
+        return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey)
+
+    def compute_hash(self, value):
+        return self.sign_string(str(value))
+
+##
+# The certificate class implements a general purpose X509 certificate, making
+# use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds
+# several addition features, such as the ability to maintain a chain of
+# parent certificates, and storage of application-specific data.
+#
+# Certificates include the ability to maintain a chain of parents. Each
+# certificate includes a pointer to it's parent certificate. When loaded
+# from a file or a string, the parent chain will be automatically loaded.
+# When saving a certificate to a file or a string, the caller can choose
+# whether to save the parent certificates as well.
+
+class Certificate:
+    digest = "md5"
+
+    cert = None
+    issuerKey = None
+    issuerSubject = None
+    parent = None
+
+    separator="-----parent-----"
+
+    ##
+    # Create a certificate object.
+    #
+    # @param create If create==True, then also create a blank X509 certificate.
+    # @param subject If subject!=None, then create a blank certificate and set
+    #     it's subject name.
+    # @param string If string!=None, load the certficate from the string.
+    # @param filename If filename!=None, load the certficiate from the file.
+
+    def __init__(self, create=False, subject=None, string=None, filename=None, intermediate=None):
+        self.data = {}
+        if create or subject:
+            self.create()
+        if subject:
+            self.set_subject(subject)
+        if string:
+            self.load_from_string(string)
+        if filename:
+            self.load_from_file(filename)
+
+        if intermediate:
+            self.set_intermediate_ca(intermediate)
+
+    ##
+    # Create a blank X509 certificate and store it in this object.
+
+    def create(self):
+        self.cert = crypto.X509()
+        self.cert.set_serial_number(3)
+        self.cert.gmtime_adj_notBefore(0)
+        self.cert.gmtime_adj_notAfter(60*60*24*365*5) # five years
+
+    ##
+    # Given a pyOpenSSL X509 object, store that object inside of this
+    # certificate object.
+
+    def load_from_pyopenssl_x509(self, x509):
+        self.cert = x509
+
+    ##
+    # Load the certificate from a string
+
+    def load_from_string(self, string):
+        # if it is a chain of multiple certs, then split off the first one and
+        # load it (support for the ---parent--- tag as well as normal chained certs)
+
+        string = string.strip()
+
+
+        if not string.startswith('-----'):
+            string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string
+
+        parts = []
+
+        if string.count('-----BEGIN CERTIFICATE-----') > 1 and \
+               string.count(Certificate.separator) == 0:
+            parts = string.split('-----END CERTIFICATE-----',1)
+            parts[0] += '-----END CERTIFICATE-----'
+        else:
+            parts = string.split(Certificate.separator, 1)
+
+        self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0])
+
+        # if there are more certs, then create a parent and let the parent load
+        # itself from the remainder of the string
+        if len(parts) > 1 and parts[1] != '':
+            self.parent = self.__class__()
+            self.parent.load_from_string(parts[1])
+
+    ##
+    # Load the certificate from a file
+
+    def load_from_file(self, filename):
+        file = open(filename)
+        string = file.read()
+        self.load_from_string(string)
+
+    ##
+    # Save the certificate to a string.
+    #
+    # @param save_parents If save_parents==True, then also save the parent certificates.
+
+    def save_to_string(self, save_parents=True):
+        string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
+        if save_parents and self.parent:
+            string = string + self.parent.save_to_string(save_parents)
+        return string
+
+    ##
+    # Save the certificate to a file.
+    # @param save_parents If save_parents==True, then also save the parent certificates.
+
+    def save_to_file(self, filename, save_parents=True, filep=None):
+        string = self.save_to_string(save_parents=save_parents)
+        if filep:
+            f = filep
+        else:
+            f = open(filename, 'w')
+        f.write(string)
+        f.close()
+
+    ##
+    # Save the certificate to a random file in /tmp/
+    # @param save_parents If save_parents==True, then also save the parent certificates.
+    def save_to_random_tmp_file(self, save_parents=True):
+        fp, filename = mkstemp(suffix='cert', text=True)
+        fp = os.fdopen(fp, "w")
+        self.save_to_file(filename, save_parents=True, filep=fp)
+        return filename
+
+    ##
+    # Sets the issuer private key and name
+    # @param key Keypair object containing the private key of the issuer
+    # @param subject String containing the name of the issuer
+    # @param cert (optional) Certificate object containing the name of the issuer
+
+    def set_issuer(self, key, subject=None, cert=None):
+        self.issuerKey = key
+        if subject:
+            # it's a mistake to use subject and cert params at the same time
+            assert(not cert)
+            if isinstance(subject, dict) or isinstance(subject, str):
+                req = crypto.X509Req()
+                reqSubject = req.get_subject()
+                if (isinstance(subject, dict)):
+                    for key in reqSubject.keys():
+                        setattr(reqSubject, key, subject[key])
+                else:
+                    setattr(reqSubject, "CN", subject)
+                subject = reqSubject
+                # subject is not valid once req is out of scope, so save req
+                self.issuerReq = req
+        if cert:
+            # if a cert was supplied, then get the subject from the cert
+            subject = cert.cert.get_subject()
+        assert(subject)
+        self.issuerSubject = subject
+
+    ##
+    # Get the issuer name
+
+    def get_issuer(self, which="CN"):
+        x = self.cert.get_issuer()
+        return getattr(x, which)
+
+    ##
+    # Set the subject name of the certificate
+
+    def set_subject(self, name):
+        req = crypto.X509Req()
+        subj = req.get_subject()
+        if (isinstance(name, dict)):
+            for key in name.keys():
+                setattr(subj, key, name[key])
+        else:
+            setattr(subj, "CN", name)
+        self.cert.set_subject(subj)
+    ##
+    # Get the subject name of the certificate
+
+    def get_subject(self, which="CN"):
+        x = self.cert.get_subject()
+        return getattr(x, which)
+
+    ##
+    # Get the public key of the certificate.
+    #
+    # @param key Keypair object containing the public key
+
+    def set_pubkey(self, key):
+        assert(isinstance(key, Keypair))
+        self.cert.set_pubkey(key.get_openssl_pkey())
+
+    ##
+    # Get the public key of the certificate.
+    # It is returned in the form of a Keypair object.
+
+    def get_pubkey(self):
+        m2x509 = X509.load_cert_string(self.save_to_string())
+        pkey = Keypair()
+        pkey.key = self.cert.get_pubkey()
+        pkey.m2key = m2x509.get_pubkey()
+        return pkey
+
+    def set_intermediate_ca(self, val):
+        self.intermediate = val
+        if val:
+            self.add_extension('basicConstraints', 1, 'CA:TRUE')
+
+
+
+    ##
+    # Add an X509 extension to the certificate. Add_extension can only be called
+    # once for a particular extension name, due to limitations in the underlying
+    # library.
+    #
+    # @param name string containing name of extension
+    # @param value string containing value of the extension
+
+    def add_extension(self, name, critical, value):
+        ext = crypto.X509Extension (name, critical, value)
+        self.cert.add_extensions([ext])
+
+    ##
+    # Get an X509 extension from the certificate
+
+    def get_extension(self, name):
+        # pyOpenSSL does not have a way to get extensions
+        m2x509 = X509.load_cert_string(self.save_to_string())
+        value = m2x509.get_ext(name).get_value()
+        return value
+
+    ##
+    # Set_data is a wrapper around add_extension. It stores the parameter str in
+    # the X509 subject_alt_name extension. Set_data can only be called once, due
+    # to limitations in the underlying library.
+
+    def set_data(self, str, field='subjectAltName'):
+        # pyOpenSSL only allows us to add extensions, so if we try to set the
+        # same extension more than once, it will not work
+        if self.data.has_key(field):
+            raise "Cannot set ", field, " more than once"
+        self.data[field] = str
+        self.add_extension(field, 0, str)
+
+    ##
+    # Return the data string that was previously set with set_data
+
+    def get_data(self, field='subjectAltName'):
+        if self.data.has_key(field):
+            return self.data[field]
+
+        try:
+            uri = self.get_extension(field)
+            self.data[field] = uri
+        except LookupError:
+            return None
+
+        return self.data[field]
+
+    ##
+    # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer().
+
+    def sign(self):
+        assert self.cert != None
+        assert self.issuerSubject != None
+        assert self.issuerKey != None
+        self.cert.set_issuer(self.issuerSubject)
+        self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest)
+
+    ##
+    # Verify the authenticity of a certificate.
+    # @param pkey is a Keypair object representing a public key. If Pkey
+    #     did not sign the certificate, then an exception will be thrown.
+
+    def verify(self, pkey):
+        # pyOpenSSL does not have a way to verify signatures
+        m2x509 = X509.load_cert_string(self.save_to_string())
+        m2pkey = pkey.get_m2_pkey()
+        # verify it
+        return m2x509.verify(m2pkey)
+
+        # XXX alternatively, if openssl has been patched, do the much simpler:
+        # try:
+        #   self.cert.verify(pkey.get_openssl_key())
+        #   return 1
+        # except:
+        #   return 0
+
+    ##
+    # Return True if pkey is identical to the public key that is contained in the certificate.
+    # @param pkey Keypair object
+
+    def is_pubkey(self, pkey):
+        return self.get_pubkey().is_same(pkey)
+
+    ##
+    # Given a certificate cert, verify that this certificate was signed by the
+    # public key contained in cert. Throw an exception otherwise.
+    #
+    # @param cert certificate object
+
+    def is_signed_by_cert(self, cert):
+        k = cert.get_pubkey()
+        result = self.verify(k)
+        return result
+
+    ##
+    # Set the parent certficiate.
+    #
+    # @param p certificate object.
+
+    def set_parent(self, p):
+        self.parent = p
+
+    ##
+    # Return the certificate object of the parent of this certificate.
+
+    def get_parent(self):
+        return self.parent
+
+    ##
+    # Verification examines a chain of certificates to ensure that each parent
+    # signs the child, and that some certificate in the chain is signed by a
+    # trusted certificate.
+    #
+    # Verification is a basic recursion: <pre>
+    #     if this_certificate was signed by trusted_certs:
+    #         return
+    #     else
+    #         return verify_chain(parent, trusted_certs)
+    # </pre>
+    #
+    # At each recursion, the parent is tested to ensure that it did sign the
+    # child. If a parent did not sign a child, then an exception is thrown. If
+    # the bottom of the recursion is reached and the certificate does not match
+    # a trusted root, then an exception is thrown.
+    #
+    # @param Trusted_certs is a list of certificates that are trusted.
+    #
+
+    def verify_chain(self, trusted_certs = None):
+        # Verify a chain of certificates. Each certificate must be signed by
+        # the public key contained in it's parent. The chain is recursed
+        # until a certificate is found that is signed by a trusted root.
+        # TODO: verify expiration time
+        #print "====Verify Chain====="
+        # if this cert is signed by a trusted_cert, then we are set
+        for trusted_cert in trusted_certs:
+            #print "***************"
+            # TODO: verify expiration of trusted_cert ?
+            #print "CLIENT CERT", self.dump()
+            #print "TRUSTED CERT", trusted_cert.dump()
+            #print "Client is signed by Trusted?", self.is_signed_by_cert(trusted_cert)
+            if self.is_signed_by_cert(trusted_cert):
+                logger.debug("Cert %s signed by trusted cert %s", self.get_subject(), trusted_cert.get_subject())
+                return trusted_cert
+
+        # if there is no parent, then no way to verify the chain
+        if not self.parent:
+            #print self.get_subject(), "has no parent"
+            raise CertMissingParent(self.get_subject())
+
+        # if it wasn't signed by the parent...
+        if not self.is_signed_by_cert(self.parent):
+            #print self.get_subject(), "is not signed by parent"
+            return CertNotSignedByParent(self.get_subject())
+
+        # if the parent isn't verified...
+        self.parent.verify_chain(trusted_certs)
+
+        return
diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py
new file mode 100644 (file)
index 0000000..e082f75
--- /dev/null
@@ -0,0 +1,851 @@
+#----------------------------------------------------------------------
+# Copyright (c) 2008 Board of Trustees, Princeton University
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
+# IN THE WORK.
+#----------------------------------------------------------------------
+##
+# Implements SFA Credentials
+#
+# Credentials are signed XML files that assign a subject gid privileges to an object gid
+##
+
+### $Id: credential.py 18654 2010-08-23 18:54:57Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/credential.py $
+
+import os
+import datetime
+from xml.dom.minidom import Document, parseString
+from tempfile import mkstemp
+from sfa.trust.certificate import Keypair
+from sfa.trust.credential_legacy import CredentialLegacy
+from sfa.trust.rights import *
+from sfa.trust.gid import *
+from sfa.util.faults import *
+
+from sfa.util.sfalogging import logger
+from dateutil.parser import parse
+
+
+
+# Two years, in seconds 
+DEFAULT_CREDENTIAL_LIFETIME = 60 * 60 * 24 * 365 * 2
+
+
+# TODO:
+# . make privs match between PG and PL
+# . Need to add support for other types of credentials, e.g. tickets
+
+
+signature_template = \
+'''
+<Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
+    <SignedInfo>
+      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
+      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+      <Reference URI="#%s">
+      <Transforms>
+        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
+      </Transforms>
+      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+      <DigestValue></DigestValue>
+      </Reference>
+    </SignedInfo>
+    <SignatureValue />
+      <KeyInfo>
+        <X509Data>
+          <X509SubjectName/>
+          <X509IssuerSerial/>
+          <X509Certificate/>
+        </X509Data>
+      <KeyValue />
+      </KeyInfo>
+    </Signature>
+'''
+
+##
+# Convert a string into a bool
+
+def str2bool(str):
+    if str.lower() in ['yes','true','1']:
+        return True
+    return False
+
+
+##
+# Utility function to get the text of an XML element
+
+def getTextNode(element, subele):
+    sub = element.getElementsByTagName(subele)[0]
+    if len(sub.childNodes) > 0:            
+        return sub.childNodes[0].nodeValue
+    else:
+        return None
+        
+##
+# Utility function to set the text of an XML element
+# It creates the element, adds the text to it,
+# and then appends it to the parent.
+
+def append_sub(doc, parent, element, text):
+    ele = doc.createElement(element)
+    ele.appendChild(doc.createTextNode(text))
+    parent.appendChild(ele)
+
+##
+# Signature contains information about an xmlsec1 signature
+# for a signed-credential
+#
+
+class Signature(object):
+   
+    def __init__(self, string=None):
+        self.refid = None
+        self.issuer_gid = None
+        self.xml = None
+        if string:
+            self.xml = string
+            self.decode()
+
+
+    def get_refid(self):
+        if not self.refid:
+            self.decode()
+        return self.refid
+
+    def get_xml(self):
+        if not self.xml:
+            self.encode()
+        return self.xml
+
+    def set_refid(self, id):
+        self.refid = id
+
+    def get_issuer_gid(self):
+        if not self.gid:
+            self.decode()
+        return self.gid        
+
+    def set_issuer_gid(self, gid):
+        self.gid = gid
+
+    def decode(self):
+        doc = parseString(self.xml)
+        sig = doc.getElementsByTagName("Signature")[0]
+        self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
+        keyinfo = sig.getElementsByTagName("X509Data")[0]
+        szgid = getTextNode(keyinfo, "X509Certificate")
+        szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
+        self.set_issuer_gid(GID(string=szgid))        
+        
+    def encode(self):
+        self.xml = signature_template % (self.get_refid(), self.get_refid())
+
+
+##
+# A credential provides a caller gid with privileges to an object gid.
+# A signed credential is signed by the object's authority.
+#
+# Credentials are encoded in one of two ways.  The legacy style places
+# it in the subjectAltName of an X509 certificate.  The new credentials
+# are placed in signed XML.
+#
+# WARNING:
+# In general, a signed credential obtained externally should
+# not be changed else the signature is no longer valid.  So, once
+# you have loaded an existing signed credential, do not call encode() or sign() on it.
+
+def filter_creds_by_caller(creds, caller_hrn):
+        """
+        Returns a list of creds who's gid caller matches the
+        specified caller hrn
+        """
+        if not isinstance(creds, list): creds = [creds]
+        caller_creds = []
+        for cred in creds:
+            try:
+                tmp_cred = Credential(string=cred)
+                if tmp_cred.get_gid_caller().get_hrn() == caller_hrn:
+                    caller_creds.append(cred)
+            except: pass
+        return caller_creds
+
+class Credential(object):
+
+    ##
+    # Create a Credential object
+    #
+    # @param create If true, create a blank x509 certificate
+    # @param subject If subject!=None, create an x509 cert with the subject name
+    # @param string If string!=None, load the credential from the string
+    # @param filename If filename!=None, load the credential from the file
+    # FIXME: create and subject are ignored!
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        self.gidCaller = None
+        self.gidObject = None
+        self.expiration = None
+        self.privileges = None
+        self.issuer_privkey = None
+        self.issuer_gid = None
+        self.issuer_pubkey = None
+        self.parent = None
+        self.signature = None
+        self.xml = None
+        self.refid = None
+        self.legacy = None
+
+        # Check if this is a legacy credential, translate it if so
+        if string or filename:
+            if string:                
+                str = string
+            elif filename:
+                str = file(filename).read()
+                
+            if str.strip().startswith("-----"):
+                self.legacy = CredentialLegacy(False,string=str)
+                self.translate_legacy(str)
+            else:
+                self.xml = str
+                self.decode()
+
+        # Find an xmlsec1 path
+        self.xmlsec_path = ''
+        paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']
+        for path in paths:
+            if os.path.isfile(path + '/' + 'xmlsec1'):
+                self.xmlsec_path = path + '/' + 'xmlsec1'
+                break
+
+    def get_subject(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject.get_subject()   
+
+    def get_signature(self):
+        if not self.signature:
+            self.decode()
+        return self.signature
+
+    def set_signature(self, sig):
+        self.signature = sig
+
+        
+    ##
+    # Translate a legacy credential into a new one
+    #
+    # @param String of the legacy credential
+
+    def translate_legacy(self, str):
+        legacy = CredentialLegacy(False,string=str)
+        self.gidCaller = legacy.get_gid_caller()
+        self.gidObject = legacy.get_gid_object()
+        lifetime = legacy.get_lifetime()
+        if not lifetime:
+            # Default to two years
+            self.set_lifetime(DEFAULT_CREDENTIAL_LIFETIME)
+        else:
+            self.set_lifetime(int(lifetime))
+        self.lifeTime = legacy.get_lifetime()
+        self.set_privileges(legacy.get_privileges())
+        self.get_privileges().delegate_all_privileges(legacy.get_delegate())
+
+    ##
+    # Need the issuer's private key and name
+    # @param key Keypair object containing the private key of the issuer
+    # @param gid GID of the issuing authority
+
+    def set_issuer_keys(self, privkey, gid):
+        self.issuer_privkey = privkey
+        self.issuer_gid = gid
+
+
+    ##
+    # Set this credential's parent
+    def set_parent(self, cred):
+        self.parent = cred
+        self.updateRefID()
+
+    ##
+    # set the GID of the caller
+    #
+    # @param gid GID object of the caller
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+        # gid origin caller is the caller's gid by default
+        self.gidOriginCaller = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    ##
+    # set the GID of the object
+    #
+    # @param gid GID object of the object
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    ##
+    # set the lifetime of this credential
+    #
+    # @param lifetime lifetime of credential
+    # . if lifeTime is a datetime object, it is used for the expiration time
+    # . if lifeTime is an integer value, it is considered the number of seconds
+    #   remaining before expiration
+
+    def set_lifetime(self, lifeTime):
+        if isinstance(lifeTime, int):
+            self.expiration = datetime.timedelta(seconds=lifeTime) + datetime.datetime.utcnow()
+        else:
+            self.expiration = lifeTime
+
+    ##
+    # get the lifetime of the credential (in datetime format)
+
+    def get_lifetime(self):
+        if not self.expiration:
+            self.decode()
+        return self.expiration
+
+    ##
+    # set the privileges
+    #
+    # @param privs either a comma-separated list of privileges of a RightList object
+
+    def set_privileges(self, privs):
+        if isinstance(privs, str):
+            self.privileges = RightList(string = privs)
+        else:
+            self.privileges = privs
+        
+
+    ##
+    # return the privileges as a RightList object
+
+    def get_privileges(self):
+        if not self.privileges:
+            self.decode()
+        return self.privileges
+
+    ##
+    # determine whether the credential allows a particular operation to be
+    # performed
+    #
+    # @param op_name string specifying name of operation ("lookup", "update", etc)
+
+    def can_perform(self, op_name):
+        rights = self.get_privileges()
+        
+        if not rights:
+            return False
+
+        return rights.can_perform(op_name)
+
+
+    ##
+    # Encode the attributes of the credential into an XML string    
+    # This should be done immediately before signing the credential.    
+    # WARNING:
+    # In general, a signed credential obtained externally should
+    # not be changed else the signature is no longer valid.  So, once
+    # you have loaded an existing signed credential, do not call encode() or sign() on it.
+
+    def encode(self):
+        # Create the XML document
+        doc = Document()
+        signed_cred = doc.createElement("signed-credential")
+        doc.appendChild(signed_cred)  
+        
+        # Fill in the <credential> bit        
+        cred = doc.createElement("credential")
+        cred.setAttribute("xml:id", self.get_refid())
+        signed_cred.appendChild(cred)
+        append_sub(doc, cred, "type", "privilege")
+        append_sub(doc, cred, "serial", "8")
+        append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
+        append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
+        append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
+        append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
+        append_sub(doc, cred, "uuid", "")
+        if not self.expiration:
+            self.set_lifetime(DEFAULT_CREDENTIAL_LIFETIME)
+        self.expiration = self.expiration.replace(microsecond=0)
+        append_sub(doc, cred, "expires", self.expiration.isoformat())
+        privileges = doc.createElement("privileges")
+        cred.appendChild(privileges)
+
+        if self.privileges:
+            rights = self.get_privileges()
+            for right in rights.rights:
+                priv = doc.createElement("privilege")
+                append_sub(doc, priv, "name", right.kind)
+                append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
+                privileges.appendChild(priv)
+
+        # Add the parent credential if it exists
+        if self.parent:
+            sdoc = parseString(self.parent.get_xml())
+            p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
+            p = doc.createElement("parent")
+            p.appendChild(p_cred)
+            cred.appendChild(p)
+
+
+        # Create the <signatures> tag
+        signatures = doc.createElement("signatures")
+        signed_cred.appendChild(signatures)
+
+        # Add any parent signatures
+        if self.parent:
+            for cur_cred in self.get_credential_list()[1:]:
+                sdoc = parseString(cur_cred.get_signature().get_xml())
+                ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
+                signatures.appendChild(ele)
+                
+        # Get the finished product
+        self.xml = doc.toxml()
+
+
+    def save_to_random_tmp_file(self):       
+        fp, filename = mkstemp(suffix='cred', text=True)
+        fp = os.fdopen(fp, "w")
+        self.save_to_file(filename, save_parents=True, filep=fp)
+        return filename
+    
+    def save_to_file(self, filename, save_parents=True, filep=None):
+        if not self.xml:
+            self.encode()
+        if filep:
+            f = filep 
+        else:
+            f = open(filename, "w")
+        f.write(self.xml)
+        f.close()
+
+    def save_to_string(self, save_parents=True):
+        if not self.xml:
+            self.encode()
+        return self.xml
+
+    def get_refid(self):
+        if not self.refid:
+            self.refid = 'ref0'
+        return self.refid
+
+    def set_refid(self, rid):
+        self.refid = rid
+
+    ##
+    # Figure out what refids exist, and update this credential's id
+    # so that it doesn't clobber the others.  Returns the refids of
+    # the parents.
+    
+    def updateRefID(self):
+        if not self.parent:
+            self.set_refid('ref0')
+            return []
+        
+        refs = []
+
+        next_cred = self.parent
+        while next_cred:
+            refs.append(next_cred.get_refid())
+            if next_cred.parent:
+                next_cred = next_cred.parent
+            else:
+                next_cred = None
+
+        
+        # Find a unique refid for this credential
+        rid = self.get_refid()
+        while rid in refs:
+            val = int(rid[3:])
+            rid = "ref%d" % (val + 1)
+
+        # Set the new refid
+        self.set_refid(rid)
+
+        # Return the set of parent credential ref ids
+        return refs
+
+    def get_xml(self):
+        if not self.xml:
+            self.encode()
+        return self.xml
+
+    ##
+    # Sign the XML file created by encode()
+    #
+    # WARNING:
+    # In general, a signed credential obtained externally should
+    # not be changed else the signature is no longer valid.  So, once
+    # you have loaded an existing signed credential, do not call encode() or sign() on it.
+
+    def sign(self):
+        if not self.issuer_privkey or not self.issuer_gid:
+            return
+        doc = parseString(self.get_xml())
+        sigs = doc.getElementsByTagName("signatures")[0]
+
+        # Create the signature template to be signed
+        signature = Signature()
+        signature.set_refid(self.get_refid())
+        sdoc = parseString(signature.get_xml())        
+        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
+        sigs.appendChild(sig_ele)
+
+        self.xml = doc.toxml()
+
+
+        # Split the issuer GID into multiple certificates if it's a chain
+        chain = GID(filename=self.issuer_gid)
+        gid_files = []
+        while chain:
+            gid_files.append(chain.save_to_random_tmp_file(False))
+            if chain.get_parent():
+                chain = chain.get_parent()
+            else:
+                chain = None
+
+
+        # Call out to xmlsec1 to sign it
+        ref = 'Sig_%s' % self.get_refid()
+        filename = self.save_to_random_tmp_file()
+        signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
+                 % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
+        os.remove(filename)
+
+        for gid_file in gid_files:
+            os.remove(gid_file)
+
+        self.xml = signed
+
+        # This is no longer a legacy credential
+        if self.legacy:
+            self.legacy = None
+
+        # Update signatures
+        self.decode()       
+
+        
+    ##
+    # Retrieve the attributes of the credential from the XML.
+    # This is automatically called by the various get_* methods of
+    # this class and should not need to be called explicitly.
+
+    def decode(self):
+        if not self.xml:
+            return
+        doc = parseString(self.xml)
+        sigs = []
+        signed_cred = doc.getElementsByTagName("signed-credential")
+
+        # Is this a signed-cred or just a cred?
+        if len(signed_cred) > 0:
+            cred = signed_cred[0].getElementsByTagName("credential")[0]
+            signatures = signed_cred[0].getElementsByTagName("signatures")
+            if len(signatures) > 0:
+                sigs = signatures[0].getElementsByTagName("Signature")
+        else:
+            cred = doc.getElementsByTagName("credential")[0]
+        
+
+        self.set_refid(cred.getAttribute("xml:id"))
+        self.set_lifetime(parse(getTextNode(cred, "expires")))
+        self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
+        self.gidObject = GID(string=getTextNode(cred, "target_gid"))   
+
+
+        # Process privileges
+        privs = cred.getElementsByTagName("privileges")[0]
+        rlist = RightList()
+        for priv in privs.getElementsByTagName("privilege"):
+            kind = getTextNode(priv, "name")
+            deleg = str2bool(getTextNode(priv, "can_delegate"))
+            if kind == '*':
+                # Convert * into the default privileges for the credential's type                
+                _ , type = urn_to_hrn(self.gidObject.get_urn())
+                rl = rlist.determine_rights(type, self.gidObject.get_urn())
+                for r in rl.rights:
+                    rlist.add(r)
+            else:
+                rlist.add(Right(kind.strip(), deleg))
+        self.set_privileges(rlist)
+
+
+        # Is there a parent?
+        parent = cred.getElementsByTagName("parent")
+        if len(parent) > 0:
+            parent_doc = parent[0].getElementsByTagName("credential")[0]
+            parent_xml = parent_doc.toxml()
+            self.parent = Credential(string=parent_xml)
+            self.updateRefID()
+
+        # Assign the signatures to the credentials
+        for sig in sigs:
+            Sig = Signature(string=sig.toxml())
+
+            for cur_cred in self.get_credential_list():
+                if cur_cred.get_refid() == Sig.get_refid():
+                    cur_cred.set_signature(Sig)
+                                    
+            
+    ##
+    # Verify
+    #   trusted_certs: A list of trusted GID filenames (not GID objects!) 
+    #                  Chaining is not supported within the GIDs by xmlsec1.
+    #    
+    # Verify that:
+    # . All of the signatures are valid and that the issuers trace back
+    #   to trusted roots (performed by xmlsec1)
+    # . The XML matches the credential schema
+    # . That the issuer of the credential is the authority in the target's urn
+    #    . In the case of a delegated credential, this must be true of the root
+    # . That all of the gids presented in the credential are valid
+    # . The credential is not expired
+    #
+    # -- For Delegates (credentials with parents)
+    # . The privileges must be a subset of the parent credentials
+    # . The privileges must have "can_delegate" set for each delegated privilege
+    # . The target gid must be the same between child and parents
+    # . The expiry time on the child must be no later than the parent
+    # . The signer of the child must be the owner of the parent
+    #
+    # -- Verify does *NOT*
+    # . ensure that an xmlrpc client's gid matches a credential gid, that
+    #   must be done elsewhere
+    #
+    # @param trusted_certs: The certificates of trusted CA certificates
+    def verify(self, trusted_certs):
+        if not self.xml:
+            self.decode()        
+
+#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
+        trusted_cert_objects = []
+        ok_trusted_certs = []
+        for f in trusted_certs:
+            try:
+                # Failures here include unreadable files
+                # or non PEM files
+                trusted_cert_objects.append(GID(filename=f))
+                ok_trusted_certs.append(f)
+            except Exception, exc:
+                logger.error("Failed to load trusted cert from %s: %r", f, exc)
+        trusted_certs = ok_trusted_certs
+
+        # Use legacy verification if this is a legacy credential
+        if self.legacy:
+            self.legacy.verify_chain(trusted_cert_objects)
+            if self.legacy.client_gid:
+                self.legacy.client_gid.verify_chain(trusted_cert_objects)
+            if self.legacy.object_gid:
+                self.legacy.object_gid.verify_chain(trusted_cert_objects)
+            return True
+        
+        # make sure it is not expired
+        if self.get_lifetime() < datetime.datetime.utcnow():
+            raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())
+
+        # Verify the signatures
+        filename = self.save_to_random_tmp_file()
+        cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
+
+        # Verify the gids of this cred and of its parents
+        for cur_cred in self.get_credential_list():
+            cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
+            cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) 
+
+        refs = []
+        refs.append("Sig_%s" % self.get_refid())
+
+        parentRefs = self.updateRefID()
+        for ref in parentRefs:
+            refs.append("Sig_%s" % ref)
+
+        for ref in refs:
+            verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
+                            % (self.xmlsec_path, ref, cert_args, filename)).read()
+            if not verified.strip().startswith("OK"):
+                raise CredentialNotVerifiable("xmlsec1 error verifying cert: " + verified)
+        os.remove(filename)
+
+        # Verify the parents (delegation)
+        if self.parent:
+            self.verify_parent(self.parent)
+
+        # Make sure the issuer is the target's authority
+        self.verify_issuer()
+        return True
+
+    ##
+    # Creates a list of the credential and its parents, with the root 
+    # (original delegated credential) as the last item in the list
+    def get_credential_list(self):    
+        cur_cred = self
+        list = []
+        while cur_cred:
+            list.append(cur_cred)
+            if cur_cred.parent:
+                cur_cred = cur_cred.parent
+            else:
+                cur_cred = None
+        return list
+    
+    ##
+    # Make sure the credential's target gid was signed by (or is the same) the entity that signed
+    # the original credential or an authority over that namespace.
+    def verify_issuer(self):                
+        root_cred = self.get_credential_list()[-1]
+        root_target_gid = root_cred.get_gid_object()
+        root_cred_signer = root_cred.get_signature().get_issuer_gid()
+
+        if root_target_gid.is_signed_by_cert(root_cred_signer):
+            # cred signer matches target signer, return success
+            return
+
+        root_target_gid_str = root_target_gid.save_to_string()
+        root_cred_signer_str = root_cred_signer.save_to_string()
+        if root_target_gid_str == root_cred_signer_str:
+            # cred signer is target, return success
+            return
+
+        # See if it the signer is an authority over the domain of the target
+        # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
+        root_cred_signer_type = root_cred_signer.get_type()
+        if (root_cred_signer_type == 'authority'):
+            #logger.debug('Cred signer is an authority')
+            # signer is an authority, see if target is in authority's domain
+            hrn = root_cred_signer.get_hrn()
+            if root_target_gid.get_hrn().startswith(hrn):
+                return
+
+        # We've required that the credential be signed by an authority
+        # for that domain. Reasonable and probably correct.
+        # A looser model would also allow the signer to be an authority
+        # in my control framework - eg My CA or CH. Even if it is not
+        # the CH that issued these, eg, user credentials.
+
+        # Give up, credential does not pass issuer verification
+
+        raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn()))
+
+
+    ##
+    # -- For Delegates (credentials with parents) verify that:
+    # . The privileges must be a subset of the parent credentials
+    # . The privileges must have "can_delegate" set for each delegated privilege
+    # . The target gid must be the same between child and parents
+    # . The expiry time on the child must be no later than the parent
+    # . The signer of the child must be the owner of the parent        
+    def verify_parent(self, parent_cred):
+        # make sure the rights given to the child are a subset of the
+        # parents rights (and check delegate bits)
+        if not parent_cred.get_privileges().is_superset(self.get_privileges()):
+            raise ChildRightsNotSubsetOfParent(
+                self.parent.get_privileges().save_to_string() + " " +
+                self.get_privileges().save_to_string())
+
+        # make sure my target gid is the same as the parent's
+        if not parent_cred.get_gid_object().save_to_string() == \
+           self.get_gid_object().save_to_string():
+            raise CredentialNotVerifiable("Target gid not equal between parent and child")
+
+        # make sure my expiry time is <= my parent's
+        if not parent_cred.get_lifetime() >= self.get_lifetime():
+            raise CredentialNotVerifiable("Delegated credential expires after parent")
+
+        # make sure my signer is the parent's caller
+        if not parent_cred.get_gid_caller().save_to_string(False) == \
+           self.get_signature().get_issuer_gid().save_to_string(False):
+            raise CredentialNotVerifiable("Delegated credential not signed by parent caller")
+                
+        # Recurse
+        if parent_cred.parent:
+            parent_cred.verify_parent(parent_cred.parent)
+
+
+    def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
+        """
+        Return a delegated copy of this credential, delegated to the 
+        specified gid's user.    
+        """
+        # get the gid of the object we are delegating
+        object_gid = self.get_gid_object()
+        object_hrn = object_gid.get_hrn()        
+        # the hrn of the user who will be delegated to
+        delegee_gid = GID(filename=delegee_gidfile)
+        delegee_hrn = delegee_gid.get_hrn()
+  
+        #user_key = Keypair(filename=keyfile)
+        #user_hrn = self.get_gid_caller().get_hrn()
+        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
+        dcred = Credential(subject=subject_string)
+        dcred.set_gid_caller(delegee_gid)
+        dcred.set_gid_object(object_gid)
+        dcred.set_parent(self)
+        dcred.set_lifetime(self.get_lifetime())
+        dcred.set_privileges(self.get_privileges())
+        dcred.get_privileges().delegate_all_privileges(True)
+        #dcred.set_issuer_keys(keyfile, delegee_gidfile)
+        dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
+        dcred.encode()
+        dcred.sign()
+
+        return dcred 
+    ##
+    # Dump the contents of a credential to stdout in human-readable format
+    #
+    # @param dump_parents If true, also dump the parent certificates
+
+    def dump(self, dump_parents=False):
+        print "CREDENTIAL", self.get_subject()
+
+        print "      privs:", self.get_privileges().save_to_string()
+
+        print "  gidCaller:"
+        gidCaller = self.get_gid_caller()
+        if gidCaller:
+            gidCaller.dump(8, dump_parents)
+
+        print "  gidObject:"
+        gidObject = self.get_gid_object()
+        if gidObject:
+            gidObject.dump(8, dump_parents)
+
+
+        if self.parent and dump_parents:
+            print "PARENT",
+            self.parent.dump_parents()
+
diff --git a/sfa/trust/credential_legacy.py b/sfa/trust/credential_legacy.py
new file mode 100644 (file)
index 0000000..c33ed6f
--- /dev/null
@@ -0,0 +1,247 @@
+##
+# Implements SFA Credentials
+#
+# Credentials are layered on top of certificates, and are essentially a
+# certificate that stores a tuple of parameters.
+##
+
+### $Id: credential.py 17477 2010-03-25 16:49:34Z jkarlin $
+### $URL: svn+ssh://svn.planet-lab.org/svn/sfa/branches/geni-api/sfa/trust/credential.py $
+
+import xmlrpclib
+
+from sfa.trust.certificate import Certificate
+from sfa.trust.rights import *
+from sfa.trust.gid import *
+from sfa.util.faults import *
+from sfa.util.sfalogging import *
+
+##
+# Credential is a tuple:
+#     (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)
+#
+# These fields are encoded using xmlrpc into the subjectAltName field of the
+# x509 certificate. Note: Call encode() once the fields have been filled in
+# to perform this encoding.
+
+class CredentialLegacy(Certificate):
+    gidCaller = None
+    gidObject = None
+    lifeTime = None
+    privileges = None
+    delegate = False
+
+    ##
+    # Create a Credential object
+    #
+    # @param create If true, create a blank x509 certificate
+    # @param subject If subject!=None, create an x509 cert with the subject name
+    # @param string If string!=None, load the credential from the string
+    # @param filename If filename!=None, load the credential from the file
+
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        Certificate.__init__(self, create, subject, string, filename)
+
+    ##
+    # set the GID of the caller
+    #
+    # @param gid GID object of the caller
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+        # gid origin caller is the caller's gid by default
+        self.gidOriginCaller = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    ##
+    # set the GID of the object
+    #
+    # @param gid GID object of the object
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    ##
+    # set the lifetime of this credential
+    #
+    # @param lifetime lifetime of credential
+
+    def set_lifetime(self, lifeTime):
+        self.lifeTime = lifeTime
+
+    ##
+    # get the lifetime of the credential
+
+    def get_lifetime(self):
+        if not self.lifeTime:
+            self.decode()
+        return self.lifeTime
+
+    ##
+    # set the delegate bit
+    #
+    # @param delegate boolean (True or False)
+
+    def set_delegate(self, delegate):
+        self.delegate = delegate
+
+    ##
+    # get the delegate bit
+
+    def get_delegate(self):
+        if not self.delegate:
+            self.decode()
+        return self.delegate
+
+    ##
+    # set the privileges
+    #
+    # @param privs either a comma-separated list of privileges of a RightList object
+
+    def set_privileges(self, privs):
+        if isinstance(privs, str):
+            self.privileges = RightList(string = privs)
+        else:
+            self.privileges = privs
+
+    ##
+    # return the privileges as a RightList object
+
+    def get_privileges(self):
+        if not self.privileges:
+            self.decode()
+        return self.privileges
+
+    ##
+    # determine whether the credential allows a particular operation to be
+    # performed
+    #
+    # @param op_name string specifying name of operation ("lookup", "update", etc)
+
+    def can_perform(self, op_name):
+        rights = self.get_privileges()
+        if not rights:
+            return False
+        return rights.can_perform(op_name)
+
+    ##
+    # Encode the attributes of the credential into a string and store that
+    # string in the alt-subject-name field of the X509 object. This should be
+    # done immediately before signing the credential.
+
+    def encode(self):
+        dict = {"gidCaller": None,
+                "gidObject": None,
+                "lifeTime": self.lifeTime,
+                "privileges": None,
+                "delegate": self.delegate}
+        if self.gidCaller:
+            dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True)
+        if self.gidObject:
+            dict["gidObject"] = self.gidObject.save_to_string(save_parents=True)
+        if self.privileges:
+            dict["privileges"] = self.privileges.save_to_string()
+        str = xmlrpclib.dumps((dict,), allow_none=True)
+        self.set_data('URI:http://' + str)
+
+    ##
+    # Retrieve the attributes of the credential from the alt-subject-name field
+    # of the X509 certificate. This is automatically done by the various
+    # get_* methods of this class and should not need to be called explicitly.
+
+    def decode(self):
+        data = self.get_data().lstrip('URI:http://')
+        
+        if data:
+            dict = xmlrpclib.loads(data)[0][0]
+        else:
+            dict = {}
+
+        self.lifeTime = dict.get("lifeTime", None)
+        self.delegate = dict.get("delegate", None)
+
+        privStr = dict.get("privileges", None)
+        if privStr:
+            self.privileges = RightList(string = privStr)
+        else:
+            self.privileges = None
+
+        gidCallerStr = dict.get("gidCaller", None)
+        if gidCallerStr:
+            self.gidCaller = GID(string=gidCallerStr)
+        else:
+            self.gidCaller = None
+
+        gidObjectStr = dict.get("gidObject", None)
+        if gidObjectStr:
+            self.gidObject = GID(string=gidObjectStr)
+        else:
+            self.gidObject = None
+
+    ##
+    # Verify that a chain of credentials is valid (see cert.py:verify). In
+    # addition to the checks for ordinary certificates, verification also
+    # ensures that the delegate bit was set by each parent in the chain. If
+    # a delegate bit was not set, then an exception is thrown.
+    #
+    # Each credential must be a subset of the rights of the parent.
+
+    def verify_chain(self, trusted_certs = None):
+        # do the normal certificate verification stuff
+        Certificate.verify_chain(self, trusted_certs)
+
+        if self.parent:
+            # make sure the parent delegated rights to the child
+            if not self.parent.get_delegate():
+                raise MissingDelegateBit(self.parent.get_subject())
+
+            # make sure the rights given to the child are a subset of the
+            # parents rights
+            if not self.parent.get_privileges().is_superset(self.get_privileges()):
+                raise ChildRightsNotSubsetOfParent(self.get_subject() 
+                                                   + " " + self.parent.get_privileges().save_to_string()
+                                                   + " " + self.get_privileges().save_to_string())
+
+        return
+
+    ##
+    # Dump the contents of a credential to stdout in human-readable format
+    #
+    # @param dump_parents If true, also dump the parent certificates
+
+    def dump(self, dump_parents=False):
+        print "CREDENTIAL", self.get_subject()
+
+        print "      privs:", self.get_privileges().save_to_string()
+
+        print "  gidCaller:"
+        gidCaller = self.get_gid_caller()
+        if gidCaller:
+            gidCaller.dump(8, dump_parents)
+
+        print "  gidObject:"
+        gidObject = self.get_gid_object()
+        if gidObject:
+            gidObject.dump(8, dump_parents)
+
+        print "   delegate:", self.get_delegate()
+
+        if self.parent and dump_parents:
+           print "PARENT",
+           self.parent.dump(dump_parents)
+
diff --git a/sfa/trust/gid.py b/sfa/trust/gid.py
new file mode 100644 (file)
index 0000000..05b58f9
--- /dev/null
@@ -0,0 +1,219 @@
+#----------------------------------------------------------------------
+# Copyright (c) 2008 Board of Trustees, Princeton University
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
+# IN THE WORK.
+#----------------------------------------------------------------------
+##
+# Implements SFA GID. GIDs are based on certificates, and the GID class is a
+# descendant of the certificate class.
+##
+
+### $Id: gid.py 18510 2010-07-13 18:32:48Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/gid.py $
+import xmlrpclib
+import uuid
+from sfa.trust.certificate import Certificate
+from sfa.util.namespace import *
+from sfa.util.sfalogging import logger
+
+##
+# Create a new uuid. Returns the UUID as a string.
+
+def create_uuid():
+    return str(uuid.uuid4().int)
+
+##
+# GID is a tuple:
+#    (uuid, urn, public_key)
+#
+# UUID is a unique identifier and is created by the python uuid module
+#    (or the utility function create_uuid() in gid.py).
+#
+# HRN is a human readable name. It is a dotted form similar to a backward domain
+#    name. For example, planetlab.us.arizona.bakers.
+#
+# URN is a human readable identifier of form:
+#   "urn:publicid:IDN+toplevelauthority[:sub-auth.]*[\res. type]\ +object name"
+#   For  example, urn:publicid:IDN+planetlab:us:arizona+user+bakers      
+#
+# PUBLIC_KEY is the public key of the principal identified by the UUID/HRN.
+# It is a Keypair object as defined in the cert.py module.
+#
+# It is expected that there is a one-to-one pairing between UUIDs and HRN,
+# but it is uncertain how this would be inforced or if it needs to be enforced.
+#
+# These fields are encoded using xmlrpc into the subjectAltName field of the
+# x509 certificate. Note: Call encode() once the fields have been filled in
+# to perform this encoding.
+
+
+class GID(Certificate):
+    uuid = None
+    hrn = None
+    urn = None
+
+    ##
+    # Create a new GID object
+    #
+    # @param create If true, create the X509 certificate
+    # @param subject If subject!=None, create the X509 cert and set the subject name
+    # @param string If string!=None, load the GID from a string
+    # @param filename If filename!=None, load the GID from a file
+
+    def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None, urn=None):
+        
+        Certificate.__init__(self, create, subject, string, filename)
+        if subject:
+            logger.debug("Creating GID for subject: %s" % subject)
+        if uuid:
+            self.uuid = int(uuid)
+        if hrn:
+            self.hrn = hrn
+            self.urn = hrn_to_urn(hrn, 'unknown')
+        if urn:
+            self.urn = urn
+            self.hrn, type = urn_to_hrn(urn)
+
+    def set_uuid(self, uuid):
+        if isinstance(uuid, str):
+            self.uuid = int(uuid)
+        else:
+            self.uuid = uuid
+
+    def get_uuid(self):
+        if not self.uuid:
+            self.decode()
+        return self.uuid
+
+    def set_hrn(self, hrn):
+        self.hrn = hrn
+
+    def get_hrn(self):
+        if not self.hrn:
+            self.decode()
+        return self.hrn
+
+    def set_urn(self, urn):
+        self.urn = urn
+        self.hrn, type = urn_to_hrn(urn)
+    def get_urn(self):
+        if not self.urn:
+            self.decode()
+        return self.urn            
+
+    def get_type(self):
+        if not self.urn:
+            self.decode()
+        _, t = urn_to_hrn(self.urn)
+        return t
+    
+    ##
+    # Encode the GID fields and package them into the subject-alt-name field
+    # of the X509 certificate. This must be called prior to signing the
+    # certificate. It may only be called once per certificate.
+
+    def encode(self):
+        if self.urn:
+            urn = self.urn
+        else:
+            urn = hrn_to_urn(self.hrn, None)
+            
+        str = "URI:" + urn
+
+        if self.uuid:
+            str += ", " + "URI:" + uuid.UUID(int=self.uuid).urn
+        
+        self.set_data(str, 'subjectAltName')
+
+        
+
+
+    ##
+    # Decode the subject-alt-name field of the X509 certificate into the
+    # fields of the GID. This is automatically called by the various get_*()
+    # functions in this class.
+
+    def decode(self):
+        data = self.get_data('subjectAltName')
+        dict = {}
+        if data:
+            if data.lower().startswith('uri:http://<params>'):
+                dict = xmlrpclib.loads(data[11:])[0][0]
+            else:
+                spl = data.split(', ')
+                for val in spl:
+                    if val.lower().startswith('uri:urn:uuid:'):
+                        dict['uuid'] = uuid.UUID(val[4:]).int
+                    elif val.lower().startswith('uri:urn:publicid:idn+'):
+                        dict['urn'] = val[4:]
+                    
+        self.uuid = dict.get("uuid", None)
+        self.urn = dict.get("urn", None)
+        self.hrn = dict.get("hrn", None)    
+        if self.urn:
+            self.hrn = urn_to_hrn(self.urn)[0]
+
+    ##
+    # Dump the credential to stdout.
+    #
+    # @param indent specifies a number of spaces to indent the output
+    # @param dump_parents If true, also dump the parents of the GID
+
+    def dump(self, indent=0, dump_parents=False):
+        print " "*indent, " hrn:", self.get_hrn()
+        print " "*indent, " urn:", self.get_urn()
+        print " "*indent, "uuid:", self.get_uuid()
+
+        if self.parent and dump_parents:
+            print " "*indent, "parent:"
+            self.parent.dump(indent+4, dump_parents)
+
+    ##
+    # Verify the chain of authenticity of the GID. First perform the checks
+    # of the certificate class (verifying that each parent signs the child,
+    # etc). In addition, GIDs also confirm that the parent's HRN is a prefix
+    # of the child's HRN.
+    #
+    # Verifying these prefixes prevents a rogue authority from signing a GID
+    # for a principal that is not a member of that authority. For example,
+    # planetlab.us.arizona cannot sign a GID for planetlab.us.princeton.foo.
+
+    def verify_chain(self, trusted_certs = None):
+        # do the normal certificate verification stuff
+        trusted_root = Certificate.verify_chain(self, trusted_certs)        
+       
+        if self.parent:
+            # make sure the parent's hrn is a prefix of the child's hrn
+            if not self.get_hrn().startswith(self.parent.get_hrn()):
+                #print self.get_hrn(), " ", self.parent.get_hrn()
+                raise GidParentHrn("This cert %s HRN doesnt start with parent HRN %s" % (self.get_hrn(), self.parent.get_hrn()))
+        else:
+            # make sure that the trusted root's hrn is a prefix of the child's
+            trusted_gid = GID(string=trusted_root.save_to_string())
+            trusted_type = trusted_gid.get_type()
+            trusted_hrn = trusted_gid.get_hrn()
+            #if trusted_type == 'authority':
+            #    trusted_hrn = trusted_hrn[:trusted_hrn.rindex('.')]
+            cur_hrn = self.get_hrn()
+            if not self.get_hrn().startswith(trusted_hrn):
+                raise GidParentHrn("Trusted roots HRN %s isnt start of this cert %s" % (trusted_hrn, cur_hrn))
+
+        return
diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py
new file mode 100644 (file)
index 0000000..2515a0a
--- /dev/null
@@ -0,0 +1,364 @@
+##
+# This module implements a hierarchy of authorities and performs a similar
+# function as the "tree" module of the original SFA prototype. An HRN
+# is assumed to be a string of authorities separated by dots. For example,
+# "planetlab.us.arizona.bakers". Each component of the HRN is a different
+# authority, with the last component being a leaf in the tree.
+#
+# Each authority is stored in a subdirectory on the registry. Inside this
+# subdirectory are several files:
+#      *.GID - GID file
+#      *.PKEY - private key file
+#      *.DBINFO - database info
+##
+
+### $Id: hierarchy.py 18243 2010-06-10 18:16:05Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/hierarchy.py $
+
+import os
+
+from sfa.util.report import *
+from sfa.trust.certificate import Keypair
+from sfa.trust.credential import *
+from sfa.trust.gid import GID, create_uuid
+from sfa.util.namespace import *
+from sfa.util.config import Config
+from sfa.util.sfaticket import SfaTicket
+
+##
+# The AuthInfo class contains the information for an authority. This information
+# includes the GID, private key, and database connection information.
+
+class AuthInfo:
+    hrn = None
+    gid_object = None
+    gid_filename = None
+    privkey_filename = None
+    dbinfo_filename = None
+
+    ##
+    # Initialize and authority object.
+    #
+    # @param xrn the human readable name of the authority (urn will be converted to hrn)
+    # @param gid_filename the filename containing the GID
+    # @param privkey_filename the filename containing the private key
+    # @param dbinfo_filename the filename containing the database info
+
+    def __init__(self, xrn, gid_filename, privkey_filename, dbinfo_filename):
+        hrn, type = urn_to_hrn(xrn)
+        self.hrn = hrn
+        self.set_gid_filename(gid_filename)
+        self.privkey_filename = privkey_filename
+        self.dbinfo_filename = dbinfo_filename
+
+    ##
+    # Set the filename of the GID
+    #
+    # @param fn filename of file containing GID
+
+    def set_gid_filename(self, fn):
+        self.gid_filename = fn
+        self.gid_object = None
+
+    def get_privkey_filename(self):
+        return self.privkey_filename
+
+    def get_gid_filename(self):
+        return self.gid_filename
+
+    ##
+    # Get the GID in the form of a GID object
+
+    def get_gid_object(self):
+        if not self.gid_object:
+            self.gid_object = GID(filename = self.gid_filename)
+        return self.gid_object
+
+    ##
+    # Get the private key in the form of a Keypair object
+
+    def get_pkey_object(self):
+        return Keypair(filename = self.privkey_filename)
+
+    ##
+    # Get the dbinfo in the form of a dictionary
+
+    def get_dbinfo(self):
+        f = file(self.dbinfo_filename)
+        dict = eval(f.read())
+        f.close()
+        return dict
+
+    ##
+    # Replace the GID with a new one. The file specified by gid_filename is
+    # overwritten with the new GID object
+    #
+    # @param gid object containing new GID
+
+    def update_gid_object(self, gid):
+        gid.save_to_file(self.gid_filename)
+        self.gid_object = gid
+
+##
+# The Hierarchy class is responsible for managing the tree of authorities.
+# Each authority is a node in the tree and exists as an AuthInfo object.
+#
+# The tree is stored on disk in a hierarchical manner than reflects the
+# structure of the tree. Each authority is a subdirectory, and each subdirectory
+# contains the GID, pkey, and dbinfo files for that authority (as well as
+# subdirectories for each sub-authority)
+
+class Hierarchy:
+    ##
+    # Create the hierarchy object.
+    #
+    # @param basedir the base directory to store the hierarchy in
+
+    def __init__(self, basedir = None):
+        if not basedir:
+            self.config = Config()
+            basedir = os.path.join(self.config.SFA_DATA_DIR, "authorities")
+        self.basedir = basedir
+    ##
+    # Given a hrn, return the filenames of the GID, private key, and dbinfo
+    # files.
+    #
+    # @param xrn the human readable name of the authority (urn will be convertd to hrn)
+
+    def get_auth_filenames(self, xrn):
+        hrn, type = urn_to_hrn(xrn)
+        leaf = get_leaf(hrn)
+        parent_hrn = get_authority(hrn)
+        directory = os.path.join(self.basedir, hrn.replace(".", "/"))
+
+        gid_filename = os.path.join(directory, leaf+".gid")
+        privkey_filename = os.path.join(directory, leaf+".pkey")
+        dbinfo_filename = os.path.join(directory, leaf+".dbinfo")
+
+        return (directory, gid_filename, privkey_filename, dbinfo_filename)
+
+    ##
+    # Check to see if an authority exists. An authority exists if it's disk
+    # files exist.
+    #
+    # @param the human readable name of the authority to check
+
+    def auth_exists(self, xrn):
+        hrn, type = urn_to_hrn(xrn) 
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+        
+        return os.path.exists(gid_filename) and \
+               os.path.exists(privkey_filename) and \
+               os.path.exists(dbinfo_filename)
+
+    ##
+    # Create an authority. A private key for the authority and the associated
+    # GID are created and signed by the parent authority.
+    #
+    # @param xrn the human readable name of the authority to create (urn will be converted to hrn) 
+    # @param create_parents if true, also create the parents if they do not exist
+
+    def create_auth(self, xrn, create_parents=False):
+        hrn, type = urn_to_hrn(xrn)
+        trace("Hierarchy: creating authority: " + hrn)
+
+        # create the parent authority if necessary
+        parent_hrn = get_authority(hrn)
+        parent_urn = hrn_to_urn(parent_hrn, 'authority')
+        if (parent_hrn) and (not self.auth_exists(parent_urn)) and (create_parents):
+            self.create_auth(parent_urn, create_parents)
+
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+
+        # create the directory to hold the files
+        try:
+            os.makedirs(directory)
+        # if the path already exists then pass
+        except OSError, (errno, strerr):
+            if errno == 17:
+                pass
+
+        if os.path.exists(privkey_filename):
+            print "using existing key", privkey_filename, "for authority", hrn
+            pkey = Keypair(filename = privkey_filename)
+        else:
+            pkey = Keypair(create = True)
+            pkey.save_to_file(privkey_filename)
+
+        gid = self.create_gid(xrn, create_uuid(), pkey)
+        gid.save_to_file(gid_filename, save_parents=True)
+
+        # XXX TODO: think up a better way for the dbinfo to work
+
+        dbinfo = Config().get_plc_dbinfo()
+        dbinfo_file = file(dbinfo_filename, "w")
+        dbinfo_file.write(str(dbinfo))
+        dbinfo_file.close()
+
+    ##
+    # Return the AuthInfo object for the specified authority. If the authority
+    # does not exist, then an exception is thrown. As a side effect, disk files
+    # and a subdirectory may be created to store the authority.
+    #
+    # @param xrn the human readable name of the authority to create (urn will be converted to hrn).
+
+    def get_auth_info(self, xrn):
+        
+        #trace("Hierarchy: getting authority: " + hrn)
+        hrn, type = urn_to_hrn(xrn)
+        if not self.auth_exists(hrn):
+            raise MissingAuthority(hrn)
+
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+
+        auth_info = AuthInfo(hrn, gid_filename, privkey_filename, dbinfo_filename)
+
+        # check the GID and see if it needs to be refreshed
+        gid = auth_info.get_gid_object()
+        gid_refreshed = self.refresh_gid(gid)
+        if gid != gid_refreshed:
+            auth_info.update_gid_object(gid_refreshed)
+
+        return auth_info
+
+    ##
+    # Create a new GID. The GID will be signed by the authority that is it's
+    # immediate parent in the hierarchy (and recursively, the parents' GID
+    # will be signed by its parent)
+    #
+    # @param hrn the human readable name to store in the GID
+    # @param uuid the unique identifier to store in the GID
+    # @param pkey the public key to store in the GID
+
+    def create_gid(self, xrn, uuid, pkey):
+        hrn, type = urn_to_hrn(xrn)
+        # Using hrn_to_urn() here to make sure the urn is in the right format
+        # If xrn was a hrn instead of a urn, then the gid's urn will be
+        # of type None 
+        urn = hrn_to_urn(hrn, type)
+        gid = GID(subject=hrn, uuid=uuid, hrn=hrn, urn=urn)
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn or hrn == self.config.SFA_INTERFACE_HRN:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            gid.set_issuer(pkey, hrn)
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            gid.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+            gid.set_parent(parent_auth_info.get_gid_object())
+            gid.set_intermediate_ca(True)
+
+        gid.set_pubkey(pkey)
+        gid.encode()
+        gid.sign()
+
+        return gid
+
+    ##
+    # Refresh a GID. The primary use of this function is to refresh the
+    # the expiration time of the GID. It may also be used to change the HRN,
+    # UUID, or Public key of the GID.
+    #
+    # @param gid the GID to refresh
+    # @param hrn if !=None, change the hrn
+    # @param uuid if !=None, change the uuid
+    # @param pubkey if !=None, change the public key
+
+    def refresh_gid(self, gid, xrn=None, uuid=None, pubkey=None):
+        # TODO: compute expiration time of GID, refresh it if necessary
+        gid_is_expired = False
+
+        # update the gid if we need to
+        if gid_is_expired or xrn or uuid or pubkey:
+            
+            if not xrn:
+                xrn = gid.get_urn()
+            if not uuid:
+                uuid = gid.get_uuid()
+            if not pubkey:
+                pubkey = gid.get_pubkey()
+
+            gid = self.create_gid(xrn, uuid, pubkey)
+
+        return gid
+
+    ##
+    # Retrieve an authority credential for an authority. The authority
+    # credential will contain the authority privilege and will be signed by
+    # the authority's parent.
+    #
+    # @param hrn the human readable name of the authority (urn is converted to hrn)
+    # @param authority type of credential to return (authority | sa | ma)
+
+    def get_auth_cred(self, xrn, kind="authority"):
+        hrn, type = urn_to_hrn(xrn) 
+        auth_info = self.get_auth_info(hrn)
+        gid = auth_info.get_gid_object()
+
+        cred = Credential(subject=hrn)
+        cred.set_gid_caller(gid)
+        cred.set_gid_object(gid)
+        cred.set_privileges(kind)
+        cred.get_privileges().delegate_all_privileges(True)
+        #cred.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn or hrn == self.config.SFA_INTERFACE_HRN:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            cred.set_issuer_keys(parent_auth_info.get_privkey_filename(), parent_auth_info.get_gid_filename())
+
+            
+            cred.set_parent(self.get_auth_cred(parent_hrn, kind))
+
+        cred.encode()
+        cred.sign()
+
+        return cred
+    ##
+    # Retrieve an authority ticket. An authority ticket is not actually a
+    # redeemable ticket, but only serves the purpose of being included as the
+    # parent of another ticket, in order to provide a chain of authentication
+    # for a ticket.
+    #
+    # This looks almost the same as get_auth_cred, but works for tickets
+    # XXX does similarity imply there should be more code re-use?
+    #
+    # @param xrn the human readable name of the authority (urn is converted to hrn)
+
+    def get_auth_ticket(self, xrn):
+        hrn, type = urn_to_hrn(xrn)
+        auth_info = self.get_auth_info(hrn)
+        gid = auth_info.get_gid_object()
+
+        ticket = SfaTicket(subject=hrn)
+        ticket.set_gid_caller(gid)
+        ticket.set_gid_object(gid)
+        ticket.set_delegate(True)
+        ticket.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            ticket.set_issuer(auth_info.get_pkey_object(), hrn)
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            ticket.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+            ticket.set_parent(self.get_auth_cred(parent_hrn))
+
+        ticket.encode()
+        ticket.sign()
+
+        return ticket
+
diff --git a/sfa/trust/rights.py b/sfa/trust/rights.py
new file mode 100644 (file)
index 0000000..59324e8
--- /dev/null
@@ -0,0 +1,288 @@
+##
+# This Module implements rights and lists of rights for the SFA. Rights
+# are implemented by two classes:
+#
+# Right - represents a single right
+#
+# RightList - represents a list of rights
+#
+# A right may allow several different operations. For example, the "info" right
+# allows "listslices", "listcomponentresources", etc.
+##
+
+
+
+##
+# privilege_table is a list of priviliges and what operations are allowed
+# per privilege.
+# Note that "*" is a privilege granted by ProtoGENI slice authorities, and we
+# give it access to the GENI AM calls
+
+privilege_table = {"authority": ["register", "remove", "update", "resolve", "list", "getcredential", "*"],
+                   "refresh": ["remove", "update"],
+                   "resolve": ["resolve", "list", "getcredential"],
+                   "sa": ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "deleteslice", "deletesliver", "updateslice",
+                          "getsliceresources", "getticket", "loanresources", "stopslice", "startslice", "renewsliver",
+                          "deleteslice", "deletesliver", "resetslice", "listslices", "listnodes", "getpolicy", "sliverstatus"],
+                   "embed": ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "renewsliver", "deleteslice", "deletesliver", "updateslice", "sliverstatus", "getsliceresources", "shutdown"],
+                   "bind": ["getticket", "loanresources", "redeemticket"],
+                   "control": ["updateslice", "createslice", "createsliver", "renewsliver", "sliverstatus", "stopslice", "startslice", "deleteslice", "deletesliver", "resetslice", "getsliceresources", "getgids"],
+                   "info": ["listslices", "listnodes", "getpolicy"],
+                   "ma": ["setbootstate", "getbootstate", "reboot", "getgids", "gettrustedcerts"],
+                   "operator": ["gettrustedcerts", "getgids"],                   
+                   "*": ["createsliver", "deletesliver", "sliverstatus", "renewsliver", "shutdown"]} 
+
+
+
+##
+# Determine tje rights that an object should have. The rights are entirely
+# dependent on the type of the object. For example, users automatically
+# get "refresh", "resolve", and "info".
+#
+# @param type the type of the object (user | sa | ma | slice | node)
+# @param name human readable name of the object (not used at this time)
+#
+# @return RightList object containing rights
+
+def determine_rights(type, name):
+    rl = RightList()
+
+    # rights seem to be somewhat redundant with the type of the credential.
+    # For example, a "sa" credential implies the authority right, because
+    # a sa credential cannot be issued to a user who is not an owner of
+    # the authority
+    if type == "user":
+        rl.add("refresh")
+        rl.add("resolve")
+        rl.add("info")
+    elif type == "sa":
+        rl.add("authority")
+        rl.add("sa")
+    elif type == "ma":
+        rl.add("authority")
+        rl.add("ma")
+    elif type == "authority":
+        rl.add("authority")
+        rl.add("sa")
+        rl.add("ma")
+    elif type == "slice":
+        rl.add("refresh")
+        rl.add("embed")
+        rl.add("bind")
+        rl.add("control")
+        rl.add("info")
+    elif type == "component":
+        rl.add("operator")
+    return rl
+
+
+##
+# The Right class represents a single privilege.
+
+
+
+class Right:
+    ##
+    # Create a new right.
+    #
+    # @param kind is a string naming the right. For example "control"
+
+    def __init__(self, kind, delegate=False):
+        self.kind = kind
+        self.delegate = delegate
+
+    ##
+    # Test to see if this right object is allowed to perform an operation.
+    # Returns True if the operation is allowed, False otherwise.
+    #
+    # @param op_name is a string naming the operation. For example "listslices".
+
+    def can_perform(self, op_name):
+        allowed_ops = privilege_table.get(self.kind.lower(), None)
+        if not allowed_ops:
+            return False
+
+        # if "*" is specified, then all ops are permitted
+        if "*" in allowed_ops:
+            return True
+
+        return (op_name.lower() in allowed_ops)
+
+    ##
+    # Test to see if this right is a superset of a child right. A right is a
+    # superset if every operating that is allowed by the child is also allowed
+    # by this object.
+    #
+    # @param child is a Right object describing the child right
+
+    def is_superset(self, child):
+        my_allowed_ops = privilege_table.get(self.kind.lower(), None)
+        child_allowed_ops = privilege_table.get(child.kind.lower(), None)
+
+        if not self.delegate:
+            return False
+
+        if "*" in my_allowed_ops:
+            return True
+
+        for right in child_allowed_ops:
+            if not right in my_allowed_ops:
+                return False
+
+        return True
+
+##
+# A RightList object represents a list of privileges.
+
+class RightList:
+    ##
+    # Create a new rightlist object, containing no rights.
+    #
+    # @param string if string!=None, load the rightlist from the string
+
+    def __init__(self, string=None):
+        self.rights = []
+        if string:
+            self.load_from_string(string)
+
+    def is_empty(self):
+        return self.rights == []
+
+    ##
+    # Add a right to this list
+    #
+    # @param right is either a Right object or a string describing the right
+
+    def add(self, right, delegate=False):
+        if isinstance(right, str):
+            right = Right(right, delegate)
+        self.rights.append(right)
+
+    ##
+    # Load the rightlist object from a string
+
+    def load_from_string(self, string):
+        self.rights = []
+
+        # none == no rights, so leave the list empty
+        if not string:
+            return
+
+        parts = string.split(",")
+        for part in parts:
+            if ':' in part:
+                spl = part.split(':')
+                kind = spl[0].strip()
+                delegate = bool(int(spl[1]))
+            else:
+                kind = part.strip()
+                delegate = 0
+            self.rights.append(Right(kind, bool(delegate)))
+
+    ##
+    # Save the rightlist object to a string. It is saved in the format of a
+    # comma-separated list.
+
+    def save_to_string(self):
+        right_names = []
+        for right in self.rights:
+            right_names.append('%s:%d' % (right.kind.strip(), right.delegate))
+
+        return ",".join(right_names)
+
+    ##
+    # Check to see if some right in this list allows an operation. This is
+    # done by evaluating the can_perform function of each operation in the
+    # list.
+    #
+    # @param op_name is an operation to check, for example "listslices"
+
+    def can_perform(self, op_name):
+        for right in self.rights:
+            if right.can_perform(op_name):
+                return True
+        return False
+
+    ##
+    # Check to see if all of the rights in this rightlist are a superset
+    # of all the rights in a child rightlist. A rightlist is a superset
+    # if there is no operation in the child rightlist that cannot be
+    # performed in the parent rightlist.
+    #
+    # @param child is a rightlist object describing the child
+
+    def is_superset(self, child):
+        for child_right in child.rights:
+            allowed = False
+            for my_right in self.rights:
+                if my_right.is_superset(child_right):
+                    allowed = True
+            if not allowed:
+                return False
+        return True
+
+
+    ##
+    # set the delegate bit to 'delegate' on
+    # all privileges
+    #
+    # @param delegate boolean (True or False)
+
+    def delegate_all_privileges(self, delegate):
+        for right in self.rights:
+            right.delegate = delegate
+
+    ##
+    # true if all privileges have delegate bit set true
+    # false otherwise
+
+    def get_all_delegate(self):
+        for right in self.rights:
+            if not right.delegate:
+                return False
+        return True
+
+
+
+    ##
+    # Determine the rights that an object should have. The rights are entirely
+    # dependent on the type of the object. For example, users automatically
+    # get "refresh", "resolve", and "info".
+    #
+    # @param type the type of the object (user | sa | ma | slice | node)
+    # @param name human readable name of the object (not used at this time)
+    #
+    # @return RightList object containing rights
+
+    def determine_rights(self, type, name):
+        rl = RightList()
+
+        # rights seem to be somewhat redundant with the type of the credential.
+        # For example, a "sa" credential implies the authority right, because
+        # a sa credential cannot be issued to a user who is not an owner of
+        # the authority
+
+        if type == "user":
+            rl.add("refresh")
+            rl.add("resolve")
+            rl.add("info")
+        elif type == "sa":
+            rl.add("authority")
+            rl.add("sa")
+        elif type == "ma":
+            rl.add("authority")
+            rl.add("ma")
+        elif type == "authority":
+            rl.add("authority")
+            rl.add("sa")
+            rl.add("ma")
+        elif type == "slice":
+            rl.add("refresh")
+            rl.add("embed")
+            rl.add("bind")
+            rl.add("control")
+            rl.add("info")
+        elif type == "component":
+            rl.add("operator")
+
+        return rl
diff --git a/sfa/trust/trusted_certs/plc_ca.crt b/sfa/trust/trusted_certs/plc_ca.crt
new file mode 100644 (file)
index 0000000..840cb29
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC7DCCAlWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBoTELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTCk5ldyBKZXJzZXkxEjAQBgNVBAcTCVByaW5jZXRvbjESMBAGA1UE
+ChMJUGxhbmV0TGFiMRIwEAYDVQQLEwlQbGFuZXRMYWIxGjAYBgNVBAMTEVBsYW5l
+dExhYiBSb290IENBMSUwIwYJKoZIhvcNAQkBFhZzdXBwb3J0QHBsYW5ldC1sYWIu
+b3JnMB4XDTA0MDIyMzE4MTMyMloXDTE0MDIyMDE4MTMyMlowgaExCzAJBgNVBAYT
+AlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRIwEAYDVQQHEwlQcmluY2V0b24xEjAQ
+BgNVBAoTCVBsYW5ldExhYjESMBAGA1UECxMJUGxhbmV0TGFiMRowGAYDVQQDExFQ
+bGFuZXRMYWIgUm9vdCBDQTElMCMGCSqGSIb3DQEJARYWc3VwcG9ydEBwbGFuZXQt
+bGFiLm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwtpNRNvNmIEX0zDu
+JcMc3zLHZz9LuXjH+UqiktPIfiMhh1sUqZE2wDfppcGRLAdC7mrmZys5GjZTO0nf
+bU/rV73hplTD8UPZojpbcvKLm5t0kocDG4aoUL+vDF7T8UlXl/T5UF0GWqFey9UY
+luCI5UeKMYdyoxhaMGiL8IBK8DUCAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAd
+BgNVHQ4EFgQU+DhxfTWp4xeIF2lUWngDOhC1lY8wDQYJKoZIhvcNAQEEBQADgYEA
+qYakXhLdtk64ppM1KmjeD0M0bGU+ZFu421MH0whxv5RROgNEwCxXicbD/9FZ2uzo
+ik7AdBAiZiyvoEkTxYkzpXHkdM6x0j6iDMjomtihUgkjtM+xaiQ6lqy9h583zhjg
+2Te0bEteMD8w8zT3Vdg8AsOPsDRZgHS3TMmTSzDg6nI=
+-----END CERTIFICATE-----
diff --git a/sfa/trust/trusted_certs/ple_ca.crt b/sfa/trust/trusted_certs/ple_ca.crt
new file mode 100644 (file)
index 0000000..cb00a40
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDODCCAqGgAwIBAgICIAYwDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMCRlIx
+GTAXBgNVBAcTEFNvcGhpYSBBbnRpcG9saXMxDjAMBgNVBAoTBUlOUklBMRkwFwYD
+VQQLExBQbGFuZXRMYWIgRXVyb3BlMSEwHwYDVQQDExhQbGFuZXRMYWIgRXVyb3Bl
+IFJvb3QgQ0EwHhcNMDkwNjE4MTI1NDAwWhcNMTkwNjE2MTI1NDAwWjB2MQswCQYD
+VQQGEwJGUjEZMBcGA1UEBxMQU29waGlhIEFudGlwb2xpczEOMAwGA1UEChMFSU5S
+SUExGTAXBgNVBAsTEFBsYW5ldExhYiBFdXJvcGUxITAfBgNVBAMTGFBsYW5ldExh
+YiBFdXJvcGUgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxu3l
+PiHQVVnkfANCyB+78ZXS0y1Nj2LaoqI/LdSZyKF8JPt+b5fXgOcOWrnNrCrS/9NV
+hyX3s4Ps5HuiVxRZi3MPtxeXz5qbU7UH+k/kR9T/Po7DGa0KAP/TmzO3jhV6EalV
+1DIkjs6wNZ5Ypv5m6M6puK1LZ+p92yPhePJPftECAwEAAaOB1DCB0TAdBgNVHQ4E
+FgQUjbA0nGWCLFZ7L/SiSdvQuMQWhOQwgaEGA1UdIwSBmTCBloAUjbA0nGWCLFZ7
+L/SiSdvQuMQWhOSheqR4MHYxCzAJBgNVBAYTAkZSMRkwFwYDVQQHExBTb3BoaWEg
+QW50aXBvbGlzMQ4wDAYDVQQKEwVJTlJJQTEZMBcGA1UECxMQUGxhbmV0TGFiIEV1
+cm9wZTEhMB8GA1UEAxMYUGxhbmV0TGFiIEV1cm9wZSBSb290IENBggIgBjAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEZFa4EGGrqGSCZivYCiKiS3vhwP
+WQ/84vTXVQr5ydiSc210bIVlPRFqdZAMd64uxdERb90734AVpGAQyBYq1ccA+yCF
+v2hW8Cx87XObp0uoL0RniA1qOr3LO69rDJAS7n7EPHhAKjOXUkzb/vvXdIAPmbra
+AQGvqcqKX7Fk9+wi
+-----END CERTIFICATE-----
diff --git a/sfa/trust/trustedroot.py b/sfa/trust/trustedroot.py
new file mode 100644 (file)
index 0000000..63f781a
--- /dev/null
@@ -0,0 +1,46 @@
+### $Id: trustedroot.py 18182 2010-06-02 20:42:30Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/trust/trustedroot.py $
+
+import os
+
+from sfa.trust.gid import *
+
+class TrustedRootList:
+    def __init__(self, dir):
+        self.basedir = dir
+        
+        # create the directory to hold the files
+        try:
+            os.makedirs(self.basedir)
+        # if the path already exists then pass
+        except OSError, (errno, strerr):
+            if errno == 17:
+                pass
+
+    def add_gid(self, gid):
+        fn = os.path.join(self.basedir, gid.get_hrn() + ".gid")
+
+        gid.save_to_file(fn)
+
+    def get_list(self):
+        gid_list = []
+
+        file_list = os.listdir(self.basedir)
+        for gid_file in file_list:
+            fn = os.path.join(self.basedir, gid_file)
+            if os.path.isfile(fn):
+                gid = GID(filename = fn)
+                gid_list.append(gid)
+
+        return gid_list
+
+    def get_file_list(self):
+        gid_file_list = []
+        
+        file_list = os.listdir(self.basedir)
+        for gid_file in file_list:
+            fn = os.path.join(self.basedir, gid_file)
+            if os.path.isfile(fn):
+                gid_file_list.append(fn)
+
+        return gid_file_list
diff --git a/sfa/util/PostgreSQL.py b/sfa/util/PostgreSQL.py
new file mode 100644 (file)
index 0000000..984e514
--- /dev/null
@@ -0,0 +1,269 @@
+#
+# PostgreSQL database interface. Sort of like DBI(3) (Database
+# independent interface for Perl).
+#
+#
+
+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 sfa.util.faults import *
+from sfa.util.debug import *
+
+if not psycopg2:
+    is8bit = re.compile("[\x80-\xff]").search
+
+    def unicast(typecast):
+        """
+        pgdb returns raw UTF-8 strings. This function casts strings that
+        apppear 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)
+
+def handle_exception(f):
+    def wrapper(*args, **kwds):
+        try: return f(*args, **kwds)
+        except Exception, fault:
+            raise SfaDBError(str(fault))
+    return wrapper
+
+class PostgreSQL:
+    def __init__(self, config):
+        self.config = config
+        self.debug = False
+#        self.debug = True
+        self.connection = None
+
+    @handle_exception
+    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.config.SFA_PLC_DB_USER,
+                                                       password = self.config.SFA_PLC_DB_PASSWORD,
+                                                       database = self.config.SFA_PLC_DB_NAME)
+                except psycopg2.OperationalError:
+                    # Fall back on TCP
+                    self.connection = psycopg2.connect(user = self.config.SFA_PLC_DB_USER,
+                                                       password = self.config.SFA_PLC_DB_PASSWORD,
+                                                       database = self.config.SFA_PLC_DB_NAME,
+                                                       host = self.config.SFA_PLC_DB_HOST,
+                                                       port = self.config.SFA_PLC_DB_PORT)
+                self.connection.set_client_encoding("UNICODE")
+            else:
+                self.connection = pgdb.connect(user = self.config.SFA_PLC_DB_USER,
+                                               password = self.config.SFA_PLC_DB_PASSWORD,
+                                               host = "%s:%d" % (self.config.SFA_PLC_DB_HOST, self.config.SFA_PLC_DB_PORT),
+                                               database = self.config.SFA_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.
+            # this needs to be done carefully though as with pattern-based filters
+            # we might have percents embedded in the query
+            # so e.g. GetPersons({'email':'*fake*'}) was resulting in .. LIKE '%sake%'
+            if psycopg2:
+                query = re.sub(r'(%\([^)]*\)|%)[df]', r'\1s', query)
+            # rewrite wildcards set by Filter.py as '***' into '%'
+            query = query.replace ('***','%')
+
+            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 SfaDBError("Please contact support: %s" % str(e))
+
+        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()
+        self.commit()
+        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)]
diff --git a/sfa/util/__init__.py b/sfa/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfa/util/api.py b/sfa/util/api.py
new file mode 100644 (file)
index 0000000..fa3bf17
--- /dev/null
@@ -0,0 +1,273 @@
+#
+# SFA XML-RPC and SOAP interfaces
+#
+### $Id: api.py 18586 2010-08-05 23:33:41Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/api.py $
+#
+
+import sys
+import os
+import traceback
+import string
+import xmlrpclib
+
+from sfa.trust.auth import Auth
+from sfa.util.config import *
+from sfa.util.faults import *
+from sfa.util.debug import *
+from sfa.trust.credential import *
+from sfa.trust.certificate import *
+from sfa.util.namespace import *
+from sfa.util.sfalogging import *
+
+# 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, "&", "&amp;")
+    s = replace(s, "<", "&lt;")
+    s = replace(s, ">", "&gt;",)
+
+    # 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:
+        raise
+        # 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
+
+
+def import_deep(name):
+    mod = __import__(name)
+    components = name.split('.')
+    for comp in components[1:]:
+        mod = getattr(mod, comp)
+    return mod
+
+class ManagerWrapper:
+    """
+    This class acts as a wrapper around an SFA interface manager module, but
+    can be used with any python module. The purpose of this class is raise a 
+    SfaNotImplemented exception if the a someone attepmts to use an attribute 
+    (could be a callable) thats not available in the library by checking the
+    library using hasattr. This helps to communicate better errors messages 
+    to the users and developers in the event that a specifiec operation 
+    is not implemented by a libarary and will generally be more helpful than
+    the standard AttributeError         
+    """
+    def __init__(self, manager, interface):
+        self.manager = manager
+        self.interface = interface
+        
+    def __getattr__(self, method):
+        
+        if not hasattr(self.manager, method):
+            raise SfaNotImplemented(method, self.interface)
+        return getattr(self.manager, method)
+        
+class BaseAPI:
+
+    cache = None
+    protocol = None
+  
+    def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", 
+                 methods='sfa.methods', peer_cert = None, interface = None, 
+                 key_file = None, cert_file = None, cache = cache):
+
+        self.encoding = encoding
+        
+        # flat list of method names
+        self.methods_module = methods_module = __import__(methods, fromlist=[methods])
+        self.methods = methods_module.all
+
+        # Better just be documenting the API
+        if config is None:
+            return
+        
+        # Load configuration
+        self.config = Config(config)
+        self.auth = Auth(peer_cert)
+        self.hrn = self.config.SFA_INTERFACE_HRN
+        self.interface = interface
+        self.key_file = key_file
+        self.key = Keypair(filename=self.key_file)
+        self.cert_file = cert_file
+        self.cert = Certificate(filename=self.cert_file)
+        self.cache = cache
+        self.credential = None
+        self.source = None 
+        self.time_format = "%Y-%m-%d %H:%M:%S"
+        self.logger=get_sfa_logger()
+        
+        # load registries
+        from sfa.server.registry import Registries
+        self.registries = Registries(self) 
+
+        # load aggregates
+        from sfa.server.aggregate import Aggregates
+        self.aggregates = Aggregates(self)
+
+
+    def get_interface_manager(self, manager_base = 'sfa.managers'):
+        """
+        Returns the appropriate manager module for this interface.
+        Modules are usually found in sfa/managers/
+        """
+        
+        if self.interface in ['registry']:
+            mgr_type = self.config.SFA_REGISTRY_TYPE
+            manager_module = manager_base + ".registry_manager_%s" % mgr_type
+        elif self.interface in ['aggregate']:
+            mgr_type = self.config.SFA_AGGREGATE_TYPE
+            manager_module = manager_base + ".aggregate_manager_%s" % mgr_type 
+        elif self.interface in ['slicemgr', 'sm']:
+            mgr_type = self.config.SFA_SM_TYPE
+            manager_module = manager_base + ".slice_manager_%s" % mgr_type
+        elif self.interface in ['component', 'cm']:
+            mgr_type = self.config.SFA_CM_TYPE
+            manager_module = manager_base + ".component_manager_%s" % mgr_type
+        else:
+            raise SfaAPIError("No manager for interface: %s" % self.interface)  
+        manager = __import__(manager_module, fromlist=[manager_base])
+        # this isnt necessary but will hlep to produce better error messages
+        # if someone tries to access an operation this manager doesn't implement  
+        manager = ManagerWrapper(manager, self.interface)
+
+        return manager
+
+    def callable(self, method):
+        """
+        Return a new instance of the specified method.
+        """
+        # Look up method
+        if method not in self.methods:
+            raise SfaInvalidAPIMethod, method
+        
+        # Get new instance of method
+        try:
+            classname = method.split(".")[-1]
+            module = __import__(self.methods_module.__name__ + "." + method, globals(), locals(), [classname])
+            callablemethod = getattr(module, classname)(self)
+            return getattr(module, classname)(self)
+        except ImportError, AttributeError:
+            raise SfaInvalidAPIMethod, 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
+        self.source = source
+        return function(*args)
+
+    
+    def handle(self, source, data, method_map):
+        """
+        Handle an XML-RPC or SOAP request from the specified source.
+        """
+        # Parse request into method name and arguments
+        try:
+            interface = xmlrpclib
+            self.protocol = 'xmlrpclib'
+            (args, method) = xmlrpclib.loads(data)
+            if method_map.has_key(method):
+                method = method_map[method]
+            methodresponse = True
+            
+        except Exception, e:
+            if SOAPpy is not None:
+                self.protocol = 'soap'
+                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 SfaFault, fault:
+            result = fault 
+        except Exception, fault:
+            traceback.print_exc(file = log)
+            result = SfaAPIError(fault)
+
+
+        # Return result
+        response = self.prepare_response(result, method)
+        return response
+    
+    def prepare_response(self, result, method=""):
+        """
+        convert result to a valid xmlrpc or soap response
+        """   
+        if self.protocol == 'xmlrpclib':
+            if not isinstance(result, SfaFault):
+                result = (result,)
+            response = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
+        elif self.protocol == 'soap':
+            if isinstance(result, Exception):
+                result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
+                result._setDetail("Fault %d: %s" % (result.faultCode, result.faultString))
+            else:
+                response = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
+        else:
+            if isinstance(result, Exception):
+                raise result 
+            
+        return response
diff --git a/sfa/util/cache.py b/sfa/util/cache.py
new file mode 100644 (file)
index 0000000..45961fe
--- /dev/null
@@ -0,0 +1,62 @@
+#
+# This module implements general purpose caching system
+#
+from __future__ import with_statement
+import time
+import threading
+from datetime import datetime
+
+# maximum lifetime of cached data (in seconds) 
+MAX_CACHE_TTL = 60 * 60
+
+class CacheData:
+
+    data = None
+    created = None
+    expires = None
+    lock = None
+
+    def __init__(self, data, ttl = MAX_CACHE_TTL):
+        self.lock = threading.RLock()
+        self.data = data
+        self.renew(ttl)
+
+    def is_expired(self):
+        return time.time() > self.expires
+
+    def get_created_date(self):
+        return str(datetime.fromtimestamp(self.created))
+
+    def get_expires_date(self):
+        return str(datetime.fromtimestamp(self.expires))
+
+    def renew(self, ttl = MAX_CACHE_TTL):
+        self.created = time.time()
+        self.expires = self.created + ttl   
+       
+    def set_data(self, data, renew=True, ttl = MAX_CACHE_TTL):
+        with self.lock: 
+            self.data = data
+            if renew:
+                self.renew(ttl)
+    
+    def get_data(self):
+        return self.data
+
+class Cache:
+
+    cache  = {}
+    lock = threading.RLock()
+   
+    def add(self, key, value, ttl = MAX_CACHE_TTL):
+        with self.lock:
+            if self.cache.has_key(key):
+                self.cache[key].set_data(value, ttl=ttl)
+            else:
+                self.cache[key] = CacheData(value, ttl)
+           
+    def get(self, key):
+        data = self.cache.get(key)
+        if not data or data.is_expired():
+            return None 
+        return data.get_data()
diff --git a/sfa/util/client.py b/sfa/util/client.py
new file mode 100644 (file)
index 0000000..4dcc92d
--- /dev/null
@@ -0,0 +1,382 @@
+##
+# This module implements the client-side of the SFA API. Stubs are provided
+# that convert the supplied parameters to the necessary format and send them
+# via XMLRPC to an SFA Server.
+#
+# TODO: Investigate ways to combine this with existing PLC API?
+##
+
+### $Id: client.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/client.py $
+
+from sfa.trust.certificate import *
+from sfa.trust.gid import *
+from sfa.trust.credential import *
+from sfa.util.record import *
+from sfa.util.sfaticket import SfaTicket
+
+##
+# The GeniClient class provides stubs for executing SFA operations. A given
+# client object connects to one server. To connect to multiple servers, create
+# multiple GeniClient objects.
+#
+# The SFA protocol uses an HTTPS connection, and the client's side of the
+# connection uses his private key. Generally, this private key must match the
+# public key that is containing in the GID that the client is providing for
+# those functions that take a GID.
+
+class GeniClient:
+    ##
+    # Create a new GeniClient object.
+    #
+    # @param url is the url of the server
+    # @param key_file = private key file of client
+    # @param cert_file = x.509 cert containing the client's public key. This
+    #      could be a GID certificate, or any x.509 cert.
+    # @param protocol The ORPC protocol to use. Can be "soap" or "xmlrpc"
+
+    def __init__(self, url, key_file, cert_file, protocol="xmlrpc"):
+        self.url = url
+        self.key_file = key_file
+        self.cert_file = cert_file
+        self.key = Keypair(filename = self.key_file)
+    
+
+        if (protocol=="xmlrpc"):
+            import xmlrpcprotocol  
+            self.server = xmlrpcprotocol.get_server(self.url, self.key_file, self.cert_file)
+        elif (protocol=="soap"):
+            import soapprotocol
+            self.server = soapprotocol.get_server(self.url, self.key_file, self.cert_file)
+        else:
+            raise Exception("Attempted use of undefined protocol %s"%protocol)
+
+
+    # -------------------------------------------------------------------------
+    # Registry Interface
+    # -------------------------------------------------------------------------
+
+    ##
+    # Create a new GID. For MAs and SAs that are physically located on the
+    # registry, this allows a owner/operator/PI to create a new GID and have it
+    # signed by his respective authority.
+    #
+    # @param cred credential of caller
+    # @param name hrn for new GID
+    # @param uuid unique identifier for new GID
+    # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
+    #
+    # @return a GID object
+
+    def create_gid(self, cred, name, uuid, pkey_string):
+        gid_str = self.server.create_gid(cred.save_to_string(save_parents=True), name, uuid, pkey_string)
+        return GID(string=gid_str)
+
+    ##
+    # Retrieve the GID for an object. This function looks up a record in the
+    # registry and returns the GID of the record if it exists.
+    # TODO: Is this function needed? It's a shortcut for Resolve()
+    #
+    # @param name hrn to look up
+    #
+    # @return a GID object
+
+    #def get_gid(self, name):
+    #   gid_str_list = self.server.get_gid(name)
+    #   gid_list = []
+    #   for str in gid_str_list:
+    #       gid_list.append(GID(string=str))
+    #  return gid_list
+
+
+    def get_gid(self, cert, hrn, type, request_hash):
+        cert_string = cert.save_to_string(save_parents=True)
+        gid_str = self.server.get_gid(cert_string, hrn, type, request_hash)
+        return GID(string=gid_str)
+    ##
+    # Get_self_credential a degenerate version of get_credential used by a
+    # client to get his initial credential when he doesn't have one. This is
+    # the same as get_credential(..., cred=None,...).
+    #
+    # The registry ensures that the client is the principal that is named by
+    # (type, name) by comparing the public key in the record's GID to the
+    # private key used to encrypt the client-side of the HTTPS connection. Thus
+    # it is impossible for one principal to retrieve another principal's
+    # credential without having the appropriate private key.
+    #
+    # @param type type of object (user | slice | sa | ma | node
+    # @param name human readable name of object
+    #
+    # @return a credential object
+
+    def get_self_credential(self, type, name):
+        cred_str = self.server.get_self_credential(type, name)
+        return Credential(string = cred_str)
+
+    ##
+    # Retrieve a credential for an object.
+    #
+    # If cred==None, then the behavior reverts to get_self_credential()
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param type type of object (user | slice | sa | ma | node)
+    # @param name human readable name of object
+    #
+    # @return a credental object
+
+    def get_credential(self, cred, type, name):
+        if cred:
+            cred = cred.save_to_string(save_parents=True) 
+        cred_str = self.server.get_credential(cred, type, name)
+        return Credential(string = cred_str)
+
+    ##
+    # List the records in an authority. The objectGID in the supplied credential
+    # should name the authority that will be listed.
+    #
+    # @param cred credential object specifying rights of the caller
+    #
+    # @return list of record objects
+
+    def list(self, cred, auth_hrn, caller_cred=None):
+        result_dict_list = self.server.list(cred.save_to_string(save_parents=True), auth_hrn, caller_cred)
+        result_rec_list = []
+        for dict in result_dict_list:
+             result_rec_list.append(SfaRecord(dict=dict))
+        return result_rec_list
+
+    ##
+    # Register an object with the registry. In addition to being stored in the
+    # SFA database, the appropriate records will also be created in the
+    # PLC databases.
+    #
+    #
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param record to register
+    #
+    # @return GID object for the newly-registered record
+
+    def register(self, cred, record, caller_cred=None):
+        gid_str = self.server.register(cred.save_to_string(save_parents=True), record.as_dict(), caller_cred)
+        return GID(string = gid_str)
+
+    
+    ##
+    # Register a peer object with the registry. 
+    #
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param record to register
+    #
+    # @return GID object for the newly-registered record
+
+    def register_peer_object(self, cred, record, caller_cred=None):
+        return self.server.register_peer_object(cred.save_to_string(save_parents=True), record, caller_cred)
+
+    ##
+    # Remove an object from the registry. If the object represents a PLC object,
+    # then the PLC records will also be removed.
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param type
+    # @param hrn
+
+    def remove(self, cred, type, hrn, caller_cred=None):
+        return self.server.remove(cred.save_to_string(save_parents=True), type, hrn, caller_cred)
+
+    ##
+    # Remove a peer object from the registry. If the object represents a PLC object,
+    # then the PLC records will also be removed.
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param type
+    # @param hrn
+
+    def remove_peer_object(self, cred, record, caller_cred=None):
+        result = self.server.remove_peer_object(cred.save_to_string(save_parents=True), record, caller_cred)
+        return result
+
+    ##
+    # Resolve an object in the registry. A given HRN may have multiple records
+    # associated with it, and therefore multiple records may be returned. The
+    # caller should check the type fields of the records to find the one that
+    # he is interested in.
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param name human readable name of object
+
+    def resolve(self, cred, name, caller_cred=None):
+        result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name, caller_cred)
+        result_rec_list = []
+        for dict in result_dict_list:
+            if dict['type'] in ['authority']:
+                result_rec_list.append(AuthorityRecord(dict=dict))
+            elif dict['type'] in ['node']:
+                result_rec_list.append(NodeRecord(dict=dict))
+            elif dict['type'] in ['slice']:
+                result_rec_list.append(SliceRecord(dict=dict))
+            elif dict['type'] in ['user']:
+                result_rec_list.append(UserRecord(dict=dict))
+            else:
+                result_rec_list.append(SfaRecord(dict=dict))
+        return result_rec_list
+
+    ##
+    # Update an object in the registry. Currently, this only updates the
+    # PLC information associated with the record. The SFA fields (name, type,
+    # GID) are fixed.
+    #
+    #
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param record a record object to be updated
+
+    def update(self, cred, record, caller_cred=None):
+        result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict(), caller_cred)
+        return result
+
+
+    #-------------------------------------------------------------------------
+    # Aggregate Interface
+    #-------------------------------------------------------------------------
+    
+    ## list resources
+    #
+    # @param cred a credential
+    # @param hrn slice hrn
+
+    def get_resources(self, cred, hrn=None, caller_cred=None):
+        result = self.server.get_resources(cred.save_to_string(save_parents=True), hrn, caller_cred)
+        return result
+
+    def get_aggregates(self, cred, hrn=None):
+        result = self.server.get_aggregates(cred.save_to_string(save_parents=True), hrn)
+        return result
+
+    def get_registries(self, cred, hrn=None):
+       result = self.server.get_registries(cred.save_to_string(save_parents=True), hrn)
+       return result
+
+    ## get policy
+    #
+    # @param cred a credential
+
+    def get_policy(self, cred):
+        result = self.server.get_policy(cred.save_to_string(save_parents=True))
+        return result
+
+    ## create slice
+    #
+    # @param cred a credential
+    # @param rspec resource specification defining how to instantiate the slice
+    
+    def create_slice(self, cred, hrn, rspec, caller_cred=None):
+        result = self.server.create_slice(cred.save_to_string(save_parents=True), hrn, rspec, caller_cred)
+        return result
+
+
+    ## delete slice
+    #
+    # @param cred a credential
+    # @param hrn slice to delete
+    def delete_slice(self, cred, hrn, caller_cred=None):
+        result = self.server.delete_slice(cred.save_to_string(save_parents=True), hrn, caller_cred)
+        return result    
+
+    # ------------------------------------------------------------------------
+    # Slice Interface
+    # ------------------------------------------------------------------------
+
+    ##
+    # Start a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def start_slice(self, cred, hrn):
+        result = self.server.start_slice(cred.save_to_string(save_parents=True), hrn)
+        return result
+
+    ##
+    # Stop a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def stop_slice(self, cred, hrn):
+        result = self.server.stop_slice(cred.save_to_string(save_parents=True), hrn)
+        return result
+
+    ##
+    # Reset a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def reset_slice(self, cred, hrn):
+        result = self.server.reset_slice(cred.save_to_string(save_parents=True), hrn)
+        return result
+
+    ##
+    # Delete a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def delete_slice(self, cred, hrn, caller_cred=None):
+        result = self.server.delete_slice(cred.save_to_string(save_parents=True), hrn, caller_cred)
+        return result
+
+    ##
+    # List the slices on a component.
+    #
+    # @param cred credential object that authorizes the caller
+    #
+    # @return a list of slice names
+
+    def get_slices(self, cred):
+        result = self.server.get_slices(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # Retrieve a ticket. This operation is currently implemented on the
+    # registry (see SFA, engineering decisions), and is not implemented on
+    # components.
+    #
+    # The ticket is filled in with information from the PLC database. This
+    # information includes resources, and attributes such as user keys and
+    # initscripts.
+    #
+    # @param cred credential object
+    # @param name name of the slice to retrieve a ticket for
+    # @param rspec resource specification dictionary
+    #
+    # @return a ticket object
+
+    def get_ticket(self, cred, name, rspec):
+        ticket_str = self.server.get_ticket(cred.save_to_string(save_parents=True), name, rspec)
+        ticket = SfaTicket(string=ticket_str)
+        return ticket
+
+    ##
+    # Redeem a ticket. This operation is currently implemented on the
+    # component.
+    #
+    # The ticket is submitted to the node manager, and the slice is instantiated
+    # or updated as appropriate.
+    #
+    # TODO: This operation should return a sliver credential and indicate
+    # whether or not the component will accept only sliver credentials, or
+    # will accept both sliver and slice credentials.
+    #
+    # @param ticket a ticket object containing the ticket
+
+    def redeem_ticket(self, ticket):
+        result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))
+        return result
+
+
+    def remove_remote_object(self, cred, hrn, record):
+        result = self.server.remove_remote_object(cred.save_to_string(save_parents=True), hrn, record)
+        return result
diff --git a/sfa/util/componentserver.py b/sfa/util/componentserver.py
new file mode 100644 (file)
index 0000000..f0944a6
--- /dev/null
@@ -0,0 +1,138 @@
+##
+# This module implements a general-purpose server layer for sfa.
+# The same basic server should be usable on the registry, component, or
+# other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+##
+
+### $Id: componentserver.py 17275 2010-03-05 21:37:10Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/componentserver.py $
+
+import sys
+import traceback
+import threading
+import socket, os
+import SocketServer
+import BaseHTTPServer
+import SimpleHTTPServer
+import SimpleXMLRPCServer
+from OpenSSL import SSL
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.credential import *
+from sfa.util.faults import *
+from sfa.plc.api import ComponentAPI 
+from sfa.util.server import verify_callback, ThreadedServer 
+from sfa.util.debug import log
+
+
+##
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
+
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+    """Secure XML-RPC request handler class.
+
+    It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
+    """
+    def setup(self):
+        self.connection = self.request
+        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+    def do_POST(self):
+        """Handles the HTTPS POST request.
+
+        It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
+        """
+        try:
+            peer_cert = Certificate()
+            peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
+            self.api = ComponentAPI(peer_cert = peer_cert, 
+                           interface = self.server.interface, 
+                           key_file = self.server.key_file, 
+                           cert_file = self.server.cert_file)
+            # get arguments
+            request = self.rfile.read(int(self.headers["content-length"]))
+            # In previous versions of SimpleXMLRPCServer, _dispatch
+            # could be overridden in this class, instead of in
+            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
+            # check to see if a subclass implements _dispatch and dispatch
+            # using that method if present.
+            #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
+            # XX TODO: Need to get the real remote address
+            remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
+            self.api.remote_addr = remote_addr
+            #remote_addr = (self.rfile.connection.remote_ip, remote_port)
+            #self.api.remote_addr = remote_addr
+            response = self.api.handle(remote_addr, request)
+
+        
+        except Exception, fault:
+            raise
+            # This should only happen if the module is buggy
+            # internal error, report as HTTP server error
+            self.send_response(500)
+            self.end_headers()
+            traceback.print_exc()
+        else:
+            # got a valid XML RPC response
+            self.send_response(200)
+            self.send_header("Content-type", "text/xml")
+            self.send_header("Content-length", str(len(response)))
+            self.end_headers()
+            self.wfile.write(response)
+
+            # shut down the connection
+            self.wfile.flush()
+            self.connection.shutdown() # Modified here!
+
+##
+# Implements an HTTPS XML-RPC server. Generally it is expected that SFA
+# functions will take a credential string, which is passed to
+# decode_authentication. Decode_authentication() will verify the validity of
+# the credential, and verify that the user is using the key that matches the
+# GID supplied in the credential.
+
+class ComponentServer(threading.Thread):
+
+    ##
+    # Create a new SfaServer object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key 
+    #   (could be a GID file)
+
+    def __init__(self, ip, port, key_file, cert_file, api=None):
+        threading.Thread.__init__(self)
+        self.key = Keypair(filename = key_file)
+        self.cert = Certificate(filename = cert_file)
+        self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+        self.trusted_cert_list = None
+        self.register_functions()
+
+
+    ##
+    # Register functions that will be served by the XMLRPC server. This
+    # function should be overrided by each descendant class.
+
+    def register_functions(self):
+        self.server.register_function(self.noop)
+
+    ##
+    # Sample no-op server function. The no-op function decodes the credential
+    # that was passed to it.
+
+    def noop(self, cred, anything):
+        self.decode_authentication(cred)
+
+        return anything
+
+    ##
+    # Execute the server, serving requests forever. 
+
+    def run(self):
+        self.server.serve_forever()
+
+
diff --git a/sfa/util/config.py b/sfa/util/config.py
new file mode 100644 (file)
index 0000000..fe92c86
--- /dev/null
@@ -0,0 +1,128 @@
+##
+# SFA Configuration Info
+#
+# This module holds configuration parameters for SFA. There are two
+# main pieces of information that are used: the database connection and
+# the PLCAPI connection
+##
+
+##
+# SFA uses a MYSQL database to store records. This database may be
+# co-located with the PLC database, or it may be a separate database. The
+# following parameters define the connection to the database.
+#
+# Note that SFA does not access any of the PLC databases directly via
+# a mysql connection; All PLC databases are accessed via PLCAPI.
+
+### $Id: config.py 16803 2010-01-26 17:49:14Z acb $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/config.py $
+
+import os.path
+import traceback
+
+from sfa.util.debug import log
+
+class Config:
+    """
+    Parse the bash/Python/PHP version of the configuration file. Very
+    fast but no type conversions.
+    """
+
+    def __init__(self, config_file = "/etc/sfa/sfa_config.py"):
+        self.config_file = None
+        self.config_path = None
+        self.data_path = None
+        self.load(config_file)
+
+    def load(self, config_file):
+        try:
+            execfile(config_file, self.__dict__)
+            self.config_file = config_file
+            # path to configuration data
+            self.config_path = os.path.dirname(config_file)
+            
+            # path to server data
+            if not hasattr(self, 'SFA_DATA_DIR'):
+                # default to /var/lib/sfa not specified in config
+                self.SFA_DATA_DIR="/var/lib/sfa"
+                self.data_path = self.SFA_DATA_DIR
+            else:
+                self.data_path = self.SFA_DATA_DIR
+                
+            # path to config data
+            if not hasattr(self, 'SFA_CONFIG_DIR'):
+                # default to /var/lib/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'
+
+            # create the data directory if it doesnt exist
+            if not os.path.isdir(self.SFA_DATA_DIR):
+                try:
+                    os.mkdir(self.SFA_DATA_DIR)
+                except: pass
+             
+        except IOError, e:
+            raise IOError, "Could not find the configuration file: %s" % config_file
+
+    def get_trustedroots_dir(self):
+        return self.config_path + os.sep + 'trusted_roots'
+
+    def get_openflow_aggrMgr_info(self):
+        aggr_mgr_ip = 'localhost'
+        if (hasattr(self,'OPENFLOW_AGGREGATE_MANAGER_IP')):
+            aggr_mgr_ip = self.OPENFLOW_AGGREGATE_MANAGER_IP
+
+        aggr_mgr_port = 2603
+        if (hasattr(self,'OPENFLOW_AGGREGATE_MANAGER_PORT')):
+            aggr_mgr_port = self.OPENFLOW_AGGREGATE_MANAGER_PORT
+
+        return (aggr_mgr_ip,aggr_mgr_port)
+
+    def get_aggregate_type(self):
+        if (hasattr(self,'SFA_AGGREGATE_TYPE')):
+            return self.SFA_AGGREGATE_TYPE
+        else:
+            return "pl"
+
+    def get_plc_dbinfo(self):
+        return {
+            'dbname' : self.SFA_PLC_DB_NAME,
+            'address' : self.SFA_PLC_DB_HOST,
+            'port' : self.SFA_PLC_DB_PORT,
+            'user' : self.SFA_PLC_DB_USER,
+            'password' : self.SFA_PLC_DB_PASSWORD
+            }
+
+    ##
+    # SFA uses a PLCAPI connection to perform operations on the registry,
+    # such as creating and deleting slices. This connection requires an account
+    # on the PLC server with full administrator access.
+    #
+    # The Url parameter controls whether the connection uses PLCAPI directly (i.e.
+    # SFA is located on the same machine as PLC), or uses a XMLRPC connection
+    # to the PLC machine. If you wish to use the API directly, then remove the Url
+    # field from the dictionary. 
+
+    def get_plc_auth(self):
+        return {
+            'AuthMethod': 'capability',
+            'Username': self.SFA_PLC_USER,
+            'AuthString':  self.SFA_PLC_PASSWORD,
+            "Url": self.SFA_PLC_URL
+            }
diff --git a/sfa/util/debug.py b/sfa/util/debug.py
new file mode 100644 (file)
index 0000000..71eb064
--- /dev/null
@@ -0,0 +1,57 @@
+### $Id: debug.py 14192 2009-07-02 08:40:01Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/debug.py $
+
+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)
diff --git a/sfa/util/faults.py b/sfa/util/faults.py
new file mode 100644 (file)
index 0000000..7391bdd
--- /dev/null
@@ -0,0 +1,290 @@
+#
+# SFA API faults
+#
+#
+
+### $Id: faults.py 18586 2010-08-05 23:33:41Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/faults.py $
+
+import xmlrpclib
+
+class SfaFault(xmlrpclib.Fault):
+    def __init__(self, faultCode, faultString, extra = None):
+        if extra:
+            faultString += ": " + str(extra)
+        xmlrpclib.Fault.__init__(self, faultCode, faultString)
+
+class SfaInvalidAPIMethod(SfaFault):
+    def __init__(self, method, interface = None, extra = None):
+        faultString = "Invalid method " + method
+        if interface:
+            faultString += " for interface " + interface
+        SfaFault.__init__(self, 100, faultString, extra)
+
+class SfaInvalidArgumentCount(SfaFault):
+    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)
+        SfaFault.__init__(self, 101, faultString, extra)
+
+class SfaInvalidArgument(SfaFault):
+    def __init__(self, extra = None, name = None):
+        if name is not None:
+            faultString = "Invalid %s value" % name
+        else:
+            faultString = "Invalid argument"
+        SfaFault.__init__(self, 102, faultString, extra)
+
+class SfaAuthenticationFailure(SfaFault):
+    def __init__(self, extra = None):
+        faultString = "Failed to authenticate call"
+        SfaFault.__init__(self, 103, faultString, extra)
+
+class SfaDBError(SfaFault):
+    def __init__(self, extra = None):
+        faultString = "Database error"
+        SfaFault.__init__(self, 106, faultString, extra)
+
+class SfaPermissionDenied(SfaFault):
+    def __init__(self, extra = None):
+        faultString = "Permission denied"
+        SfaFault.__init__(self, 108, faultString, extra)
+
+class SfaNotImplemented(SfaFault):
+    def __init__(self, interface=None, extra = None):
+        faultString = "Not implemented"
+        if interface:
+            faultString += " at interface " + interface 
+        SfaFault.__init__(self, 109, faultString, extra)
+
+class SfaAPIError(SfaFault):
+    def __init__(self, extra = None):
+        faultString = "Internal API error"
+        SfaFault.__init__(self, 111, faultString, extra)
+
+class MalformedHrnException(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Malformed HRN: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class TreeException(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Tree Exception: %(value)s, " % locals()
+        SfaFault.__init__(self, 111, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class NonExistingRecord(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Non exsiting record %(value)s, " % locals()
+        SfaFault.__init__(self, 111, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class ExistingRecord(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Existing record: %(value)s, " % locals()
+        SfaFault.__init__(self, 111, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+    
+class NonexistingCredType(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Non existing record: %(value)s, " % locals()
+        SfaFault.__init__(self, 111, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class NonexistingFile(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Non existing file: %(value)s, " % locals()
+        SfaFault.__init__(self, 111, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class InvalidRPCParams(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Invalid RPC Params: %(value)s, " % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+# SMBAKER exceptions follow
+
+class ConnectionKeyGIDMismatch(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Connection Key GID mismatch: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra) 
+    def __str__(self):
+        return repr(self.value)
+
+class MissingCallerGID(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Missing Caller GID: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra) 
+    def __str__(self):
+        return repr(self.value)
+
+class RecordNotFound(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Record not found: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class UnknownSfaType(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Unknown SFA Type: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class MissingAuthority(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Missing authority: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class PlanetLabRecordDoesNotExist(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "PlanetLab record does not exist : %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class PermissionError(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Permission error: %(value)s" % locals()
+        SfaFault.__init__(self, 108, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class InsufficientRights(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Insufficient rights: %(value)s" % locals()
+        SfaFault.__init__(self, 108, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class MissingDelegateBit(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Missing delegate bit: %(value)s" % locals()
+        SfaFault.__init__(self, 108, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class ChildRightsNotSubsetOfParent(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Child rights not subset of parent: %(value)s" % locals()
+        SfaFault.__init__(self, 103, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class CertMissingParent(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Cert missing parent: %(value)s" % locals()
+        SfaFault.__init__(self, 103, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class CertNotSignedByParent(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Cert not signed by parent: %(value)s" % locals()
+        SfaFault.__init__(self, 103, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+    
+class GidParentHrn(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Cert URN is not an extension of its parent: %(value)s" % locals()
+        SfaFault.__init__(self, 103, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+        
+class GidInvalidParentHrn(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "GID invalid parent hrn: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class SliverDoesNotExist(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Sliver does not exist : %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class BadRequestHash(xmlrpclib.Fault):
+    def __init__(self, hash = None, extra = None):
+        faultString = "bad request hash: " + str(hash)
+        xmlrpclib.Fault.__init__(self, 902, faultString)
+
+class MissingTrustedRoots(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Trusted root directory does not exist: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra) 
+    def __str__(self):
+        return repr(self.value)
+
+class MissingSfaInfo(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Missing information: %(value)s" % locals()
+        SfaFault.__init__(self, 102, faultString, extra) 
+    def __str__(self):
+        return repr(self.value)
+
+class InvalidRSpec(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Invalid RSpec: %(value)s" % locals()
+        SfaFault.__init__(self, 108, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class AccountNotEnabled(SfaFault):
+    def __init__(self,  extra = None):
+        faultString = "Account Disabled"
+        SfaFault.__init__(self, 108, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
+
+class CredentialNotVerifiable(SfaFault):
+    def __init__(self, value, extra = None):
+        self.value = value
+        faultString = "Unable to verify credential: %(value)s, " %locals()
+        SfaFault.__init__(self, 115, faultString, extra)
+    def __str__(self):
+        return repr(self.value)
diff --git a/sfa/util/filter.py b/sfa/util/filter.py
new file mode 100644 (file)
index 0000000..0e36250
--- /dev/null
@@ -0,0 +1,218 @@
+# $Id: filter.py 16477 2010-01-05 16:31:37Z thierry $
+# $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/filter.py $
+from types import StringTypes
+try:
+    set
+except NameError:
+    from sets import Set
+    set = Set
+
+import time
+import pgdb
+from sfa.util.faults import *
+from sfa.util.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 ( { 'node_type' : 'regular' , 'hostname' : '*.edu' , '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 } )
+    and that would return regular (usual) 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
+        valid_fields = {}
+        for field in filter:
+            if field in fields:
+                valid_fields[field] = filter[field]
+        dict.__init__(self, valid_fields)
+
+        # 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 = dict ( [ ( field, Mixed (expected, [expected])) 
+                                 for (field,expected) in fields.iteritems()
+                                 if python_type(expected) not in (list, tuple, set) ] )
+
+        # Null filter means no filter
+        Parameter.__init__(self, self.fields, doc = doc, nullok = True)
+
+    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)    
+
+    def sql(self, 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 SfaInvalidArgument, "Invalid filter field '%s'" % field
+
+                if isinstance(value, (list, tuple, set)):
+                    # handling filters like '~slice_id':[]
+                    # this should return true, as it's the opposite of 'slice_id':[] which is false
+                    # prior to this fix, 'slice_id':[] would have returned ``slice_id IN (NULL) '' which is unknown 
+                    # so it worked by coincidence, but the negation '~slice_ids':[] would return false too
+                    if not value:
+                        field=""
+                        operator=""
+                        value = "FALSE"
+                    else:
+                        operator = "IN"
+                        value = map(str, map(self.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"
+                        # insert *** in pattern instead of either * or %
+                        # we dont use % as requests are likely to %-expansion later on
+                        # actual replacement to % done in PostgreSQL.py
+                        value = value.replace ('*','***')
+                        value = value.replace ('%','***')
+                        value = str(self.quote(value))
+                    else:
+                        operator = "="
+                        if modifiers['<']:
+                            operator='<'
+                        if modifiers['>']:
+                            operator='>'
+                        if modifiers['[']:
+                            operator='<='
+                        if modifiers[']']:
+                            operator='>='
+                        else:
+                            value = str(self.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 SfaInvalidArgument, "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 SfaInvalidArgument, "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)
diff --git a/sfa/util/method.py b/sfa/util/method.py
new file mode 100644 (file)
index 0000000..40bc5a5
--- /dev/null
@@ -0,0 +1,306 @@
+#
+# Base class for all SfaAPI functions
+#
+#
+
+### $Id: method.py 18544 2010-07-30 20:42:56Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/method.py $
+
+import os, time
+from types import *
+from types import StringTypes
+import traceback
+import textwrap
+import xmlrpclib
+
+
+from sfa.util.faults import * 
+from sfa.util.parameter import Parameter, Mixed, python_type, xmlrpc_type
+from sfa.trust.auth import Auth
+from sfa.util.debug import profile, log
+
+# we inherit object because we use new-style classes for legacy methods
+class Method (object):
+    """
+    Base class for all SfaAPI functions. At a minimum, all SfaAPI
+    functions must define:
+
+    interfaces = [allowed interfaces]
+    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.
+    """
+
+    interfaces = []
+    accepts = []
+    returns = bool
+    status = "current"
+
+    def call(self, *args):
+        """
+        Method body for all SfaAPI 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 SfaAPI functions. Type checks
+        arguments, authenticates, and executes call().
+        """
+
+        try:
+            start = time.time()
+            methodname = self.name
+            if not self.api.interface or self.api.interface not in self.interfaces:
+                raise SfaInvalidAPIMethod(methodname, self.api.interface) 
+
+            # legacy code cannot be type-checked, due to the way Method.args() works
+            if not hasattr(self,"skip_typecheck"):
+                (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 SfaInvalidArgumentCount(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.SFA_API_DEBUG or hasattr(self, 'message'):
+                # XX print to some log file
+                # print >> log, "some output"
+                   pass
+
+            return result
+
+        except SfaFault, fault:
+
+            caller = ""
+
+            # Prepend caller and method name to expected faults
+            fault.faultString = caller + ": " +  self.name + ": " + fault.faultString
+            runtime = time.time() - start
+           
+            if self.api.config.SFA_API_DEBUG:
+                traceback.print_exc()
+            raise fault
+
+
+    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"
+
+        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 SfaInvalidArgument, 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 SfaInvalidArgument("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 SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+            if max is not None and \
+               len(value.encode(self.api.encoding)) > max:
+                raise SfaInvalidArgument, "%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 SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
+            if max is not None and len(value) > max:
+                raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
+        else:
+            if min is not None and value < min:
+                raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
+            if max is not None and value > max:
+                raise SfaInvalidArgument, "%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 SfaInvalidArgument("'%s' not specified" % key, name)
+
+        #if auth is not None:
+        #    auth.check(self, *args)
diff --git a/sfa/util/misc.py b/sfa/util/misc.py
new file mode 100644 (file)
index 0000000..e81123b
--- /dev/null
@@ -0,0 +1,49 @@
+### $Id: misc.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/misc.py $
+
+from sfa.util.faults import *
+
+def get_leaf(hrn):
+    parts = hrn.split(".")
+    return ".".join(parts[-1:])
+
+def get_authority(hrn):
+    parts = hrn.split(".")
+    return ".".join(parts[:-1])
+
+def hrn_to_pl_slicename(hrn):
+    parts = hrn.split(".")
+    return parts[-2] + "_" + parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc authority name
+def hrn_to_pl_authname(hrn):
+    parts = hrn.split(".")
+    return parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc login_base
+def hrn_to_pl_login_base(hrn):
+    return hrn_to_pl_authname(hrn)
+
+def hostname_to_hrn(auth_hrn, login_base, hostname):
+    """
+    Convert hrn to plantelab name.
+    """
+    sfa_hostname = ".".join([auth_hrn, login_base, hostname.split(".")[0]])
+    return sfa_hostname
+
+def slicename_to_hrn(auth_hrn, slicename):
+    """
+    Convert hrn to planetlab name.
+    """
+    parts = slicename.split("_")
+    slice_hrn = ".".join([auth_hrn, parts[0]]) + "." + "_".join(parts[1:])
+
+    return slice_hrn
+
+def email_to_hrn(auth_hrn, email):
+    parts = email.split("@")
+    username = parts[0]
+    username = username.replace(".", "_") 
+    person_hrn = ".".join([auth_hrn, username])
+    
+    return person_hrn 
diff --git a/sfa/util/namespace.py b/sfa/util/namespace.py
new file mode 100644 (file)
index 0000000..90a4e82
--- /dev/null
@@ -0,0 +1,108 @@
+### $Id: namespace.py 18515 2010-07-15 20:41:59Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/namespace.py $
+
+from sfa.util.faults import *
+URN_PREFIX = "urn:publicid:IDN"
+
+def get_leaf(hrn):
+    parts = hrn.split(".")
+    return ".".join(parts[-1:])
+
+def get_authority(xrn):
+    hrn, type = urn_to_hrn(xrn)
+    if type and type == 'authority':
+        return hrn
+    
+    parts = hrn.split(".")
+    return ".".join(parts[:-1])
+
+def hrn_to_pl_slicename(hrn):
+    parts = hrn.split(".")
+    return parts[-2] + "_" + parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc authority name
+def hrn_to_pl_authname(hrn):
+    parts = hrn.split(".")
+    return parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc login_base
+def hrn_to_pl_login_base(hrn):
+    return hrn_to_pl_authname(hrn)
+
+def hostname_to_hrn(auth_hrn, login_base, hostname):
+    """
+    Convert hrn to plantelab name.
+    """
+    sfa_hostname = ".".join([auth_hrn, login_base, hostname.split(".")[0]])
+    return sfa_hostname
+
+def slicename_to_hrn(auth_hrn, slicename):
+    """
+    Convert hrn to planetlab name.
+    """
+    parts = slicename.split("_")
+    slice_hrn = ".".join([auth_hrn, parts[0]]) + "." + "_".join(parts[1:])
+
+    return slice_hrn
+
+def email_to_hrn(auth_hrn, email):
+    parts = email.split("@")
+    username = parts[0]
+    username = username.replace(".", "_").replace("+", "_") 
+    person_hrn = ".".join([auth_hrn, username])
+    
+    return person_hrn 
+
+def urn_to_hrn(urn):
+    """
+    convert a urn to hrn
+    return a tuple (hrn, type)
+    """
+
+    # if this is already a hrn dont do anything
+    if not urn or not urn.startswith(URN_PREFIX):
+        return urn, None
+
+    name = urn[len(URN_PREFIX):]
+    hrn_parts = name.split("+")
+    
+    type = hrn_parts.pop(2)
+
+    # convert hrn_parts (list) into hrn (str) by doing the following    
+    # remove blank elements
+    # replace ':' with '.'
+    # join list elements using '.'
+    hrn = '.'.join([part.replace(':', '.') for part in hrn_parts if part]) 
+    
+    # Remove the authority name (e.g. '.sa')
+    if type == 'authority':
+        hrn = hrn[:hrn.rindex('.')]        
+   
+    return str(hrn), str(type) 
+    
+    
+def hrn_to_urn(hrn, type=None):
+    """
+    convert an hrn and type to a urn string
+    """
+    # if  this is already a urn dont do anything 
+    if not hrn or hrn.startswith(URN_PREFIX):
+        return hrn
+
+    authority = get_authority(hrn)
+    name = get_leaf(hrn)
+     
+    if type == 'authority':
+        authority = hrn
+        name = 'sa'   
+    
+    if authority.startswith("plc"):
+        if type == None:
+            urn = "+".join(['',authority.replace('.',':'),name])
+        else:
+            urn = "+".join(['',authority.replace('.',':'),type,name])
+
+    else:
+        urn = "+".join(['',authority,type,name])
+        
+    return URN_PREFIX + urn
diff --git a/sfa/util/nodemanager.py b/sfa/util/nodemanager.py
new file mode 100644 (file)
index 0000000..1671b9d
--- /dev/null
@@ -0,0 +1,46 @@
+import tempfile
+import commands
+import os
+
+class NodeManager:
+
+    method = None
+
+    def __init__(self, config):
+        self.config = config
+
+    def __getattr__(self, method):
+        self.method = method
+        return self.__call__
+    
+    def __call__(self, *args):
+        method = self.method
+        sfa_slice_prefix = self.config.SFA_CM_SLICE_PREFIX 
+        sfa_slice = sfa_slice_prefix + "_sfacm"
+        python = "/usr/bin/python"
+        vserver_path = "/vservers/%s" % (sfa_slice)
+        script_path = "/tmp/"
+        path = "%(vserver_path)s/%(script_path)s" % locals()
+        (fd, filename) = tempfile.mkstemp(dir=path)        
+        scriptname = script_path + os.sep + filename.split(os.sep)[-1:][0]
+        # define the script to execute
+        script = """
+#!%(python)s
+import xmlrpclib
+s = xmlrpclib.ServerProxy('http://127.0.0.1:812')
+print s.%(method)s%(args)s"""  % locals()
+
+        try:    
+            # write the script to a temporary file
+            f = open(filename, 'w')
+            f.write(script % locals())
+            f.close()
+            # make the file executeable
+            chmod_cmd = "/bin/chmod 775 %(filename)s" % locals()
+            (status, output) = commands.getstatusoutput(chmod_cmd)
+
+            # execute the commad as a slice with root NM privs    
+            cmd = 'su - %(sfa_slice)s -c "%(python)s %(scriptname)s"' % locals()
+            (status, output) = commands.getstatusoutput(cmd)
+            return (status, output)  
+        finally: os.unlink(filename)
diff --git a/sfa/util/parameter.py b/sfa/util/parameter.py
new file mode 100644 (file)
index 0000000..367d106
--- /dev/null
@@ -0,0 +1,108 @@
+#
+# Shared type definitions
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: parameter.py 16477 2010-01-05 16:31:37Z thierry $
+#
+
+### $Id: parameter.py 16477 2010-01-05 16:31:37Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/parameter.py $
+
+from types import *
+from sfa.util.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 SfaAPIError, "XML-RPC cannot marshal %s objects" % arg_type
diff --git a/sfa/util/policy.py b/sfa/util/policy.py
new file mode 100644 (file)
index 0000000..3e8332c
--- /dev/null
@@ -0,0 +1,23 @@
+### $Id: policy.py 15724 2009-11-11 19:52:12Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/policy.py $
+
+import os
+
+from sfa.util.storage import *
+from sfa.util.debug import log
+
+class Policy(SimpleStorage):
+
+    def __init__(self, api):
+        self.api = api
+        path = self.api.config.SFA_CONFIG_DIR
+        filename = ".".join([self.api.interface, self.api.hrn, "policy"])    
+        filepath = path + os.sep + filename
+        self.policy_file = filepath
+        default_policy = {'slice_whitelist': [],
+                          'slice_blacklist': [],
+                          'node_whitelist': [],
+                          'node_blacklist': []} 
+        SimpleStorage.__init__(self, self.policy_file, default_policy)
+        self.load()          
diff --git a/sfa/util/prefixTree.py b/sfa/util/prefixTree.py
new file mode 100755 (executable)
index 0000000..93b0c5c
--- /dev/null
@@ -0,0 +1,97 @@
+class prefixNode:
+
+    def __init__(self, prefix):
+        self.prefix = prefix
+        self.children = []
+    
+
+class prefixTree:
+    
+    def __init__(self):
+        self.root = prefixNode("")
+
+    def insert(self, prefix, node = None):
+        """
+        insert a prefix into the tree
+        """
+        if not node:
+            node = self.root
+
+        parts = prefix.split(".")
+        length = len(parts)
+
+        if length > 1:
+            for i in range(1, length + 1):
+                name = ".".join(parts[:i])
+                if not self.exists(name) and not name == prefix:
+                    self.insert(name)
+         
+        if prefix.startswith(node.prefix):
+            if prefix == node.prefix:
+                pass
+            elif not node.children:
+                node.children.append(prefixNode(prefix))
+            else:
+                inserted  = False
+                for child in node.children:
+                    if prefix.startswith(child.prefix):
+                        self.insert(prefix, child)
+                        inserted = True
+                if not inserted:
+                    node.children.append(prefixNode(prefix)) 
+
+    def load(self, prefix_list):
+        """
+        load a list of prefixes into the tree
+        """
+        for prefix in prefix_list:
+            self.insert(prefix)
+
+    def exists(self, prefix, node = None):
+        """
+        returns true if the specified prefix exists anywhere in the tree,
+        false if it doesnt. 
+        """
+        if not node:
+            node = self.root
+
+        if not prefix.startswith(node.prefix):
+            return False
+        elif node.prefix == prefix:
+            return True
+        elif not node.children:
+            return False
+        else:
+            for child in node.children:
+                if prefix.startswith(child.prefix):
+                    return self.exists(prefix, child)
+
+    def best_match(self, prefix, node = None):
+        """
+        searches the tree and returns the prefix that best matches the 
+        specified prefix  
+        """
+        if not node:
+            node = self.root
+        
+        if prefix.startswith(node.prefix):
+            if not node.children:
+                return node.prefix
+            for child in node.children:
+                if prefix.startswith(child.prefix):
+                    return self.best_match(prefix, child)
+            return node.prefix
+         
+    def dump(self, node = None):
+        """
+        print the tree
+        """
+        if not node:
+            node = self.root
+            print node.prefix
+
+        for child in node.children:
+            print child.prefix, 
+         
+        for child in node.children:
+            self.dump(child)
diff --git a/sfa/util/record.py b/sfa/util/record.py
new file mode 100644 (file)
index 0000000..a0d836c
--- /dev/null
@@ -0,0 +1,399 @@
+##
+# Implements support for SFA records
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+##
+
+### $Id: record.py 17916 2010-05-04 21:01:12Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/record.py $
+
+from types import StringTypes
+
+from sfa.trust.gid import *
+
+import sfa.util.report
+from sfa.util.rspec import *
+from sfa.util.parameter import *
+from sfa.util.namespace import *
+from sfa.util.row import Row
+
+class SfaRecord(Row):
+    """ 
+    The SfaRecord class implements an SFA Record. A SfaRecord is a tuple
+    (Hrn, GID, Type, Info).
+    Hrn specifies the Human Readable Name of the object
+    GID is the GID of the object
+    Type is user | authority | slice | component
+    Info is comprised of the following sub-fields
+           pointer = a pointer to the record in the PL database
+    The pointer is interpreted depending on the type of the record. For example,
+    if the type=="user", then pointer is assumed to be a person_id that indexes
+    into the persons table.
+    A given HRN may have more than one record, provided that the records are
+    of different types.
+    """
+
+    table_name = 'sfa'
+    
+    primary_key = 'record_id'
+
+    ### the wsdl generator assumes this is named 'fields'
+    internal_fields = {
+        'record_id': Parameter(int, 'An id that uniquely identifies this record', ro=True),
+        'pointer': Parameter(int, 'An id that uniquely identifies this record in an external database ')
+    }
+
+    fields = {
+        'authority': Parameter(str, "The authority for this record"),
+        'peer_authority': Parameter(str, "The peer authority for this record"),
+        'hrn': Parameter(str, "Human readable name of object"),
+        'gid': Parameter(str, "GID of the object"),
+        'type': Parameter(str, "Record type"),
+        'last_updated': Parameter(int, 'Date and time of last update', ro=True),
+        'date_created': Parameter(int, 'Date and time this record was created', ro=True),
+    }
+    all_fields = dict(fields.items() + internal_fields.items())
+    ##
+    # Create an SFA Record
+    #
+    # @param name if !=None, assign the name of the record
+    # @param gid if !=None, assign the gid of the record
+    # @param type one of user | authority | slice | component
+    # @param pointer is a pointer to a PLC record
+    # @param dict if !=None, then fill in this record from the dictionary
+
+    def __init__(self, hrn=None, gid=None, type=None, pointer=None, peer_authority=None, dict=None, string=None):
+        self.dirty = True
+        self.hrn = None
+        self.gid = None
+        self.type = None
+        self.pointer = None
+        self.set_peer_auth(peer_authority)
+        if hrn:
+            self.set_name(hrn)
+        if gid:
+            self.set_gid(gid)
+        if type:
+            self.set_type(type)
+        if pointer:
+            self.set_pointer(pointer)
+        if dict:
+            self.load_from_dict(dict)
+        if string:
+            self.load_from_string(string)
+
+
+    def validate_last_updated(self, last_updated):
+        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
+        
+    def update(self, new_dict):
+        if isinstance(new_dict, list):
+            new_dict = new_dict[0]
+
+        # Convert any boolean strings to real bools
+        for key in new_dict:
+            if isinstance(new_dict[key], StringTypes):
+                if new_dict[key].lower() in ["true"]:
+                    new_dict[key] = True
+                elif new_dict[key].lower() in ["false"]:
+                    new_dict[key] = False
+        dict.update(self, new_dict)
+
+    ##
+    # Set the name of the record
+    #
+    # @param hrn is a string containing the HRN
+
+    def set_name(self, hrn):
+        """
+        Set the name of the record
+        """
+        self.hrn = hrn
+        self['hrn'] = hrn
+        self.dirty = True
+
+    ##
+    # Set the GID of the record
+    #
+    # @param gid is a GID object or the string representation of a GID object
+
+    def set_gid(self, gid):
+        """
+        Set the GID of the record
+        """
+
+        if isinstance(gid, StringTypes):
+            self.gid = gid
+            self['gid'] = gid
+        else:
+            self.gid = gid.save_to_string(save_parents=True)
+            self['gid'] = gid.save_to_string(save_parents=True)
+        self.dirty = True
+
+    ##
+    # Set the type of the record
+    #
+    # @param type is a string: user | authority | slice | component
+
+    def set_type(self, type):
+        """
+        Set the type of the record
+        """
+        self.type = type
+        self['type'] = type
+        self.dirty = True
+
+    ##
+    # Set the pointer of the record
+    #
+    # @param pointer is an integer containing the ID of a PLC record
+
+    def set_pointer(self, pointer):
+        """
+        Set the pointer of the record
+        """
+        self.pointer = pointer
+        self['pointer'] = pointer
+        self.dirty = True
+
+
+    def set_peer_auth(self, peer_authority):
+        self.peer_authority = peer_authority
+        self['peer_authority'] = peer_authority
+        self.dirty = True
+
+    ##
+    # Return the name (HRN) of the record
+
+    def get_name(self):
+        """
+        Return the name (HRN) of the record
+        """
+        return self.hrn
+
+    ##
+    # Return the type of the record
+
+    def get_type(self):
+        """
+        Return the type of the record
+        """
+        return self.type
+
+    ##
+    # Return the pointer of the record. The pointer is an integer that may be
+    # used to look up the record in the PLC database. The evaluation of pointer
+    # depends on the type of the record
+
+    def get_pointer(self):
+        """
+        Return the pointer of the record. The pointer is an integer that may be
+        used to look up the record in the PLC database. The evaluation of pointer
+        depends on the type of the record
+        """
+        return self.pointer
+
+    ##
+    # Return the GID of the record, in the form of a GID object
+    # TODO: not the best name for the function, because we have things called
+    # gidObjects in the Cred
+
+    def get_gid_object(self):
+        """
+        Return the GID of the record, in the form of a GID object
+        """
+        return GID(string=self.gid)
+
+    ##
+    # Returns a list of field names in this record. 
+
+    def get_field_names(self):
+        """
+        Returns a list of field names in this record.
+        """
+        return self.fields.keys()
+
+    ##
+    # Given a field name ("hrn", "gid", ...) return the value of that field.
+    #
+    # @param fieldname is the name of field to be returned
+
+    def get_field_value_string(self, fieldname):
+        """
+        Given a field name ("hrn", "gid", ...) return the value of that field.
+        """
+        if fieldname == "authority":
+            val = get_authority(self['hrn'])
+        else:
+            try:
+                val = getattr(self, fieldname)
+            except:
+                val = self[fieldname] 
+        if isinstance(val, str):
+            return "'" + str(val) + "'"
+        else:
+            return str(val)
+
+    ##
+    # Given a list of field names, return a list of values for those public.
+    #
+    # @param fieldnames is a list of field names
+
+    def get_field_value_strings(self, fieldnames):
+        """
+        Given a list of field names, return a list of values for those public.
+        """
+        return [ self.get_field_value_string (fieldname) for fieldname in fieldnames ]
+
+    ##
+    # Return the record in the form of a dictionary
+
+    def as_dict(self):
+        """
+        Return the record in the form of a dictionary
+        """
+        return dict(self)
+
+    ##
+    # Load the record from a dictionary
+    #
+    # @param dict dictionary to load record public from
+
+    def load_from_dict(self, dict):
+        """
+        Load the record from a dictionary 
+        """
+        self.set_name(dict['hrn'])
+        gidstr = dict.get("gid", None)
+        if gidstr:
+            self.set_gid(dict['gid'])
+
+        if "pointer" in dict:
+           self.set_pointer(dict['pointer'])
+
+        self.set_type(dict['type'])
+        self.update(dict)        
+    
+    ##
+    # Save the record to a string. The string contains an XML representation of
+    # the record.
+
+    def save_to_string(self):
+        """
+        Save the record to a string. The string contains an XML representation of
+        the record.
+        """
+        recorddict = self.as_dict()
+        filteredDict = dict([(key, val) for (key, val) in recorddict.iteritems() if key in self.fields.keys()])
+        record = RecordSpec()
+        record.parseDict(filteredDict)
+        str = record.toxml()
+        #str = xmlrpclib.dumps((dict,), allow_none=True)
+        return str
+
+    ##
+    # Load the record from a string. The string is assumed to contain an XML
+    # representation of the record.
+
+    def load_from_string(self, str):
+        """
+        Load the record from a string. The string is assumed to contain an XML
+        representation of the record.
+        """
+        #dict = xmlrpclib.loads(str)[0][0]
+        
+        record = RecordSpec()
+        record.parseString(str)
+        record_dict = record.toDict()
+        sfa_dict = record_dict['record']
+        self.load_from_dict(sfa_dict)
+
+    ##
+    # Dump the record to stdout
+    #
+    # @param dump_parents if true, then the parents of the GID will be dumped
+
+    def dump(self, dump_parents=False):
+        """
+        Walk tree and dump records.
+        """
+        #print "RECORD", self.name
+        #print "        hrn:", self.name
+        #print "       type:", self.type
+        #print "        gid:"
+        #if (not self.gid):
+        #    print "        None"
+        #else:
+        #    self.get_gid_object().dump(8, dump_parents)
+        #print "    pointer:", self.pointer
+       
+        order = SfaRecord.fields.keys() 
+        for key in self.keys():
+            if key not in order:
+                order.append(key)
+        for key in order:
+            if key in self and key in self.fields:
+                if key in 'gid' and self[key]:
+                    gid = GID(string=self[key])
+                    print "     %s:" % key
+                    gid.dump(8, dump_parents)
+                else:    
+                    print "     %s: %s" % (key, self[key])
+    
+    def getdict(self):
+        return dict(self)
+    
+
+class UserRecord(SfaRecord):
+
+    fields = {
+        'email': Parameter(str, 'email'),
+        'first_name': Parameter(str, 'First name'),
+        'last_name': Parameter(str, 'Last name'),
+        'phone': Parameter(str, 'Phone Number'),
+        'keys': Parameter(str, 'Public key'),
+        'slices': Parameter([str], 'List of slices this user belongs to'),
+        }
+    fields.update(SfaRecord.fields)
+    
+class SliceRecord(SfaRecord):
+    fields = {
+        'name': Parameter(str, 'Slice name'),
+        'url': Parameter(str, 'Slice url'),
+        'expires': Parameter(int, 'Date and time this slice exipres'),
+        'researcher': Parameter([str], 'List of users for this slice'),
+        'PI': Parameter([str], 'List of PIs responsible for this slice'),
+        'description': Parameter([str], 'Description of this slice'), 
+        }
+    fields.update(SfaRecord.fields)
+
+class NodeRecord(SfaRecord):
+    fields = {
+        'hostname': Parameter(str, 'This nodes dns name'),
+        'node_type': Parameter(str, 'Type of node this is'),
+        'node_type': Parameter(str, 'Type of node this is'),
+        'latitude': Parameter(str, 'latitude'),
+        'longitude': Parameter(str, 'longitude'),
+        }
+    fields.update(SfaRecord.fields)
+
+
+class AuthorityRecord(SfaRecord):
+    fields =  {
+        'name': Parameter(str, 'Name'),
+        'login_base': Parameter(str, 'login base'),
+        'enabled': Parameter(bool, 'Is this site enabled'),
+        'url': Parameter(str, 'URL'),
+        'nodes': Parameter([str], 'List of nodes at this site'),  
+        'operator': Parameter([str], 'List of operators'),
+        'researcher': Parameter([str], 'List of researchers'),
+        'PI': Parameter([str], 'List of Principal Investigators'),
+        }
+    fields.update(SfaRecord.fields)
+    
+
diff --git a/sfa/util/report.py b/sfa/util/report.py
new file mode 100644 (file)
index 0000000..9a882f0
--- /dev/null
@@ -0,0 +1,11 @@
+def trace(x, logger=None):
+    if logger:
+        logger.info(x)
+    else:
+        print x
+
+def error(x, logger=None):
+    if logger:
+        logger.error(x)
+    else:    
+        print x
diff --git a/sfa/util/row.py b/sfa/util/row.py
new file mode 100644 (file)
index 0000000..7334fcc
--- /dev/null
@@ -0,0 +1,57 @@
+
+class Row(dict):
+
+    # Set this to the name of the table that stores the row.
+    # e.g. table_name = "nodes"
+    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.
+    # e.g. primary_key="record_id"
+    primary_key = None
+
+    # Set this to the names of tables that reference this table's
+    # primary key.
+    join_tables = []
+
+    def validate(self):
+        """
+        Validates values. Will validate a value with a custom function
+        if a function named 'validate_[key]' exists.
+        """
+        # Warn about mandatory fields
+        # XX TODO: Support checking for mandatory fields later
+        #mandatory_fields = self.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 SfaInvalidArgument, 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 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 SfaInvalidArgument, "'%s' not in the future" % human
+
+        return human
diff --git a/sfa/util/rspec.py b/sfa/util/rspec.py
new file mode 100644 (file)
index 0000000..bd2cf8b
--- /dev/null
@@ -0,0 +1,420 @@
+### $Id: rspec.py 18557 2010-08-03 19:18:39Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/rspec.py $
+
+import sys
+import pprint
+import os
+import httplib
+from xml.dom import minidom
+from types import StringTypes, ListType
+from lxml import etree
+from StringIO import StringIO
+
+
+class RSpec:
+
+    def __init__(self, xml = None, xsd = None, NSURL = None):
+        '''
+        Class to manipulate RSpecs.  Reads and parses rspec xml into python dicts
+        and reads python dicts and writes rspec xml
+
+        self.xsd = # Schema.  Can be local or remote file.
+        self.NSURL = # If schema is remote, Name Space URL to query (full path minus filename)
+        self.rootNode = # root of the DOM
+        self.dict = # dict of the RSpec.
+        self.schemaDict = {} # dict of the Schema
+        '''
+        self.xsd = xsd
+        self.rootNode = None
+        self.dict = {}
+        self.schemaDict = {}
+        self.NSURL = NSURL 
+        if xml:
+            if type(xml) == file:
+                self.parseFile(xml)
+            if type(xml) in StringTypes:
+                self.parseString(xml)
+            self.dict = self.toDict() 
+        if xsd:
+            self._parseXSD(self.NSURL + self.xsd)
+
+
+    def _getText(self, nodelist):
+        rc = ""
+        for node in nodelist:
+            if node.nodeType == node.TEXT_NODE:
+                rc = rc + node.data
+        return rc
+  
+    # The rspec is comprised of 2 parts, and 1 reference:
+    # attributes/elements describe individual resources
+    # complexTypes are used to describe a set of attributes/elements
+    # complexTypes can include a reference to other complexTypes.
+  
+  
+    def _getName(self, node):
+        '''Gets name of node. If tag has no name, then return tag's localName'''
+        name = None
+        if not node.nodeName.startswith("#"):
+            if node.localName:
+                name = node.localName
+            elif node.attributes.has_key("name"):
+                name = node.attributes.get("name").value
+        return name     
+    # Attribute.  {name : nameofattribute, {items: values})
+    def _attributeDict(self, attributeDom):
+        '''Traverse single attribute node.  Create a dict {attributename : {name: value,}]}'''
+        node = {} # parsed dict
+        for attr in attributeDom.attributes.keys():
+            node[attr] = attributeDom.attributes.get(attr).value
+        return node
+  
+    def appendToDictOrCreate(self, dict, key, value):
+        if (dict.has_key(key)):
+            dict[key].append(value)
+        else:
+            dict[key]=[value]
+        return dict
+
+    def toGenDict(self, nodeDom=None, parentdict=None, siblingdict={}, parent=None):
+        """
+        convert an XML to a nested dict:
+          * Non-terminal nodes (elements with string children and attributes) are simple dictionaries
+          * Terminal nodes (the rest) are nested dictionaries
+        """
+
+        if (not nodeDom):
+            nodeDom=self.rootNode
+
+        curNodeName = nodeDom.localName
+
+        if (nodeDom.hasChildNodes()):
+            childdict={}
+            for attribute in nodeDom.attributes.keys():
+                childdict = self.appendToDictOrCreate(childdict, attribute, nodeDom.getAttribute(attribute))
+            for child in nodeDom.childNodes[:-1]:
+                if (child.nodeValue):
+                    siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, child.nodeValue)
+                else:
+                    childdict = self.toGenDict(child, None, childdict, curNodeName)
+
+            child = nodeDom.childNodes[-1]
+            if (child.nodeValue):
+                siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, child.nodeValue)
+                if (childdict):
+                    siblingdict = self.appendToDictOrCreate(siblingdict, curNodeName, childdict)
+            else:
+                siblingdict = self.toGenDict(child, siblingdict, childdict, curNodeName)
+        else:
+            childdict={}
+            for attribute in nodeDom.attributes.keys():
+                childdict = self.appendToDictOrCreate(childdict, attribute, nodeDom.getAttribute(attribute))
+
+            self.appendToDictOrCreate(siblingdict, curNodeName, childdict)
+            
+        if (parentdict is not None):
+            parentdict = self.appendToDictOrCreate(parentdict, parent, siblingdict)
+            return parentdict
+        else:
+            return siblingdict
+
+
+
+    def toDict(self, nodeDom = None):
+        """
+        convert this rspec to a dict and return it.
+        """
+        node = {}
+        if not nodeDom:
+             nodeDom = self.rootNode
+  
+        elementName = nodeDom.nodeName
+        if elementName and not elementName.startswith("#"):
+            # attributes have tags and values.  get {tag: value}, else {type: value}
+            node[elementName] = self._attributeDict(nodeDom)
+            # resolve the child nodes.
+            if nodeDom.hasChildNodes():
+                for child in nodeDom.childNodes:
+                    childName = self._getName(child)
+                    
+                    # skip null children
+                    if not childName: continue
+
+                    # initialize the possible array of children
+                    if not node[elementName].has_key(childName): node[elementName][childName] = []
+
+                    if isinstance(child, minidom.Text):
+                        # add if data is not empty
+                        if child.data.strip():
+                            node[elementName][childName].append(nextchild.data)
+                    elif child.hasChildNodes() and isinstance(child.childNodes[0], minidom.Text):
+                        for nextchild in child.childNodes:  
+                            node[elementName][childName].append(nextchild.data)
+                    else:
+                        childdict = self.toDict(child)
+                        for value in childdict.values():
+                            node[elementName][childName].append(value)
+
+        return node
+
+  
+    def toxml(self):
+        """
+        convert this rspec to an xml string and return it.
+        """
+        return self.rootNode.toxml()
+
+  
+    def toprettyxml(self):
+        """
+        print this rspec in xml in a pretty format.
+        """
+        return self.rootNode.toprettyxml()
+
+  
+    def __removeWhitespaceNodes(self, parent):
+        for child in list(parent.childNodes):
+            if child.nodeType == minidom.Node.TEXT_NODE and child.data.strip() == '':
+                parent.removeChild(child)
+            else:
+                self.__removeWhitespaceNodes(child)
+
+    def parseFile(self, filename):
+        """
+        read a local xml file and store it as a dom object.
+        """
+        dom = minidom.parse(filename)
+        self.__removeWhitespaceNodes(dom)
+        self.rootNode = dom.childNodes[0]
+
+
+    def parseString(self, xml):
+        """
+        read an xml string and store it as a dom object.
+        """
+        dom = minidom.parseString(xml)
+        self.__removeWhitespaceNodes(dom)
+        self.rootNode = dom.childNodes[0]
+
+    def _httpGetXSD(self, xsdURI):
+        # split the URI into relevant parts
+        host = xsdURI.split("/")[2]
+        if xsdURI.startswith("https"):
+            conn = httplib.HTTPSConnection(host,
+                httplib.HTTPSConnection.default_port)
+        elif xsdURI.startswith("http"):
+            conn = httplib.HTTPConnection(host,
+                httplib.HTTPConnection.default_port)
+        conn.request("GET", xsdURI)
+        # If we can't download the schema, raise an exception
+        r1 = conn.getresponse()
+        if r1.status != 200: 
+            raise Exception
+        return r1.read().replace('\n', '').replace('\t', '').strip() 
+
+
+    def _parseXSD(self, xsdURI):
+        """
+        Download XSD from URL, or if file, read local xsd file and set
+        schemaDict.
+        
+        Since the schema definiton is a global namespace shared by and
+        agreed upon by others, this should probably be a URL.  Check
+        for URL, download xsd, parse, or if local file, use that.
+        """
+        schemaDom = None
+        if xsdURI.startswith("http"):
+            try: 
+                schemaDom = minidom.parseString(self._httpGetXSD(xsdURI))
+            except Exception, e:
+                # logging.debug("%s: web file not found" % xsdURI)
+                # logging.debug("Using local file %s" % self.xsd")
+                print e
+                print "Can't find %s on the web. Continuing." % xsdURI
+        if not schemaDom:
+            if os.path.exists(xsdURI):
+                # logging.debug("using local copy.")
+                print "Using local %s" % xsdURI
+                schemaDom = minidom.parse(xsdURI)
+            else:
+                raise Exception("Can't find xsd locally")
+        self.schemaDict = self.toDict(schemaDom.childNodes[0])
+
+
+    def dict2dom(self, rdict, include_doc = False):
+        """
+        convert a dict object into a dom object.
+        """
+     
+        def elementNode(tagname, rd):
+            element = minidom.Element(tagname)
+            for key in rd.keys():
+                if isinstance(rd[key], StringTypes) or isinstance(rd[key], int):
+                    element.setAttribute(key, str(rd[key]))
+                elif isinstance(rd[key], dict):
+                    child = elementNode(key, rd[key])
+                    element.appendChild(child)
+                elif isinstance(rd[key], list):
+                    for item in rd[key]:
+                        if isinstance(item, dict):
+                            child = elementNode(key, item)
+                            element.appendChild(child)
+                        elif isinstance(item, StringTypes) or isinstance(item, int):
+                            child = minidom.Element(key)
+                            text = minidom.Text()
+                            text.data = item
+                            child.appendChild(text)
+                            element.appendChild(child) 
+            return element
+        
+        # Minidom does not allow documents to have more then one
+        # child, but elements may have many children. Because of
+        # this, the document's root node will be the first key/value
+        # pair in the dictionary.  
+        node = elementNode(rdict.keys()[0], rdict.values()[0])
+        if include_doc:
+            rootNode = minidom.Document()
+            rootNode.appendChild(node)
+        else:
+            rootNode = node
+        return rootNode
+
+    def parseDict(self, rdict, include_doc = True):
+        """
+        Convert a dictionary into a dom object and store it.
+        """
+        self.rootNode = self.dict2dom(rdict, include_doc).childNodes[0]
+    def getDictsByTagName(self, tagname, dom = None):
+        """
+        Search the dom for all elements with the specified tagname
+        and return them as a list of dicts
+        """
+        if not dom:
+            dom = self.rootNode
+        dicts = []
+        doms = dom.getElementsByTagName(tagname)
+        dictlist = [self.toDict(d) for d in doms]
+        for item in dictlist:
+            for value in item.values():
+                dicts.append(value)
+        return dicts
+
+    def getDictByTagNameValue(self, tagname, value, dom = None):
+        """
+        Search the dom for the first element with the specified tagname
+        and value and return it as a dict.
+        """
+        tempdict = {}
+        if not dom:
+            dom = self.rootNode
+        dicts = self.getDictsByTagName(tagname, dom)
+        
+        for rdict in dicts:
+            if rdict.has_key('name') and rdict['name'] in [value]:
+                return rdict
+              
+        return tempdict
+
+
+    def filter(self, tagname, attribute, blacklist = [], whitelist = [], dom = None):
+        """
+        Removes all elements where:
+        1. tagname matches the element tag
+        2. attribute matches the element attribte
+        3. attribute value is in valuelist  
+        """
+
+        tempdict = {}
+        if not dom:
+            dom = self.rootNode
+       
+        if dom.localName in [tagname] and dom.attributes.has_key(attribute):
+            if whitelist and dom.attributes.get(attribute).value not in whitelist:
+                dom.parentNode.removeChild(dom)
+            if blacklist and dom.attributes.get(attribute).value in blacklist:
+                dom.parentNode.removeChild(dom)
+           
+        if dom.hasChildNodes():
+            for child in dom.childNodes:
+                self.filter(tagname, attribute, blacklist, whitelist, child) 
+
+
+    def merge(self, rspecs, tagname, dom=None):
+        """
+        Merge this rspec with the requested rspec based on the specified 
+        starting tag name. The start tag (and all of its children) will be merged  
+        """
+        tempdict = {}
+        if not dom:
+            dom = self.rootNode
+
+        whitelist = []
+        blacklist = []
+            
+        if dom.localName in [tagname] and dom.attributes.has_key(attribute):
+            if whitelist and dom.attributes.get(attribute).value not in whitelist:
+                dom.parentNode.removeChild(dom)
+            if blacklist and dom.attributes.get(attribute).value in blacklist:
+                dom.parentNode.removeChild(dom)
+
+        if dom.hasChildNodes():
+            for child in dom.childNodes:
+                self.filter(tagname, attribute, blacklist, whitelist, child) 
+
+    def validateDicts(self):
+        types = {
+            'EInt' : int,
+            'EString' : str,
+            'EByteArray' : list,
+            'EBoolean' : bool,
+            'EFloat' : float,
+            'EDate' : date}
+
+
+    def pprint(self, r = None, depth = 0):
+        """
+        Pretty print the dict
+        """
+        line = ""
+        if r == None: r = self.dict
+        # Set the dept
+        for tab in range(0,depth): line += "    "
+        # check if it's nested
+        if type(r) == dict:
+            for i in r.keys():
+                print line + "%s:" % i
+                self.pprint(r[i], depth + 1)
+        elif type(r) in (tuple, list):
+            for j in r: self.pprint(j, depth + 1)
+        # not nested so just print.
+        else:
+            print line + "%s" %  r
+    
+
+
+class RecordSpec(RSpec):
+
+    root_tag = 'record'
+    def parseDict(self, rdict, include_doc = False):
+        """
+        Convert a dictionary into a dom object and store it.
+        """
+        self.rootNode = self.dict2dom(rdict, include_doc)
+
+    def dict2dom(self, rdict, include_doc = False):
+        record_dict = rdict
+        if not len(rdict.keys()) == 1:
+            record_dict = {self.root_tag : rdict}
+        return RSpec.dict2dom(self, record_dict, include_doc)
+
+        
+# vim:ts=4:expandtab
+    
diff --git a/sfa/util/rspecHelper.py b/sfa/util/rspecHelper.py
new file mode 100755 (executable)
index 0000000..8d00239
--- /dev/null
@@ -0,0 +1,326 @@
+#! /usr/bin/env python
+
+import sys
+from copy import deepcopy
+from lxml import etree
+from StringIO import StringIO
+from optparse import OptionParser
+
+
+def merge_rspecs(rspecs):
+    """
+    Merge merge a list of RSpecs into 1 RSpec, and return the result.
+    rspecs must be a valid RSpec string or list of RSpec strings.
+    """
+    if not rspecs or not isinstance(rspecs, list):
+        return rspecs
+
+    rspec = None
+    for tmp_rspec in rspecs:
+        try:
+            tree = etree.parse(StringIO(tmp_rspec))
+        except etree.XMLSyntaxError:
+            # consider failing silently here
+            message = str(agg_rspec) + ": " + str(sys.exc_info()[1])
+            raise InvalidRSpec(message)
+
+        root = tree.getroot()
+        if root.get("type") in ["SFA"]:
+            if rspec == None:
+                rspec = root
+            else:
+                for network in root.iterfind("./network"):
+                    rspec.append(deepcopy(network))
+                for request in root.iterfind("./request"):
+                    rspec.append(deepcopy(request))
+    return etree.tostring(rspec, xml_declaration=True, pretty_print=True)
+
+class RSpec:
+    def __init__(self, xml):
+        parser = etree.XMLParser(remove_blank_text=True)
+        tree = etree.parse(StringIO(xml), parser)
+        self.rspec = tree.getroot()
+
+    def get_node_element(self, hostname):
+        names = self.rspec.iterfind("./network/site/node/hostname")
+        for name in names:
+            if name.text == hostname:
+                return name.getparent()
+        return None
+        
+    def get_node_list(self):
+        result = self.rspec.xpath("./network/site/node/hostname/text()")
+        return result
+
+    def get_sliver_list(self):
+        result = self.rspec.xpath("./network/site/node[sliver]/hostname/text()")
+        return result
+
+    def add_sliver(self, hostname):
+        node = self.get_node_element(hostname)
+        etree.SubElement(node, "sliver")
+
+    def remove_sliver(self, hostname):
+        node = self.get_node_element(hostname)
+        node.remove(node.find("sliver"))
+
+    def attributes_list(self, elem):
+        opts = []
+        if elem is not None:
+            for e in elem:
+                opts.append((e.tag, e.text))
+        return opts
+
+    def get_default_sliver_attributes(self):
+        defaults = self.rspec.find(".//sliver_defaults")
+        return self.attributes_list(defaults)
+
+    def get_sliver_attributes(self, hostname):
+        node = self.get_node_element(hostname)
+        sliver = node.find("sliver")
+        return self.attributes_list(sliver)
+
+    def add_attribute(self, elem, name, value):
+        opt = etree.SubElement(elem, name)
+        opt.text = value
+
+    def add_default_sliver_attribute(self, name, value):
+        defaults = self.rspec.find(".//sliver_defaults")
+        if defaults is None:
+            defaults = etree.Element("sliver_defaults")
+            network = self.rspec.find(".//network")
+            network.insert(0, defaults)
+        self.add_attribute(defaults, name, value)
+
+    def add_sliver_attribute(self, hostname, name, value):
+        node = self.get_node_element(hostname)
+        sliver = node.find("sliver")
+        self.add_attribute(sliver, name, value)
+
+    def remove_attribute(self, elem, name, value):
+        if elem is not None:
+            opts = elem.iterfind(name)
+            if opts is not None:
+                for opt in opts:
+                    if opt.text == value:
+                        elem.remove(opt)
+
+    def remove_default_sliver_attribute(self, name, value):
+        defaults = self.rspec.find(".//sliver_defaults")
+        self.remove_attribute(defaults, name, value)
+
+    def remove_sliver_attribute(self, hostname, name, value):
+        node = self.get_node_element(hostname)
+        sliver = node.find("sliver")
+        self.remove_attribute(sliver, name, value)
+
+    def get_site_nodes(self, siteid):
+        query = './/site[@id="%s"]/node/hostname/text()' % siteid
+        result = self.rspec.xpath(query)
+        return result
+        
+    def get_link_list(self):
+        linklist = []
+        links = self.rspec.iterfind(".//link")
+        for link in links:
+            (end1, end2) = link.get("endpoints").split()
+            name = link.find("description")
+            linklist.append((name.text, 
+                             self.get_site_nodes(end1), 
+                             self.get_site_nodes(end2)))
+        return linklist
+
+    def get_vlink_list(self):
+        vlinklist = []
+        vlinks = self.rspec.iterfind(".//vlink")
+        for vlink in vlinks:
+            endpoints = vlink.get("endpoints")
+            (end1, end2) = endpoints.split()
+            query = './/node[@id="%s"]/hostname/text()'
+            node1 = self.rspec.xpath(query % end1)[0]
+            node2 = self.rspec.xpath(query % end2)[0]
+            desc = "%s <--> %s" % (node1, node2) 
+            kbps = vlink.find("kbps")
+            vlinklist.append((endpoints, desc, kbps.text))
+        return vlinklist
+
+    def query_links(self, fromnode, tonode):
+        fromsite = fromnode.getparent()
+        tosite = tonode.getparent()
+        fromid = fromsite.get("id")
+        toid = tosite.get("id")
+
+        query = ".//link[@endpoints = '%s %s']" % (fromid, toid)
+        results = self.rspec.xpath(query)
+        if results == None:
+            query = ".//link[@endpoints = '%s %s']" % (toid, fromid)
+            results = self.rspec.xpath(query)
+        return results
+
+    def query_vlinks(self, endpoints):
+        query = ".//vlink[@endpoints = '%s']" % endpoints
+        results = self.rspec.xpath(query)
+        return results
+            
+    
+    def add_vlink(self, fromhost, tohost, kbps):
+        fromnode = self.get_node_element(fromhost)
+        tonode = self.get_node_element(tohost)
+        links = self.query_links(fromnode, tonode)
+
+        for link in links:
+            vlink = etree.SubElement(link, "vlink")
+            fromid = fromnode.get("id")
+            toid = tonode.get("id")
+            vlink.set("endpoints", "%s %s" % (fromid, toid))
+            self.add_attribute(vlink, "kbps", kbps)
+        
+
+    def remove_vlink(self, endpoints):
+        vlinks = self.query_vlinks(endpoints)
+        for vlink in vlinks:
+            vlink.getparent().remove(vlink)
+
+    def toxml(self):
+        return etree.tostring(self.rspec, pretty_print=True, 
+                              xml_declaration=True)
+
+    def __str__(self):
+        return self.toxml()
+
+    def save(self, filename):
+        f = open(filename, "w")
+        f.write(self.toxml())
+        f.close()
+
+
+class Commands:
+    def __init__(self, usage, description, epilog=None):
+        self.parser = OptionParser(usage=usage, description=description,
+                                   epilog=epilog)
+        self.parser.add_option("-i", "", dest="infile", metavar="FILE",
+                               help="read RSpec from FILE (default is stdin)")
+        self.parser.add_option("-o", "", dest="outfile", metavar="FILE",
+                               help="write output to FILE (default is stdout)")
+        self.nodefile = False
+        self.attributes = {}
+
+    def add_nodefile_option(self):
+        self.nodefile = True
+        self.parser.add_option("-n", "", dest="nodefile", 
+                               metavar="FILE",
+                               help="read node list from FILE"),
+
+    def add_show_attributes_option(self):
+        self.parser.add_option("-s", "--show-attributes", action="store_true", 
+                               dest="showatt", default=False, 
+                               help="show sliver attributes")
+
+    def add_attribute_options(self):
+        self.parser.add_option("", "--capabilities", action="append",
+                               metavar="<cap1,cap2,...>",
+                               help="Vserver bcapabilities")
+        self.parser.add_option("", "--codemux", action="append",
+                               metavar="<host,local-port>",
+                               help="Demux HTTP between slices using " +
+                               "localhost ports")
+        self.parser.add_option("", "--cpu-pct", action="append",
+                               metavar="<num>", 
+                               help="Reserved CPU percent (e.g., 25)")
+        self.parser.add_option("", "--cpu-share", action="append",
+                               metavar="<num>", 
+                               help="Number of CPU shares (e.g., 5)")
+        self.parser.add_option("", "--delegations", 
+                               metavar="<slice1,slice2,...>", action="append",
+                               help="List of slices with delegation authority")
+        self.parser.add_option("", "--disk-max", 
+                               metavar="<num>", action="append",
+                               help="Disk quota (1k disk blocks)")
+        self.parser.add_option("", "--initscript", 
+                               metavar="<name>", action="append",
+                               help="Slice initialization script (e.g., stork)")
+        self.parser.add_option("", "--ip-addresses", action="append",
+                               metavar="<IP addr>", 
+                               help="Add an IP address to a sliver")
+        self.parser.add_option("", "--net-i2-max-kbyte", 
+                               metavar="<KBytes>", action="append",
+                               help="Maximum daily network Tx limit " +
+                               "to I2 hosts.")
+        self.parser.add_option("", "--net-i2-max-rate", 
+                               metavar="<Kbps>", action="append",
+                               help="Maximum bandwidth over I2 routes")
+        self.parser.add_option("", "--net-i2-min-rate", 
+                               metavar="<Kbps>", action="append",
+                               help="Minimum bandwidth over I2 routes")
+        self.parser.add_option("", "--net-i2-share", 
+                               metavar="<num>", action="append",
+                               help="Number of bandwidth shares over I2 routes")
+        self.parser.add_option("", "--net-i2-thresh-kbyte", 
+                               metavar="<KBytes>", action="append",
+                               help="Limit sent to I2 hosts before warning, " +
+                               "throttling")
+        self.parser.add_option("", "--net-max-kbyte", 
+                               metavar="<KBytes>", action="append",
+                               help="Maximum daily network Tx limit " +
+                               "to non-I2 hosts.")
+        self.parser.add_option("", "--net-max-rate", 
+                               metavar="<Kbps>", action="append",
+                               help="Maximum bandwidth over non-I2 routes")
+        self.parser.add_option("", "--net-min-rate", 
+                               metavar="<Kbps>", action="append",
+                               help="Minimum bandwidth over non-I2 routes")
+        self.parser.add_option("", "--net-share", 
+                               metavar="<num>", action="append",
+                               help="Number of bandwidth shares over non-I2 " +
+                               "routes")
+        self.parser.add_option("", "--net-thresh-kbyte", 
+                               metavar="<KBytes>", action="append",
+                               help="Limit sent to non-I2 hosts before " +
+                               "warning, throttling")
+        self.parser.add_option("", "--vsys", 
+                               metavar="<name>", action="append",
+                               help="Vsys script (e.g., fd_fusemount)")
+        self.parser.add_option("", "--vsys-vnet", 
+                               metavar="<IP network>", action="append",
+                               help="Allocate a virtual private network")
+
+    def get_attribute_dict(self):
+        attrlist = ['capabilities','codemux','cpu_pct','cpu_share',
+                    'delegations','disk_max','initscript','ip_addresses',
+                    'net_i2_max_kbyte','net_i2_max_rate','net_i2_min_rate',
+                    'net_i2_share','net_i2_thresh_kbyte',
+                    'net_max_kbyte','net_max_rate','net_min_rate',
+                    'net_share','net_thresh_kbyte',
+                    'vsys','vsys_vnet']
+        attrdict = {}
+        for attr in attrlist:
+            value = getattr(self.opts, attr, None)
+            if value is not None:
+                attrdict[attr] = value
+        return attrdict
+
+    def prep(self):
+        (self.opts, self.args) = self.parser.parse_args()
+
+        if self.opts.infile:
+            sys.stdin = open(self.opts.infile, "r")
+        xml = sys.stdin.read()
+        self.rspec = RSpec(xml)
+            
+        if self.nodefile:
+            if self.opts.nodefile:
+                f = open(self.opts.nodefile, "r")
+                self.nodes = f.read().split()
+                f.close()
+            else:
+                self.nodes = self.args
+
+        if self.opts.outfile:
+            sys.stdout = open(self.opts.outfile, "w")
+
+
+
+
+
+
+
diff --git a/sfa/util/server.py b/sfa/util/server.py
new file mode 100644 (file)
index 0000000..4f26186
--- /dev/null
@@ -0,0 +1,280 @@
+##
+# This module implements a general-purpose server layer for sfa.
+# The same basic server should be usable on the registry, component, or
+# other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+##
+
+### $Id: server.py 18501 2010-07-09 20:20:48Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/server.py $
+
+import sys
+import traceback
+import threading
+import socket, os
+import SocketServer
+import BaseHTTPServer
+import SimpleHTTPServer
+import SimpleXMLRPCServer
+from OpenSSL import SSL
+from Queue import Queue
+from sfa.trust.certificate import Keypair, Certificate
+from sfa.trust.credential import *
+from sfa.util.faults import *
+from sfa.plc.api import SfaAPI
+from sfa.util.cache import Cache 
+from sfa.util.debug import log
+from sfa.util.sfalogging import logger
+##
+# Verification callback for pyOpenSSL. We do our own checking of keys because
+# we have our own authentication spec. Thus we disable several of the normal
+# prohibitions that OpenSSL places on certificates
+
+def verify_callback(conn, x509, err, depth, preverify):
+    # if the cert has been preverified, then it is ok
+    if preverify:
+       #print "  preverified"
+       return 1
+
+
+    # the certificate verification done by openssl checks a number of things
+    # that we aren't interested in, so we look out for those error messages
+    # and ignore them
+
+    # XXX SMBAKER: I don't know what this error is, but it's being returned
+    # by newer pl nodes.
+    if err == 9:
+       #print "  X509_V_ERR_CERT_NOT_YET_VALID"
+       return 1
+
+    # allow self-signed certificates
+    if err == 18:
+       #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+       return 1
+
+    # allow certs that don't have an issuer
+    if err == 20:
+       #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+       return 1
+
+    # allow chained certs with self-signed roots
+    if err == 19:
+        return 1
+    
+    # allow certs that are untrusted
+    if err == 21:
+       #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+       return 1
+
+    # allow certs that are untrusted
+    if err == 27:
+       #print "  X509_V_ERR_CERT_UNTRUSTED"
+       return 1
+
+    print "  error", err, "in verify_callback"
+
+    return 0
+
+##
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+    """Secure XML-RPC request handler class.
+
+    It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
+    """
+    def setup(self):
+        self.connection = self.request
+        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+    def do_POST(self):
+        """Handles the HTTPS POST request.
+
+        It was copied out from SimpleXMLRPCServer.py and modified to shutdown 
+        the socket cleanly.
+        """
+        try:
+            peer_cert = Certificate()
+            peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
+            self.api = SfaAPI(peer_cert = peer_cert, 
+                              interface = self.server.interface, 
+                              key_file = self.server.key_file, 
+                              cert_file = self.server.cert_file,
+                              cache = self.cache)
+            # get arguments
+            request = self.rfile.read(int(self.headers["content-length"]))
+            remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
+            self.api.remote_addr = remote_addr            
+            response = self.api.handle(remote_addr, request, self.server.method_map)
+        except Exception, fault:
+            # This should only happen if the module is buggy
+            # internal error, report as HTTP server error
+            traceback.print_exc()
+            response = self.api.prepare_response(fault)
+            #self.send_response(500)
+            #self.end_headers()
+       
+        # got a valid response
+        self.send_response(200)
+        self.send_header("Content-type", "text/xml")
+        self.send_header("Content-length", str(len(response)))
+        self.end_headers()
+        self.wfile.write(response)
+
+        # shut down the connection
+        self.wfile.flush()
+        self.connection.shutdown() # Modified here!
+
+##
+# Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
+class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+    def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
+        """Secure XML-RPC server.
+
+        It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
+        """
+        self.logRequests = logRequests
+        self.interface = None
+        self.key_file = key_file
+        self.cert_file = cert_file
+        self.method_map = {}
+        # add cache to the request handler
+        HandlerClass.cache = Cache()
+        #for compatibility with python 2.4 (centos53)
+        if sys.version_info < (2, 5):
+            SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
+        else:
+           SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
+        SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
+        ctx = SSL.Context(SSL.SSLv23_METHOD)
+        ctx.use_privatekey_file(key_file)        
+        ctx.use_certificate_file(cert_file)
+        # If you wanted to verify certs against known CAs.. this is how you would do it
+        #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
+        ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
+        ctx.set_verify_depth(5)
+        ctx.set_app_data(self)
+        self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
+                                                        self.socket_type))
+        self.server_bind()
+        self.server_activate()
+
+    # _dispatch
+    #
+    # Convert an exception on the server to a full stack trace and send it to
+    # the client.
+
+    def _dispatch(self, method, params):
+        try:
+            return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
+        except:
+            # can't use format_exc() as it is not available in jython yet
+            # (evein in trunk).
+            type, value, tb = sys.exc_info()
+            raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
+
+## From Active State code: http://code.activestate.com/recipes/574454/
+# This is intended as a drop-in replacement for the ThreadingMixIn class in 
+# module SocketServer of the standard lib. Instead of spawning a new thread 
+# for each request, requests are processed by of pool of reusable threads.
+class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
+    """
+    use a thread pool instead of a new thread on every request
+    """
+    # XX TODO: Make this configurable
+    # config = Config()
+    # numThreads = config.SFA_SERVER_NUM_THREADS
+    numThreads = 25
+    allow_reuse_address = True  # seems to fix socket.error on server restart
+
+    def serve_forever(self):
+        """
+        Handle one request at a time until doomsday.
+        """
+        # set up the threadpool
+        self.requests = Queue()
+
+        for x in range(self.numThreads):
+            t = threading.Thread(target = self.process_request_thread)
+            t.setDaemon(1)
+            t.start()
+
+        # server main loop
+        while True:
+            self.handle_request()
+            
+        self.server_close()
+
+    
+    def process_request_thread(self):
+        """
+        obtain request from queue instead of directly from server socket
+        """
+        while True:
+            SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
+
+    
+    def handle_request(self):
+        """
+        simply collect requests and put them on the queue for the workers.
+        """
+        try:
+            request, client_address = self.get_request()
+        except socket.error:
+            return
+        if self.verify_request(request, client_address):
+            self.requests.put((request, client_address))
+
+class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
+    pass
+##
+# Implements an HTTPS XML-RPC server. Generally it is expected that SFA
+# functions will take a credential string, which is passed to
+# decode_authentication. Decode_authentication() will verify the validity of
+# the credential, and verify that the user is using the key that matches the
+# GID supplied in the credential.
+
+class SfaServer(threading.Thread):
+
+    ##
+    # Create a new SfaServer object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key 
+    #   (could be a GID file)
+
+    def __init__(self, ip, port, key_file, cert_file):
+        threading.Thread.__init__(self)
+        self.key = Keypair(filename = key_file)
+        self.cert = Certificate(filename = cert_file)
+        #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+        self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+        self.trusted_cert_list = None
+        self.register_functions()
+
+
+    ##
+    # Register functions that will be served by the XMLRPC server. This
+    # function should be overridden by each descendant class.
+
+    def register_functions(self):
+        self.server.register_function(self.noop)
+
+    ##
+    # Sample no-op server function. The no-op function decodes the credential
+    # that was passed to it.
+
+    def noop(self, cred, anything):
+        self.decode_authentication(cred)
+        return anything
+
+    ##
+    # Execute the server, serving requests forever. 
+
+    def run(self):
+        self.server.serve_forever()
+
+
diff --git a/sfa/util/sfalogging.py b/sfa/util/sfalogging.py
new file mode 100644 (file)
index 0000000..0e17ccb
--- /dev/null
@@ -0,0 +1,22 @@
+import logging
+import os
+
+#SFA access log initialization
+TMPDIR = os.getenv("TMPDIR", "/tmp")
+SFA_HTTPD_ACCESS_LOGFILE = TMPDIR + "/" + 'sfa_httpd_access.log'
+SFA_ACCESS_LOGFILE='/var/log/sfa_access.log'
+logger=logging.getLogger()
+logger.setLevel(logging.INFO)
+
+try:
+    logfile=logging.FileHandler(SFA_ACCESS_LOGFILE)
+except IOError:
+    # This is usually a permissions error becaue the file is
+    # owned by root, but httpd is trying to access it.
+    logfile=logging.FileHandler(SFA_HTTPD_ACCESS_LOGFILE)
+    
+formatter = logging.Formatter("%(asctime)s - %(message)s")
+logfile.setFormatter(formatter)
+logger.addHandler(logfile)
+def get_sfa_logger():
+    return logger
diff --git a/sfa/util/sfatablesRuntime.py b/sfa/util/sfatablesRuntime.py
new file mode 100644 (file)
index 0000000..14ec6f7
--- /dev/null
@@ -0,0 +1,38 @@
+from sfatables.runtime import SFATablesRules
+
+def fetch_context(slice_hrn, user_hrn, contexts):
+    """
+    Returns the request context required by sfatables. At some point, this
+    mechanism should be changed to refer to "contexts", which is the
+    information that sfatables is requesting. But for now, we just return
+    the basic information needed in a dict.
+    """
+    slice_hrn = urn_to_hrn(slice_xrn)[0]
+    user_hrn = urn_to_hrn(user_xrn)[0]
+    base_context = {'sfa':{'user':{'hrn':user_hrn}, 'slice':{'hrn':slice_hrn}}}
+    return base_context
+
+def run_sfatables(chain, hrn, origin_hrn, rspec, context_callback = None ):
+    """
+    Run the rspec through sfatables
+    @param chain Name of rule chain
+    @param hrn Object's hrn
+    @param origin_hrn Original caller's hrn
+    @param rspec Incoming rspec
+    @param context_callback Callback used to generate the request context  
+
+    @return rspec
+    """
+    if not context_callback:
+        context_callback = fetch_context
+
+    chain = chain.upper()
+    rules = SFATablesRules(chain)
+    if rules.sorted_rule_list:
+        contexts = rules.contexts
+        request_context = context_callback(hrn, origin_hrn, contexts)
+        rules.set_context(request_context)
+        newrspec = rules.apply(rspec)
+    else:
+        newrspec = rspec
+    return newrspec
diff --git a/sfa/util/sfaticket.py b/sfa/util/sfaticket.py
new file mode 100644 (file)
index 0000000..670ad14
--- /dev/null
@@ -0,0 +1,130 @@
+#
+# implements SFA tickets
+#
+
+### $Id: sfaticket.py 18530 2010-07-23 21:27:18Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/sfaticket.py $
+
+import xmlrpclib
+
+from sfa.trust.certificate import Certificate
+from sfa.trust.rights import *
+from sfa.trust.gid import *
+
+# Ticket is tuple:
+#   (gidCaller, gidObject, attributes, rspec, delegate)
+#
+#    gidCaller = GID of the caller performing the operation
+#    gidObject = GID of the slice
+#    attributes = slice attributes (keys, vref, instantiation, etc)
+#    rspec = resources
+
+class SfaTicket(Certificate):
+    gidCaller = None
+    gidObject = None
+    attributes = {}
+    rspec = {}
+    delegate = False
+
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        Certificate.__init__(self, create, subject, string, filename)
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    def set_attributes(self, gid):
+        self.attributes = gid
+
+    def get_attributes(self):
+        if not self.attributes:
+            self.decode()
+        return self.attributes
+
+    def set_rspec(self, gid):
+        self.rspec = gid
+
+    def get_rspec(self):
+        if not self.rspec:
+            self.decode()
+        return self.rspec
+
+    def set_delegate(self, delegate):
+        self.delegate = delegate
+
+    def get_delegate(self):
+        if not self.delegate:
+            self.decode()
+        return self.delegate
+
+    def encode(self):
+        dict = {"gidCaller": None,
+                "gidObject": None,
+                "attributes": self.attributes,
+                "rspec": self.rspec,
+                "delegate": self.delegate}
+        if self.gidCaller:
+            dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True)
+        if self.gidObject:
+            dict["gidObject"] = self.gidObject.save_to_string(save_parents=True)
+        str = "URI:" + xmlrpclib.dumps((dict,), allow_none=True)
+        self.set_data(str)
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_data()[4:])[0][0]
+        else:
+            dict = {}
+
+        self.attributes = dict.get("attributes", {})
+        self.rspec = dict.get("rspec", {})
+        self.delegate = dict.get("delegate", False)
+
+        gidCallerStr = dict.get("gidCaller", None)
+        if gidCallerStr:
+            self.gidCaller = GID(string=gidCallerStr)
+        else:
+            self.gidCaller = None
+
+        gidObjectStr = dict.get("gidObject", None)
+        if gidObjectStr:
+            self.gidObject = GID(string=gidObjectStr)
+        else:
+            self.gidObject = None
+
+    def dump(self, dump_parents=False):
+        print "TICKET", self.get_subject()
+
+        print "  gidCaller:"
+        gidCaller = self.get_gid_caller()
+        if gidCaller:
+            gidCaller.dump(8, dump_parents)
+
+        print "  gidObject:"
+        gidObject = self.get_gid_object()
+        if gidObject:
+            gidObject.dump(8, dump_parents)
+
+        print "  attributes:"
+        for attrname in self.get_attributes().keys():
+            print "        ", attrname, self.get_attributes()[attrname]
+
+        print "       rspec:"
+        print "        ", self.get_rspec()
+
+        if self.parent and dump_parents:
+           print "PARENT",
+           self.parent.dump(dump_parents)
diff --git a/sfa/util/soapprotocol.py b/sfa/util/soapprotocol.py
new file mode 100644 (file)
index 0000000..8d9b3d1
--- /dev/null
@@ -0,0 +1,36 @@
+# SOAP-specific code for SFA Client
+
+import pdb
+from ZSI.client import Binding
+from httplib import HTTPSConnection
+
+def xmlrpc_like_callable (soap_callable, *x):
+    soap_result = soap_callable(*x)
+    xmlrpc_result = soap_result['Result']
+    return xmlrpc_result
+        
+class SFACallable:
+     def __init__(self, soap_callable):
+        self.soap_callable = soap_callable
+
+     def __call__(self, *args):
+         outer_result = self.soap_callable(*args)
+         return outer_result['Result']
+
+
+class SFASoapBinding(Binding):
+    def __getattr__(self, attr):
+        soap_callable = Binding.__getattr__(self, attr)
+        return SFACallable(soap_callable)
+
+
+def get_server(url, key_file, cert_file):
+    auth = {
+        'transport' : HTTPSConnection,
+        'transdict' : {'cert_file' : cert_file, 
+                       'key_file' : key_file
+                      },
+     }
+
+    return SFASoapBinding(url=url, **auth)
+
diff --git a/sfa/util/specdict.py b/sfa/util/specdict.py
new file mode 100644 (file)
index 0000000..ed1bb4c
--- /dev/null
@@ -0,0 +1,134 @@
+##
+# SpecDict
+#
+# SpecDict defines a means for converting a dictionary with plc specific keys
+# to a dict with rspec specific keys. 
+#
+# SpecDict.fields dict defines all the rspec specific attribute and their 
+# expected type. 
+# 
+# SpecDict.plc_fields defines a one to one mapping of plc attribute to rspec 
+# attribute
+
+### $Id: specdict.py 15485 2009-10-26 18:31:09Z sapanb $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/specdict.py $
+
+from types import StringTypes, ListType
+
+class SpecDict(dict):
+    """
+    Base class of SpecDict objects. 
+    """
+    fields = {}
+    plc_fields = {}
+    type = None
+        
+    def __init__(self, spec_dict):
+        # convert plc dict and initialize self
+        sdict = self.plcToSpec(spec_dict)
+        dict.__init__(self, sdict)
+
+
+    def plcToSpec(self, spec_dict):
+        """
+        Defines how to convert a plc dict to rspec dict
+        """
+        spec = {}
+        for field in self.fields:
+            value = ""
+            expected = self.fields[field]
+            if isinstance(expected, StringTypes):
+                if self.plc_fields.has_key(field):
+                    plc_field = self.plc_fields[field]
+                    if spec_dict.has_key(plc_field):
+                        value = spec_dict[plc_field]
+            elif isinstance(expected, ListType):
+                expected = expected[0]
+                if self.plc_fields.has_key(field):
+                    plc_field = self.plc_fields[field]
+                    if spec_dict.has_key(plc_field):
+                        value = [expected(value) for value in spec_dict[plc_field]]
+            spec[field] = value
+        return {self.type: spec}
+
+#
+# fields = { geni_field:  type.  Could be class for nested classes, otherwise prob str}
+# plc_fields = {geni_field : plc_field}
+#
+class IfSpecDict(SpecDict):
+    type = 'IfSpec'
+    fields = {'name': '',
+              'addr': '',
+              'type': '',
+              'init_params': '',
+              'min_rate': '',
+              'max_rate': '',
+              'max_kbyte': '',
+              'ip_spoof': ''}
+    plc_fields = {'name': 'is_primary', # XXX needs munging to return name instead of True or False
+                 'addr': 'ip',
+                 'type': 'type'}
+class LinkSpecDict(SpecDict):
+    type = 'LinkSpec'
+    fields = {'min_alloc': '', 
+              'max_alloc': '', 
+              'type': '', 
+              'start_time': '', 
+              'bw': '', 
+              'duration': '', 
+              'init_params': '',
+              'endpoints': [IfSpecDict]}
+    plc_fields = {'min_alloc': 'min_alloc',
+              'max_alloc': 'max_alloc', 
+              'type': 'type', 
+              'start_time': 'start_time', 
+              'bw': 'bw', 
+              'duration': 'duration', 
+              'init_params': 'init_params',
+              'endpoints': 'endpoints'}
+  
+            
+class NodeSpecDict(SpecDict):
+    type = 'NodeSpec'
+    fields = {'name': '',
+              'type': '',
+              'init_params': '',
+              'cpu_min': '',
+              'cpu_share': '',
+              'cpu_pct': '',
+              'disk_max': '',
+              'start_time': '',
+              'duration': '',
+              'net_if': [IfSpecDict]}
+
+    plc_fields = {'name': 'hostname',
+                  'net_if': 'interfaces'}  
+
+class NetSpecDict(SpecDict):
+    type = 'NetSpec'
+    fields = {'name': '',
+              'start_time': '',
+              'duration': '',
+              'nodes': [NodeSpecDict],
+              'links': [LinkSpecDict],
+             }
+    plc_fields = {'name': 'name',
+                  'start_time': 'start_time',
+                  'duration': 'duration',
+                  'nodes': 'nodes',
+                  'links': 'links'}
+
+class RSpecDict(SpecDict):
+    type = 'RSpec'
+    fields = {'start_time': '',
+              'duration': '',
+              'networks': [NetSpecDict]
+             }
+    plc_fields = {'networks': 'networks',
+                  'start_time': 'start_tim',
+                  'duration': 'duration'
+                 }
+
+# vim:ts=4:expandtab
diff --git a/sfa/util/storage.py b/sfa/util/storage.py
new file mode 100644 (file)
index 0000000..6ed30d3
--- /dev/null
@@ -0,0 +1,73 @@
+### $Id: storage.py 14252 2009-07-03 14:40:59Z thierry $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/storage.py $
+
+import os
+
+from sfa.util.rspec import RecordSpec
+
+class SimpleStorage(dict):
+    """
+    Handles storing and loading python dictionaries. The storage file created
+    is a string representation of the native python dictionary.
+    """
+    db_filename = None
+    type = 'dict'
+    
+    def __init__(self, db_filename, db = {}):
+
+        dict.__init__(self, db)
+        self.db_filename = db_filename
+    
+    def load(self):
+        if os.path.exists(self.db_filename) and os.path.isfile(self.db_filename):
+            db_file = open(self.db_filename, 'r')
+            dict.__init__(self, eval(db_file.read()))
+        elif os.path.exists(self.db_filename) and not os.path.isfile(self.db_filename):
+            raise IOError, '%s exists but is not a file. please remove it and try again' \
+                           % self.db_filename
+        else:
+            self.write()
+            self.load()
+    def write(self):
+        db_file = open(self.db_filename, 'w')  
+        db_file.write(str(self))
+        db_file.close()
+    
+    def sync(self):
+        self.write()
+
+class XmlStorage(SimpleStorage):
+    """
+    Handles storing and loading python dictionaries. The storage file created
+    is a xml representation of the python dictionary.
+    """ 
+    db_filename = None
+    type = 'xml'
+
+    def load(self):
+        """
+        Parse an xml file and store it as a dict
+        """ 
+        data = RecordSpec()
+        if os.path.exists(self.db_filename) and os.path.isfile(self.db_filename):
+            data.parseFile(self.db_filename)
+            dict.__init__(self, data.toDict())
+        elif os.path.exists(self.db_filename) and not os.path.isfile(self.db_filename):
+            raise IOError, '%s exists but is not a file. please remove it and try again' \
+                           % self.db_filename
+        else:
+            self.write()
+            self.load()
+
+    def write(self):
+        data = RecordSpec()
+        data.parseDict(self)
+        db_file = open(self.db_filename, 'w')
+        db_file.write(data.toprettyxml())
+        db_file.close()
+
+    def sync(self):
+        self.write()
+
+                
diff --git a/sfa/util/table.py b/sfa/util/table.py
new file mode 100644 (file)
index 0000000..de9fe2e
--- /dev/null
@@ -0,0 +1,196 @@
+### $Id: table.py 18182 2010-06-02 20:42:30Z tmack $
+### $URL: http://svn.planet-lab.org/svn/sfa/trunk/sfa/util/table.py $
+#
+# implements support for SFA records stored in db tables
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+
+import report
+import pgdb
+
+from sfa.util.PostgreSQL import *
+from sfa.trust.gid import *
+from sfa.util.record import *
+from sfa.util.debug import *
+from sfa.util.config import *
+from sfa.util.filter import *
+
+class SfaTable(list):
+
+    SFA_TABLE_PREFIX = "sfa"
+
+    def __init__(self, record_filter = None):
+
+        # pgsql doesn't like table names with "." in them, to replace it with "$"
+        self.tablename = SfaTable.SFA_TABLE_PREFIX
+        self.config = Config()
+        self.db = PostgreSQL(self.config)
+
+        if record_filter:
+            records = self.find(record_filter)
+            for record in records:
+                self.append(record)             
+
+    def exists(self):
+        sql = "SELECT * from pg_tables"
+        tables = self.db.selectall(sql)
+        tables = filter(lambda row: row['tablename'].startswith(self.SFA_TABLE_PREFIX), tables)
+        if tables:
+            return True
+        return False
+    def db_fields(self, obj=None):
+        
+        db_fields = self.db.fields(self.SFA_TABLE_PREFIX)
+        return dict( [ (key,value) for (key, value) in obj.items() \
+                        if key in db_fields and
+                        self.is_writable(key, value, SfaRecord.fields)] )      
+
+    @staticmethod
+    def is_writable (key,value,dict):
+        # if not mentioned, assume it's writable (e.g. deleted ...)
+        if key not in dict: return True
+        # if mentioned but not linked to a Parameter object, idem
+        if not isinstance(dict[key], Parameter): return True
+        # if not marked ro, it's writable
+        if not dict[key].ro: return True
+
+        return False
+
+
+    def create(self):
+        
+        querystr = "CREATE TABLE " + self.tablename + " ( \
+                record_id serial PRIMARY KEY , \
+                hrn text NOT NULL, \
+                authority text NOT NULL, \
+                peer_authority text, \
+                gid text, \
+                type text NOT NULL, \
+                pointer integer, \
+                date_created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+                last_updated timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP);"
+        template = "CREATE INDEX %s_%s_idx ON %s (%s);"
+        indexes = [template % ( self.tablename, field, self.tablename, field) \
+                   for field in ['hrn', 'type', 'authority', 'peer_authority', 'pointer']]
+        # IF EXISTS doenst exist in postgres < 8.2
+        try:
+            self.db.do('DROP TABLE IF EXISTS ' + self.tablename)
+        except:
+            try:
+                self.db.do('DROP TABLE' + self.tablename)
+            except:
+                pass
+         
+        self.db.do(querystr)
+        for index in indexes:
+            self.db.do(index)
+        
+        self.db.commit()
+    
+    def remove(self, record):
+        params = {'record_id': record['record_id']}
+        template = "DELETE FROM %s " % self.tablename
+        sql = template + "WHERE record_id = %(record_id)s"
+        self.db.do(sql, params)
+        
+        # if this is a site, remove all records where 'authority' == the 
+        # site's hrn
+        if record['type'] == 'authority':
+            params = {'authority': record['hrn']}
+            sql = template + "WHERE authority = %(authority)s"
+            self.db.do(sql, params)
+        self.db.commit() 
+
+    def insert(self, record):
+        db_fields = self.db_fields(record)
+        keys = db_fields.keys()
+        values = [self.db.param(key, value) for (key, value) in db_fields.items()]
+        query_str = "INSERT INTO " + self.tablename + \
+                       "(" + ",".join(keys) + ") " + \
+                       "VALUES(" + ",".join(values) + ")"
+        self.db.do(query_str, db_fields)
+        self.db.commit()
+        result = self.find({'hrn': record['hrn'], 'type': record['type'], 'peer_authority': record['peer_authority']})
+        if not result:
+            record_id = None
+        elif isinstance(result, list):
+            record_id = result[0]['record_id']
+        else:
+            record_id = result['record_id']
+
+        return record_id
+
+    def update(self, record):
+        db_fields = self.db_fields(record)
+        keys = db_fields.keys()
+        values = [self.db.param(key, value) for (key, value) in db_fields.items()]
+        columns = ["%s = %s" % (key, value) for (key, value) in zip(keys, values)]
+        query_str = "UPDATE %s SET %s WHERE record_id = %s" % \
+                    (self.tablename, ", ".join(columns), record['record_id'])
+        self.db.do(query_str, db_fields)
+        self.db.commit()
+
+    def quote_string(self, value):
+        return str(self.db.quote(value))
+
+    def quote(self, value):
+        return self.db.quote(value)
+
+    def find(self, record_filter = None, columns=None):
+        if not columns:
+            columns = "*"
+        else:
+            columns = ",".join(columns)
+        sql = "SELECT %s FROM %s WHERE True " % (columns, self.tablename)
+        
+        if isinstance(record_filter, (list, tuple, set)):
+            ints = filter(lambda x: isinstance(x, (int, long)), record_filter)
+            strs = filter(lambda x: isinstance(x, StringTypes), record_filter)
+            record_filter = Filter(SfaRecord.all_fields, {'record_id': ints, 'hrn': strs})
+            sql += "AND (%s) %s " % record_filter.sql("OR") 
+        elif isinstance(record_filter, dict):
+            record_filter = Filter(SfaRecord.all_fields, record_filter)        
+            sql += " AND (%s) %s" % record_filter.sql("AND")
+        elif isinstance(record_filter, StringTypes):
+            record_filter = Filter(SfaRecord.all_fields, {'hrn':[record_filter]})    
+            sql += " AND (%s) %s" % record_filter.sql("AND")
+        elif isinstance(record_filter, int):
+            record_filter = Filter(SfaRecord.all_fields, {'record_id':[record_filter]})    
+            sql += " AND (%s) %s" % record_filter.sql("AND")
+
+        results = self.db.selectall(sql)
+        if isinstance(results, dict):
+            results = [results]
+        return results
+
+    def findObjects(self, record_filter = None, columns=None):
+        
+        results = self.find(record_filter, columns) 
+        result_rec_list = []
+        for result in results:
+            if result['type'] in ['authority']:
+                result_rec_list.append(AuthorityRecord(dict=result))
+            elif result['type'] in ['node']:
+                result_rec_list.append(NodeRecord(dict=result))
+            elif result['type'] in ['slice']:
+                result_rec_list.append(SliceRecord(dict=result))
+            elif result['type'] in ['user']:
+                result_rec_list.append(UserRecord(dict=result))
+            else:
+                result_rec_list.append(SfaRecord(dict=result))
+        return result_rec_list
+
+
+    def drop(self):
+        try:
+            self.db.do('DROP TABLE IF EXISTS ' + self.tablename)
+            self.db.commit()
+        except:
+            try:
+                self.db.do('DROP TABLE ' + self.tablename)
+                self.db.commit()
+            except:
+                pass
+    
+    def sfa_records_purge(self):
+        self.drop()
diff --git a/sfa/util/threadmanager.py b/sfa/util/threadmanager.py
new file mode 100755 (executable)
index 0000000..3d5dd03
--- /dev/null
@@ -0,0 +1,71 @@
+import threading
+import time
+from Queue import Queue
+
+def ThreadedMethod(callable, queue):
+    """
+    A function decorator that returns a running thread. The thread
+    runs the specified callable and stores the result in the specified
+    results queue
+    """
+    def wrapper(args, kwds):
+        class ThreadInstance(threading.Thread): 
+            def run(self):
+                try:
+                    queue.put(callable(*args, **kwds))
+                except:
+                    # ignore errors
+                    pass
+        thread = ThreadInstance()
+        thread.start()
+        return thread
+    return wrapper
+
+
+class ThreadManager:
+    """
+    ThreadManager executes a callable in a thread and stores the result
+    in a thread safe queue. 
+    """
+    queue = Queue()
+    threads = []
+
+    def run (self, method, *args, **kwds):
+        """
+        Execute a callable in a separate thread.    
+        """
+        method = ThreadedMethod(method, self.queue)
+        thread = method(args, kwds)
+        self.threads.append(thread)
+
+    start = run
+
+    def get_results(self):
+        """
+        Return a list of all the results so far. Blocks until 
+        all threads are finished. 
+        """
+        for thread in self.threads:
+            thread.join()
+        results = []
+        while not self.queue.empty():
+            results.append(self.queue.get())  
+        return results
+           
+if __name__ == '__main__':
+
+    def f(name, n, sleep=1):
+        nums = []
+        for i in range(n, n+5):
+            print "%s: %s" % (name, i)
+            nums.append(i)
+            time.sleep(sleep)
+        return nums
+
+    threads = ThreadManager()
+    threads.run(f, "Thread1", 10, 2)
+    threads.run(f, "Thread2", -10, 1)
+
+    results = threads.get_results()
+    print "Results:", results
diff --git a/sfa/util/xmlrpcprotocol.py b/sfa/util/xmlrpcprotocol.py
new file mode 100644 (file)
index 0000000..11a4317
--- /dev/null
@@ -0,0 +1,54 @@
+# XMLRPC-specific code for SFA Client
+
+import xmlrpclib
+
+##
+# ServerException, ExceptionUnmarshaller
+#
+# Used to convert server exception strings back to an exception.
+#    from usenet, Raghuram Devarakonda
+
+class ServerException(Exception):
+    pass
+
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+    def close(self):
+        try:
+            return xmlrpclib.Unmarshaller.close(self)
+        except xmlrpclib.Fault, e:
+            raise ServerException(e.faultString)
+
+##
+# XMLRPCTransport
+#
+# A transport for XMLRPC that works on top of HTTPS
+
+class XMLRPCTransport(xmlrpclib.Transport):
+    key_file = None
+    cert_file = None
+    def make_connection(self, host):
+        # create a HTTPS connection object from a host descriptor
+        # host may be a string, or a (host, x509-dict) tuple
+        import httplib
+        host, extra_headers, x509 = self.get_host_info(host)
+        try:
+            HTTPS = httplib.HTTPS()
+        except AttributeError:
+            raise NotImplementedError(
+                "your version of httplib doesn't support HTTPS"
+                )
+        else:
+            return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))
+
+    def getparser(self):
+        unmarshaller = ExceptionUnmarshaller()
+        parser = xmlrpclib.ExpatParser(unmarshaller)
+        return parser, unmarshaller
+
+def get_server(url, key_file, cert_file, debug=False):
+    transport = XMLRPCTransport()
+    transport.key_file = key_file
+    transport.cert_file = cert_file
+
+    return xmlrpclib.ServerProxy(url, transport, allow_none=True, verbose=debug)
+
diff --git a/sfatables/README b/sfatables/README
new file mode 100644 (file)
index 0000000..892442f
--- /dev/null
@@ -0,0 +1,265 @@
+sfatables is a tool for defining access and admission control policies
+in an SFA network, in much the same way as iptables is for ip
+networks. This file gives an overview of the tool and then describes
+its design and implementation.
+
+Example command
+---------------
+
+An sfatables configuration consists of lists of rules that are applied
+to incoming and outgoing rspecs. Each rule consists of a 'match', to
+evaluate a given request against a certain set of criteria and a
+'target', to perform a corresponding action. Rules are manipulated by
+using the 'sfatables' command.
+
+Consider the following example:
+
+sfatables -A INCOMING -- -m hrn --user-hrn plc.princeton -- -j RESTRICT_TO_NODES --blacklist plc.mit
+
+The statement in this example has three parts: the command, the match
+and the target, separated by the token '--'.
+
+* The command is '-A', which means 'Add rule.' 
+
+* The match is defined in the segment '-m hrn --user-hrn
+  plc.princeton.' Here, '-m hrn' specifies the 'match type', and
+  '--user-hrn' provides an argument specific to the match type.
+
+* The target is defined by the segment '-j RESTRICT_TO_NODES
+  --blacklist plc.princeton.' '-j RESTRICT_TO_NODES' defines the
+  target type (RESTRICT_TO_NODES) and '--blacklist' defines a
+  parameter specific to this type.
+
+sfatables comes with a default set of matches and targets, which can
+be extended using a simple interface.
+
+When you execute this command, you should see it in your current
+configuration by running 'sfatables -L INCOMING'
+
+# ./sfatables -L INCOMING
+
+# Rule  Match Arguments                Target            Arguments        
+# 1     hrn   user-hrn=plc.princeton.* RESTRICT_TO_NODES blacklist=plc.mit
+
+With this configuration, every time a request is received from
+plc.princeton.*, nodes matching the blacklist prefix (plc.mit) are
+dropped from the rspec.
+
+The basis for deploying rules using sfatables is the library of
+matches and targets. A set of such rules constitutes a 'policy', which
+as we will see is a portable piece of information that can be
+exchanged with users, peers, and policy makers to make resource
+allocation and peering a more effective process.
+
+XPath crash course -- read this now, or deal with frustration in the
+remainder of the document
+-----------------------------------------------------
+
+XPath is used to select sets of nodes in an XML file. It is like the
+'SELECT' command in SQL, but has the advantage of applying to tree
+structures, which are more general than relations. That is, while a
+relation (a table) has a depth = 2, a tree can have an arbitrary
+depth. This property allows us to consicely refer to criteria such as 'the nodes in the
+site corresponding to a user named Alice.' This particular command
+might look like: '/user[name='Alice']/site/node.'
+
+An XPath expression is like a directory path, with the following key
+differences.
+
+* In a directory path the relationship between X/Y is a parent-child
+  relationship. In XPath, this can be one of a large number of
+  relationships, including 'sibling', 'parent', 'ancestor',
+  'descendant' etc. The most frequently used relationships are:
+
+    child - e.g. site/node
+
+  and
+
+    descendant - e.g. user//node
+
+* Each level can be filtered with a predicate; e.g.,
+  'site[startswith(@hrn,'plc')]/nodes' means all nodes in sites that
+  have the prefix 'plc'.
+
+* Some terms have an '@' in them, meaning that they are attributes;
+  e.g., to retrieve the value of p in the following data, we would use
+  the expression "/x/y/@p"
+
+   <x>
+   <y p="q"/>
+   </x>
+
+Example match
+-------------
+
+A match specification consists of a 'context', a set of arguments, and
+a 'processor.' The context defines the information associated with a
+request that this match operates on. Think of it as the input
+parameters to the match. The arguments define values specific to the
+rule. The processor refers to the program that actually evaluates the
+match.
+
+<match name="hrn">
+    <context select="//sfa/current/user@hrn"/>
+    <rule>
+        <argument>
+            <name>user-hrn</name>
+            <help>HRN of the user requesting resouces</help>
+            <operand>HRN</operand>
+        </argument>
+    </rule>
+    <processor filename="hrn.xsl"/>
+</match>
+
+Now, when we run the command in the previous example:
+
+sfatables -A INCOMING -- -m hrn --user-hrn plc.princeton -- -j RESTRICT_TO_NODES --blacklist plc.mit
+
+... this match specification is parameterized and dropped in the
+sfatables configuration directory. The paramterized version of the
+match is given below:
+
+<match name="hrn">
+    <!-- Empty context. We _always_ get the hrn of the current user -->
+    <context select="//sfa/current/user@hrn"/>
+    <rule>
+        <argument>
+            <name>user-hrn</name>
+            <help>HRN of the user requesting resouces</help>
+            <operand>HRN</operand>
+        <value>plc.princeton</value>   <------------------
+    </argument>
+    </rule>
+    <processor filename="hrn.xsl"/>
+</match>
+
+Notice the additional 'value' tag. Let's list the entries in the
+configuration directory.
+
+# ls -l /etc/sfatables/INCOMING
+
+sapan@joyce ~/Projects/planetlab/sfa/sfatables/targets $ 
+total 16
+-rw-r--r-- 1 sapan sapan 671 Sep 11 12:13 sfatables-1-match
+-rw-r--r-- 1 sapan sapan 646 Sep 11 12:13 sfatables-1-target
+
+As you can see, a configuration is simply a set of match-target pairs.
+
+Finally, this is what the match processor looks like:
+
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:variable name="context-hrn" select="hrn"/>
+    <xsl:template match="user">
+                    <xsl:choose>
+                    <xsl:when test="starts-with($context-hrn, hrn)">
+                        True <!--Match -->
+                    </xsl:when>
+                    <xsl:otherwise>
+                        False <!-- No match -->
+                    </xsl:otherwise>
+                </xsl:choose>
+        <xsl:value-of select="$result"/>
+    </xsl:template>
+        
+</xsl:stylesheet>
+
+It is written in XSLT. If the syntax of XSLT were not XML-based, then
+it might have looked as follows:
+
+context-hrn = //sfa/user/hrn 
+request-hrn = //request/user/hrn
+
+result = 
+  if (starts_with(context-hrn,request-hrn)) then
+    True
+  else
+    False
+  return result
+
+This is exactly what the previous fragment of code says, albeit in a
+different format.
+
+Example target
+--------------
+
+Targets are specified just like matches. If you haven't read the match
+example, then now is a good time to do that. Here's an example target:
+
+<target name="RESTRICT_TO_NODES">
+    <!-- The context is empty, since this target does not require any input from SFA -->
+    <context select=""/>
+    <rule>
+        <argument>
+            <name>whitelist</name>
+            <help>Prefix of nodes to whitelist for this match.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <argument>
+            <name>blacklist</name>
+            <help>Prefix of nodes to blacklist for this match.</help>
+            <operand>PREFIX</operand>
+        </argument>
+    </rule>
+    <processor filename="restrict_to_nodes.xsl"/>
+</target>
+
+and the corresponding target processor:
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:variable name="whitelist_prefix" select="//rspec//rule/argument[name='whitelist']/value"/>
+    <xsl:variable name="blacklist_prefix" select="//rspec//rule/argument[name='blacklist']/value"/>
+
+    <!-- Drop nodes that are not in the whitelist -->
+    <xsl:template match="node">
+            <xsl:choose>
+                <xsl:when test="starts-with(@name,$whitelist_prefix) and not($blacklist_prefix and starts-with(@name,$blacklist_prefix))">
+                    <xsl:copy-of select="."/>
+                </xsl:when>
+                <xsl:otherwise/>
+            </xsl:choose>
+    </xsl:template>
+
+    <xsl:template match="sfatables-input"/>
+</xsl:stylesheet>
+
+[TODO: explain this target]
+
+
+Contexts
+--------
+
+Matches and targets are associated with specific contexts. A target may use a
+variety of criteria to process a request, and may need to look them up in the
+SFA database. The 'context' contains an xpath expression that isolates the
+items that a match or target may refer to. For example, if a match needs access
+to the nodes corresponding to a slice's site, then the context may be '/sfa/slice[@name=/context/slice/@name]/nodes'.
+
+
+Here's a summary of the model:
+-----------------------------
+
+An AM can inherit from a set of elements (E).
+
+Each element in E is associated with three things:
+
+    * A er... 'micro-rspec'
+
+    * an abstract database schema - S, which the AM is expected to be
+      able to generate on the fly.
+
+    * a set of matches and targets. 
+
+Matches and targets may use pieces of information from S by specifying
+them in their context (see the 'context' part of matches and targets
+above).
diff --git a/sfatables/TO_CLEANUP b/sfatables/TO_CLEANUP
new file mode 100644 (file)
index 0000000..bb7db58
--- /dev/null
@@ -0,0 +1,3 @@
+1# Load sfatables rules only once, not on every request.
+2# Set request context based on the context requested by the match/target.
+3# Remember the element type at run time
diff --git a/sfatables/__init__.py b/sfatables/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sfatables/command.py b/sfatables/command.py
new file mode 100644 (file)
index 0000000..1cb58e7
--- /dev/null
@@ -0,0 +1,26 @@
+import os, time
+
+class Command:
+    options = []
+    help = ''
+    type='command'
+    matches = False
+    targets = False
+    action = 'store_const'
+
+    def __init__(self):
+        self.options = []
+        self.help = ''
+        self.type = 'command'
+        self.matches = False
+        self.targets = False
+        self.action = 'store_const'
+
+        return
+
+    def call(self, coptions, moptions, toptions):
+        # Override this function
+        return True
+
+    def __call__(self, coptions, moptions, toptions):
+        return self.call(coptions,moptions,toptions)
diff --git a/sfatables/commands/Add.py b/sfatables/commands/Add.py
new file mode 100644 (file)
index 0000000..e7657ff
--- /dev/null
@@ -0,0 +1,70 @@
+import os, time
+import libxml2
+from sfatables.command import Command
+from sfatables.globals import *
+
+class Add(Command):
+    def __init__(self):
+        self.options = [('-A','--add')]
+        self.help = 'Add a rule to a chain'
+        self.matches = True
+        self.targets = True
+        return
+
+    def getnextfilename(self,type,chain):
+        dir = sfatables_config + "/"+chain;
+        last_rule_number = 0
+
+        for (root, dirs, files) in os.walk(dir):
+            for file in files:
+                if (file.startswith('sfatables-') and file.endswith(type)):
+                    number_str = file.split('-')[1]
+                    number = int(number_str)
+                    if (number>last_rule_number):
+                        last_rule_number = number
+
+        return "sfatables-%d-%s"%(last_rule_number+1,type)
+
+    def call_gen(self, chain, type, dir, options):
+        filename = os.path.join(dir, options.name+".xml")
+        xmldoc = libxml2.parseFile(filename)
+    
+        p = xmldoc.xpathNewContext()
+
+        supplied_arguments = options.arguments
+        if (hasattr(options,'element') and options.element):
+            element = options.element
+        else:
+            element='*'
+
+        for option in supplied_arguments:
+            option_name = option['name']
+            option_value = getattr(options,option_name)
+
+            if (hasattr(options,option_name) and getattr(options,option_name)):
+                context = p.xpathEval("//rule[@element='%s' or @element='*']/argument[name='%s']"%(element, option_name))
+                if (not context):
+                    raise Exception('Unknown option %s for match %s and element %s'%(option,option['name'], element))
+                else:
+                    # Add the value of option
+                    valueNode = libxml2.newNode('value')
+                    valueNode.addContent(option_value)
+                    context[0].addChild(valueNode)
+
+        filename = self.getnextfilename(type,chain)
+        file_path = os.path.join(sfatables_config, chain, filename)
+        if not os.path.isdir(os.path.dirname(file_path)):
+            os.makedirs(os.path.dirname(file_path))
+        xmldoc.saveFile(file_path)
+        p.xpathFreeContext()
+        xmldoc.freeDoc()
+
+        return True
+
+    def call(self, command_options, match_options, target_options):
+        chain = command_options.args[0]
+        ret = self.call_gen(chain, 'match',match_dir, match_options)
+        if (ret):
+            ret = self.call_gen(chain, 'target',target_dir, target_options)
+
+        return ret
diff --git a/sfatables/commands/Delete.py b/sfatables/commands/Delete.py
new file mode 100644 (file)
index 0000000..50b1d62
--- /dev/null
@@ -0,0 +1,32 @@
+import os, time
+from sfatables.globals import *
+from sfatables.command import Command
+
+class Delete(Command):
+    options = [('-D','--delete')]
+    help = 'Delete a rule from a chain'
+    key='delete_rule'
+    matches = False
+    targets = False
+
+    def __init__(self):
+        return
+
+    def call(self, command_options, match_options, target_options):
+
+        if (len(command_options.args)<2):
+            print "Please specify the chain and the rule number to delete, e.g. sfatables -D INCOMING 1."
+            return
+
+        chain = command_options.args[0]
+
+
+        rule_number = command_options.args[1]
+        chain_dir = sfatables_config + "/" + chain
+
+        match_path = chain_dir + "/" + "sfatables-%s-match"%rule_number
+        target_path = chain_dir + "/" + "sfatables-%s-target"%rule_number
+
+        os.unlink(match_path)
+        os.unlink(target_path)
+
diff --git a/sfatables/commands/Insert.py b/sfatables/commands/Insert.py
new file mode 100644 (file)
index 0000000..d401092
--- /dev/null
@@ -0,0 +1,65 @@
+import os, time
+import libxml2
+from sfatables.command import Command
+from sfatables.globals import *
+
+class Insert(Command):
+    def __init__(self):
+        self.options = [('-I','--insert')]
+        self.help = 'Insert a rule into a chain'
+        self.matches = True
+        self.targets = True
+        return
+
+    def call_gen(self, chain, type, dir, options, file_path):
+        filename = os.path.join(dir, options.name+".xml")
+        xmldoc = libxml2.parseFile(filename)
+    
+        p = xmldoc.xpathNewContext()
+
+        supplied_arguments = options.arguments
+        if (hasattr(options,'element') and options.element):
+            element = options.element
+        else:
+            element='*'
+
+        for option in supplied_arguments:
+            option_name = option['name']
+            option_value = getattr(options,option_name)
+
+            if (hasattr(options,option_name) and getattr(options,option_name)):
+                context = p.xpathEval("//rule[@element='%s' or @element='*']/argument[name='%s']"%(element, option_name))
+                if (not context):
+                    raise Exception('Unknown option %s for match %s and element %s'%(option,option['name'], element))
+                else:
+                    # Add the value of option
+                    valueNode = libxml2.newNode('value')
+                    valueNode.addContent(option_value)
+                    context[0].addChild(valueNode)
+
+        if not os.path.isdir(os.path.dirname(file_path)):
+            os.makedirs(os.path.dirname(file_path))
+        xmldoc.saveFile(file_path)
+        p.xpathFreeContext()
+        xmldoc.freeDoc()
+
+        return True
+
+    def call(self, command_options, match_options, target_options):
+        if (len(command_options.args)<2):
+            print "Please specify the chain and the rule number to insert, e.g. sfatables -I INCOMING 1 -- ...."
+            return
+
+        chain = command_options.args[0]
+
+        rule_number = command_options.args[1]
+        chain_dir = sfatables_config + "/" + chain
+
+        match_path = chain_dir + "/" + "sfatables-%s-match"%rule_number
+        target_path = chain_dir + "/" + "sfatables-%s-target"%rule_number
+
+        ret = self.call_gen(chain, 'match',match_dir, match_options, match_path)
+        if (ret):
+            ret = self.call_gen(chain, 'target',target_dir, target_options, target_path)
+
+        return ret
diff --git a/sfatables/commands/List.py b/sfatables/commands/List.py
new file mode 100644 (file)
index 0000000..95d2038
--- /dev/null
@@ -0,0 +1,103 @@
+import os, time
+import libxml2
+import pdb
+
+from sfatables.globals import *
+from sfatables.pretty import Pretty
+from sfatables.command import Command
+
+class List(Command):
+    
+    def __init__(self):
+        self.options = [('-L','--list')]
+        self.help = 'List a chain'
+        self.key='list_rule'
+        self.matches = False
+        self.targets = False
+
+        return
+
+    def get_info(self, type, xmlextension_path):
+        xmldoc = libxml2.parseFile(xmlextension_path)
+        p = xmldoc.xpathNewContext()
+        
+        ext_name_node = p.xpathEval("/%s/@name"%type)
+        ext_name = ext_name_node[0].content
+
+        name_nodes = p.xpathEval("//rule/argument[value!='']/name")
+        value_nodes = p.xpathEval("//rule/argument[value!='']/value")
+        element_nodes = p.xpathEval("//argument[value!='']/parent::rule/@element")
+
+        if (len(element_nodes)>1):
+            raise Exception("Invalid rule %s contains multiple elements."%xmlextension_path)
+
+        element = []
+        argument_str = ""
+        if element_nodes:
+            element = element_nodes[0].content
+
+            names = [n.content for n in name_nodes]
+            values = [v.content for v in value_nodes]
+
+            name_values = zip(names,values)
+            name_value_pairs = map(lambda (n,v):n+'='+v, name_values)
+
+            argument_str = ",".join(name_value_pairs)
+
+        p.xpathFreeContext()
+        xmldoc.freeDoc()
+
+        return {'name':ext_name, 'arguments':argument_str, 'element':element}
+
+    def get_rule_list(self, chain_dir_path):
+        broken_semantics = os.walk(chain_dir_path)
+        rule_numbers = {}
+
+        for (root, dirs, files) in broken_semantics:
+            for file in files:
+                if (file.startswith('sfatables')):
+                    (magic,number,type) = file.split('-')
+                    rule_numbers[int(number)]=1
+
+        rule_list = rule_numbers.keys()
+        rule_list.sort()
+        return rule_list
+
+    def call(self, command_options, match_options, target_options):
+        if (len(command_options.args) < 1):
+            print "Please specify the name of the chain you would like to list, e.g. sfatables -L INCOMING."
+            return
+
+        chain = command_options.args[0]
+        chain_dir = os.path.join(sfatables_config, chain)
+
+        rule_list = self.get_rule_list(chain_dir)
+
+        pretty = Pretty(['Rule','Match','Arguments','Target','Element','Arguments'])
+
+        for number in rule_list:
+            match_file = "sfatables-%d-%s"%(number,'match')
+            target_file = "sfatables-%d-%s"%(number,'target')
+
+            match_path = sfatables_config + '/' + chain + '/' + match_file
+            target_path = sfatables_config + '/' + chain + '/' + target_file
+
+            match_info = self.get_info ('match',match_path)
+            target_info = self.get_info ('target',target_path)
+
+            pretty.push_row(["%d"%number,
+                             match_info['name'],
+                             match_info['arguments'],
+                             target_info['name'],
+                             target_info['element'],
+                             target_info['arguments']])
+
+        pretty.pprint()
+
+
+
+
+
+
+
+
diff --git a/sfatables/commands/SetDefault.py b/sfatables/commands/SetDefault.py
new file mode 100644 (file)
index 0000000..b840706
--- /dev/null
@@ -0,0 +1,19 @@
+import os, time
+from sfatables.command import Command
+
+class SetDefault(Command):
+    options = [('-P','--default')]
+    help = 'Set the default rule for a chain'
+    key='set_default_rule'
+    matches = False
+    targets = False
+
+    def __init__(self):
+        return
+
+    def call(self):
+        # Override this function
+        return True
+
+    def __call__(self, option, opt_str, value, parser, *args, **kwargs):
+        return self.call(option)
diff --git a/sfatables/commands/__init__.py b/sfatables/commands/__init__.py
new file mode 100644 (file)
index 0000000..273696c
--- /dev/null
@@ -0,0 +1,7 @@
+all = """
+Add
+Delete
+Insert
+List
+SetDefault
+""".split()
diff --git a/sfatables/commands/moo.py b/sfatables/commands/moo.py
new file mode 100644 (file)
index 0000000..bd8c88e
--- /dev/null
@@ -0,0 +1,210 @@
+import os, time
+
+class Command:
+    commandline_options = []
+    help = "Add a new rule"
+
+    def __init__(self):
+        if (len(commandline_options!=2)):
+            raise Exception("Internal error: each command must supply 2 command line options")
+
+               
+    def __call__(self, option, opt_str, value, parser, *args, **kwargs):
+        return True
+
+        
+
+    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"
+
+        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 SfaInvalidArgument, 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 SfaInvalidArgument("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 SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+            if max is not None and \
+               len(value.encode(self.api.encoding)) > max:
+                raise SfaInvalidArgument, "%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 SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
+            if max is not None and len(value) > max:
+                raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
+        else:
+            if min is not None and value < min:
+                raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
+            if max is not None and value > max:
+                raise SfaInvalidArgument, "%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 SfaInvalidArgument("'%s' not specified" % key, name)
+
+        #if auth is not None:
+        #    auth.check(self, *args)
diff --git a/sfatables/globals.py b/sfatables/globals.py
new file mode 100644 (file)
index 0000000..fe6788d
--- /dev/null
@@ -0,0 +1,5 @@
+import os.path
+
+sfatables_config = '/etc/sfatables'
+match_dir = os.path.join(sfatables_config, "matches")
+target_dir = os.path.join(sfatables_config, "targets")
diff --git a/sfatables/matches/all.xml b/sfatables/matches/all.xml
new file mode 100644 (file)
index 0000000..f21fc09
--- /dev/null
@@ -0,0 +1,12 @@
+<!-- 
+"context" specifies the subset of the requestor context that this match needs to see. It is specified as an xpath expression.
+For this simple match, we just need to look at sfa-input. 
+
+"rule" specifies a set of arguments that the match accepts.
+-->
+
+<match name="all">
+    <!-- Empty context. We _always_ get the hrn of the current user -->
+    <context select=""/>
+    <processor filename="all.xsl"/>
+</match>
diff --git a/sfatables/matches/hrn.xml b/sfatables/matches/hrn.xml
new file mode 100644 (file)
index 0000000..14bc26e
--- /dev/null
@@ -0,0 +1,19 @@
+<!-- 
+"context" specifies the subset of the requestor context that this match needs to see. It is specified as an xpath expression.
+For this simple match, we just need to look at sfa-input. 
+
+"rule" specifies a set of arguments that the match accepts.
+-->
+
+<match name="hrn">
+    <!-- Empty context. We _always_ get the hrn of the current user -->
+    <rule element='*'>
+    <context select="//sfa/current/user@hrn"/>
+        <argument>
+            <name>user-hrn</name>
+            <help>HRN of the user requesting resouces</help>
+            <operand>HRN</operand>
+        </argument>
+    <processor filename="hrn.xsl"/>
+    </rule>
+</match>
diff --git a/sfatables/matches/slice-whitelist.xml b/sfatables/matches/slice-whitelist.xml
new file mode 100644 (file)
index 0000000..ad3c840
--- /dev/null
@@ -0,0 +1,7 @@
+<match name="slice-whitelist">
+    <!-- Empty context. We _always_ get the hrn of the current user -->
+    <rule element='*'>
+      <context select="//sfa/current/slice@hrn"/>
+      <processor filename="slice-whitelist.xsl"/>
+    </rule>
+</match>
diff --git a/sfatables/matches/slice.xml b/sfatables/matches/slice.xml
new file mode 100644 (file)
index 0000000..e3ee7db
--- /dev/null
@@ -0,0 +1,19 @@
+<!-- 
+"context" specifies the subset of the requestor context that this match needs to see. It is specified as an xpath expression.
+For this simple match, we just need to look at sfa-input. 
+
+"rule" specifies a set of arguments that the match accepts.
+-->
+
+<match name="slice">
+    <!-- Empty context. We _always_ get the hrn of the current user -->
+    <rule element='*'>
+    <context select="//sfa/current/slice@hrn"/>
+        <argument>
+            <name>hrn</name>
+            <help>HRN of the slice requesting resources</help>
+            <operand>HRN</operand>
+        </argument>
+    <processor filename="slice-hrn.xsl"/>
+    </rule>
+</match>
diff --git a/sfatables/pretty.py b/sfatables/pretty.py
new file mode 100644 (file)
index 0000000..9498aac
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+
+class Pretty:
+    rows = []
+    column_width = []
+
+    def __init__(self, header):
+        self.rows.append(header)
+        for c in header:
+            self.column_width.append(len(header))
+
+    def push_row (self, row):
+        self.rows.append(row)
+        num = 0
+        for c in row:
+            if (self.column_width[num] < len(c)):
+                self.column_width[num] = len(c)
+            num = num + 1
+        return
+
+    def pprint (self):
+        print '\n'
+
+        for rule in self.rows:
+            cur_line = ""
+            num = 0
+
+            for r in rule:
+                cur_line = cur_line + "%s "%r
+                if (self.column_width[num] > len(r)):
+                    padding0 = ''.zfill(self.column_width[num] - len(r))
+                    padding = padding0.replace('0',' ')
+                    cur_line = cur_line + padding
+                num = num + 1
+
+            print cur_line
+
+
diff --git a/sfatables/processors/__sfatables_rule_wrap_up__.xsl b/sfatables/processors/__sfatables_rule_wrap_up__.xsl
new file mode 100644 (file)
index 0000000..00e43b6
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- 
+    This processor is called at the end, to remove sfatables-specific tags.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+        <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="match-context"/>
+    <xsl:template match="target-context"/>
+</xsl:stylesheet>
diff --git a/sfatables/processors/__sfatables_wrap_up__.xsl b/sfatables/processors/__sfatables_wrap_up__.xsl
new file mode 100644 (file)
index 0000000..3b73dc3
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- 
+    This processor is called at the end, to remove sfatables-specific tags.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+        <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="request-context"/>
+</xsl:stylesheet>
diff --git a/sfatables/processors/accept.xsl b/sfatables/processors/accept.xsl
new file mode 100644 (file)
index 0000000..9a397ef
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+    <xsl:template match="sfatables-input"/>
+</xsl:stylesheet>
diff --git a/sfatables/processors/all.xsl b/sfatables/processors/all.xsl
new file mode 100644 (file)
index 0000000..469220c
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:template match="/">
+            <result verdict="True"/> <!-- No match -->
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/hrn.xsl b/sfatables/processors/hrn.xsl
new file mode 100644 (file)
index 0000000..47a8b62
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:variable name="context-hrn" select="//request-context/sfa/user/hrn"/>
+
+    <!-- Magic sauce -->
+
+    <xsl:template match="@* | node()">
+            <xsl:apply-templates select="@* | node()"/>
+    </xsl:template>
+
+    <xsl:template match="//match-context/argument[name='user-hrn']">
+                    <xsl:value-of select="hrn"/>
+                    <xsl:choose>
+                    <xsl:when test="starts-with($context-hrn, value)">
+                        <result verdict="True"/> <!--Match -->
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <result verdict="False"/> <!-- No match -->
+                    </xsl:otherwise>
+                </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/legacy-restrict-to-nodes.xsl b/sfatables/processors/legacy-restrict-to-nodes.xsl
new file mode 100644 (file)
index 0000000..6548ceb
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:variable name="whitelist_suffix" select="//RSpec//target-context/argument[name='whitelist-dns-suffix']/value"/>
+    <xsl:variable name="blacklist_suffix" select="//RSpec//target-context/argument[name='blacklist-dns-suffix']/value"/>
+
+    <!-- Drop nodes that are not in the whitelist. This is the legacy version that works on the current
+    rspec. The current rspec refers to dns names, not sfa names for nodes.-->
+    <xsl:template match="NodeSpec">
+            <xsl:choose>
+                <xsl:when test="(not($whitelist_suffix) or contains(@name,$whitelist_suffix)) and (not($blacklist_suffix) or not(contains(@name,$blacklist_suffix)))">
+                    <xsl:copy-of select="."/>
+                </xsl:when>
+                <xsl:otherwise/>
+            </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/link/restrict_slice.xml b/sfatables/processors/link/restrict_slice.xml
new file mode 100644 (file)
index 0000000..2a3f1b5
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+    <!-- End of magic sauce -->
+
+    <!-- Read in the value of the argument. See 'example_vini_rspec.xml' for an example of such an argument -->
+    <xsl:variable name="max-link-bandwidth" select="//rspec//rule-context/argument[name='max-link-bandwidth']/value"/>
+
+    <!-- Drop Linkspecs for which bw > max-link-bandwidth -->
+    <xsl:template match="LinkSpec">
+            <xsl:choose>
+                <xsl:when test="bw &lt; $max-link-bandwidth">
+                    <xsl:copy-of select="."/>
+                </xsl:when>
+                <xsl:otherwise/>
+            </xsl:choose> 
+    </xsl:template>
+
+    <xsl:template match="sfatables-input"/>
+</xsl:stylesheet>
+
diff --git a/sfatables/processors/max_link_kbps.xsl b/sfatables/processors/max_link_kbps.xsl
new file mode 100644 (file)
index 0000000..7170fd8
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <!-- Magic sauce copied from a manual. This fragment basically
+    copies everything except for stuff that explicitly matches with
+    the templates defined below. In the case of such a match, the
+    matched node is treated differently.-->
+  <xsl:template match="@* | node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@* | node()"/>
+    </xsl:copy>
+  </xsl:template>
+  <!-- End of magic sauce -->
+
+  <!-- Read in the value of the argument. -->
+  <xsl:variable name="max-link-kbps" select="//RSpec//target-context/argument[name='max-link-kbps']/value"/>
+
+  <!-- Modify LinkSpecs for which kbps > max-link-kbps -->
+  <xsl:template match="vlink/kbps">
+    <xsl:choose>
+      <xsl:when test=". &gt; $max-link-kbps">
+       <kbps><xsl:value-of select="$max-link-kbps"/></kbps>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:copy-of select="."/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+      
+  <!-- Fill in missing kbps values --> 
+  <xsl:template match="vlink[not(kbps)]">
+    <xsl:copy>
+      <xsl:copy-of select="@* | *"/>
+      <kbps><xsl:value-of select="$max-link-kbps"/></kbps>
+    </xsl:copy>
+  </xsl:template>
+  
+  <xsl:template match="sfatables-input"/>
+</xsl:stylesheet>
+
diff --git a/sfatables/processors/max_node_kbps.xsl b/sfatables/processors/max_node_kbps.xsl
new file mode 100644 (file)
index 0000000..89b4df8
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <!-- Magic sauce copied from a manual. This fragment basically
+    copies everything except for stuff that explicitly matches with
+    the templates defined below. In the case of such a match, the
+    matched node is treated differently.-->
+  <xsl:template match="@* | node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@* | node()"/>
+    </xsl:copy>
+  </xsl:template>
+  <!-- End of magic sauce -->
+
+  <!-- Read in the value of the argument. -->
+  <xsl:variable name="max-node-kbps" select="//RSpec//target-context/argument[name='max-node-kbps']/value"/>
+  <xsl:variable name="blacklist_suffix" select="//RSpec//target-context/argument[name='on-legacy-node']/value"/>
+
+  <!-- Modify NodeSpec for which kbps > max-link-kbps -->
+  <xsl:template match="NodeSpec/max_rate">
+      <xsl:choose>
+          <xsl:when test=". &gt; $max-node-kbps">
+              <max_rate><xsl:value-of select="$max-node-kbps"/></max_rate>
+          </xsl:when>
+          <xsl:otherwise>
+              <xsl:copy-of select="."/>
+          </xsl:otherwise>
+      </xsl:choose>
+  </xsl:template>
+      
+  <!-- Fill in missing kbps values --> 
+  <xsl:template match="NodeSpec[not(max_rate)]">
+      <xsl:choose>
+          <xsl:when test="(not($blacklist_suffix) or not(contains(@name,$blacklist_suffix)))"> 
+              <!-- We're OK on this node, simply copy the nodespec -->
+              <xsl:copy-of select="."/>
+          </xsl:when>
+          <xsl:otherwise>
+              <xsl:choose>
+                  <xsl:when test="not(max_rate)">
+                      <xsl:copy>
+                          <xsl:copy-of select="@*"/>
+                          <max_rate><xsl:value-of select="$max-node-kbps"/></max_rate>
+                      </xsl:copy>
+                  </xsl:when>
+                  <xsl:otherwise>
+                      <xsl:choose>
+                          <xsl:when test=". &gt; $max-node-kbps">
+                              <max_rate><xsl:value-of select="$max-node-kbps"/></max_rate>
+                          </xsl:when>
+                          <xsl:otherwise>
+                              <xsl:copy-of select="."/>
+                          </xsl:otherwise>
+                      </xsl:choose>
+                  </xsl:otherwise>
+              </xsl:choose>
+          </xsl:otherwise>
+      </xsl:choose>
+  </xsl:template>
+  <xsl:template match="sfatables-input"/>
+</xsl:stylesheet>
diff --git a/sfatables/processors/reject.xsl b/sfatables/processors/reject.xsl
new file mode 100644 (file)
index 0000000..0c07e5e
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:template match="/">
+<sfa-verdict>
+    Your resource-allocation request has been rejected. 
+</sfa-verdict>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/sfatables/processors/restrict_to_flowspec.xsl b/sfatables/processors/restrict_to_flowspec.xsl
new file mode 100644 (file)
index 0000000..a919da7
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:variable name="wswitch" select="//RSpec//target-context/argument[name='whitelist-switch']/value"/>
+    <xsl:variable name="wtpsrc" select="//RSpec//target-context/argument[name='whitelist-tpsrc']/value"/>
+    <xsl:variable name="wtpdst" select="//RSpec//target-context/argument[name='whitelist-tpdst']/value"/>
+    <xsl:variable name="wipsrc" select="//RSpec//target-context/argument[name='whitelist-ipsrc']/value"/>
+    <xsl:variable name="wipdst" select="//RSpec//target-context/argument[name='whitelist-ipdst']/value"/>
+
+    <!-- Drop nodes that are not in the whitelist -->
+    <xsl:template match="//switchEntry">
+            <xsl:choose>
+                <xsl:when test="(nodeId==$wswitch) and 
+                    (interfaceEntry/flowSpaceEntry/tp_src==$wtpsrc) and
+                    (interfaceEntry/flowSpaceEntry/tp_dst==$wtpdst)">
+                    <xsl:copy-of select="."/>
+                </xsl:when>
+                <xsl:otherwise/>
+            </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/restrict_to_nodes.xsl b/sfatables/processors/restrict_to_nodes.xsl
new file mode 100644 (file)
index 0000000..12ff4cd
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <!-- Magic sauce copied from a manual. This fragment basically copies everything except for
+    stuff that explicitly matches with the templates defined below. In the case of such a match,
+    the matched node is treated differently.-->
+    <xsl:template match="@* | node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:variable name="whitelist_prefix" select="//RSpec//target-context/argument[name='whitelist']/value"/>
+    <xsl:variable name="blacklist_prefix" select="//RSpec//target-context/argument[name='blacklist']/value"/>
+
+    <!-- Drop nodes that are not in the whitelist -->
+    <xsl:template match="node">
+            <xsl:choose>
+                <xsl:when test="starts-with(@name,$whitelist_prefix) and not($blacklist_prefix and starts-with(@name,$blacklist_prefix))">
+                    <xsl:copy-of select="."/>
+                </xsl:when>
+                <xsl:otherwise/>
+            </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/slice-hrn.xsl b/sfatables/processors/slice-hrn.xsl
new file mode 100644 (file)
index 0000000..1ba5942
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:variable name="context-hrn" select="//request-context/sfa/slice/hrn"/>
+
+    <!-- Magic sauce -->
+
+    <xsl:template match="@* | node()">
+            <xsl:apply-templates select="@* | node()"/>
+    </xsl:template>
+
+    <xsl:template match="//match-context/argument[name='hrn']">
+                    <xsl:value-of select="hrn"/>
+                    <xsl:choose>
+                    <xsl:when test="starts-with($context-hrn, value)">
+                        <result verdict="True"/> <!--Match -->
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <result verdict="False"/> <!-- No match -->
+                    </xsl:otherwise>
+                </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/sfatables/processors/slice-whitelist.xsl b/sfatables/processors/slice-whitelist.xsl
new file mode 100644 (file)
index 0000000..8ca8deb
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- This is a good example of an sfatables match. It's function is to verify the slice in context against a whitelist -->
+
+<!-- The following lines should precede each match/target. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:exsl="http://exslt.org/common"
+    extension-element-prefixes="exsl"
+    version="1.0">
+
+    <!-- Enter your whitelist prefixes here. Since XSLT cannot read from/write to an external archive, we need to embed the whitelist in here -->
+
+    <xsl:variable name="whitelist_data">
+        <hrn>plc.princeton.acb</hrn>
+        <hrn>plc.princeton.sapanb</hrn>
+        <hrn>plc.princeton.codeen</hrn>
+    </xsl:variable>
+
+    <!-- Read the whitelist into a variable. Normally, one should be able to refer to $whitelist_data directly, but apparently this is something you need to do because libxml2 does not support xslt 2.0 -->
+
+    <xsl:variable name="whitelist" select="exsl:node-set($whitelist_data)/hrn"/>
+
+    <!-- Read the value of the current slice's hrn -->
+    <xsl:variable name="current_slice_hrn" select="//request-context/sfa/slice/hrn"/>
+
+    <!-- Define a function that checks if one of a list of prefixes (lst) matches $current_slice_hrn". It's a stupid idea to call a function a 'template' but whatever...  -->
+    <xsl:template name="recurse_list">
+        <xsl:param name="lst"/>
+        <xsl:choose>
+            <xsl:when test="count($lst)!=0">
+                <!-- Indexing in xpath has a base=1 -->
+                <xsl:variable name="head" select="$lst[1]"/>
+                <xsl:choose>
+                    <xsl:when test="starts-with($current_slice_hrn,$head)">
+                        <result verdict="True"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:call-template name="recurse_list">
+                            <xsl:with-param name="lst" select="$lst/following-sibling::*"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:when>
+            <xsl:otherwise>
+                <result verdict="False"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template match="/">
+        <xsl:call-template name="recurse_list">
+            <xsl:with-param name="lst" select="$whitelist"/>
+        </xsl:call-template>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/sfatables/processors/test.xml b/sfatables/processors/test.xml
new file mode 100644 (file)
index 0000000..4985734
--- /dev/null
@@ -0,0 +1,3 @@
+<request-context>
+<slice><hrn>plc.princeton.sapanb</hrn></slice>
+</request-context>
diff --git a/sfatables/runtime.py b/sfatables/runtime.py
new file mode 100644 (file)
index 0000000..a536335
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+
+import sys
+import os
+import pdb
+import libxml2
+
+from optparse import OptionParser
+from sfatables import commands
+from sfatables.globals import *
+from sfatables.commands.List import *
+from sfatables.xmlrule import *
+
+class SFATablesRules:
+    def __init__(self, chain_name):
+        self.active_context = {}
+        self.contexts = None # placeholder for rspec_manger
+        self.sorted_rule_list = []
+        self.final_processor = '__sfatables_wrap_up__.xsl'
+        chain_dir_path = os.path.join(sfatables_config,chain_name)
+        rule_list = List().get_rule_list(chain_dir_path)
+        for rule_number in rule_list:
+            self.sorted_rule_list = self.sorted_rule_list + [XMLRule(chain_name, rule_number)]
+        return
+
+    def wrap_up(self, doc):
+        filepath = os.path.join(sfatables_config, 'processors', self.final_processor)
+
+        if not os.path.exists(filepath):
+            raise Exception('Could not find final rule filter')
+
+        styledoc = libxml2.parseFile(filepath)
+        style = libxslt.parseStylesheetDoc(styledoc)
+        result = style.applyStylesheet(doc, None)
+        stylesheet_result = style.saveResultToString(result)
+        style.freeStylesheet()
+        doc.freeDoc()
+        result.freeDoc()
+
+        return stylesheet_result
+
+    def set_context(self, request_context):
+        self.active_context = request_context
+        return
+
+    def create_xml_node(self, name, context_dict):
+        node = libxml2.newNode(name)
+        for k in context_dict.keys():
+            if (type(context_dict[k])==dict):
+                childNode = self.create_xml_node(k, context_dict[k])
+                node.addChild(childNode)
+            else:
+                childNode = libxml2.newNode(k)
+                childNode.addContent(context_dict[k])
+                node.addChild(childNode)
+        return node
+                
+    def add_request_context_to_rspec(self, doc):
+        p = doc.xpathNewContext()
+        context = p.xpathEval("//RSpec")
+        if (not context):
+            raise Exception('Request is not an rspec')
+        else:
+            # Add the request context
+            requestNode = self.create_xml_node('request-context',self.active_context)
+            context[0].addChild(requestNode)
+        p.xpathFreeContext()
+        return doc
+
+    
+    def apply(self, rspec):
+        if (self.sorted_rule_list):
+            doc = libxml2.parseDoc(rspec)
+            doc = self.add_request_context_to_rspec(doc)
+
+            intermediate_rspec = doc
+
+            for rule in self.sorted_rule_list:
+                (matched,intermediate_rspec) = rule.apply_interpreted(intermediate_rspec)
+                if (rule.terminal and matched):
+                    break
+
+            final_rspec = self.wrap_up(intermediate_rspec) 
+        else:
+            final_rspec = rspec
+
+        return final_rspec
+
+    def print_rules(self):
+        for rule in self.sorted_rule_list:
+            print rule.processors
+
+def main():
+    incoming = SFATablesRules('INCOMING')
+    incoming.set_context({'sfa':{'user':{'hrn':'plc.princeton.sapanb'}}})
+
+    outgoing = SFATablesRules('OUTGOING')
+    print "%d rules loaded for INCOMING chain"%len(incoming.sorted_rule_list)
+    incoming.print_rules()
+
+    print "%d rules loaded for OUTGOING chain"%len(outgoing.sorted_rule_list)
+    outgoing.print_rules()
+
+    rspec = open(sys.argv[1]).read()
+    newrspec = incoming.apply(rspec)
+    print newrspec
+    return
+
+if __name__=="__main__":
+    main()
diff --git a/sfatables/sfatables b/sfatables/sfatables
new file mode 100755 (executable)
index 0000000..b413ef1
--- /dev/null
@@ -0,0 +1,138 @@
+#!/usr/bin/python 
+
+# This file parses an sfatables command and generates XML files that parameterize
+# matches and targets. Each such XML file defines a rule. Rules are dropped in directories
+# that represent 'chains.' SFA loads rules from various chains and invokes them at certain
+# 'hook points.' For example, it invokes rules in the 'OUTGOING' chain before returning
+# the output of 'get_resources.'
+
+import sys
+import os
+import pdb
+import glob
+import libxml2
+
+from optparse import OptionParser
+from sfatables import commands
+from sfatables.xmlextension import Xmlextension
+from sfatables.globals import *
+
+def load_commands(module, list):
+    command_dict={}
+
+    for command_name in list:
+        command_module = __import__(".".join([module,command_name]),fromlist=[module])
+        command = getattr(command_module, command_name)
+        command_dict[command_name]=command()
+
+    return command_dict
+
+def load_xml_extensions(module, ext_dir):
+    ext_dict={}
+
+    exts = glob.glob(ext_dir + os.path.sep + "*")
+    for ext in exts:
+        module = Xmlextension(ext)
+        # get the filename and get rid of the ".xml" extension
+        ext_name = os.path.extsep.join(os.path.splitext(os.path.basename(ext))[:-1])
+        ext_dict[ext_name]=module
+
+    return ext_dict
+
+
+def create_parser(command_dict):
+    parser = OptionParser(usage="sfatables [command] [chain] [match] [target]",
+                             description='See "man sfatables" for more detail.')
+    
+    for k in command_dict.keys():
+        command = command_dict[k]
+        for (short_option,long_option) in command.options:
+            parser.add_option(short_option,long_option,dest=command.type,action=command.action,const=k,help=command.help,metavar="CHAIN")
+
+    return parser
+
+def create_parser_xml_ext(ext_dict):
+    parser = OptionParser(usage="sfatables [command] [chain] [match] [target]",
+                             description='See "man sfatables" for more detail.')
+    
+    for k in ext_dict.keys():
+        command = ext_dict[k]
+        for arg in command.arguments:
+            parser.add_option('',"--"+arg['name'],dest=arg['name'],help=arg['help'],metavar=arg['target'])
+
+    return parser
+
+
+def partition(sep, lst):
+    ret = []
+    curpart = []
+    for item in lst:
+        if (item==sep):
+            ret.append(curpart)
+            curpart=[]
+        else:
+            curpart.append(item)
+    ret.append(curpart)
+
+    return ret
+
+
+def main():
+    # sfatables <command> -- <match> -- <target>
+    pargs = partition('--', sys.argv[1:])
+
+    command_dict = load_commands("sfatables.commands",commands.all)
+    command_parser = create_parser(command_dict)
+    (options, args) = command_parser.parse_args(pargs[0])
+    setattr(options, 'args', args)
+
+    command = command_dict[options.command]
+
+    if (command.matches):
+        if (len(pargs)<2):
+            raise Exception("Must specify match for this command")
+        match_dict = load_xml_extensions("sfatables.matches",match_dir)
+        match_parser = create_parser_xml_ext(match_dict)
+        matches_str = ",".join(match_dict.keys())
+        match_parser.add_option('-m','--match',dest='name',help='Match name (one of %s)'%matches_str, metavar = 'MATCH')
+        match_parser.add_option('-n','--negate',dest='negate',help='Negate result',action='store_true')
+        (match_options, args) = match_parser.parse_args(pargs[1])
+        try:
+            name = match_options.name
+        except Exception:
+            print "Must specify match name with -m"
+
+        if (match_dict.has_key(name)):
+            setattr(match_options, 'arguments', match_dict[name].arguments)
+        else:
+            raise Exception('Match %s not found'%name)
+
+    else:
+        match_options=None
+
+    if (command.targets):
+        if (len(pargs)<3):
+            raise Exception("Must specify a target for this command")
+        target_dict = load_xml_extensions("sfatables.targets",target_dir)
+        target_parser = create_parser_xml_ext(target_dict)
+        targets_str = ",".join(target_dict.keys())
+        target_parser.add_option('-j','--jump',dest='name',help='Target name (one of %s)'%targets_str, metavar = 'TARGET')
+        target_parser.add_option('-e','--element',dest='element',help='Element name', metavar = 'ELEMENT')
+        (target_options, args) = target_parser.parse_args(pargs[2])
+        try:
+            name = target_options.name
+        except Exception:
+            print "Must specify target name with -j"
+
+        if (target_dict.has_key(name)):
+            setattr(target_options, 'arguments', target_dict[name].arguments)
+        else:
+            raise Exception('Target %s not found'%name)
+
+    else:
+        target_options = None
+
+    command(options, match_options, target_options)
+
+if __name__=='__main__':
+    main()
diff --git a/sfatables/targets/ACCEPT.xml b/sfatables/targets/ACCEPT.xml
new file mode 100644 (file)
index 0000000..d97c68d
--- /dev/null
@@ -0,0 +1,10 @@
+<target name="ACCEPT">
+    <!-- The context is empty, since this target does not require any input from SFA -->
+    <rule element="*">
+        <context select=""/>
+        <attributes>
+            <attribute terminal="yes"/>
+        </attributes>
+        <processor filename="accept.xsl"/>
+    </rule>
+</target>
diff --git a/sfatables/targets/REJECT.xml b/sfatables/targets/REJECT.xml
new file mode 100644 (file)
index 0000000..8beeb3c
--- /dev/null
@@ -0,0 +1,10 @@
+<target name="REJECT">
+    <!-- The context is empty, since this target does not require any input from SFA -->
+    <rule element="*">
+        <context select=""/>
+        <attributes>
+            <attribute terminal="yes"/>
+        </attributes>
+        <processor filename="reject.xsl"/>
+    </rule>
+</target>
diff --git a/sfatables/targets/RESTRICT_SLICE_DOMAIN.xml b/sfatables/targets/RESTRICT_SLICE_DOMAIN.xml
new file mode 100644 (file)
index 0000000..3fc0841
--- /dev/null
@@ -0,0 +1,63 @@
+<target name="RESTRICT_SLICE_DOMAIN">
+    <rule element="legacy-node">
+        <!-- The context is empty, since this target does not require any input from SFA -->
+        <context select=""/>
+        <argument>
+            <name>whitelist-dns-suffix</name>
+            <help>Suffix of nodes to whitelist for this match.</help>
+            <operand>SUFFIX</operand>
+        </argument>
+        <argument>
+            <name>blacklist-dns-suffix</name>
+            <help>Suffix of nodes to blacklist for this match.</help>
+            <operand>SUFFIX</operand>
+        </argument>
+        <processor filename="legacy-restrict-to-nodes.xsl"/>
+    </rule>
+    <rule element="node">
+        <!-- The context is empty, since this target does not require any input from SFA -->
+        <context select=""/>
+        <argument>
+            <name>whitelist</name>
+            <help>Prefix of nodes to whitelist for this match.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <argument>
+            <name>blacklist</name>
+            <help>Prefix of nodes to blacklist for this match.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <processor filename="restrict_to_nodes.xsl"/>
+    </rule>
+    <rule element="flowspec">
+        <context select=""/>
+        <argument>
+            <name>whitelist-switch</name>
+            <help>Switch id to whitelist.</help>
+            <operand>PREFIX</operand>
+        </argument>
+
+        <argument>
+            <name>whitelist-tpsrc</name>
+            <help>Source port to whitelist.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <argument>
+            <name>whitelist-tpdst</name>
+            <help>Destination port to whitelist.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <argument>
+            <name>whitelist-ipdst</name>
+            <help>Destination IP to whitelist.</help>
+            <operand>PREFIX</operand>
+        </argument>
+        <argument>
+            <name>whitelist-ipsrc</name>
+            <help>Source IP to whitelist.</help>
+            <operand>PREFIX</operand>
+        </argument>
+
+        <processor filename="restrict_flowspec.xsl"/>
+    </rule>
+</target>
diff --git a/sfatables/targets/RESTRICT_SLICE_PROP.xml b/sfatables/targets/RESTRICT_SLICE_PROP.xml
new file mode 100644 (file)
index 0000000..4f6c146
--- /dev/null
@@ -0,0 +1,26 @@
+<target name="RESTRICT_SLICE_PROP">
+    <!-- The context is empty, since this target does not require any input from SFA -->
+    <rule element="node">
+    <context select=""/>
+        <argument>
+            <name>max-node-kbps</name>
+            <help>Maximum node bandwidth in kilobytes per second.</help>
+            <operand>kbps</operand>
+        </argument>
+        <argument>
+            <name>on-node</name>
+            <help>Node to impose the limit on. Empty => all nodes.</help>
+            <operand>kbps</operand>
+        </argument>
+    <processor filename="max_node_kbps.xsl"/>
+    </rule>
+    <rule element="link">
+    <context select=""/>
+        <argument>
+            <name>max-link-kbps</name>
+            <help>Maximum link bandwidth in kilobytes per second.</help>
+            <operand>kbps</operand>
+        </argument>
+    <processor filename="max_link_kbps.xsl"/>
+    </rule>
+    </target>
diff --git a/sfatables/test.rspec b/sfatables/test.rspec
new file mode 100644 (file)
index 0000000..387594b
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- run "runtime.py sfarspec" to apply current rule set to this file -->
+
+<RSpec>
+     <request>
+        <nodespec name="foobar">
+            <node name="plc.princeton.planetlab-01"/>
+            <node name="plc.princeton.planetlab-02"/>
+            <node name="plc.princeton.planetlab-03"/>
+            <node name="plc.princeton.planetlab-04"/>
+            <node name="plc.tp.planetlab3"/>
+        </nodespec>
+    </request>
+</RSpec>
diff --git a/sfatables/test.sfarspec b/sfatables/test.sfarspec
new file mode 100644 (file)
index 0000000..641095e
--- /dev/null
@@ -0,0 +1,16 @@
+<!-- run "runtime.py sfarspec" to apply current rule set to this file -->
+
+<RSpec>
+     <request-context><sfa><user><hrn>plc.princeton.sapanb</hrn></user></sfa></request-context>
+     <match-context><argument><name>user-hrn</name><value>plc.princeton.sapanb</value></argument></match-context>
+     <target-context><argument><name>blacklist-dns-suffix</name><value>bing</value></argument></target-context>
+     <request>
+        <NodeSpec name="boobar">
+            <node name="plc.princeton.planetlab-01"/>
+            <node name="plc.princeton.planetlab-02"/>
+            <node name="plc.princeton.planetlab-03"/>
+            <node name="plc.princeton.planetlab-04"/>
+            <node name="plc.tp.planetlab3"/>
+        </NodeSpec>
+    </request>
+</RSpec>
diff --git a/sfatables/vini.rspec b/sfatables/vini.rspec
new file mode 100644 (file)
index 0000000..ffe034c
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<RSpec type="VINI">
+  <request>
+    <sliver nodeid="n18"/>
+    <sliver nodeid="n20"/>
+    <sliver nodeid="n22"/>
+    <sliver nodeid="n26"/>
+    <sliver nodeid="n28"/>
+    <sliver nodeid="n30"/>
+    <sliver nodeid="n32"/>
+    <sliver nodeid="n34"/>
+    <sliver nodeid="n36"/>
+    <vlink endpoints="n18 n22"/>
+    <vlink endpoints="n18 n26"/>
+    <vlink endpoints="n18 n28"/>
+    <vlink endpoints="n20 n22"/>
+    <vlink endpoints="n22 n26"/>
+    <vlink endpoints="n26 n30"/>
+    <vlink endpoints="n28 n30"/>
+    <vlink endpoints="n28 n32"/>
+    <vlink endpoints="n30 n36"/>
+    <vlink endpoints="n34 n36"/>
+    <vlink endpoints="n32 n36"/>
+    <vlink endpoints="n32 n34"/>
+  </request>
+</RSpec>
diff --git a/sfatables/xmlextension.py b/sfatables/xmlextension.py
new file mode 100644 (file)
index 0000000..5e298db
--- /dev/null
@@ -0,0 +1,47 @@
+# Matches and targets are specified using XML files.
+# They provide the following information:
+#   - The context required by the match
+#   - The processor that actually implements the match or target
+#   - The parameters that the processor needs to evaluate the context
+
+import libxml2
+from sfatables.globals import *
+
+class Xmlextension:
+    def __init__(self, file_path):
+
+        self.context = ""
+        self.processor = ""
+        self.operand = "VALUE"
+        self.arguments = []
+        self.terminal = 0
+
+        self.xmldoc = libxml2.parseFile(file_path)
+
+        # TODO: Check xmldoc against a schema
+        p = self.xmldoc.xpathNewContext()
+
+        # <context select="..."/>
+        # <rule><argument param="..."/></rule>
+        # <processor name="..."/>
+
+        context = p.xpathEval('//context/@select')
+        self.context = context[0].content
+
+        processor = p.xpathEval('//processor/@filename')
+        self.context = processor[0].content
+
+        name = p.xpathEval('//rule/argument/name')
+        help = p.xpathEval('//rule/argument/help')
+        target = p.xpathEval('//rule/argument/operand')
+
+        context = p.xpathEval('//attributes/attribute[@terminal="yes"]')
+        self.terminal = (context != [])
+
+        self.arguments = map(lambda (name,help,target):{'name':name.content,'help':help.content,'target':target.content}, zip(name,help,target))
+        
+        p.xpathFreeContext()
+        self.xmldoc.freeDoc()
+
+        return
+
diff --git a/sfatables/xmlrule.py b/sfatables/xmlrule.py
new file mode 100644 (file)
index 0000000..52bea31
--- /dev/null
@@ -0,0 +1,138 @@
+import libxml2
+import libxslt
+from sfatables.globals import *
+
+class XMLRule:
+    def apply_processor(self, type, doc, output_xpath_filter=None):
+        processor = self.processors[type]
+
+        # XXX TO CLEAN UP
+        filepath = os.path.join(sfatables_config, 'processors', processor)
+        # XXX
+
+        styledoc = libxml2.parseFile(filepath)
+        style = libxslt.parseStylesheetDoc(styledoc)
+        result = style.applyStylesheet(doc, None)
+        if (output_xpath_filter):
+            p = result.xpathNewContext()
+            xpath_result = p.xpathEval(output_xpath_filter)
+            if (xpath_result == []):
+                raise Exception("Could not apply processor %s."%processor)
+
+            stylesheet_result = xpath_result
+            p.xpathFreeContext()
+        else:
+            stylesheet_result = result #style.saveResultToString(result)
+
+        style.freeStylesheet()
+        #doc.freeDoc()
+        #result.freeDoc()
+
+        return stylesheet_result
+
+    def wrap_up(self, doc):
+        filepath = os.path.join(sfatables_config, 'processors', self.final_processor)
+
+        if not os.path.exists(filepath):
+            raise Exception('Could not find final rule filter')
+
+        styledoc = libxml2.parseFile(filepath)
+        style = libxslt.parseStylesheetDoc(styledoc)
+        result = style.applyStylesheet(doc, None)
+        stylesheet_result = result#style.saveResultToString(result)
+        style.freeStylesheet()
+        #doc.freeDoc()
+        #result.freeDoc()
+
+        return stylesheet_result
+
+    def match(self, rspec):
+        match_result = self.apply_processor('match',rspec,"//result/@verdict") 
+        return (match_result[0].content=='True')
+
+    def target(self, rspec):
+        target_result = self.apply_processor('target',rspec,None)
+        return target_result
+
+    def add_rule_context_to_rspec(self, doc):
+        p = doc.xpathNewContext()
+        context = p.xpathEval("//RSpec")
+        if (not context):
+            raise Exception('Request is not an rspec')
+        else:
+            # Add the request context
+            matchNode = libxml2.newNode('match-context')
+            for match_argument in self.arguments['match']:
+                matchNode.addChild(match_argument)
+
+            targetNode = libxml2.newNode('target-context')
+            for target_argument in self.arguments['target']:
+                targetNode.addChild(target_argument)
+
+            context[0].addChild(matchNode)
+            context[0].addChild(targetNode)
+        p.xpathFreeContext()
+
+        return doc
+
+    def apply_interpreted(self, rspec):
+        rspec = self.add_rule_context_to_rspec(rspec)
+        # Interpreted
+        #
+        # output =
+        #    if (match(match_args, rspec)
+        #       then target(target_args, rspec)
+        #       else rspec
+        
+        import pdb
+        if (self.match(rspec)):
+            return (True,self.wrap_up(self.target(rspec)))
+        else:
+            return (False,self.wrap_up(rspec))
+
+
+    def apply_compiled(rspec):
+        # Not supported yet
+        return None
+
+    def load_xml_extension (self, type, chain, rule_number):
+        filename = sfatables_config+"/"+chain+"/"+"sfatables-%d-%s"%(rule_number,type)
+
+        self.xmldoc = libxml2.parseFile(filename)
+        p = self.xmldoc.xpathNewContext()
+
+        context = p.xpathEval('//context/@select')
+        self.context[type] = context[0].content
+
+        processor = p.xpathEval('//processor/@filename')
+
+        context = p.xpathEval('//attributes/attribute[@terminal="yes"]')
+        if (context != []):
+            self.terminal = 1
+        
+        self.processors[type] = processor[0].content
+        self.arguments[type] = p.xpathEval('//rule//argument[value!=""]')
+
+        p.xpathFreeContext()
+
+
+    def __init__(self, chain=None, rule_number=None):
+        self.rule_number = None
+        self.chain = None
+        self.xmldoc = None
+        self.terminal = 0
+        self.final_processor = '__sfatables_rule_wrap_up__.xsl'
+
+        self.arguments = {'match':None,'target':None}
+        self.processors = {'match':None,'target':None}
+        self.context = {'match':None,'target':None}
+
+        if (chain and rule_number):
+            self.load_xml_extension('match', chain, rule_number)
+            self.load_xml_extension('target',chain, rule_number)
+            self.rule_number = rule_number
+            self.chain = chain
+        return
+        
+    def free(self):
+        self.xmldoc.freeDoc()
diff --git a/tests/client/README b/tests/client/README
new file mode 100644 (file)
index 0000000..6d4ae3d
--- /dev/null
@@ -0,0 +1 @@
+these files used to be in geniwrapper/cmdline
diff --git a/tests/client/demoAggregate.sh b/tests/client/demoAggregate.sh
new file mode 100644 (file)
index 0000000..ccb4b5f
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+export PATH=$PATH:/etc/sfa
+source sfi_config
+CWD=$(pwd)
+
+DEMO_AUTH='planetlab.us'
+DEMO_PL_AUTH='planetlab.us.pl'
+
+echo XXXXX ------------------------------------------------
+echo XXXXX list contents of authority
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py list $DEMO_PL_AUTH
+
+echo XXXXX ------------------------------------------------
+echo XXXXX show contents of authority
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py show $DEMO_PL_AUTH
+
+
+echo XXXXX ------------------------------------------------
+echo XXXXX show users authority
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py show $DEMO_PL_AUTH
+
+echo XXXXX ------------------------------------------------
+echo XXXXX list available node +rspec+
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py nodes
+
+echo XXXXX ------------------------------------------------
+echo XXXXX list available node +dns+
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py nodes dns
+
+echo XXXXX ------------------------------------------------
+echo XXXXX list slices at the aggregate
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py slices 
+
+
+echo XXXXX ------------------------------------------------
+echo XXXXX list resources being used by pl_tmack
+echo XXXXX ------------------------------------------------
+
+python $(CWD)/sfi.py resources planetlab.us.pl.tmack
+
+
+
+
diff --git a/tests/client/testAggregate.py b/tests/client/testAggregate.py
new file mode 100644 (file)
index 0000000..0f9e77e
--- /dev/null
@@ -0,0 +1,32 @@
+from pprint import pprint
+
+from sfa.util.client import *
+from sfa.trust.credential import *
+
+cred = Credential(filename = 'tmack.pl.sa.cred')
+slicehrn = 'planetlab.us.pl.tmack'
+print cred.get_privileges().save_to_string()
+
+r = GeniClient('https://128.112.139.120:12345', 'tmack.pkey', 'tmack.cert') 
+a = GeniClient('https://128.112.139.120:12346', 'tmack.pkey', 'tmack.cert')
+
+#pprint(r.list(cred, 'planetlab.us.princeton'))
+pprint(a.get_policy(cred))
+
+print "components at this aggregate"
+components = a.list_components()
+pprint(components)
+
+print "resources being used by %(slicehrn)s" % locals()
+tmack_components = a.list_resources(cred, slicehrn)
+pprint(tmack_components)
+
+#print "removing %(slicehrn)s from all nodes" % locals()
+#a.delete_slice(cred, slicehrn)
+
+print "adding %(slicehrn)s back to its original nodes" % locals()
+a.list_resources(cred, slicehrn)
+a.create_slice(cred, slicehrn, components)
+a.list_resources(cred, slicehrn)
+
diff --git a/tests/client/testSfi.sh b/tests/client/testSfi.sh
new file mode 100644 (file)
index 0000000..2d2d180
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+export PATH=$PATH:/etc/sfa
+source sfi_config
+CWD=$(pwd)
+
+rm -f saved_record.*
+
+python $(CWD)/sfi.py show -o saved_record.$SFI_USER $SFI_USER
+python $(CWD)/sfi.py list -o saved_record.$SFI_AUTH $SFI_AUTH
+
+#rm -f saved_record.*
diff --git a/tests/client/testSfiDelegate.sh b/tests/client/testSfiDelegate.sh
new file mode 100644 (file)
index 0000000..72f6057
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+export PATH=$PATH:/etc/sfa
+source sfi_config
+CWD=$(pwd)
+
+rm -f saved_record.*
+
+python $(CWD)/sfi.py show $SFI_USER
+python $(CWD)/sfi.py delegate --user plc.arizona.gackscentral
+
+#rm -f saved_record.*
diff --git a/tests/client/testSfiSliceRegister.sh b/tests/client/testSfiSliceRegister.sh
new file mode 100644 (file)
index 0000000..0e65123
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/bash
+
+export PATH=$PATH:/etc/sfa
+source sfi_config
+CWD=$(pwd)
+
+# private key to use when creating GID for new slice
+export TEST_KEY=$CWD/testkey.pkey
+
+export TEST_SLICE_GID=$CWD/testslice.gid
+export TEST_SLICE_RECORD=$CWD/testslice.record
+export TEST_SLICE_HRN=$SFI_AUTH.testslice
+
+rm ~/.sfi/*.cred
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Removing the test slice. this will cause an error if the slice does
+echo XXXXX not exist -- this error can be ignored
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py remove --type slice $TEST_SLICE_HRN
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Creating a record for the test slice
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/editRecord.py --hrn $TEST_SLICE_HRN --pubkeyfile $TEST_KEY --type slice --addresearcher $SFI_USER --outfile $TEST_SLICE_RECORD
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Adding the test slice, $TEST_SLICE_HRN
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py add $TEST_SLICE_RECORD
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX The slice should have one researcher, $SFI_USER
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py show $TEST_SLICE_HRN
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Adding $TEST_USER_2 to the slice researchers
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/editRecord.py --infile $TEST_SLICE_RECORD --outfile $TEST_SLICE_RECORD --addresearcher $TEST_USER_2
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Updating the slice
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py update $TEST_SLICE_RECORD
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX The slice record should now have two users: $SFI_USER, $TEST_USER_2
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py show $TEST_SLICE_HRN
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Removing $SFI_USER from the slice researchers
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/editRecord.py --infile $TEST_SLICE_RECORD --outfile $TEST_SLICE_RECORD --delresearcher $SFI_USER
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Updating the slice
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py update $TEST_SLICE_RECORD
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX The slice record should now have one users: $TEST_USER_2
+echo XXXXX -------------------------------------------------------------------
+
+python $(CWD)/sfi.py show $TEST_SLICE_HRN
+
+echo XXXXX -------------------------------------------------------------------
+echo XXXXX Updating the slice
+echo XXXXX ... this verifies escalation from slice_cred to auth_cred
+echo XXXXX -------------------------------------------------------------------
+
+rm ~/.sfi/slice_testslice.cred
+python $(CWD)/sfi.py update $TEST_SLICE_RECORD
+
diff --git a/tests/testAll.py b/tests/testAll.py
new file mode 100755 (executable)
index 0000000..9990ffc
--- /dev/null
@@ -0,0 +1,12 @@
+from testRights import *
+from testCert import *
+from testGid import *
+from testCred import *
+from testKeypair import *
+from testMisc import *
+from testHierarchy import *
+from testRecord import *
+from testTable import *
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testCert.py b/tests/testCert.py
new file mode 100755 (executable)
index 0000000..0a6a90c
--- /dev/null
@@ -0,0 +1,204 @@
+import unittest
+import xmlrpclib
+from sfa.trust.certificate import Certificate, Keypair
+
+class TestCert(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testCreate(self):
+      cert = Certificate()
+      cert.create()
+
+   def testSetAndGetSubject(self):
+      cert = Certificate()
+      cert.create()
+      cert.set_subject("test")
+      subj = cert.get_subject()
+      self.assertEqual(subj, "test")
+
+   def testSign(self):
+      cert = Certificate(subject="test")
+
+      # create an issuer and sign the certificate
+      issuerKey = Keypair(create=True)
+      issuerSubject = "testissuer"
+      cert.set_issuer(issuerKey, issuerSubject)
+      cert.sign()
+
+   def testAddExtension(self):
+      cert = Certificate(subject="test")
+      cert.add_extension("subjectAltName", 0, "URI:http://foovalue")
+
+      self.assertEqual(cert.get_extension("subjectAltName"),
+                       "URI:http://foovalue")
+
+   def testSetData(self):
+      cert = Certificate(subject="test")
+      data = "this is a test"
+      cert.set_data(data)
+      self.assertEqual(cert.get_data(), data)
+
+      # try something a bit more complicated, like an xmlrpc encoding of
+      # some parameters
+      cert = Certificate(subject="test")
+      data = xmlrpclib.dumps((1, "foo", ["a", "b"], {"c": "d", "e": "f"}, True))
+      cert.set_data(data)
+      self.assertEqual(cert.get_data(), data)
+
+
+   def testSaveAndLoadString(self):
+      cert = Certificate(subject="test")
+      cert.add_extension("subjectAltName", 0, "URI:http://foovalue")
+
+      # create an issuer and sign the certificate
+      issuerKey = Keypair(create=True)
+      issuerSubject = "testissuer"
+      cert.set_issuer(issuerKey, issuerSubject)
+      cert.sign()
+
+      certstr = cert.save_to_string()
+
+      #print certstr
+
+      cert2 = Certificate()
+      cert2.load_from_string(certstr)
+
+      # read back the subject and make sure it is correct
+      subj = cert2.get_subject()
+      self.assertEqual(subj, "test")
+
+      # read back the issuer and make sure it is correct
+      issuerName = cert2.get_issuer()
+      self.assertEqual(issuerName, "testissuer")
+
+      # read back the extension and make sure it is correct
+      self.assertEqual(cert2.get_extension("subjectAltName"),
+                       "URI:http://foovalue")
+
+   def testLongExtension(self):
+      cert = Certificate(subject="test")
+
+      # should produce something around 256 KB
+      veryLongString = "URI:http://"
+      shortString = ""
+      for i in range(1, 80):
+          shortString = shortString + "abcdefghijklmnopqrstuvwxyz012345"
+      for i in range(1, 100):
+          veryLongString = veryLongString + shortString + str(i)
+
+      cert.add_extension("subjectAltName", 0, veryLongString)
+
+      # create an issuer and sign the certificate
+      issuerKey = Keypair(create=True)
+      issuerSubject = "testissuer"
+      cert.set_issuer(issuerKey, issuerSubject)
+      cert.sign()
+
+      certstr = cert.save_to_string()
+
+      cert2 = Certificate()
+      cert2.load_from_string(certstr)
+      val = cert2.get_extension("subjectAltName")
+      self.assertEqual(val, veryLongString)
+
+   def testVerify(self):
+      cert = Certificate(subject="test")
+
+      # create an issuer and sign the certificate
+      issuerKey = Keypair(create=True)
+      issuerSubject = "testissuer"
+      cert.set_issuer(issuerKey, issuerSubject)
+      cert.sign()
+
+      result = cert.verify(issuerKey)
+      self.assert_(result)
+
+      # create another key
+      issuerKey2 = Keypair(create=True)
+      issuerSubject2 = "wrongissuer"
+
+      # and make sure it doesn't verify
+      result = cert.verify(issuerKey2)
+      self.assert_(not result)
+
+      # load the cert from a string, and verify again
+      cert2 = Certificate(string = cert.save_to_string())
+      result = cert2.verify(issuerKey)
+      self.assert_(result)
+      result = cert2.verify(issuerKey2)
+      self.assert_(not result)
+
+   def test_is_signed_by(self):
+      cert1 = Certificate(subject="one")
+
+      key1 = Keypair()
+      key1.create()
+      cert1.set_pubkey(key1)
+
+      # create an issuer and sign the certificate
+      issuerKey = Keypair(create=True)
+      issuerSubject = "testissuer"
+      cert1.set_issuer(issuerKey, issuerSubject)
+      cert1.sign()
+
+      cert2 = Certificate(subject="two")
+
+      key2 = Keypair(create=True)
+      cert2.set_pubkey(key2)
+
+      cert2.set_issuer(key1, cert=cert1)
+
+      # cert2 is signed by cert1
+      self.assert_(cert2.is_signed_by_cert(cert1))
+      # cert1 is not signed by cert2
+      self.assert_(not cert1.is_signed_by_cert(cert2))
+
+   def test_parents(self):
+      cert_root = Certificate(subject="root")
+      key_root = Keypair(create=True)
+      cert_root.set_pubkey(key_root)
+      cert_root.set_issuer(key_root, "root")
+      cert_root.sign()
+
+      cert1 = Certificate(subject="one")
+      key1 = Keypair(create=True)
+      cert1.set_pubkey(key1)
+      cert1.set_issuer(key_root, "root")
+      cert1.sign()
+
+      cert2 = Certificate(subject="two")
+      key2 = Keypair(create=True)
+      cert2.set_pubkey(key2)
+      cert2.set_issuer(key1, cert=cert1)
+      cert2.set_parent(cert1)
+      cert2.sign()
+
+      cert3 = Certificate(subject="three")
+      key3 = Keypair(create=True)
+      cert3.set_pubkey(key3)
+      cert3.set_issuer(key2, cert=cert2)
+      cert3.set_parent(cert2)
+      cert3.sign()
+
+      self.assert_(cert1.verify(key_root))
+      self.assert_(cert2.is_signed_by_cert(cert1))
+      self.assert_(cert3.is_signed_by_cert(cert2))
+
+      cert3.verify_chain([cert_root])
+
+      # now save the chain to a string and load it into a new certificate
+      str_chain = cert3.save_to_string(save_parents=True)
+      cert4 = Certificate(string = str_chain)
+
+      # verify the newly loaded chain still verifies
+      cert4.verify_chain([cert_root])
+
+      # verify the parentage
+      self.assertEqual(cert4.get_parent().get_subject(), "two")
+      self.assertEqual(cert4.get_parent().get_parent().get_subject(), "one")
+
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testCred.py b/tests/testCred.py
new file mode 100755 (executable)
index 0000000..32fac4a
--- /dev/null
@@ -0,0 +1,173 @@
+import unittest
+from sfa.trust.credential import *
+from sfa.trust.rights import *
+from sfa.trust.gid import *
+from sfa.trust.certificate import *
+
+class TestCred(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testCreate(self):
+      cred = Credential(create=True)
+
+   def testDefaults(self):
+      cred = Credential(subject="testCredential")
+
+      self.assertEqual(cred.get_gid_caller(), None)
+      self.assertEqual(cred.get_gid_object(), None)
+
+   def testLoadSave(self):
+      cred = Credential(subject="testCredential")
+
+      gidCaller = GID(subject="caller", uuid=create_uuid(), hrn="foo.caller")
+      gidObject = GID(subject="object", uuid=create_uuid(), hrn="foo.object")
+      lifeTime = 12345
+      delegate = True
+      rights = "embed:1,bind:1"
+
+      cred.set_gid_caller(gidCaller)
+      self.assertEqual(cred.get_gid_caller().get_subject(), gidCaller.get_subject())
+
+      cred.set_gid_object(gidObject)
+      self.assertEqual(cred.get_gid_object().get_subject(), gidObject.get_subject())
+
+      cred.set_lifetime(lifeTime)
+      
+      cred.set_privileges(rights)
+      self.assertEqual(cred.get_privileges().save_to_string(), rights)
+
+      cred.get_privileges().delegate_all_privileges(delegate)
+
+      cred.encode()
+
+      cred_str = cred.save_to_string()
+
+      # re-load the credential from a string and make sure its fields are
+      # intact
+      cred2 = Credential(string = cred_str)
+      self.assertEqual(cred2.get_gid_caller().get_subject(), gidCaller.get_subject())
+      self.assertEqual(cred2.get_gid_object().get_subject(), gidObject.get_subject())
+      self.assertEqual(cred2.get_privileges().get_all_delegate(), delegate)
+      self.assertEqual(cred2.get_privileges().save_to_string(), rights)
+
+
+
+   def createSignedGID(self, subject, urn, issuer_pkey = None, issuer_gid = None):
+      gid = GID(subject=subject, uuid=1, urn=urn)
+      keys = Keypair(create=True)
+      gid.set_pubkey(keys)
+      if issuer_pkey:
+         gid.set_issuer(issuer_pkey, str(issuer_gid.get_issuer()))
+      else:
+         gid.set_issuer(keys, subject)
+
+      gid.encode()
+      gid.sign()
+      return gid, keys
+
+   
+   
+
+   def testDelegationAndVerification(self):
+      gidAuthority, keys = self.createSignedGID("site", "urn:publicid:IDN+plc+authority+site")
+      gidCaller, ckeys = self.createSignedGID("site.foo", "urn:publicid:IDN+plc:site+user+foo",
+                                          keys, gidAuthority)
+      gidObject, _ = self.createSignedGID("site.slice", "urn:publicid:IDN+plc:site+slice+bar_slice",
+                                          keys, gidAuthority)
+      gidDelegatee, _ = self.createSignedGID("site.delegatee", "urn:publicid:IDN+plc:site+user+delegatee",
+                                             keys, gidAuthority)
+
+      cred = Credential()
+      cred.set_gid_caller(gidCaller)
+      cred.set_gid_object(gidObject)
+      cred.set_lifetime(3600)
+      cred.set_privileges("embed:1, bind:1")
+      cred.encode()
+
+      gidAuthority.save_to_file("/tmp/auth_gid")
+      keys.save_to_file("/tmp/auth_key")
+      cred.set_issuer_keys("/tmp/auth_key", "/tmp/auth_gid")
+      cred.sign()
+
+
+      cred.verify(['/tmp/auth_gid'])
+
+      # Test copying
+      cred2 = Credential(string=cred.save_to_string())
+      cred2.verify(['/tmp/auth_gid'])
+
+
+      # Test delegation
+      delegated = Credential()
+      delegated.set_gid_caller(gidDelegatee)
+      delegated.set_gid_object(gidObject)      
+      delegated.set_parent(cred)
+      delegated.set_lifetime(600)
+      delegated.set_privileges("embed:1, bind:1")
+      gidCaller.save_to_file("/tmp/caller_gid")
+      ckeys.save_to_file("/tmp/caller_pkey")      
+      
+      delegated.set_issuer_keys("/tmp/caller_pkey", "/tmp/caller_gid")
+
+      delegated.encode()
+
+      delegated.sign()
+      
+      # This should verify
+      delegated.verify(['/tmp/auth_gid'])
+
+      backup = Credential(string=delegated.get_xml())
+
+      # Test that verify catches an incorrect lifetime      
+      delegated.set_lifetime(6000)
+      delegated.encode()
+      delegated.sign()
+      try:
+         delegated.verify(['/tmp/auth_gid'])
+         assert(1==0)
+      except CredentialNotVerifiable:
+         pass
+
+      # Test that verify catches an incorrect signer
+      delegated = Credential(string=backup.get_xml())
+      delegated.set_issuer_keys("/tmp/auth_key", "/tmp/auth_gid")
+      delegated.encode()
+      delegated.sign()
+
+      try:
+         delegated.verify(['/tmp/auth_gid'])
+         assert(1==0)
+      except CredentialNotVerifiable:
+         pass
+
+
+      # Test that verify catches a changed gid
+      delegated = Credential(string=backup.get_xml())
+      delegated.set_gid_object(delegated.get_gid_caller())
+      delegated.encode()
+      delegated.sign()
+
+      try:
+         delegated.verify(['/tmp/auth_gid'])
+         assert(1==0)
+      except CredentialNotVerifiable:
+         pass
+
+
+      # Test that verify catches a credential with the wrong authority for the object
+      test = Credential(string=cred.get_xml())
+      test.set_issuer_keys("/tmp/caller_pkey", "/tmp/caller_gid")
+      test.encode()
+      test.sign()
+
+      try:
+         test.verify(['/tmp/auth_gid'])
+         assert(1==0)
+      except CredentialNotVerifiable:
+         pass      
+      
+      # Test that * gets translated properly
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testGid.py b/tests/testGid.py
new file mode 100755 (executable)
index 0000000..85f9240
--- /dev/null
@@ -0,0 +1,63 @@
+import unittest
+import xmlrpclib
+from sfa.trust.certificate import Keypair
+from sfa.trust.gid import *
+
+class TestGid(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testSetGetHrn(self):
+      gid = GID(subject="test")
+      hrn = "test.hrn"
+
+      gid.set_hrn(hrn)
+      self.assertEqual(gid.get_hrn(), hrn)
+
+   def testSetGetUuid(self):
+      gid = GID(subject="test")
+      u = uuid.uuid4().int
+
+      gid.set_uuid(u)
+      self.assertEqual(gid.get_uuid(), u)
+
+   def testEncodeDecode(self):
+      gid = GID(subject="test")
+      u = uuid.uuid4().int
+      hrn = "test.hrn"
+
+      gid.set_uuid(u)
+      gid.set_hrn(hrn)
+
+      gid.encode()
+      gid.decode()
+
+      self.assertEqual(gid.get_hrn(), hrn)
+      self.assertEqual(gid.get_uuid(), u)
+
+   def testSaveAndLoadString(self):
+      gid = GID(subject="test")
+
+      u = uuid.uuid4().int
+      hrn = "test.hrn"
+
+      gid.set_uuid(u)
+      gid.set_hrn(hrn)
+
+      # create an issuer and sign the certificate
+      issuerKey =  Keypair(create = True)
+      issuerSubject = "testissuer"
+      gid.set_issuer(issuerKey, issuerSubject)
+      gid.sign()
+
+      certstr = gid.save_to_string()
+
+      #print certstr
+
+      gid2 = GID(string = certstr)
+
+      self.assertEqual(gid.get_hrn(), hrn)
+      self.assertEqual(gid.get_uuid(), u)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testHierarchy.py b/tests/testHierarchy.py
new file mode 100755 (executable)
index 0000000..723c6db
--- /dev/null
@@ -0,0 +1,51 @@
+import unittest
+import os
+from sfa.util.faults import *
+from sfa.trust.hierarchy import *
+from sfa.util.config import *
+
+BASEDIR = "test_hierarchy"
+PURGE_BASEDIR = "rm -rf test_hierarchy"
+
+class TestHierarchy(unittest.TestCase):
+    def setUp(self):
+        os.system(PURGE_BASEDIR)
+        pass
+
+    def testInit(self):
+        h = Hierarchy(BASEDIR)
+
+    def testGetAuthInfo(self):
+        h = Hierarchy(BASEDIR)
+
+        name = "planetlab.us.arizona.stork"
+
+        self.assertEqual(h.auth_exists(name), False)
+
+        self.assertRaises(MissingAuthority, h.get_auth_info, name)
+
+        h.create_auth(name, create_parents=True)
+        auth_info = h.get_auth_info(name)
+        self.assert_(auth_info)
+
+        gid = auth_info.get_gid_object()
+        self.assert_(gid)
+        self.assertEqual(gid.get_subject(), name)
+
+        pubkey = auth_info.get_pkey_object()
+        self.assert_(gid)
+
+        # try to get it again, make sure it's still there
+        auth_info2 = h.get_auth_info(name)
+        self.assert_(auth_info2)
+
+        gid = auth_info2.get_gid_object()
+        self.assert_(gid)
+        self.assertEqual(gid.get_subject(), name)
+
+        pubkey = auth_info2.get_pkey_object()
+        self.assert_(gid)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testInterfaces.py b/tests/testInterfaces.py
new file mode 100755 (executable)
index 0000000..c43da2c
--- /dev/null
@@ -0,0 +1,321 @@
+#!/usr/bin/python
+import sys
+import os
+import random
+import string
+import unittest
+import sfa.util.xmlrpcprotocol as xmlrpc
+from unittest import TestCase
+from optparse import OptionParser
+from sfa.util.xmlrpcprotocol import ServerException
+from sfa.util.namespace import *
+from sfa.util.config import *
+from sfa.trust.certificate import *
+from sfa.trust.credential import *
+from sfa.util.sfaticket import *
+from sfa.util.rspec import *
+from sfa.client import sfi
+
+def random_string(size):
+    return "".join(random.sample(string.letters, size))
+
+class Client:
+    registry = None
+    aggregate = None
+    sm = None
+    cm = None
+    key = None
+    cert = None
+    credential = None
+    type = None            
+    def __init__(self, options):
+        try: self.config = config = Config(options.config_file)
+        except:
+            print "failed to read config_file %s" % options.config_file
+            sys.exit(1)
+        key_path = os.path.dirname(options.config_file)
+        user_name = self.config.SFI_USER.split('.')[-1:][0]
+        key_file = key_path + os.sep + user_name + '.pkey'
+        cert_file = key_path + os.sep + user_name + '.cert'
+        self.key = Keypair(filename=key_file)
+        self.cert = Certificate(subject=self.config.SFI_USER)
+        self.cert.set_pubkey(self.key)
+        self.cert.set_issuer(self.key, self.config.SFI_USER)
+        self.cert.sign()
+        self.cert.save_to_file(cert_file)        
+        SFI_AGGREGATE = config.SFI_SM.replace('12347', '12346')
+        SFI_CM = 'http://' + options.cm_host + ':12346'
+        self.registry = xmlrpc.get_server(config.SFI_REGISTRY, key_file, cert_file)
+        self.aggregate = xmlrpc.get_server(SFI_AGGREGATE, key_file, cert_file)
+        self.sm = xmlrpc.get_server(config.SFI_SM, key_file, cert_file)
+        self.cm = xmlrpc.get_server(SFI_CM, key_file, cert_file)
+        self.hrn = config.SFI_USER
+        # XX defaulting to user, but this should be configurable so we can
+        # test from components persepctive
+        self.type = 'user'
+        self.credential = self.get_credential(self.hrn)
+        
+    def get_credential(self, hrn = None, type = 'user'):
+        if not hrn: hrn = self.hrn 
+        if hrn == self.hrn:
+            cert = self.cert.save_to_string(save_parents=True)
+            request_hash = self.key.compute_hash([cert, 'user', hrn])
+            credential = self.registry.get_self_credential(cert, type, hrn, request_hash)
+            return credential
+        else:
+            if not self.credential:
+                self.credential = self.get_credential(self.hrn, 'user')
+            return self.registry.get_credential(self.credential, type, hrn)     
+
+class BasicTestCase(unittest.TestCase):
+    def __init__(self, testname, client, test_slice=None):
+        unittest.TestCase.__init__(self, testname)
+        self.client = client
+        self.slice = test_slice
+    
+    def setUp(self):
+        self.registry = self.client.registry
+        self.aggregate = self.client.aggregate
+        self.sm = self.client.sm
+        self.cm = self.client.cm
+        self.credential = self.client.credential
+        self.hrn = self.client.hrn
+        self.type = self.client.type  
+                
+# Registry tests
+class RegistryTest(BasicTestCase):
+
+    def setUp(self):
+        """
+        Make sure test records dont exsit
+        """
+        BasicTestCase.setUp(self)
+
+    def testGetSelfCredential(self):
+        cred = self.client.get_credential()
+        # this will raise an openssl error if the credential string isnt valid
+        Credential(string=cred)
+
+    def testRegister(self):
+        authority = get_authority(self.hrn)
+        auth_cred = self.client.get_credential(authority, 'authority')
+        auth_record = {'hrn': '.'.join([authority, random_string(10).lower()]),
+                       'type': 'authority'}
+        node_record = {'hrn': '.'.join([authority, random_string(10)]),
+                       'type': 'node',
+                       'hostname': random_string(6) + '.' + random_string(6)}
+        slice_record = {'hrn': '.'.join([authority, random_string(10)]),
+                        'type': 'slice', 'researcher': [self.hrn]}
+        user_record = {'hrn': '.'.join([authority, random_string(10)]),
+                       'type': 'user',
+                       'email': random_string(6) +'@'+ random_string(5) +'.'+ random_string(3),
+                       'first_name': random_string(7),
+                       'last_name': random_string(7)}
+
+        all_records = [auth_record, node_record, slice_record, user_record]
+        for record in all_records:
+            try:
+                self.registry.register(auth_cred, record)
+                self.registry.resolve(self.credential, record['hrn'])
+            except:
+                raise
+            finally:
+                try: self.registry.remove(auth_cred, record['type'], record['hrn'])
+                except: pass
+
+    
+    def testRegisterPeerObject(self):
+        assert True
+   
+    def testUpdate(self):
+        authority = get_authority(self.hrn)
+        auth_cred = self.client.get_credential(authority, 'authority')
+        records = self.registry.resolve(self.credential, self.hrn)
+        if not records: assert False
+        record = records[0]
+        self.registry.update(auth_cred, record) 
+
+    def testResolve(self):
+        authority = get_authority(self.hrn)
+        self.registry.resolve(self.credential, self.hrn)
+   
+    def testRemove(self):
+        authority = get_authority(self.hrn)
+        auth_cred = self.client.get_credential(authority, 'authority')
+        record = {'hrn': ".".join([authority, random_string(10)]),
+                       'type': 'slice'}
+        self.registry.register(auth_cred, record)
+        self.registry.remove(auth_cred, record['type'], record['hrn'])
+        # should generate an exception
+        try:
+            self.registry.resolve(self.credential,  record['hrn'])
+            assert False
+        except:       
+            assert True
+    def testRemovePeerObject(self):
+        assert True
+
+    def testList(self):
+        authority = get_authority(self.client.hrn)
+        self.registry.list(self.credential, authority)
+             
+    def testGetRegistries(self):
+        self.registry.get_registries(self.credential)
+    
+    def testGetAggregates(self):
+        self.registry.get_aggregates(self.credential)
+
+    def testGetTrustedCerts(self):
+        # this should fail unless we are a node
+        callable = self.registry.get_trusted_certs
+        server_exception = False 
+        try:
+            callable(self.credential)
+        except ServerException:
+            server_exception = True
+        finally:
+            if self.type in ['user'] and not server_exception:
+                assert False
+            
+
+class AggregateTest(BasicTestCase):
+    def setUp(self):
+        BasicTestCase.setUp(self)
+        
+    def testGetSlices(self):
+        self.aggregate.get_slices(self.credential)
+
+    def testGetResources(self):
+        # available resources
+        agg_rspec = self.aggregate.get_resources(self.credential)
+        # resources used by a slice
+        slice_rspec = self.aggregate.get_resources(self.credential, self.slice['hrn'])
+        # will raise an exception if the rspec isnt valid
+        RSpec(xml=agg_rspec)
+        RSpec(xml=slice_rspec)
+
+    def testCreateSlice(self):
+        # get availabel resources   
+        rspec = self.aggregate.get_resources(self.credential)
+        slice_credential = self.client.get_credential(self.slice['hrn'], 'slice')
+        self.aggregate.create_slice(slice_credential, self.slice['hrn'], rspec)
+
+    def testDeleteSlice(self):
+        slice_credential = self.client.get_credential(self.slice['hrn'], 'slice')
+        self.aggregate.delete_slice(slice_credential, self.slice['hrn'])
+
+    def testGetTicket(self):
+        slice_credential = self.client.get_credential(self.slice['hrn'], 'slice')
+        rspec = self.aggregate.get_resources(self.credential)
+        ticket = self.aggregate.get_ticket(slice_credential, self.slice['hrn'], rspec)
+        # will raise an exception if the ticket inst valid
+        SfaTicket(string=ticket)        
+
+class SlicemgrTest(AggregateTest):
+    def setUp(self):
+        AggregateTest.setUp(self)
+        
+        # force calls to go through slice manager   
+        self.aggregate = self.sm
+
+        # get the slice credential
+        
+
+class ComponentTest(BasicTestCase):
+    def setUp(self):
+        BasicTestCase.setUp(self)
+        self.slice_cred = self.client.get_credential(self.slice['hrn'], 'slice')
+
+    def testStartSlice(self):
+        self.cm.start_slice(self.slice_cred, self.slice['hrn'])
+
+    def testStopSlice(self):
+        self.cm.stop_slice(self.slice_cred, self.slice['hrn'])
+
+    def testDeleteSlice(self):
+        self.cm.delete_slice(self.slice_cred, self.slice['hrn'])
+
+    def testRestartSlice(self):
+        self.cm.restart_slice(self.slice_cred, self.slice['hrn'])
+
+    def testGetSlices(self):
+        self.cm.get_slices(self.slice_cred, self.slice['hrn'])
+
+    def testRedeemTicket(self):
+        rspec = self.aggregate.get_resources(self.credential)
+        ticket = self.aggregate.get_ticket(slice_cred, self.slice['hrn'], rspec)
+        self.cm.redeem_ticket(slice_cred, ticket)
+
+
+def test_names(testcase):
+    return [name for name in dir(testcase) if name.startswith('test')]
+
+def create_slice(client):
+    # register a slice that will be used for some test
+    authority = get_authority(client.hrn)
+    auth_cred = client.get_credential(authority, 'authority')
+    slice_record = {'hrn': ".".join([authority, random_string(10)]),
+                    'type': 'slice', 'researcher': [client.hrn]}
+    client.registry.register(auth_cred, slice_record)
+    return  slice_record
+def delete_slice(cleint, slice):
+    authority = get_authority(client.hrn)
+    auth_cred = client.get_credential(authority, 'authority')
+    if slice:
+        client.registry.remove(auth_cred, 'slice', slice['hrn'])
+    
+if __name__ == '__main__':
+
+    args = sys.argv
+    prog_name = args[0]
+    default_config_dir = os.path.expanduser('~/.sfi/sfi_config')
+    default_cm = "echo.cs.princeton.edu"
+    parser = OptionParser(usage="%(prog_name)s [options]" % locals())
+    parser.add_option('-f', '--config_file', dest='config_file', default=default_config_dir,
+                      help='config file. default is %s' % default_config_dir)
+    parser.add_option('-r', '--registry', dest='registry', action='store_true',
+                      default=False, help='run registry tests')
+    parser.add_option('-a', '--aggregate', dest='aggregate', action='store_true',
+                      default=False, help='run aggregate tests')
+    parser.add_option('-s', '--slicemgr', dest='slicemgr', action='store_true',
+                      default=False, help='run slicemgr tests')
+    parser.add_option('-c', '--component', dest='component', action='store_true',
+                      default=False, help='run component tests')
+    parser.add_option('-d', '--cm_host', dest='cm_host', default=default_cm, 
+                      help='dns name of component to test. default is %s' % default_cm)
+    parser.add_option('-A', '--all', dest='all', action='store_true',
+                      default=False, help='run component tests')
+    
+    options, args = parser.parse_args()
+    suite = unittest.TestSuite()
+    client = Client(options)
+    test_slice = {}
+    
+    # create the test slice if necessary
+    if options.all or options.slicemgr or options.aggregate \
+       or options.component:
+        test_slice = create_slice(client)
+
+    if options.registry or options.all:
+        for name in test_names(RegistryTest):
+            suite.addTest(RegistryTest(name, client))
+
+    if options.aggregate or options.all: 
+        for name in test_names(AggregateTest):
+            suite.addTest(AggregateTest(name, client, test_slice))
+
+    if options.slicemgr or options.all: 
+        for name in test_names(SlicemgrTest):
+            suite.addTest(SlicemgrTest(name, client, test_slice))
+
+    if options.component or options.all: 
+        for name in test_names(ComponentTest):
+            suite.addTest(ComponentTest(name, client, test_slice))
+    
+    # run tests 
+    unittest.TextTestRunner(verbosity=2).run(suite)
+
+    # remove teset slice
+    delete_slice(client, test_slice)
diff --git a/tests/testKeypair.py b/tests/testKeypair.py
new file mode 100755 (executable)
index 0000000..ebe1ede
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+import sys
+sys.path.append('..')
+
+import unittest
+import xmlrpclib
+import base64
+from sfa.trust.certificate import Keypair
+
+class TestKeypair(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testCreate(self):
+      k = Keypair()
+      k.create()
+
+   def testSaveLoadFile(self):
+      k = Keypair()
+      k.create()
+
+      k.save_to_file("test.key")
+
+      k2 = Keypair()
+      k2.load_from_file("test.key")
+
+      self.assertEqual(k.as_pem(), k2.as_pem())
+
+   def test_get_m2_pkey(self):
+      k = Keypair()
+      k.create()
+
+      m2 = k.get_m2_pkey()
+      self.assert_(m2 != None)
+
+   def test_get_openssl_pkey(self):
+      k = Keypair()
+      k.create()
+
+      pk = k.get_openssl_pkey()
+      self.assert_(pk != None)
+
+   def test_sign_verify(self):
+      k = Keypair()
+      k.create()
+
+      data = "this is a test"
+      sig = k.sign_string(data)
+
+      print k.verify_string(data, sig)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testMisc.py b/tests/testMisc.py
new file mode 100755 (executable)
index 0000000..831d949
--- /dev/null
@@ -0,0 +1,18 @@
+import unittest
+from sfa.util.misc import *
+
+class TestMisc(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testGetLeft(self):
+      self.assertEqual(get_leaf("foo"), "foo")
+      self.assertEqual(get_leaf("foo.bar"), "bar")
+
+   def testGetAuthority(self):
+      self.assertEqual(get_authority("foo"), "")
+      self.assertEqual(get_authority("foo.bar"), "foo")
+      self.assertEqual(get_authority("foo.bar.x"), "foo.bar")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testRecord.py b/tests/testRecord.py
new file mode 100755 (executable)
index 0000000..081a9bf
--- /dev/null
@@ -0,0 +1,15 @@
+import unittest
+import xmlrpclib
+from sfa.trust.gid import *
+from sfa.trust.config import *
+from sfa.util.record import *
+
+class TestRecord(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def testCreate(self):
+        r = SfaRecord()
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/testRights.py b/tests/testRights.py
new file mode 100755 (executable)
index 0000000..a67a25f
--- /dev/null
@@ -0,0 +1,86 @@
+import unittest
+from sfa.trust.rights import *
+
+class TestRight(unittest.TestCase):
+   def setUp(self):
+      pass
+
+   def testRightInit(self):
+      right = Right("embed")
+      self.assertEqual(right.kind, "embed")
+
+   def testRightCanPerform(self):
+      right = Right("embed")
+      self.assert_(right.can_perform("getticket"))
+      self.assert_(not right.can_perform("resolve"))
+
+   def testIsSuperset(self):
+      pright = Right("sa")
+      cright = Right("embed")
+      self.assert_(pright.is_superset(cright))
+      self.assert_(not cright.is_superset(pright))
+
+      pright = Right("embed")
+      cright = Right("embed")
+      self.assert_(pright.is_superset(cright))
+      self.assert_(cright.is_superset(pright))
+
+      pright = Right("control")
+      cright = Right("embed")
+      self.assert_(not pright.is_superset(cright))
+      self.assert_(not cright.is_superset(pright))
+
+class TestRightList(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def testInit(self):
+        # create a blank right list
+        rightList = RightList()
+
+        # create a right list with "embed" in it
+        rightList = RightList(string="embed")
+
+    def testAsString(self):
+        rightList = RightList()
+        self.assertEqual(rightList.save_to_string(), "")
+
+        rightList = RightList(string="embed")
+        self.assertEqual(rightList.save_to_string(), "embed")
+
+        rightList = RightList(string="embed,resolve")
+        self.assertEqual(rightList.save_to_string(), "embed,resolve")
+
+    def testCanPerform(self):
+        rightList = RightList(string="embed")
+        self.assert_(rightList.can_perform("getticket"))
+        self.assert_(not rightList.can_perform("resolve"))
+
+        rightList = RightList(string="embed,resolve")
+        self.assert_(rightList.can_perform("getticket"))
+        self.assert_(rightList.can_perform("resolve"))
+
+    def testIsSuperset(self):
+        pRightList = RightList(string="sa")
+        cRightList = RightList(string="embed")
+        self.assert_(pRightList.is_superset(cRightList))
+        self.assert_(not cRightList.is_superset(pRightList))
+
+        pRightList = RightList(string="embed")
+        cRightList = RightList(string="embed")
+        self.assert_(pRightList.is_superset(cRightList))
+        self.assert_(cRightList.is_superset(pRightList))
+
+        pRightList = RightList(string="control")
+        cRightList = RightList(string="embed")
+        self.assert_(not pRightList.is_superset(cRightList))
+        self.assert_(not cRightList.is_superset(pRightList))
+
+        pRightList = RightList(string="control,sa")
+        cRightList = RightList(string="embed")
+        self.assert_(pRightList.is_superset(cRightList))
+        self.assert_(not cRightList.is_superset(pRightList))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/wsdl/Makefile b/wsdl/Makefile
new file mode 100644 (file)
index 0000000..1abba02
--- /dev/null
@@ -0,0 +1,49 @@
+# attempt to update at build-time
+
+WSDLS = sfa.wsdl registry.wsdl slice-manager.wsdl aggregate.wsdl 
+
+all: $(WSDLS)
+
+# temporary: turn off wsdl generation as it is failing
+#TEMPORARY_OFF = yes
+
+ifdef TEMPORARY_OFF
+registry.wsdl slice-manager.wsdl aggregate.wsdl sfa.wsdl:
+       touch $@
+else
+registry.wsdl:
+       PYTHONPATH=../ ./sfa2wsdl.py --registry > $@
+
+slice-manager.wsdl:
+       PYTHONPATH=../ ./sfa2wsdl.py --slice-manager > $@
+
+aggregate.wsdl:
+       PYTHONPATH=../ ./sfa2wsdl.py --aggregate > $@
+
+sfa.wsdl:
+       PYTHONPATH=../ ./sfa2wsdl.py --registry --slice-manager --aggregate > $@
+endif
+
+#################### install
+# DESTDIR set but the calling Makefile in ../
+INSTALL_PATH=/var/www/html/wsdl
+INSTALLED=$(foreach wsdl,$(WSDLS),$(DESTDIR)$(INSTALL_PATH)/$(wsdl))
+
+install: $(INSTALLED)
+
+$(DESTDIR)$(INSTALL_PATH):
+       install -d -m 0755 $(DESTDIR)$(INSTALL_PATH)
+
+$(DESTDIR)$(INSTALL_PATH)/%: wsdl=$(notdir $@)
+$(DESTDIR)$(INSTALL_PATH)/%: $(DESTDIR)$(INSTALL_PATH) %
+       install -c -m 0755 $(wsdl) $@
+
+#################### clean
+clean:
+       rm -f *wsdl
+
+install-clean:
+       rm -f $(INSTALLED)
+
+.PHONY: all clean install install-clean
+
diff --git a/wsdl/apistub.py b/wsdl/apistub.py
new file mode 100644 (file)
index 0000000..198883f
--- /dev/null
@@ -0,0 +1,12 @@
+import sfa.methods
+
+
+methods = sfa.methods.all
+
+def callable(method):
+    classname = method.split(".")[-1]
+    module = __import__("sfa.methods." + method, globals(), locals(), [classname])
+    callablemethod = getattr(module, classname)(None)
+    return getattr(module, classname)(None)
+
+
diff --git a/wsdl/globals.py b/wsdl/globals.py
new file mode 100644 (file)
index 0000000..36c1042
--- /dev/null
@@ -0,0 +1,4 @@
+#!/usr/bin/python
+
+plc_ns="http://www.planet-lab.org/sfa"
+version="2009/07"
diff --git a/wsdl/sfa2wsdl.py b/wsdl/sfa2wsdl.py
new file mode 100755 (executable)
index 0000000..f70c7a1
--- /dev/null
@@ -0,0 +1,343 @@
+#!/usr/bin/python
+#
+# Sapan Bhatia <sapanb@cs.princeton.edu>
+#
+# Generates a WSDL for sfa
+
+
+import os, sys
+import time
+import pdb
+import xml.dom.minidom
+import xml.dom.ext
+import apistub
+import inspect
+
+from types import *
+from optparse import OptionParser
+
+from sfa.trust.auth import Auth
+from sfa.util.parameter import Parameter,Mixed
+
+import globals
+
+class SoapError(Exception):
+     def __init__(self, value):
+         self.value = value
+     def __str__(self):
+         return repr(self.value)
+try:
+    set
+except NameError:
+    from sets import Set
+    set = Set
+
+class WSDLGen:
+    complex_types = {}
+    services = {}
+    num_types = 0
+    wsdl = None
+    types = None
+
+    def __init__(self, interface_options):
+        self.interface_options = interface_options
+
+    def filter_argname(self,argname):
+        if (not self.interface_options.lite or (argname!="cred")):
+            if (argname.find('(') != -1):
+                # The name has documentation in it :-/
+                brackright = argname.split('(')[1]
+                if (brackright.find(')') == -1):
+                        raise Exception("Please fix the argument %s to be well-formed.\n"%argname)
+                inbrack = brackright.split(')')[0]
+                argname = inbrack
+        return argname
+
+    def fold_complex_type_names(self,acc, arg):
+        name = arg.doc
+        if (type(acc)==list):
+            acc.append(name)
+        else:
+            p_i_b = acc.doc
+            acc = [p_i_b,name]
+        return acc
+
+    def fold_complex_type(self,acc, arg):
+        name = self.name_complex_type(arg)
+        self.complex_types[arg]=name
+        if (type(acc)==list):
+            acc.append(name)
+        else:
+            p_i_b = self.name_complex_type(acc)
+            acc = [p_i_b,name]
+        return acc
+
+    def name_complex_type(self,arg):
+
+        types_section = self.types.getElementsByTagName("xsd:schema")[0]
+
+        #pdb.set_trace()
+        if (isinstance(arg, Mixed)):
+            inner_types = reduce(self.fold_complex_type, arg)
+            inner_names = reduce(self.fold_complex_type_names, arg)
+            if (inner_types[-1]=="none"):
+                inner_types=inner_types[:-1]
+                min_args = 0
+            else:
+                min_args = 1
+        
+            self.num_types=self.num_types+1
+            type_name = "Type%d"%self.num_types
+            complex_type = types_section.appendChild(self.types.createElement("xsd:complexType"))
+            complex_type.setAttribute("name", type_name)
+
+            choice = complex_type.appendChild(self.types.createElement("xsd:choice"))
+            for n,t in zip(inner_names,inner_types):
+                element = choice.appendChild(self.types.createElement("element"))
+                n = self.filter_argname(n)
+                element.setAttribute("name", n)
+                element.setAttribute("type", "%s"%t)
+                element.setAttribute("minOccurs","%d"%min_args)
+            return "xsdl:%s"%type_name
+        elif (isinstance(arg, Parameter)):
+            return (self.name_simple_type(arg.type))
+        elif type(arg) == ListType or type(arg) == TupleType:
+            inner_type = self.name_complex_type(arg[0]) 
+            self.num_types=self.num_types+1
+            type_name = "Type%d"%self.num_types
+            complex_type = types_section.appendChild(self.types.createElement("xsd:complexType"))
+            type_name = self.filter_argname(type_name)
+            complex_type.setAttribute("name", type_name)
+            complex_content = complex_type.appendChild(self.types.createElement("xsd:complexContent"))
+            restriction = complex_content.appendChild(self.types.createElement("xsd:restriction"))
+            restriction.setAttribute("base","soapenc:Array")
+            attribute = restriction.appendChild(self.types.createElement("xsd:attribute"))
+            attribute.setAttribute("ref","soapenc:arrayType")
+            attribute.setAttribute("wsdl:arrayType","%s[]"%inner_type)
+
+            return "xsdl:%s"%type_name
+
+        elif type(arg) == DictType or arg == DictType or (inspect.isclass(arg) and issubclass(arg, dict)):
+            self.num_types=self.num_types+1
+            type_name = "Type%d"%self.num_types
+            complex_type = types_section.appendChild(self.types.createElement("xsd:complexType"))
+            type_name = self.filter_argname(type_name)
+            complex_type.setAttribute("name", type_name)
+            complex_content = complex_type.appendChild(self.types.createElement("xsd:sequence"))
+     
+            for k in arg.fields:
+                inner_type = self.name_complex_type(arg.fields[k]) 
+                element=complex_content.appendChild(self.types.createElement("xsd:element"))
+                element.setAttribute("name",k)
+                element.setAttribute("type",inner_type)
+
+            return "xsdl:%s"%type_name 
+        else:
+            return (self.name_simple_type(arg))
+
+    def name_simple_type(self,arg_type):
+        # A Parameter is reported as an instance, even though it is serialized as a type <>
+        if type(arg_type) == InstanceType:
+            return (self.name_simple_type(arg_type.type))
+        if arg_type == None:
+            return "none"
+        if arg_type == DictType:
+            return "xsd:anyType"
+        if arg_type in (ListType, TupleType):
+            return "xsd:arrayType"
+        elif arg_type == IntType or arg_type == LongType:
+            return "xsd:int"
+        elif arg_type == bool:
+            return "xsd:boolean"
+        elif arg_type == FloatType:
+            return "xsd:double"
+        elif arg_type in StringTypes:
+            return "xsd:string"
+        else:
+           pdb.set_trace()
+           raise SoapError, "Cannot handle %s objects" % arg_type
+
+    def param_type(self, arg):
+        return (self.name_complex_type(arg))
+
+    def add_wsdl_ports_and_bindings (self):
+        for method in apistub.methods:
+
+            # Skip system. methods
+            if "system." in method:
+                continue
+
+            function = apistub.callable(method) # Commented documentation
+            #lines = ["// " + line.strip() for line in function.__doc__.strip().split("\n")]
+            #print "\n".join(lines)
+            #print
+
+            
+            in_el = self.wsdl.firstChild.appendChild(self.wsdl.createElement("message"))
+            in_el.setAttribute("name", method + "_in")
+
+            for service_name in function.interfaces:
+                if (self.services.has_key(service_name)):
+                    if (not method in self.services[service_name]):
+                        self.services[service_name].append(method)
+                else:
+                    self.services[service_name]=[method]
+
+            # Arguments
+
+            if (function.accepts):
+                (min_args, max_args, defaults) = function.args()
+                for (argname,argtype) in zip(max_args,function.accepts):
+                    argname = self.filter_argname(argname)
+                    arg_part = in_el.appendChild(self.wsdl.createElement("part"))
+                    arg_part.setAttribute("name", argname)
+                    arg_part.setAttribute("type", self.param_type(argtype))
+                    
+            # Return type            
+            return_type = function.returns
+            out_el = self.wsdl.firstChild.appendChild(self.wsdl.createElement("message"))
+            out_el.setAttribute("name", method + "_out")
+            ret_part = out_el.appendChild(self.wsdl.createElement("part"))
+            ret_part.setAttribute("name", "Result")
+            ret_part.setAttribute("type", self.param_type(return_type))
+
+            # Port connecting arguments with return type
+
+            port_el = self.wsdl.firstChild.appendChild(self.wsdl.createElement("portType"))
+            port_el.setAttribute("name", method + "_port")
+            
+            op_el = port_el.appendChild(self.wsdl.createElement("operation"))
+            op_el.setAttribute("name", method)
+            inp_el=self.wsdl.createElement("input")
+            inp_el.setAttribute("message","tns:" + method + "_in")
+            inp_el.setAttribute("name",method+"_request")
+            op_el.appendChild(inp_el)
+            out_el = self.wsdl.createElement("output")
+            out_el.setAttribute("message","tns:" + method + "_out")
+            out_el.setAttribute("name",method+"_response")
+            op_el.appendChild(out_el)
+
+            # Bindings
+
+            bind_el = self.wsdl.firstChild.appendChild(self.wsdl.createElement("binding"))
+            bind_el.setAttribute("name", method + "_binding")
+            bind_el.setAttribute("type", "tns:" + method + "_port")
+            
+            soap_bind = bind_el.appendChild(self.wsdl.createElement("soap:binding"))
+            soap_bind.setAttribute("style", "rpc")
+            soap_bind.setAttribute("transport","http://schemas.xmlsoap.org/soap/http")
+
+            
+            wsdl_op = bind_el.appendChild(self.wsdl.createElement("operation"))
+            wsdl_op.setAttribute("name", method)
+            wsdl_op.appendChild(self.wsdl.createElement("soap:operation")).setAttribute("soapAction",
+                    "urn:" + method)
+
+            
+            wsdl_input = wsdl_op.appendChild(self.wsdl.createElement("input"))
+            input_soap_body = wsdl_input.appendChild(self.wsdl.createElement("soap:body"))
+            input_soap_body.setAttribute("use", "encoded")
+            input_soap_body.setAttribute("namespace", "urn:" + method)
+            input_soap_body.setAttribute("encodingStyle","http://schemas.xmlsoap.org/soap/encoding/")
+
+            
+            wsdl_output = wsdl_op.appendChild(self.wsdl.createElement("output"))
+            output_soap_body = wsdl_output.appendChild(self.wsdl.createElement("soap:body"))
+            output_soap_body.setAttribute("use", "encoded")
+            output_soap_body.setAttribute("namespace", "urn:" + method)
+            output_soap_body.setAttribute("encodingStyle","http://schemas.xmlsoap.org/soap/encoding/")
+            
+
+    def add_wsdl_services(self):
+        for service in self.services.keys():
+            if (getattr(self.interface_options,service)):
+                service_el = self.wsdl.firstChild.appendChild(self.wsdl.createElement("service"))
+                service_el.setAttribute("name", service)
+
+                for method in self.services[service]:
+                        name=method
+                        servport_el = service_el.appendChild(self.wsdl.createElement("port"))
+                        servport_el.setAttribute("name", name + "_port")
+                        servport_el.setAttribute("binding", "tns:" + name + "_binding")
+
+                        soapaddress = servport_el.appendChild(self.wsdl.createElement("soap:address"))
+                        soapaddress.setAttribute("location", "%s/%s" % (globals.plc_ns,service))
+
+
+    def compute_wsdl_definitions(self):
+        wsdl_text_header = """
+            <wsdl:definitions
+            name="sfa_autogenerated"
+            targetNamespace="%s/2009/07/sfa.wsdl"
+            xmlns="http://schemas.xmlsoap.org/wsdl/"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsdl="%s/2009/07/schema"
+            xmlns:tns="%s/2009/07/sfa.wsdl"
+            xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+            xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
+            """ % (globals.plc_ns,globals.plc_ns,globals.plc_ns)
+            
+        self.wsdl = xml.dom.minidom.parseString(wsdl_text_header)
+        
+
+    def compute_wsdl_definitions_and_types(self):
+        wsdl_text_header = """
+        <wsdl:definitions
+            name="sfa_autogenerated"
+            targetNamespace="%s/2009/07/sfa.wsdl"
+            xmlns="http://schemas.xmlsoap.org/wsdl/"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsdl="%s/2009/07/schema"
+            xmlns:tns="%s/2009/07/sfa.wsdl"
+            xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+            xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
+            <types>
+                <xsd:schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="%s/2009/07/schema"/>
+            </types>
+        </wsdl:definitions> """ % (globals.plc_ns, globals.plc_ns, globals.plc_ns, globals.plc_ns)
+        self.types = xml.dom.minidom.parseString(wsdl_text_header)
+        
+
+    def add_wsdl_types(self):
+        wsdl_types = self.wsdl.importNode(self.types.getElementsByTagName("types")[0], True)
+        self.wsdl.firstChild.appendChild(wsdl_types)
+
+    def generate_wsdl(self):
+        self.compute_wsdl_definitions_and_types()
+        self.compute_wsdl_definitions()
+        self.add_wsdl_ports_and_bindings()
+        self.add_wsdl_types()
+        self.add_wsdl_services()
+
+    def pretty_print(self):
+        if (self.wsdl):
+            xml.dom.ext.PrettyPrint(self.wsdl)
+        else:
+            raise Exception("Empty WSDL")
+
+def main():
+    parser = OptionParser()
+    parser.add_option("-r", "--registry", dest="registry", action="store_true", 
+                              help="Generate registry.wsdl", metavar="FILE")
+    parser.add_option("-s", "--slice-manager",
+                              action="store_true", dest="slicemgr", 
+                              help="Generate sm.wsdl")
+    parser.add_option("-a", "--aggregate", action="store_true", dest="aggregate",
+                              help="Generate am.wsdl")
+    parser.add_option("-c", "--component", action="store_true", dest="component",
+                              help="Generate cm.wsdl")
+    parser.add_option("-g", "--geni-aggregate", action="store_true", dest="geni_am",
+                      help="Generate gm.wsdl")
+    parser.add_option("-l", "--lite", action="store_true", dest="lite",
+                              help="Generate LITE version of the interface, in which calls exclude credentials")
+    (interface_options, args) = parser.parse_args()
+
+    gen = WSDLGen(interface_options)
+    gen.generate_wsdl()
+    gen.pretty_print()
+    
+
+if __name__ == "__main__":
+        main()
diff --git a/xmlbuilder-0.9/LICENSE b/xmlbuilder-0.9/LICENSE
new file mode 100644 (file)
index 0000000..0d0f57c
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License\r
+\r
+Copyright (c) 2008 Konstantin Danilov aka koder\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining a copy\r
+of this software and associated documentation files (the "Software"), to deal\r
+in the Software without restriction, including without limitation the rights\r
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+copies of the Software, and to permit persons to whom the Software is\r
+furnished to do so, subject to the following conditions:\r
+\r
+The above copyright notice and this permission notice shall be included in\r
+all copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+THE SOFTWARE.\r
diff --git a/xmlbuilder-0.9/MANIFEST.in b/xmlbuilder-0.9/MANIFEST.in
new file mode 100644 (file)
index 0000000..207725e
--- /dev/null
@@ -0,0 +1,2 @@
+include xmlbuild/docs *.rst\r
+include . LICENSE
\ No newline at end of file
diff --git a/xmlbuilder-0.9/PKG-INFO b/xmlbuilder-0.9/PKG-INFO
new file mode 100644 (file)
index 0000000..d931c15
--- /dev/null
@@ -0,0 +1,80 @@
+Metadata-Version: 1.0\r
+Name: xmlbuilder\r
+Version: 0.9\r
+Summary: Pythonic way to create xml files\r
+Home-page: http://pypi.python.org/pypi/xmlbuilder\r
+Author: koder\r
+Author-email: koder_dot_mail@gmail_dot_com\r
+License: MIT\r
+Download-URL: http://pypi.python.org/pypi/xmlbuilder\r
+Description: Example of usage:\r
+        -----------------\r
+        \r
+        \r
+        from __future__ import with_statement\r
+        from xmlbuilder import XMLBuilder\r
+        x = XMLBuilder(format=True)\r
+        with x.root(a = 1):\r
+        with x.data:\r
+        [x &lt;&lt; ('node',{'val':i}) for i in range(10)]\r
+        \r
+        print str(x)\r
+        \r
+        will print\r
+        \r
+        &lt;root a="1"&gt;\r
+        &lt;data&gt;\r
+        &lt;node val="0" /&gt;\r
+        &lt;node val="1" /&gt;\r
+        &lt;node val="2" /&gt;\r
+        &lt;node val="3" /&gt;\r
+        &lt;node val="4" /&gt;\r
+        &lt;node val="5" /&gt;\r
+        &lt;node val="6" /&gt;\r
+        &lt;node val="7" /&gt;\r
+        &lt;node val="8" /&gt;\r
+        &lt;node val="9" /&gt;\r
+        &lt;/data&gt;\r
+        &lt;/root&gt;\r
+        \r
+        Mercurial repo:http://hg.assembla.com/MyPackages/\r
+        \r
+        Documentations\r
+        --------------\r
+        `XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to\r
+        simplify xml files creation as much as possible. Althow it can produce\r
+        structured result with identated child tags. `XMLBuilder` use python `with`\r
+        statement to define xml tag levels and `&lt;&lt;` operator for simple cases -\r
+        text and tag without childs.\r
+        \r
+        First we need to create xmlbuilder\r
+        \r
+        from xmlbuilder import XMLBuilder\r
+        # params - encoding = 'utf8',\r
+        # builder = None, - ElementTree.TreeBuilder\r
+        # tab_level = None, - current tab l;evel - for formatted output only\r
+        # format = False, - create formatted output\r
+        # tab_step = " " * 4 - indentation step\r
+        xml = XMLBuilder()\r
+        \r
+        \r
+        Use `with` statement to make document structure\r
+        #create and open tag 'root_tag' with text 'text' and attributes\r
+        with xml.root_tag(text,attr1=val1,attr2=val2):\r
+        #create and open tag 'sub_tag'\r
+        with xml.sub_tag(text,attr3=val3):\r
+        #create tag which are not valid python identificator\r
+        with xml('one-more-sub-tag',attr7=val37):\r
+        xml &lt;&lt; "Some textual data"\r
+        #here tag 'one-more-sub-tag' are closed\r
+        #Tags without children can be created using `&lt;&lt;` operator\r
+        for val in range(15):\r
+        xml &lt;&lt; ('message',"python rocks!"[:i])\r
+        #create 15 child tag like &lt;message&gt; python r&lt;/message&gt;\r
+        #all tags closed\r
+        node = ~x # get etree.ElementTree object\r
+        xml_data = str(x)\r
+        unicode_xml_data = unicode(x)\r
+        \r
+Keywords: xml\r
+Platform: UNKNOWN\r
diff --git a/xmlbuilder-0.9/README.txt b/xmlbuilder-0.9/README.txt
new file mode 100644 (file)
index 0000000..7a7131f
--- /dev/null
@@ -0,0 +1 @@
+Pythonic way to build xml files
\ No newline at end of file
diff --git a/xmlbuilder-0.9/setup.cfg b/xmlbuilder-0.9/setup.cfg
new file mode 100644 (file)
index 0000000..b14b0bc
--- /dev/null
@@ -0,0 +1,5 @@
+[egg_info]\r
+tag_build = \r
+tag_date = 0\r
+tag_svn_revision = 0\r
+\r
diff --git a/xmlbuilder-0.9/setup.py b/xmlbuilder-0.9/setup.py
new file mode 100644 (file)
index 0000000..931cb31
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env python\r
+#-------------------------------------------------------------------------------\r
+import os\r
+import sys\r
+import glob\r
+import os.path\r
+from setuptools import setup\r
+#from distutils.core import setup\r
+#-------------------------------------------------------------------------------\r
+if 'upload' in sys.argv:\r
+    # for .pypirc file\r
+    try:\r
+        os.environ['HOME']\r
+    except KeyError:\r
+        os.environ['HOME'] = '..\\'\r
+#-------------------------------------------------------------------------------\r
+fpath = lambda x : os.path.join(*x.split('/'))\r
+#-------------------------------------------------------------------------------\r
+PYPI_URL = 'http://pypi.python.org/pypi/xmlbuilder'\r
+ld = open(fpath('xmlbuilder/docs/long_descr.rst')).read()\r
+ld = ld.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')\r
+setup(\r
+    name = "xmlbuilder",\r
+    fullname = "xmlbuilder",\r
+    version = "0.9",\r
+    packages = ["xmlbuilder"],\r
+    package_dir = {'xmlbuilder':'xmlbuilder'},\r
+    author = "koder",\r
+    author_email = "koder_dot_mail@gmail_dot_com",\r
+    maintainer = 'koder',\r
+    maintainer_email = "koder_dot_mail@gmail_dot_com",\r
+    description = "Pythonic way to create xml files",\r
+    license = "MIT",\r
+    keywords = "xml",\r
+    test_suite = "xml_buider.tests",\r
+    url = PYPI_URL,\r
+    download_url = PYPI_URL,\r
+    long_description = ld,\r
+    #include_package_data = True,\r
+    #package_data = {'xmlbuilder':["docs/*.rst"]},\r
+    #data_files = [('', ['xmlbuilder/docs/long_descr.rst'])]\r
+)\r
+#-------------------------------------------------------------------------------\r
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO b/xmlbuilder-0.9/xmlbuilder.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..bb65a9d
--- /dev/null
@@ -0,0 +1,80 @@
+Metadata-Version: 1.0
+Name: xmlbuilder
+Version: 0.9
+Summary: Pythonic way to create xml files
+Home-page: http://pypi.python.org/pypi/xmlbuilder
+Author: koder
+Author-email: koder_dot_mail@gmail_dot_com
+License: MIT
+Download-URL: http://pypi.python.org/pypi/xmlbuilder
+Description: Example of usage:
+        -----------------
+        
+        
+        from __future__ import with_statement
+        from xmlbuilder import XMLBuilder
+        x = XMLBuilder(format=True)
+        with x.root(a = 1):
+        with x.data:
+        [x &lt;&lt; ('node',{'val':i}) for i in range(10)]
+        
+        print str(x)
+        
+        will print
+        
+        &lt;root a="1"&gt;
+        &lt;data&gt;
+        &lt;node val="0" /&gt;
+        &lt;node val="1" /&gt;
+        &lt;node val="2" /&gt;
+        &lt;node val="3" /&gt;
+        &lt;node val="4" /&gt;
+        &lt;node val="5" /&gt;
+        &lt;node val="6" /&gt;
+        &lt;node val="7" /&gt;
+        &lt;node val="8" /&gt;
+        &lt;node val="9" /&gt;
+        &lt;/data&gt;
+        &lt;/root&gt;
+        
+        Mercurial repo:http://hg.assembla.com/MyPackages/
+        
+        Documentations
+        --------------
+        `XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to
+        simplify xml files creation as much as possible. Althow it can produce
+        structured result with identated child tags. `XMLBuilder` use python `with`
+        statement to define xml tag levels and `&lt;&lt;` operator for simple cases -
+        text and tag without childs.
+        
+        First we need to create xmlbuilder
+        
+        from xmlbuilder import XMLBuilder
+        # params - encoding = 'utf8',
+        # builder = None, - ElementTree.TreeBuilder
+        # tab_level = None, - current tab l;evel - for formatted output only
+        # format = False, - create formatted output
+        # tab_step = " " * 4 - indentation step
+        xml = XMLBuilder()
+        
+        
+        Use `with` statement to make document structure
+        #create and open tag 'root_tag' with text 'text' and attributes
+        with xml.root_tag(text,attr1=val1,attr2=val2):
+        #create and open tag 'sub_tag'
+        with xml.sub_tag(text,attr3=val3):
+        #create tag which are not valid python identificator
+        with xml('one-more-sub-tag',attr7=val37):
+        xml &lt;&lt; "Some textual data"
+        #here tag 'one-more-sub-tag' are closed
+        #Tags without children can be created using `&lt;&lt;` operator
+        for val in range(15):
+        xml &lt;&lt; ('message',"python rocks!"[:i])
+        #create 15 child tag like &lt;message&gt; python r&lt;/message&gt;
+        #all tags closed
+        node = ~x # get etree.ElementTree object
+        xml_data = str(x)
+        unicode_xml_data = unicode(x)
+        
+Keywords: xml
+Platform: UNKNOWN
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..4cc27de
--- /dev/null
@@ -0,0 +1,13 @@
+LICENSE
+MANIFEST.in
+PKG-INFO
+README.txt
+setup.cfg
+setup.py
+xmlbuilder/__init__.py
+xmlbuilder.egg-info/PKG-INFO
+xmlbuilder.egg-info/SOURCES.txt
+xmlbuilder.egg-info/dependency_links.txt
+xmlbuilder.egg-info/top_level.txt
+xmlbuilder/docs/long_descr.rst
+xmlbuilder/tests/__init__.py
\ No newline at end of file
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt b/xmlbuilder-0.9/xmlbuilder.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..9f1d486
--- /dev/null
@@ -0,0 +1 @@
+xmlbuilder
diff --git a/xmlbuilder-0.9/xmlbuilder/__init__.py b/xmlbuilder-0.9/xmlbuilder/__init__.py
new file mode 100644 (file)
index 0000000..1be17b0
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python\r
+#-------------------------------------------------------------------------------\r
+from __future__ import with_statement\r
+#-------------------------------------------------------------------------------\r
+from xml.etree.ElementTree import TreeBuilder,tostring\r
+#-------------------------------------------------------------------------------\r
+__all__ = ["XMLBuilder"]\r
+__doc__ = """\r
+XMLBuilder is simple library build on top of ElementTree.TreeBuilder to\r
+simplify xml files creation as much as possible. Althow it can produce\r
+structured result with identated child tags. `XMLBuilder` use python `with`\r
+statement to define xml tag levels and `<<` operator for simple cases -\r
+text and tag without childs.\r
+\r
+from __future__ import with_statement\r
+from xmlbuilder import XMLBuilder\r
+x = XMLBuilder(format=True)\r
+with x.root(a = 1):\r
+    with x.data:\r
+        [x << ('node',{'val':i}) for i in range(10)]\r
+\r
+etree_node = ~x\r
+print str(x)\r
+"""\r
+#-------------------------------------------------------------------------------\r
+class _XMLNode(object):\r
+    """Class for internal usage"""\r
+    def __init__(self,parent,name,builder):\r
+        self.builder = builder\r
+        self.name = name\r
+        self.text = []\r
+        self.attrs = {}\r
+        self.entered = False\r
+        self.parent = parent\r
+    def __call__(self,*dt,**mp):\r
+        text = "".join(dt)\r
+        if self.entered:\r
+            self.builder.data(text)\r
+        else:\r
+            self.text.append(text)\r
+        if self.entered:\r
+            raise ValueError("Can't add attributes to already opened element")\r
+        smp = dict((k,str(v)) for k,v in mp.items())\r
+        self.attrs.update(smp)\r
+        return self\r
+    def __enter__(self):\r
+        self.parent += 1\r
+        self.builder.start(self.name,self.attrs)\r
+        self.builder.data("".join(self.text))\r
+        self.entered = True\r
+        return self\r
+    def __exit__(self,x,y,z):\r
+        self.parent -= 1\r
+        self.builder.end(self.name)\r
+        return False\r
+#-------------------------------------------------------------------------------\r
+class XMLBuilder(object):\r
+    """XmlBuilder(encoding = 'utf-8', # result xml file encoding\r
+            builder = None, #etree.TreeBuilder or compatible class\r
+            tab_level = None, #current tabulation level - string\r
+            format = False,   # make formatted output\r
+            tab_step = " " * 4) # tabulation step\r
+    use str(builder) or unicode(builder) to get xml text or\r
+    ~builder to obtaine etree.ElementTree\r
+    """\r
+    def __init__(self,encoding = 'utf-8',\r
+                      builder = None,\r
+                      tab_level = None,\r
+                      format = False,\r
+                      tab_step = " " * 4):\r
+        self.__builder = builder or TreeBuilder()\r
+        self.__encoding = encoding \r
+        if format :\r
+            if tab_level is None:\r
+                tab_level = ""\r
+        if tab_level is not None:\r
+            if not format:\r
+                raise ValueError("format is False, but tab_level not None")\r
+        self.__tab_level = tab_level # current format level\r
+        self.__tab_step = tab_step   # format step\r
+        self.__has_sub_tag = False   # True, if current tag had childrens\r
+        self.__node = None\r
+    # called from _XMLNode when tag opened\r
+    def __iadd__(self,val):\r
+        self.__has_sub_tag = False\r
+        if self.__tab_level is not None:\r
+            self.__builder.data("\n" + self.__tab_level)\r
+            self.__tab_level += self.__tab_step\r
+        return self\r
+    # called from XMLNode when tag closed\r
+    def __isub__(self,val):\r
+        if self.__tab_level is not None:\r
+            self.__tab_level = self.__tab_level[:-len(self.__tab_step)]\r
+            if self.__has_sub_tag:\r
+                self.__builder.data("\n" + self.__tab_level)\r
+        self.__has_sub_tag = True\r
+        return self\r
+    def __getattr__(self,name):\r
+        return _XMLNode(self,name,self.__builder)\r
+    def __call__(self,name,*dt,**mp):\r
+        x = _XMLNode(self,name,self.__builder)\r
+        x(*dt,**mp)\r
+        return x\r
+    #create new tag or add text\r
+    #possible shift values\r
+    #string - text\r
+    #tuple(string1,string2,dict) - new tag with name string1,attrs = dict,and text string2\r
+    #dict and string2 are optional\r
+    def __lshift__(self,val):\r
+        if isinstance(val,basestring):\r
+            self.__builder.data(val)\r
+        else:\r
+            self.__has_sub_tag = True\r
+            assert hasattr(val,'__len__'),\\r
+                'Shifted value should be tuple or list like object not %r' % val\r
+            assert hasattr(val,'__getitem__'),\\r
+                'Shifted value should be tuple or list like object not %r' % val\r
+            name = val[0]\r
+            if len(val) == 3:\r
+                text = val[1]\r
+                attrs = val[2]\r
+            elif len(val) == 1:\r
+                text = ""\r
+                attrs = {}\r
+            elif len(val) == 2:\r
+                if isinstance(val[1],basestring):\r
+                    text = val[1]\r
+                    attrs = {}\r
+                else:\r
+                    text = ""\r
+                    attrs = val[1]\r
+            if self.__tab_level is not None:\r
+                self.__builder.data("\n" + self.__tab_level)\r
+            self.__builder.start(name,\r
+                                 dict((k,str(v)) for k,v in attrs.items()))\r
+            if text:\r
+                self.__builder.data(text)\r
+            self.__builder.end(name)\r
+        return self # to allow xml << some1 << some2 << some3\r
+    #close builder\r
+    def __invert__(self):\r
+        if self.__node is not None:\r
+            return self.__node\r
+        self.__node = self.__builder.close()\r
+        return self.__node\r
+    def __str__(self):\r
+        """return generated xml"""\r
+        return tostring(~self,self.__encoding)\r
+    def __unicode__(self):\r
+        """return generated xml"""\r
+        res = tostring(~self,self.__encoding)\r
+        return res.decode(self.__encoding)\r
+#-------------------------------------------------------------------------------\r
diff --git a/xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst b/xmlbuilder-0.9/xmlbuilder/docs/long_descr.rst
new file mode 100644 (file)
index 0000000..4e82bc8
--- /dev/null
@@ -0,0 +1,68 @@
+Example of usage:\r
+-----------------\r
+\r
+\r
+from __future__ import with_statement\r
+from xmlbuilder import XMLBuilder\r
+x = XMLBuilder(format=True)\r
+with x.root(a = 1):\r
+    with x.data:\r
+        [x << ('node',{'val':i}) for i in range(10)]\r
+\r
+print str(x)\r
+\r
+will print\r
+\r
+<root a="1">\r
+    <data>\r
+        <node val="0" />\r
+        <node val="1" />\r
+        <node val="2" />\r
+        <node val="3" />\r
+        <node val="4" />\r
+        <node val="5" />\r
+        <node val="6" />\r
+        <node val="7" />\r
+        <node val="8" />\r
+        <node val="9" />\r
+    </data>\r
+</root>\r
+\r
+Mercurial repo:http://hg.assembla.com/MyPackages/\r
+\r
+Documentations\r
+--------------\r
+`XMLBuilder` is simple library build on top of `ElementTree.TreeBuilder` to\r
+simplify xml files creation as much as possible. Althow it can produce\r
+structured result with identated child tags. `XMLBuilder` use python `with`\r
+statement to define xml tag levels and `<<` operator for simple cases -\r
+text and tag without childs.\r
+\r
+First we need to create xmlbuilder\r
+\r
+    from xmlbuilder import XMLBuilder\r
+    # params - encoding = 'utf8',\r
+    # builder = None, - ElementTree.TreeBuilder \r
+    # tab_level = None, - current tab l;evel - for formatted output only\r
+    # format = False, - create formatted output\r
+    # tab_step = " " * 4 - indentation step\r
+    xml = XMLBuilder()\r
+\r
+\r
+Use `with` statement to make document structure\r
+    #create and open tag 'root_tag' with text 'text' and attributes\r
+    with xml.root_tag(text,attr1=val1,attr2=val2):\r
+        #create and open tag 'sub_tag'\r
+        with xml.sub_tag(text,attr3=val3):\r
+            #create tag which are not valid python identificator\r
+            with xml('one-more-sub-tag',attr7=val37):\r
+                xml << "Some textual data"\r
+            #here tag 'one-more-sub-tag' are closed\r
+                       #Tags without children can be created using `<<` operator\r
+            for val in range(15):\r
+                xml << ('message',"python rocks!"[:i])\r
+            #create 15 child tag like <message> python r</message>\r
+    #all tags closed\r
+    node = ~x # get etree.ElementTree object\r
+    xml_data = str(x)\r
+    unicode_xml_data = unicode(x)\r
diff --git a/xmlbuilder-0.9/xmlbuilder/tests/__init__.py b/xmlbuilder-0.9/xmlbuilder/tests/__init__.py
new file mode 100644 (file)
index 0000000..67eaa67
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python\r
+from __future__ import with_statement\r
+#-------------------------------------------------------------------------------\r
+import unittest\r
+from xml.etree.ElementTree import fromstring\r
+#-------------------------------------------------------------------------------\r
+from xmlbuilder import XMLBuilder\r
+#-------------------------------------------------------------------------------\r
+def xmlStructureEqual(xml1,xml2):\r
+    tree1 = fromstring(xml1)\r
+    tree2 = fromstring(xml2)\r
+    return _xmlStructureEqual(tree1,tree2)\r
+#-------------------------------------------------------------------------------\r
+def _xmlStructureEqual(tree1,tree2):\r
+    if tree1.tag != tree2.tag:\r
+        return False\r
+    attr1 = list(tree1.attrib.items())\r
+    attr1.sort()\r
+    attr2 = list(tree2.attrib.items())\r
+    attr2.sort()\r
+    if attr1 != attr2:\r
+        return False\r
+    return tree1.getchildren() == tree2.getchildren()\r
+#-------------------------------------------------------------------------------\r
+result1 = \\r
+"""\r
+<root>\r
+    <array />\r
+    <array len="10">\r
+        <el val="0" />\r
+        <el val="1">xyz</el>\r
+        <el val="2">abc</el>\r
+        <el val="3" />\r
+        <el val="4" />\r
+        <el val="5" />\r
+        <sup-el val="23">test  </sup-el>\r
+    </array>\r
+</root>\r
+""".strip()\r
+#-------------------------------------------------------------------------------\r
+class TestXMLBuilder(unittest.TestCase):\r
+    def testShift(self):\r
+        xml = (XMLBuilder() << ('root',))\r
+        self.assertEqual(str(xml),"<root />")\r
+        \r
+        xml = XMLBuilder()\r
+        xml << ('root',"some text")\r
+        self.assertEqual(str(xml),"<root>some text</root>")\r
+        \r
+        xml = XMLBuilder()\r
+        xml << ('root',{'x':1,'y':'2'})\r
+        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>some text</root>"))\r
+        \r
+        xml = XMLBuilder()\r
+        xml << ('root',{'x':1,'y':'2'})\r
+        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'></root>"))\r
+\r
+        xml = XMLBuilder()\r
+        xml << ('root',{'x':1,'y':'2'})\r
+        self.assert_(not xmlStructureEqual(str(xml),"<root x='2' y='2'></root>"))\r
+\r
+        \r
+        xml = XMLBuilder()\r
+        xml << ('root',"gonduras.ua",{'x':1,'y':'2'})\r
+        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>gonduras.ua</root>"))\r
+        \r
+        xml = XMLBuilder()\r
+        xml << ('root',"gonduras.ua",{'x':1,'y':'2'})\r
+        self.assert_(xmlStructureEqual(str(xml),"<root x='1' y='2'>gonduras.com</root>"))\r
+    #---------------------------------------------------------------------------\r
+    def testWith(self):\r
+        xml = XMLBuilder()\r
+        with xml.root(lenght = 12):\r
+            pass\r
+        self.assertEqual(str(xml),'<root lenght="12" />')\r
+        \r
+        xml = XMLBuilder()\r
+        with xml.root():\r
+            xml << "text1" << "text2" << ('some_node',)\r
+        self.assertEqual(str(xml),"<root>text1text2<some_node /></root>")\r
+    #---------------------------------------------------------------------------\r
+    def testFormat(self):\r
+        x = XMLBuilder('utf-8',format = True)\r
+        with x.root():\r
+            x << ('array',)\r
+            with x.array(len = 10):\r
+                with x.el(val = 0):\r
+                    pass\r
+                with x.el('xyz',val = 1):\r
+                    pass\r
+                x << ("el","abc",{'val':2}) << ('el',dict(val=3))\r
+                x << ('el',dict(val=4)) << ('el',dict(val='5'))\r
+                with x('sup-el',val = 23):\r
+                    x << "test  "\r
+        self.assertEqual(str(x),result1)\r
+#-------------------------------------------------------------------------------\r
+if __name__ == '__main__':\r
+    unittest.main()\r
+#-------------------------------------------------------------------------------\r