more consistently rename plc_spec['hostname'] into plc_spec['host_box']
[tests.git] / system / Substrate.py
index e660b47..cffc5fa 100644 (file)
 # .  and their admissible load (max # of myplcs)
 # . the pool of DNS-names and IP-addresses available for nodes
 # 
+# #################### implem. note
+# 
+# this model relies on 'sensing' the substrate, 
+# i.e. probing all the boxes for their running instances of vservers and qemu
+# this is how we get rid of tracker inconsistencies 
+# however there is a 'black hole' between the time where a given address is 
+# allocated and when it actually gets used/pingable
+# this is why we still need a shared knowledge among running tests
+# in a file named /root/starting
+# this is connected to the Pool class 
+# 
 # ####################
 
 import os.path, sys
@@ -78,7 +89,8 @@ class PoolItem:
     def __init__ (self,hostname,userdata):
         self.hostname=hostname
         self.userdata=userdata
-        # slot holds 'busy' or 'free' or 'fake' or None
+        # slot holds 'busy' or 'free' or 'mine' or 'starting' or None
+        # 'mine' is for our own stuff, 'starting' from the concurrent tests
         self.status=None
         self.ip=None
 
@@ -95,15 +107,16 @@ class Pool:
     def __init__ (self, tuples,message):
         self.pool= [ PoolItem (h,u) for (h,u) in tuples ] 
         self.message=message
-        self._sensed=False
 
     def sense (self):
-        if self._sensed: return
         print 'Checking IP pool',self.message,
         for item in self.pool:
-            if self.check_ping (item.hostname): item.status='busy'
-            else:                               item.status='free'
-        self._sensed=True
+            if item.status is not None: 
+                continue
+            if self.check_ping (item.hostname): 
+                item.status='busy'
+            else:
+                item.status='free'
         print 'Done'
 
     def list (self):
@@ -123,14 +136,14 @@ class Pool:
 
     def next_free (self):
         for i in self.pool:
-            if i.status in ['busy','fake']: continue
-            i.status='fake'
-            return (i.hostname,i.userdata)
+            if i.status == 'free':
+                i.status='mine'
+                return (i.hostname,i.userdata)
         raise Exception,"No IP address available in pool %s"%self.message
 
-# OS-dependent ping option (support for macos, for convenience)
+    # OS-dependent ping option (support for macos, for convenience)
     ping_timeout_option = None
-# checks whether a given hostname/ip responds to ping
+    # checks whether a given hostname/ip responds to ping
     def check_ping (self,hostname):
         if not Pool.ping_timeout_option:
             (status,osname) = commands.getstatusoutput("uname -s")
@@ -147,6 +160,39 @@ class Pool:
         else:           print '-',
         return status == 0
 
+    # the place were other test instances tell about their not-yet-started
+    # instances, that go undetected through sensing
+    starting='/root/starting'
+    def add_starting (self, name):
+        try:    items=[line.strip() for line in file(Pool.starting).readlines()]
+        except: items=[]
+        if not name in items:
+            file(Pool.starting,'a').write(name+'\n')
+        for i in self.pool:
+            if i.hostname==name: i.status='mine'
+            
+    def load_starting (self):
+        try:    items=[line.strip() for line in file(Pool.starting).readlines()]
+        except: items=[]
+        for item in items:
+            for i in self.pool:
+                if i.hostname==item and i.status==None: i.status='starting'
+
+    def release_my_starting (self):
+        for i in self.pool:
+            if i.status=='mine': 
+                self.del_starting(i.hostname)
+                i.status=None
+
+    def del_starting (self, name):
+        try:    items=[line.strip() for line in file(Pool.starting).readlines()]
+        except: items=[]
+        if name in items:
+            f=file(Pool.starting,'w')
+            for item in items: 
+                if item != name: f.write(item+'\n')
+            f.close()
+    
 ####################
 class Box:
     def __init__ (self,hostname):
@@ -292,10 +338,10 @@ class PlcBox (Box):
         self.plc_instances.remove(plc_instance)
 
     # fill one slot even though this one is not started yet
-    def add_fake (self, plcname):
-        fake=PlcInstance('fake_'+plcname,0,self)
-        fake.set_now()
-        self.plc_instances.append(fake)
+    def add_dummy (self, plcname):
+        dummy=PlcInstance('dummy_'+plcname,0,self)
+        dummy.set_now()
+        self.plc_instances.append(dummy)
 
     def line(self): 
         msg="%s [max=%d,%d free] (%s)"%(self.hostname, self.max_plcs,self.free_spots(),self.uname())
@@ -358,8 +404,9 @@ class PlcBox (Box):
 #            print self.margin_outline(self.vplcname(longname)),"%(vserver_line)s [=%(longname)s]"%locals()
 
         # scan timestamps
+        running_ctx_ids = [ i.ctxid for i in self.plc_instances ]
         command=   ['grep','.']
-        command += ['/vservers/%s/timestamp'%b for b in ctx_dict.values()]
+        command += ['/vservers/%s/timestamp'%b for b in running_ctx_ids]
         command += ['/dev/null']
         ts_lines=self.backquote_ssh(command,trash_err=True).split('\n')
         for ts_line in ts_lines:
