Merging with head
[nepi.git] / src / nepi / testbeds / planetlab / node.py
index 245e636..6514372 100644 (file)
@@ -8,6 +8,8 @@ import rspawn
 import time
 import os
 import collections
+import cStringIO
+import resourcealloc
 
 from nepi.util import server
 
@@ -22,12 +24,12 @@ class Node(object):
         #   There are replacements that are applied with string formatting,
         #   so '%' has to be escaped as '%%'.
         'architecture' : ('arch','value'),
-        'operating_system' : ('fcdistro','value'),
+        'operatingSystem' : ('fcdistro','value'),
         'pl_distro' : ('pldistro','value'),
-        'min_reliability' : ('reliability%(timeframe)s', ']value'),
-        'max_reliability' : ('reliability%(timeframe)s', '[value'),
-        'min_bandwidth' : ('bw%(timeframe)s', ']value'),
-        'max_bandwidth' : ('bw%(timeframe)s', '[value'),
+        'minReliability' : ('reliability%(timeframe)s', ']value'),
+        'maxReliability' : ('reliability%(timeframe)s', '[value'),
+        'minBandwidth' : ('bw%(timeframe)s', ']value'),
+        'maxBandwidth' : ('bw%(timeframe)s', '[value'),
     }    
     
     DEPENDS_PIDFILE = '/tmp/nepi-depends.pid'
@@ -41,14 +43,14 @@ class Node(object):
         # Attributes
         self.hostname = None
         self.architecture = None
-        self.operating_system = None
+        self.operatingSystem = None
         self.pl_distro = None
         self.site = None
         self.emulation = None
-        self.min_reliability = None
-        self.max_reliability = None
-        self.min_bandwidth = None
-        self.max_bandwidth = None
+        self.minReliability = None
+        self.maxReliability = None
+        self.minBandwidth = None
+        self.maxBandwidth = None
         self.min_num_external_ifaces = None
         self.max_num_external_ifaces = None
         self.timeframe = 'm'
@@ -68,6 +70,21 @@ class Node(object):
         # Those are filled when an actual node is allocated
         self._node_id = None
     
+    @property
+    def _nepi_testbed_environment_setup(self):
+        command = cStringIO.StringIO()
+        command.write('export PYTHONPATH=$PYTHONPATH:%s' % (
+            ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.pythonpath])
+        ))
+        command.write(' ; export PATH=$PATH:%s' % (
+            ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.pythonpath])
+        ))
+        if self.env:
+            for envkey, envvals in self.env.iteritems():
+                for envval in envvals:
+                    command.write(' ; export %s=%s' % (envkey, envval))
+        return command.getvalue()
+    
     def build_filters(self, target_filters, filter_map):
         for attr, tag in filter_map.iteritems():
             value = getattr(self, attr, None)
@@ -89,9 +106,14 @@ class Node(object):
         
         # get initial candidates (no tag filters)
         basefilters = self.build_filters({}, self.BASEFILTERS)
+        rootfilters = basefilters.copy()
         if filter_slice_id:
             basefilters['|slice_ids'] = (filter_slice_id,)
         
+        # only pick healthy nodes
+        basefilters['run_level'] = 'boot'
+        basefilters['boot_state'] = 'boot'
+        
         # keyword-only "pseudofilters"
         extra = {}
         if self.site:
@@ -107,7 +129,7 @@ class Node(object):
             
             # don't bother if there's no filter defined
             if attr in applicable:
-                tagfilter = basefilters.copy()
+                tagfilter = rootfilters.copy()
                 tagfilter['tagname'] = tagname % replacements
                 tagfilter[expr % replacements] = getattr(self,attr)
                 tagfilter['node_id'] = list(candidates)
@@ -162,6 +184,42 @@ class Node(object):
             candidates = set(filter(predicate, candidates))
             
         return candidates
+    
+    def make_filter_description(self):
+        """
+        Makes a human-readable description of filtering conditions
+        for find_candidates.
+        """
+        
+        # get initial candidates (no tag filters)
+        filters = self.build_filters({}, self.BASEFILTERS)
+        
+        # keyword-only "pseudofilters"
+        if self.site:
+            filters['peer'] = self.site
+            
+        # filter by tag, one tag at a time
+        applicable = self.applicable_filters
+        for tagfilter in self.TAGFILTERS.iteritems():
+            attr, (tagname, expr) = tagfilter
+            
+            # don't bother if there's no filter defined
+            if attr in applicable:
+                filters[attr] = getattr(self,attr)
+        
+        # filter by vsys tags - special case since it doesn't follow
+        # the usual semantics
+        if self.required_vsys:
+            filters['vsys'] = ','.join(list(self.required_vsys))
+        
+        # filter by iface count
+        if self.min_num_external_ifaces is not None or self.max_num_external_ifaces is not None:
+            filters['num_ifaces'] = '-'.join([
+                str(self.min_num_external_ifaces or '0'),
+                str(self.max_num_external_ifaces or 'inf')
+            ])
+            
+        return '; '.join(map('%s: %s'.__mod__,filters.iteritems()))
 
     def assign_node_id(self, node_id):
         self._node_id = node_id
@@ -291,3 +349,46 @@ class Node(object):
             return False
     
 
+    def configure_routes(self, routes, devs):
+        """
+        Add the specified routes to the node's routing table
+        """
+        rules = []
+        
+        for route in routes:
+            for dev in devs:
+                if dev.routes_here(route):
+                    # Schedule rule
+                    dest, prefix, nexthop = route
+                    rules.append(
+                        "add %s%s gw %s %s" % (
+                            dest,
+                            (("/%d" % (prefix,)) if prefix and prefix != 32 else ""),
+                            nexthop,
+                            dev.if_name,
+                        )
+                    )
+                    
+                    # Stop checking
+                    break
+            else:
+                raise RuntimeError, "Route %s cannot be bound to any virtual interface " \
+                    "- PL can only handle rules over virtual interfaces. Candidates are: %s" % (route,devs)
+        
+        (out,err),proc = server.popen_ssh_command(
+            "( sudo -S bash -c 'cat /vsys/vroute.out >&2' & ) ; sudo -S bash -c 'cat > /vsys/vroute.in' ; sleep 0.1" % dict(
+                home = server.shell_escape(self.home_path)),
+            host = self.hostname,
+            port = None,
+            user = self.slicename,
+            agent = None,
+            ident_key = self.ident_path,
+            server_key = self.server_key,
+            stdin = '\n'.join(rules)
+            )
+        
+        if proc.wait() or err:
+            raise RuntimeError, "Could not set routes (%s) errors: %s%s" % (rules,out,err)
+        
+        
+