Several execution fixes:
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 21 Apr 2011 12:16:38 +0000 (14:16 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 21 Apr 2011 12:16:38 +0000 (14:16 +0200)
 * execution test case
 * testbed needs username/password to work
 * early assignment of nodes to an existing PL node if unambiguously available
 * other minor stuff

src/nepi/testbeds/planetlab/execute.py
src/nepi/testbeds/planetlab/interfaces.py
src/nepi/testbeds/planetlab/metadata_v01.py
src/nepi/testbeds/planetlab/node.py
src/nepi/testbeds/planetlab/plcapi.py
test/testbeds/planetlab/execute.py

index 9781661..8611834 100644 (file)
@@ -12,19 +12,49 @@ class TestbedController(testbed_impl.TestbedController):
         self.slicename = None
         self._traces = dict()
         
-        import node, interfaces
+        import node, interfaces, application
         self._node = node
         self._interfaces = interfaces
+        self._app = application
 
     @property
     def home_directory(self):
         return self._home_directory
+    
+    @property
+    def plapi(self):
+        if not hasattr(self, '_plapi'):
+            import plcapi
+            
+            if self.authUser:
+                self._plapi = plcapi.PLCAPI(
+                    username = self.authUser,
+                    password = self.authString)
+            else:
+                # anonymous access - may not be enough for much
+                self._plapi = plcapi.PLCAPI()
+        return self._plapi
+    
+    @property
+    def slice_id(self):
+        if not hasattr(self, '_slice_id'):
+            slices = self.plapi.GetSlices(self.slicename, fields=('slice_id',))
+            if slices:
+                self._slice_id = slices[0]['slice_id']
+            else:
+                # If it wasn't found, don't remember this failure, keep trying
+                return None
+        return self._slice_id
 
     def do_setup(self):
         self._home_directory = self._attributes.\
             get_attribute_value("homeDirectory")
         self.slicename = self._attributes.\
             get_attribute_value("slice")
+        self.authUser = self._attributes.\
+            get_attribute_value("authUser")
+        self.authString = self._attributes.\
+            get_attribute_value("authPass")
 
     def do_create(self):
         # Create node elements per XML data
@@ -39,6 +69,18 @@ class TestbedController(testbed_impl.TestbedController):
         
         # Wait for all nodes to be ready
         self.wait_nodes()
+    
+    def do_resource_discovery(self):
+        # Do what?
+        pass
+
+    def do_provisioning(self):
+        # Que te recontra?
+        pass
+    
+    def wait_nodes(self):
+        # Suuure...
+        pass
 
     def set(self, time, guid, name, value):
         super(TestbedController, self).set(time, guid, name, value)
@@ -93,45 +135,45 @@ class TestbedController(testbed_impl.TestbedController):
         self._traces[trace_id] = trace
 
     def _make_node(self, parameters):
-        node = self._node.Node()
+        node = self._node.Node(self.plapi)
         
         # Note: there is 1-to-1 correspondence between attribute names
         #   If that changes, this has to change as well
-        for attr in parameters.get_attribute_names():
-            setattr(node, attr, parameters.get_attribute_value(attr))
+        for attr,val in parameters.iteritems():
+            setattr(node, attr, val)
         
         return node
     
     def _make_node_iface(self, parameters):
-        iface = self._interfaces.NodeIface()
+        iface = self._interfaces.NodeIface(self.plapi)
         
         # Note: there is 1-to-1 correspondence between attribute names
         #   If that changes, this has to change as well
-        for attr in parameters.get_attribute_names():
-            setattr(iface, attr, parameters.get_attribute_value(attr))
+        for attr,val in parameters.iteritems():
+            setattr(iface, attr, val)
         
         return iface
     
     def _make_tun_iface(self, parameters):
-        iface = self._interfaces.TunIface()
+        iface = self._interfaces.TunIface(self.plapi)
         
         # Note: there is 1-to-1 correspondence between attribute names
         #   If that changes, this has to change as well
-        for attr in parameters.get_attribute_names():
-            setattr(iface, attr, parameters.get_attribute_value(attr))
+        for attr,val in parameters.iteritems():
+            setattr(iface, attr, val)
         
         return iface
     
     def _make_internet(self, parameters):
-        return self._node.Internet()
+        return self._interfaces.Internet(self.plapi)
     
     def _make_application(self, parameters):
-        app = self._app.Application()
+        app = self._app.Application(self.plapi)
         
         # Note: there is 1-to-1 correspondence between attribute names
         #   If that changes, this has to change as well
-        for attr in parameters.get_attribute_names():
-            setattr(app, attr, parameters.get_attribute_value(attr))
+        for attr,val in parameters.iteritems():
+            setattr(app, attr, val)
         
         return app
         
index d1dea80..4be8f53 100644 (file)
@@ -18,6 +18,7 @@ class NodeIface(object):
         self.address = None
         self.lladdr = None
         self.netprefix = None
+        self.netmask = None
         self.broadcast = True
         self._interface_id = None
 
@@ -27,6 +28,13 @@ class NodeIface(object):
         # These get initialized when the iface is connected to the internet
         self.has_internet = False
 
+    def __str__(self):
+        return "%s<ip:%s/%s up mac:%s>" % (
+            self.__class__.__name__,
+            self.address, self.netmask,
+            self.lladdr,
+        )
+
     def add_address(self, address, netprefix, broadcast):
         raise RuntimeError, "Cannot add explicit addresses to public interface"
     
@@ -40,13 +48,13 @@ class NodeIface(object):
             siblings: other NodeIface elements attached to the same node
         """
         
-        if (self.node or self.node._node_id) is None:
+        if self.node is None or self.node._node_id is None:
             raise RuntimeError, "Cannot pick interface without an assigned node"
         
         avail = self._api.GetInterfaces(
             node_id=self.node._node_id, 
             is_primary=self.primary,
-            fields=('interface_id','mac','netmask','ip') ))
+            fields=('interface_id','mac','netmask','ip') )
         
         used = set([sibling._interface_id for sibling in siblings
                     if sibling._interface_id is not None])
@@ -59,12 +67,13 @@ class NodeIface(object):
                 self.address = candidate['ip']
                 self.lladdr = candidate['mac']
                 self.netprefix = candidate['netmask']
+                self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
                 return
         else:
             raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
 
     def validate(self):
-        if not element.has_internet:
+        if not self.has_internet:
             raise RuntimeError, "All external interface devices must be connected to the Internet"
     
 
@@ -87,6 +96,14 @@ class TunIface(object):
         # These get initialized when the iface is connected to its node
         self.node = None
 
+    def __str__(self):
+        return "%s<ip:%s/%s %s%s>" % (
+            self.__class__.__name__,
+            self.address, self.netmask,
+            " up" if self.up else " down",
+            " snat" if self.snat else "",
+        )
+
     def add_address(self, address, netprefix, broadcast):
         if (self.address or self.netprefix or self.netmask) is not None:
             raise RuntimeError, "Cannot add more than one address to TUN interfaces"
index d8879a1..2142da6 100644 (file)
@@ -50,7 +50,7 @@ def create_node(testbed_instance, guid):
 
 def create_nodeiface(testbed_instance, guid):
     parameters = testbed_instance._get_parameters(guid)
-    element = testbed_instance.create_node_iface(parameters)
+    element = testbed_instance._make_node_iface(parameters)
     testbed_instance.elements[guid] = element
 
 def create_tuniface(testbed_instance, guid):
@@ -74,8 +74,6 @@ def start_application(testbed_instance, guid):
     parameters = testbed_instance._get_parameters(guid)
     traces = testbed_instance._get_traces(guid)
     app = testbed_instance.elements[guid]
-    sudo = parameters["sudo"]
-    command = parameters["command"]
     
     app.stdout = testbed_instance.trace_filename(guid, "stdout")
     app.stderr = testbed_instance.trace_filename(guid, "stderr")
@@ -90,17 +88,16 @@ def status_application(testbed_instance, guid):
         return STATUS_NOT_STARTED
     app = testbed_instance.elements[guid]
     # TODO
-    return STATUS_NOT_STARTED
+    return STATUS_FINISHED
 
 ### Configure functions ###
 
 def configure_nodeiface(testbed_instance, guid):
     element = testbed_instance._elements[guid]
-    if not guid in testbed_instance._add_address:
-        return
     
     # Cannot explicitly configure addresses
-    del testbed_instance._add_address[guid]
+    if guid in testbed_instance._add_address:
+        del testbed_instance._add_address[guid]
     
     # Get siblings
     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
@@ -119,6 +116,7 @@ def configure_tuniface(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
@@ -130,6 +128,12 @@ def configure_tuniface(testbed_instance, guid):
 def configure_node(testbed_instance, guid):
     element = testbed_instance._elements[guid]
     
+    # If we have only one candidate, simply use it
+    candidates = element.find_candidates(
+        filter_slice_id = testbed_instance.slice_id)
+    if len(candidates) == 1:
+        element.assign_node_id(iter(candidates).next())
+    
     # Do some validations
     element.validate()
 
@@ -354,9 +358,9 @@ traces = dict({
         }) 
     })
 
-create_order = [ NODE, NODEIFACE, TUNIFACE, APPLICATION ]
+create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
 
-configure_order = [ NODE, NODEIFACE, TUNIFACE, APPLICATION ]
+configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
 
 factories_info = dict({
     NODE: dict({
@@ -425,6 +429,20 @@ testbed_attributes = dict({
             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
             "validation_function": validation.is_string
         }),
+        "auth_user": dict({
+            "name": "authUser",
+            "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
+            "type": Attribute.STRING,
+            "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
+            "validation_function": validation.is_string
+        }),
+        "auth_pass": dict({
+            "name": "authPass",
+            "help": "The PlanetLab user's password.",
+            "type": Attribute.STRING,
+            "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
+            "validation_function": validation.is_string
+        }),
     })
 
 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
index e9a9557..e5fd483 100644 (file)
@@ -62,12 +62,14 @@ class Node(object):
             + filter(has, self.TAGFILTERS.iterkeys())
         )
     
-    def find_candidates(self):
+    def find_candidates(self, filter_slice_id=None):
         fields = ('node_id',)
         replacements = {'timeframe':self.timeframe}
         
         # get initial candidates (no tag filters)
         basefilters = self.build_filters({}, self.BASEFILTERS)
+        if filter_slice_id:
+            basefilters['|slice_ids'] = (filter_slice_id,)
         
         # keyword-only "pseudofilters"
         extra = {}
@@ -115,6 +117,37 @@ class Node(object):
             
         return candidates
 
+    def assign_node_id(self, node_id):
+        self._node_id = node_id
+        self.fetch_node_info()
+    
+    def fetch_node_info(self):
+        info = self._api.GetNodes(self._node_id)
+        tags = dict( (t['tagname'],t['value'])
+                     for t in self._api.GetNodeTags(node_id=self._node_id, fields=('tagname','value')) )
+
+        self.min_num_external_ifaces = None
+        self.max_num_external_ifaces = None
+        self.timeframe = 'm'
+        
+        replacements = {'timeframe':self.timeframe}
+        for attr, tag in self.BASEFILTERS.iteritems():
+            if tag in info:
+                value = info[tag]
+                setattr(self, attr, value)
+        for attr, (tag,_) in self.TAGFILTERS.iteritems():
+            tag = tag % replacements
+            if tag in tags:
+                value = tags[tag]
+                setattr(self, attr, value)
+        
+        if 'peer_id' in info:
+            self.site = self._api.peer_map[info['peer_id']]
+        
+        if 'interface_ids' in info:
+            self.min_num_external_ifaces = \
+            self.max_num_external_ifaces = len(info['interface_ids'])
+
     def validate(self):
         pass
 
index ab8fd2e..2c1752e 100644 (file)
@@ -115,6 +115,10 @@ class PLCAPI(object):
                 (peer['peername'], peer['peer_id'])
                 for peer in peers
             )
+            self._peer_map = dict(
+                (peer['peer_id'], peer['shortname'])
+                for peer in peers
+            )
             return self._peer_map
     
 
@@ -224,10 +228,22 @@ class PLCAPI(object):
         else:
             fieldstuple = ()
         if interfaceIdOrIp is not None:
-            return self.api.GetNodeTags(self.auth, interfaceIdOrIp, *fieldstuple)
+            return self.api.GetInterfaces(self.auth, interfaceIdOrIp, *fieldstuple)
         else:
             filters = kw.pop('filters',{})
             filters.update(kw)
-            return self.api.GetNodeTags(self.auth, filters, *fieldstuple)
+            return self.api.GetInterfaces(self.auth, filters, *fieldstuple)
+        
+    def GetSlices(self, sliceIdOrName=None, fields=None, **kw):
+        if fields is not None:
+            fieldstuple = (fields,)
+        else:
+            fieldstuple = ()
+        if sliceIdOrName is not None:
+            return self.api.GetSlices(self.auth, sliceIdOrName, *fieldstuple)
+        else:
+            filters = kw.pop('filters',{})
+            filters.update(kw)
+            return self.api.GetSlices(self.auth, filters, *fieldstuple)
         
 
index b02f542..bb3c77f 100755 (executable)
@@ -3,13 +3,14 @@
 
 import getpass
 from nepi.util.constants import STATUS_FINISHED
-from nepi.testbeds import netns
+from nepi.testbeds import planetlab
 import os
 import shutil
 import tempfile
 import test_util
 import time
 import unittest
+import re
 
 class NetnsExecuteTestCase(unittest.TestCase):
     def setUp(self):
@@ -18,6 +19,53 @@ class NetnsExecuteTestCase(unittest.TestCase):
     def tearDown(self):
         shutil.rmtree(self.root_dir)
 
+    def test_simple(self):
+        testbed_version = "01"
+        instance = planetlab.TestbedController(testbed_version)
+        
+        instance.defer_configure("homeDirectory", self.root_dir)
+        instance.defer_configure("slice", "inria_nepi12")
+        instance.defer_configure("authUser", "claudio-daniel.freire@inria.fr")
+        instance.defer_configure("authPass", getpass.getpass())
+        
+        instance.defer_create(2, "Node")
+        instance.defer_create_set(2, "hostname", "onelab11.pl.sophia.inria.fr")
+        instance.defer_create(3, "Node")
+        instance.defer_create_set(3, "hostname", "onelab10.pl.sophia.inria.fr")
+        instance.defer_create(4, "NodeInterface")
+        instance.defer_connect(2, "devs", 4, "node")
+        instance.defer_create(5, "NodeInterface")
+        instance.defer_connect(3, "devs", 5, "node")
+        instance.defer_create(6, "Internet")
+        instance.defer_connect(4, "inet", 6, "devs")
+        instance.defer_connect(5, "inet", 6, "devs")
+        instance.defer_create(7, "Application")
+        instance.defer_create_set(7, "command", "ping -qc1 {#GUID-3.addr[0].[ip]#}")
+        instance.defer_add_trace(7, "stdout")
+        instance.defer_connect(7, "node", 2, "apps")
+
+        instance.do_setup()
+        instance.do_create()
+        instance.do_connect()
+        instance.do_configure()
+        
+        print instance.elements[4]
+        print instance.elements[5]
+        
+        instance.start()
+        while instance.status(7) != STATUS_FINISHED:
+            time.sleep(0.5)
+        ping_result = instance.trace(7, "stdout")
+        comp_result = r"""PING .* \(.*) \d*\(\d*\) bytes of data.
+
+--- .* ping statistics ---
+1 packets transmitted, 1 received, 0% packet loss, time \d*ms.*
+"""
+        self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE))
+        instance.stop()
+        instance.shutdown()
+        
+
 if __name__ == '__main__':
     unittest.main()