Add .hgignore: pyc and ~ files
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Fri, 8 Apr 2011 13:54:12 +0000 (15:54 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Fri, 8 Apr 2011 13:54:12 +0000 (15:54 +0200)
.hgignore [new file with mode: 0644]
src/nepi/testbeds/planetlab/__init__.py [new file with mode: 0644]
src/nepi/testbeds/planetlab/constants.py [new file with mode: 0644]
src/nepi/testbeds/planetlab/execute.py [new file with mode: 0644]
src/nepi/testbeds/planetlab/metadata_v01.py [new file with mode: 0644]
src/nepi/testbeds/planetlab/plcapi.py [new file with mode: 0644]

diff --git a/.hgignore b/.hgignore
new file mode 100644 (file)
index 0000000..163d7b0
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,7 @@
+#use glob syntax.
+syntax: glob
+
+*.pyc
+*~
+build
+
diff --git a/src/nepi/testbeds/planetlab/__init__.py b/src/nepi/testbeds/planetlab/__init__.py
new file mode 100644 (file)
index 0000000..3b3f853
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from constants import TESTBED_ID
+from execute import TestbedInstance 
+
diff --git a/src/nepi/testbeds/planetlab/constants.py b/src/nepi/testbeds/planetlab/constants.py
new file mode 100644 (file)
index 0000000..3580509
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+TESTBED_ID = "planetlab"
+
diff --git a/src/nepi/testbeds/planetlab/execute.py b/src/nepi/testbeds/planetlab/execute.py
new file mode 100644 (file)
index 0000000..c6fa941
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from constants import TESTBED_ID
+from nepi.core import testbed_impl
+import os
+
+class TestbedInstance(testbed_impl.TestbedInstance):
+    def __init__(self, testbed_version):
+        super(TestbedInstance, self).__init__(TESTBED_ID, testbed_version)
+        self._netns = None
+        self._home_directory = None
+        self._traces = dict()
+
+    @property
+    def home_directory(self):
+        return self._home_directory
+
+    @property
+    def netns(self):
+        return self._netns
+
+    def do_setup(self):
+        self._home_directory = self._attributes.\
+            get_attribute_value("homeDirectory")
+        self._netns = self._load_netns_module()
+
+    def set(self, time, guid, name, value):
+        super(TestbedInstance, self).set(time, guid, name, value)
+        # TODO: take on account schedule time for the task 
+        element = self._elements[guid]
+        if element:
+            setattr(element, name, value)
+
+    def get(self, time, guid, name):
+        # TODO: take on account schedule time for the task
+        element = self._elements[guid]
+        return getattr(element, name)
+
+    def action(self, time, guid, action):
+        raise NotImplementedError
+
+    def trace(self, guid, trace_id):
+        fd = open("%s" % self.trace_filename(guid, trace_id), "r")
+        content = fd.read()
+        fd.close()
+        return content
+
+    def shutdown(self):
+        for trace in self._traces.values():
+            trace.close()
+        for element in self._elements.values():
+            element.destroy()
+
+    def trace_filename(self, guid, trace_id):
+        # TODO: Need to be defined inside a home!!!! with and experiment id_code
+        return os.path.join(self.home_directory, "%d_%s" % (guid, trace_id))
+
+    def follow_trace(self, trace_id, trace):
+        self._traces[trace_id] = trace
+
+    def _load_netns_module(self):
+        # TODO: Do something with the configuration!!!
+        import sys
+        __import__("netns")
+        netns_mod = sys.modules["netns"]
+        # enable debug
+        enable_debug = self._attributes.get_attribute_value("enableDebug")
+        if enable_debug:
+            netns_mod.environ.set_log_level(netns_mod.environ.LOG_DEBUG)
+        return netns_mod
+
diff --git a/src/nepi/testbeds/planetlab/metadata_v01.py b/src/nepi/testbeds/planetlab/metadata_v01.py
new file mode 100644 (file)
index 0000000..2a0666c
--- /dev/null
@@ -0,0 +1,390 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from constants import TESTBED_ID
+from nepi.core import metadata
+from nepi.core.attributes import Attribute
+from nepi.util import validation
+from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
+        STATUS_FINISHED
+
+NODE = "Node"
+NODEIFACE = "NodeInterface"
+APPLICATION = "Application"
+
+PL_TESTBED_ID = "planetlab"
+
+### Connection functions ####
+
+### Creation functions ###
+
+def create_node(testbed_instance, guid):
+    parameters = testbed_instance._get_parameters(guid)
+    element = testbed_instance.pl.Node()
+    testbed_instance.elements[guid] = element
+
+def create_nodeiface(testbed_instance, guid):
+    parameters = testbed_instance._get_parameters(guid)
+    element = testbed_instance.pl.Iface()
+    testbed_instance.elements[guid] = element
+
+def create_application(testbed_instance, guid):
+    testbed_instance.elements[guid] = None # Delayed construction 
+
+### Start/Stop functions ###
+
+def start_application(testbed_instance, guid):
+    parameters = testbed_instance._get_parameters(guid)
+    traces = testbed_instance._get_traces(guid)
+    user = parameters["user"]
+    command = parameters["command"]
+    stdout = stderr = None
+    if "stdout" in traces:
+        filename = testbed_instance.trace_filename(guid, "stdout")
+        stdout = open(filename, "wb")
+        testbed_instance.follow_trace("stdout", stdout)
+    if "stderr" in traces:
+        filename = testbed_instance.trace_filename(guid, "stderr")
+        stderr = open(filename, "wb")
+        testbed_instance.follow_trace("stderr", stderr)
+
+    node_guid = testbed_instance.get_connected(guid, "node", "apps")
+    if len(node_guid) == 0:
+        raise RuntimeError("Can't instantiate interface %d outside netns \
+                node" % guid)
+    node = testbed_instance.elements[node_guid[0]]
+    element  = node.Popen(command, shell = True, stdout = stdout, 
+            stderr = stderr, user = user)
+    testbed_instance.elements[guid] = element
+
+### Status functions ###
+
+def status_application(testbed_instance, guid):
+    if guid not in testbed_instance.elements.keys():
+        return STATUS_NOT_STARTED
+    app = testbed_instance.elements[guid]
+    if app.poll() == None:
+        return STATUS_RUNNING
+    return STATUS_FINISHED
+
+### Configure functions ###
+
+def configure_device(testbed_instance, guid):
+    element = testbed_instance._elements[guid]
+    if not guid in testbed_instance._add_address:
+        return
+    addresses = testbed_instance._add_address[guid]
+    for address in addresses:
+        (address, netprefix, broadcast) = address
+        # TODO: Decide if we should add a ipv4 or ipv6 address
+        element.add_v4_address(address, netprefix)
+
+def configure_node(testbed_instance, guid):
+    element = testbed_instance._elements[guid]
+    if not guid in testbed_instance._add_route:
+        return
+    routes = testbed_instance._add_route[guid]
+    for route in routes:
+        (destination, netprefix, nexthop) = route
+        element.add_route(prefix = destination, prefix_len = netprefix,
+            nexthop = nexthop)
+
+### Factory information ###
+
+connector_types = dict({
+    "apps": dict({
+                "help": "Connector from node to applications", 
+                "name": "apps",
+                "max": -1, 
+                "min": 0
+            }),
+    "devs": dict({
+                "help": "Connector from node to network interfaces", 
+                "name": "devs",
+                "max": -1, 
+                "min": 0
+            }),
+    "node": dict({
+                "help": "Connector to a Node", 
+                "name": "node",
+                "max": 1, 
+                "min": 1
+            }),
+    "p2p": dict({
+                "help": "Connector to a P2PInterface", 
+                "name": "p2p",
+                "max": 1, 
+                "min": 0
+            }),
+    "fd": dict({
+                "help": "Connector to a network interface that can receive a file descriptor", 
+                "name": "fd",
+                "max": 1, 
+                "min": 0
+            }),
+    "switch": dict({
+                "help": "Connector to a switch", 
+                "name": "switch",
+                "max": 1, 
+                "min": 0
+            })
+   })
+
+connections = [
+    dict({
+        "from": (TESTBED_ID, NODE, "devs"),
+        "to":   (TESTBED_ID, P2PIFACE, "node"),
+        "code": None,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, NODE, "devs"),
+        "to":   (TESTBED_ID, TAPIFACE, "node"),
+        "code": None,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, NODE, "devs"),
+        "to":   (TESTBED_ID, NODEIFACE, "node"),
+        "code": None,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, P2PIFACE, "p2p"),
+        "to":   (TESTBED_ID, P2PIFACE, "p2p"),
+        "code": None,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, TAPIFACE, "fd"),
+        "to":   (NS3_TESTBED_ID, FDNETDEV, "fd"),
+        "code": connect_fd_local,
+        "can_cross": True
+    }),
+     dict({
+        "from": (TESTBED_ID, SWITCH, "devs"),
+        "to":   (TESTBED_ID, NODEIFACE, "switch"),
+        "code": connect_switch,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, NODE, "apps"),
+        "to":   (TESTBED_ID, APPLICATION, "node"),
+        "code": None,
+        "can_cross": False
+    })
+]
+
+attributes = dict({
+    "forward_X11": dict({      
+                "name": "forward_X11",
+                "help": "Forward x11 from main namespace to the node",
+                "type": Attribute.BOOL, 
+                "value": False,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_bool
+            }),
+    "lladdr": dict({      
+                "name": "lladdr", 
+                "help": "Mac address", 
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_mac_address
+            }),
+    "up": dict({
+                "name": "up",
+                "help": "Link up",
+                "type": Attribute.BOOL,
+                "value": False,
+                "validation_function": validation.is_bool
+            }),
+    "device_name": dict({
+                "name": "name",
+                "help": "Device name",
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            }),
+    "mtu":  dict({
+                "name": "mtu", 
+                "help": "Maximum transmition unit for device",
+                "type": Attribute.INTEGER,
+                "validation_function": validation.is_integer
+            }),
+    "broadcast": dict({ 
+                "name": "broadcast",
+                "help": "Broadcast address",
+                "type": Attribute.STRING,
+                "validation_function": validation.is_string # TODO: should be is address!
+            }),
+    "multicast": dict({      
+                "name": "multicast",
+                "help": "Multicast enabled",
+                "type": Attribute.BOOL,
+                "value": False,
+                "validation_function": validation.is_bool
+            }),
+    "arp": dict({
+                "name": "arp",
+                "help": "ARP enabled",
+                "type": Attribute.BOOL,
+                "value": False,
+                "validation_function": validation.is_bool
+            }),
+    "command": dict({
+                "name": "command",
+                "help": "Command line string",
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            }),
+    "user": dict({
+                "name": "user",
+                "help": "System user",
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            }),
+    "stdin": dict({
+                "name": "stdin",
+                "help": "Standard input",
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            }),
+    })
+
+traces = dict({
+    "stdout": dict({
+                "name": "stdout",
+                "help": "Standard output stream"
+              }),
+    "stderr": dict({
+                "name": "stderr",
+                "help": "Application standard error",
+        }) 
+    })
+
+create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH,
+        APPLICATION ]
+
+configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, NODE,
+        APPLICATION ]
+
+factories_info = dict({
+    NODE: dict({
+            "allow_routes": True,
+            "help": "Emulated Node with virtualized network stack",
+            "category": "topology",
+            "create_function": create_node,
+            "configure_function": configure_node,
+            "box_attributes": ["forward_X11"],
+            "connector_types": ["devs", "apps"]
+       }),
+    P2PIFACE: dict({
+            "allow_addresses": True,
+            "help": "Point to point network interface",
+            "category": "devices",
+            "create_function": create_p2piface,
+            "configure_function": configure_device,
+            "box_attributes": ["lladdr", "up", "device_name", "mtu", 
+                "multicast", "broadcast", "arp"],
+            "connector_types": ["node", "p2p"]
+       }),
+    TAPIFACE: dict({
+            "allow_addresses": True,
+            "help": "Tap device network interface",
+            "category": "devices",
+            "create_function": create_tapiface,
+            "configure_function": configure_device,
+            "box_attributes": ["lladdr", "up", "device_name", "mtu", 
+                "multicast", "broadcast", "arp"],
+            "connector_types": ["node", "fd"]
+        }),
+    NODEIFACE: dict({
+            "allow_addresses": True,
+            "help": "Node network interface",
+            "category": "devices",
+            "create_function": create_nodeiface,
+            "configure_function": configure_device,
+            "box_attributes": ["lladdr", "up", "device_name", "mtu", 
+                "multicast", "broadcast", "arp"],
+            "connector_types": ["node", "switch"]
+        }),
+    SWITCH: dict({
+            "display_name": "Switch",
+            "help": "Switch interface",
+            "category": "devices",
+            "create_function": create_switch,
+            "box_attributes": ["up", "device_name", "mtu", "multicast"],
+             #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
+             #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
+             #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
+             #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
+             #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
+           "connector_types": ["devs"]
+        }),
+    APPLICATION: dict({
+            "help": "Generic executable command line application",
+            "category": "applications",
+            "create_function": create_application,
+            "start_function": start_application,
+            "status_function": status_application,
+            "box_attributes": ["command", "user"],
+            "connector_types": ["node"],
+            "traces": ["stdout", "stderr"]
+        }),
+})
+
+testbed_attributes = dict({
+        "enable_debug": dict({
+                "name": "enableDebug",
+                "help": "Enable netns debug output",
+                "type": Attribute.BOOL,
+                "value": False,
+                "validation_function": validation.is_bool
+            }),
+         "home_directory": dict({
+                "name": "homeDirectory",
+                "help": "Path to the directory where traces and other files \
+                        will be stored",
+                "type": Attribute.STRING,
+                "value": "",
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            })
+    })
+
+class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
+    @property
+    def connector_types(self):
+        return connector_types
+
+    @property
+    def connections(self):
+        return connections
+
+    @property
+    def attributes(self):
+        return attributes
+
+    @property
+    def traces(self):
+        return traces
+
+    @property
+    def create_order(self):
+        return create_order
+
+    @property
+    def configure_order(self):
+        return configure_order
+
+    @property
+    def factories_info(self):
+        return factories_info
+
+    @property
+    def testbed_attributes(self):
+        return testbed_attributes
+
diff --git a/src/nepi/testbeds/planetlab/plcapi.py b/src/nepi/testbeds/planetlab/plcapi.py
new file mode 100644 (file)
index 0000000..a31e100
--- /dev/null
@@ -0,0 +1,210 @@
+import xmlrpclib
+
+class PLCAPI(object):
+    _expected_methods = set(
+        ['AddNodeTag', 'AddConfFile', 'DeletePersonTag', 'AddNodeType', 'DeleteBootState', 'SliceListNames', 'DeleteKey', 
+         'SliceGetTicket', 'SliceUsersList', 'SliceUpdate', 'GetNodeGroups', 'SliceCreate', 'GetNetworkMethods', 'GetNodeFlavour', 
+         'DeleteNode', 'BootNotifyOwners', 'AddPersonKey', 'AddNode', 'UpdateNodeGroup', 'GetAddressTypes', 'AddIlink', 'DeleteNetworkType', 
+         'GetInitScripts', 'GenerateNodeConfFile', 'AddSite', 'BindObjectToPeer', 'SliceListUserSlices', 'GetPeers', 'AddPeer', 'DeletePeer', 
+         'AddRole', 'DeleteRole', 'SetPersonPrimarySite', 'AddSiteAddress', 'SliceDelete', 'NotifyPersons', 'GetKeyTypes', 'GetConfFiles', 
+         'GetIlinks', 'AddTagType', 'GetNodes', 'DeleteNodeTag', 'DeleteSliceFromNodesWhitelist', 'UpdateAddress', 'ResetPassword', 
+         'AddSliceToNodesWhitelist', 'AddRoleToTagType', 'AddLeases', 'GetAddresses', 'AddInitScript', 'RebootNode', 'GetPCUTypes', 
+         'RefreshPeer', 'GetBootMedium', 'UpdateKey', 'UpdatePCU', 'GetSession', 'AddInterfaceTag', 'UpdatePCUType', 'GetInterfaces', 
+         'SliceExtendedInfo', 'SliceNodesList', 'DeleteRoleFromTagType', 'DeleteSlice', 'GetSites', 'DeleteMessage', 'GetSliceFamily', 
+         'GetPlcRelease', 'UpdateTagType', 'AddSliceInstantiation', 'ResolveSlices', 'GetSlices', 'DeleteRoleFromPerson', 'GetSessions', 
+         'UpdatePeer', 'VerifyPerson', 'GetPersonTags', 'DeleteKeyType', 'AddSlice', 'SliceUserAdd', 'DeleteSession', 'GetMessages', 
+         'DeletePCU', 'GetPeerData', 'DeletePersonFromSite', 'DeleteTagType', 'GetPCUs', 'UpdateLeases', 'AddMessage', 
+         'DeletePCUProtocolType', 'DeleteInterfaceTag', 'AddPersonToSite', 'GetSlivers', 'SliceNodesDel', 'DeleteAddressTypeFromAddress', 
+         'AddNodeGroup', 'GetSliceTags', 'DeleteSite', 'GetSiteTags', 'UpdateMessage', 'DeleteSliceFromNodes', 'SliceRenew', 
+         'UpdatePCUProtocolType', 'DeleteSiteTag', 'GetPCUProtocolTypes', 'GetEvents', 'GetSliceTicket', 'AddPersonTag', 'BootGetNodeDetails', 
+         'DeleteInterface', 'DeleteNodeGroup', 'AddPCUProtocolType', 'BootCheckAuthentication', 'AddSiteTag', 'AddAddressTypeToAddress', 
+         'DeleteConfFile', 'DeleteInitScript', 'DeletePerson', 'DeleteIlink', 'DeleteAddressType', 'AddBootState', 'AuthCheck', 
+         'NotifySupport', 'GetSliceInstantiations', 'AddPCUType', 'AddPCU', 'AddSession', 'GetEventObjects', 'UpdateSiteTag', 
+         'UpdateNodeTag', 'AddPerson', 'BlacklistKey', 'UpdateInitScript', 'AddSliceToNodes', 'RebootNodeWithPCU', 'GetNodeTags', 
+         'GetSliceKeys', 'GetSliceSshKeys', 'AddNetworkMethod', 'SliceNodesAdd', 'DeletePersonFromSlice', 'ReportRunlevel', 
+         'GetNetworkTypes', 'UpdateSite', 'DeleteConfFileFromNodeGroup', 'UpdateNode', 'DeleteSliceInstantiation', 'DeleteSliceTag', 
+         'BootUpdateNode', 'UpdatePerson', 'UpdateConfFile', 'SliceUserDel', 'DeleteLeases', 'AddConfFileToNodeGroup', 'UpdatePersonTag', 
+         'DeleteConfFileFromNode', 'AddPersonToSlice', 'UnBindObjectFromPeer', 'AddNodeToPCU', 'GetLeaseGranularity', 'DeletePCUType', 
+         'GetTagTypes', 'GetNodeTypes', 'UpdateInterfaceTag', 'GetRoles', 'UpdateSlice', 'UpdateSliceTag', 'AddSliceTag', 'AddNetworkType', 
+         'AddInterface', 'AddAddressType', 'AddRoleToPerson', 'DeleteNodeType', 'GetLeases', 'UpdateInterface', 'SliceInfo', 'DeleteAddress', 
+         'SliceTicketGet', 'GetPersons', 'GetWhitelist', 'AddKeyType', 'UpdateAddressType', 'GetPeerName', 'DeleteNetworkMethod', 
+         'UpdateIlink', 'AddConfFileToNode', 'GetKeys', 'DeleteNodeFromPCU', 'GetInterfaceTags', 'GetBootStates', 'SetInterfaceSens', 'SetNodeLoadm', 
+         'GetInterfaceRate', 'GetNodeLoadw', 'SetInterfaceKey', 'GetNodeSlices', 'GetNodeLoadm', 'SetSliceVref', 'GetInterfaceIwpriv', 'SetNodeLoadw', 
+         'SetNodeSerial', 'GetNodePlainBootstrapfs', 'SetNodeMEMw', 'GetNodeResponse', 'SetInterfaceRate', 'SetSliceInitscript', 
+         'SetNodeFcdistro', 'GetNodeLoady', 'SetNodeArch', 'SetNodeKargs', 'SetNodeMEMm', 'SetNodeBWy', 'SetNodeBWw', 
+         'SetInterfaceSecurityMode', 'SetNodeBWm', 'SetNodeASType', 'GetNodeKargs', 'GetPersonColumnconf', 'GetNodeResponsem', 
+         'GetNodeCPUy', 'GetNodeCramfs', 'SetNodeSlicesw', 'SetPersonColumnconf', 'SetNodeSlicesy', 'GetNodeCPUw', 'GetNodeBWy', 
+         'GetNodeCPUm', 'GetInterfaceDriver', 'GetNodeLoad', 'GetInterfaceMode', 'GetNodeSerial', 'SetNodeSlicesm', 'SetNodeLoady', 
+         'GetNodeReliabilityw', 'SetSliceFcdistro', 'GetNodeReliabilityy', 'SetInterfaceEssid', 'SetSliceInitscriptCode', 
+         'GetNodeExtensions', 'GetSliceOmfControl', 'SetNodeCity', 'SetInterfaceIfname', 'SetNodeHrn', 'SetNodeNoHangcheck', 
+         'GetNodeNoHangcheck', 'GetSliceFcdistro', 'SetNodeCountry', 'SetNodeKvariant', 'GetNodeKvariant', 'GetNodeMEMy', 
+         'SetInterfaceIwpriv', 'GetNodeMEMw', 'SetInterfaceBackdoor', 'GetInterfaceFreq', 'SetInterfaceChannel', 'SetInterfaceNw', 
+         'GetPersonShowconf', 'GetSliceInitscriptCode', 'SetNodeMEM', 'GetInterfaceEssid', 'GetNodeMEMm', 'SetInterfaceMode', 
+         'SetInterfaceIwconfig', 'GetNodeSlicesm', 'GetNodeBWm', 'SetNodePlainBootstrapfs', 'SetNodeRegion', 'SetNodeCPU', 
+         'GetNodeSlicesw', 'SetNodeBW', 'SetNodeSlices', 'SetNodeCramfs', 'GetNodeSlicesy', 'GetInterfaceKey', 'GetSliceInitscript', 
+         'SetNodeCPUm', 'SetSliceArch', 'SetNodeLoad', 'SetNodeResponse', 'GetSliceSliverHMAC', 'GetNodeBWw', 'GetNodeRegion', 
+         'SetNodeMEMy', 'GetNodeASType', 'SetNodePldistro', 'GetSliceArch', 'GetNodeCountry', 'SetSliceOmfControl', 'GetNodeHrn', 
+         'GetNodeCity', 'SetInterfaceAlias', 'GetNodeBW', 'GetNodePldistro', 'GetSlicePldistro', 'SetNodeASNumber', 'GetSliceHmac', 
+         'SetSliceHmac', 'GetNodeMEM', 'GetNodeASNumber', 'GetInterfaceAlias', 'GetSliceVref', 'GetNodeArch', 'GetSliceSshKey', 
+         'GetInterfaceKey4', 'GetInterfaceKey2', 'GetInterfaceKey3', 'GetInterfaceKey1', 'GetInterfaceBackdoor', 'GetInterfaceIfname', 
+         'SetSliceSliverHMAC', 'SetNodeReliability', 'GetNodeCPU', 'SetPersonShowconf', 'SetNodeExtensions', 'SetNodeCPUy', 
+         'SetNodeCPUw', 'GetNodeResponsew', 'SetNodeResponsey', 'GetInterfaceSens', 'SetNodeResponsew', 'GetNodeResponsey', 
+         'GetNodeReliability', 'GetNodeReliabilitym', 'SetNodeResponsem', 'SetInterfaceDriver', 'GetInterfaceSecurityMode', 
+         'SetNodeDeployment', 'SetNodeReliabilitym', 'GetNodeFcdistro', 'SetInterfaceFreq', 'GetInterfaceNw', 'SetNodeReliabilityy', 
+         'SetNodeReliabilityw', 'GetInterfaceIwconfig', 'SetSlicePldistro', 'SetSliceSshKey', 'GetNodeDeployment', 'GetInterfaceChannel', 
+         'SetInterfaceKey2', 'SetInterfaceKey3', 'SetInterfaceKey1', 'SetInterfaceKey4'])
+     
+    _required_methods = set()
+
+    def __init__(self, username=None, password=None, sessionkey=None,
+            hostname = "www.planet-lab.eu",
+            urlpattern = "https://%(hostname)s:443/PLCAPI/",
+            localPeerName = "PLE"):
+        if sessionkey is not None:
+            self.auth = dict(AuthMethod='session', session=sessionkey)
+        elif username is not None and password is not None:
+            self.auth = dict(AuthMethod='password', Username=username, AuthString=password)
+        else:
+            self.auth = dict(AuthMethod='anonymous')
+        
+        self._localPeerName = localPeerName
+        
+        self.api = xmlrpclib.ServerProxy(
+            urlpattern % {'hostname':hostname},
+            allow_none = True)
+        
+    def test(self):
+        import warnings
+        
+        # validate XMLRPC server checking supported API calls
+        methods = set(self.api.system.listMethods())
+        if self._required_methods - methods:
+            warnings.warn("Unsupported REQUIRED methods: %s" % ( ", ".join(sorted(self._required_methods - methods)), ) )
+            return False
+        if self._expected_methods - methods:
+            warnings.warn("Unsupported EXPECTED methods: %s" % ( ", ".join(sorted(self._expected_methods - methods)), ) )
+        
+        try:
+            # test authorization
+            network_types = self.api.GetNetworkTypes(self.auth)
+        except (xmlrpclib.ProtocolError, xmlrpclib.Fault),e:
+            warnings.warn(str(e))
+        
+        return True
+    
+    
+    @property
+    def network_types(self):
+        try:
+            return self._network_types
+        except AttributeError:
+            self._network_types = self.api.GetNetworkTypes(self.auth)
+            return self._network_types
+    
+    @property
+    def peer_map(self):
+        try:
+            return self._peer_map
+        except AttributeError:
+            peers = self.api.GetPeers(self.auth, {}, ['shortname','peername','peer_id'])
+            self._peer_map = dict(
+                (peer['shortname'], peer['peer_id'])
+                for peer in peers
+            )
+            self._peer_map.update(
+                (peer['peername'], peer['peer_id'])
+                for peer in peers
+            )
+            return self._peer_map
+    
+
+    def GetNodeFlavour(self, node):
+        """
+        Returns detailed information on a given node's flavour, i.e. its base installation.
+
+        This depends on the global PLC settings in the PLC_FLAVOUR area, optionnally overridden by any of the following tags if set on that node:
+        'arch', 'pldistro', 'fcdistro', 'deployment', 'extensions'
+        
+        Params:
+        
+            * node : int or string
+                - int, Node identifier
+                - string, Fully qualified hostname
+        
+        Returns:
+
+            struct
+                * extensions : array of string, extensions to add to the base install
+                * fcdistro : string, the fcdistro this node should be based upon
+                * nodefamily : string, the nodefamily this node should be based upon
+                * plain : boolean, use plain bootstrapfs image if set (for tests)  
+        """
+        if not isinstance(node, (str, int, long)):
+            raise ValueError, "Node must be either a non-unicode string or an int"
+        return self.api.GetNodeFlavour(self.auth, node)
+    
+    def GetNodes(self, nodeIdOrName=None, fields=None, **kw):
+        """
+        Returns an array of structs containing details about nodes. 
+        If nodeIdOrName is specified and is an array of node identifiers or hostnames, 
+        or the filters keyword argument with struct of node attributes, 
+        or node attributes by keyword argument,
+        only nodes matching the filter will be returned.
+
+        If fields is specified, only the specified details will be returned. 
+        NOTE that if fields is unspecified, the complete set of native fields are returned, 
+        which DOES NOT include tags at this time.
+
+        Some fields may only be viewed by admins.
+        
+        Special params:
+            
+            fields: an optional list of fields to retrieve. The default is all.
+            
+            filters: an optional mapping with custom filters, which is the only
+                way to support complex filters like negation and numeric comparisons.
+                
+            peer: a string (or sequence of strings) with the name(s) of peers
+                to filter - or None for local nodes.
+        """
+        if fields is not None:
+            fieldstuple = (fields,)
+        else:
+            fieldstuple = ()
+        if nodeIdOrName is not None:
+            return self.api.GetNodes(self.auth, nodeIdOrName, *fieldstuple)
+        else:
+            filters = kw.pop('filters',{})
+            
+            if 'peer' in kw:
+                peer = kw.pop('peer')
+                
+                nameToId = self.peer_map.get
+                
+                if hasattr(peer, '__iter__'):
+                    # we can't mix local and external nodes, so
+                    # split and re-issue recursively in that case
+                    if None in peer or self._localPeerName in peer:
+                        if None in peer:    
+                            peer.remove(None)
+                        if self._localPeerName:
+                            peer.remove(self._localPeerName)
+                        return (
+                            self.GetNodes(nodeIdOrName, fields, filters=filters, peer=peer, **kw)
+                            + self.GetNodes(nodeIdOrName, fields, filters=filters, peer=None, **kw)
+                        )
+                    else:
+                        peer_filter = map(nameToId, peer)
+                elif peer is None or peer == self._localPeerName:
+                    peer_filter = None
+                else:
+                    peer_filter = nameToId(peer)
+                
+                filters['peer_id'] = peer_filter
+            
+            filters.update(kw)
+            return self.api.GetNodes(self.auth, filters, *fieldstuple)
+    
+    
+    
+