@@ -372,6 +419,7 @@ class PlcBox (Box):
                 q=self.plc_instance_by_vservername(vservername)
                 if not q: 
                     print 'WARNING unattached plc instance',ts_line
+                    print 'was expeting to find',vservername,'in',[i.vservername for i in self.plc_instances]
                     continue
                 q.set_timestamp(timestamp)
             except:  print 'WARNING, could not parse ts line',ts_line
@@ -429,10 +477,10 @@ class QemuBox (Box):
         self.qemu_instances.remove(qemu_instance)
 
     # fill one slot even though this one is not started yet
-    def add_fake (self, nodename):
-        fake=QemuInstance('fake_'+nodename,0,self)
-        fake.set_now()
-        self.qemu_instances.append(fake)
+    def add_dummy (self, nodename):
+        dummy=QemuInstance('dummy_'+nodename,0,self)
+        dummy.set_now()
+        self.qemu_instances.append(dummy)
 
     def line (self):
         msg="%s [max=%d,%d free] (%s)"%(self.hostname, self.max_qemus,self.free_spots(),self.driver())
@@ -549,9 +597,6 @@ class Substrate:
         self.vplc_pool = Pool (self.vplc_ips(),"for vplcs")
         self.vnode_pool = Pool (self.vnode_ips(),"for vnodes")
 
-        self.vnode_pool.list()
-
-
 #    def build_box_names (self):
 #        return [ h for h in self.build_boxes_spec() ]
 #    def plc_boxes (self):
@@ -609,15 +654,17 @@ class Substrate:
                                              plc_instance_to_kill.plc_box.hostname)
 
         utils.header( 'plc %s -> box %s'%(plc['name'],plc_box.line()))
-        plc_box.add_fake(plc['name'])
+        plc_box.add_dummy(plc['name'])
         #### OK we have a box to run in, let's find an IP address
         # look in options
         if options.ips_vplc:
             vplc_hostname=options.ips_vplc.pop()
         else:
             self.vplc_pool.sense()
+            self.vplc_pool.load_starting()
             (vplc_hostname,unused)=self.vplc_pool.next_free()
         vplc_ip = self.vplc_pool.get_ip(vplc_hostname)
+        self.vplc_pool.add_starting(vplc_hostname)
 
         #### compute a helpful vserver name
         # remove domain in hostname
@@ -628,7 +675,7 @@ class Substrate:
         #### apply in the plc_spec
         # # informative
         # label=options.personality.replace("linux","")
-        mapper = {'plc': [ ('*' , {'hostname':plc_box.hostname,
+        mapper = {'plc': [ ('*' , {'host_box':plc_box.hostname,
                                    # 'name':'%s-'+label,
                                    'name': plc_name,
                                    'vservername':vservername,
@@ -675,30 +722,31 @@ class Substrate:
                                                  qemu_instance_to_kill.qemu_box.hostname)
 
             utils.header( 'node %s -> qemu box %s'%(nodename,qemu_box.line()))
-            qemu_box.add_fake(nodename)
+            qemu_box.add_dummy(nodename)
             #### OK we have a box to run in, let's find an IP address
             # look in options
             if options.ips_vnode:
-                qemu_hostname=options.ips_vnode.pop()
-                mac=self.vnode_pool.retrieve_userdata(qemu_hostname)
-                print 'case 1 hostname',qemu_hostname,'mac',mac
+                vnode_hostname=options.ips_vnode.pop()
+                mac=self.vnode_pool.retrieve_userdata(vnode_hostname)
             else:
                 self.vnode_pool.sense()
-                (qemu_hostname,mac)=self.vnode_pool.next_free()
-                print 'case 2 hostname',qemu_hostname,'mac',mac
-            ip=self.vnode_pool.get_ip (qemu_hostname)
-            utils.header("Attaching %s on IP %s MAC %s"%(plc['name'],qemu_hostname,mac))
+                self.vnode_pool.load_starting()
+                (vnode_hostname,mac)=self.vnode_pool.next_free()
+            ip=self.vnode_pool.get_ip (vnode_hostname)
+            self.vnode_pool.add_starting(vnode_hostname)
 
-            if qemu_hostname.find('.')<0:
-                qemu_hostname += "."+self.domain()
+            if vnode_hostname.find('.')<0:
+                vnode_hostname += "."+self.domain()
             nodemap={'host_box':qemu_box.hostname,
-                     'node_fields:hostname':qemu_hostname,
+                     'node_fields:hostname':vnode_hostname,
                      'interface_fields:ip':ip, 
                      'interface_fields:mac':mac,
                      }
             nodemap.update(self.network_settings())
             maps.append ( (nodename, nodemap) )
 
+            utils.header("Attaching %s on IP %s MAC %s"%(plc['name'],vnode_hostname,mac))
+
         return test_mapper.map({'node':maps})[0]
 
     def localize_sfa_rspec (self,plc,options):
@@ -713,6 +761,12 @@ class Substrate:
                 plc['sfa']['sfa_slice_rspec']['part4'] = node['node_fields']['hostname']
        return plc
 
+    #################### release:
+    def release (self,options):
+        self.vplc_pool.release_my_starting()
+        self.vnode_pool.release_my_starting()
+        pass
+
     #################### show results for interactive mode
     def list_all (self):
         self.sense()