python3 - first draft looks kind of OK
[tests.git] / system / TestNode.py
index 37c4dd7..b018598 100644 (file)
@@ -1,7 +1,10 @@
 # Thierry Parmentelat <thierry.parmentelat@inria.fr>
 # Copyright (C) 2010 INRIA 
 #
-import sys, os, os.path, time, base64
+import sys
+import os, os.path
+import time
+import base64
 
 import utils
 from TestUser import TestUser
@@ -9,8 +12,8 @@ from TestBoxQemu import TestBoxQemu
 from TestSsh import TestSsh
 from Completer import CompleterTask
 
-class CompleterTaskNodeSsh (CompleterTask):
-    def __init__ (self, hostname, qemuname, local_key, command=None,
+class CompleterTaskNodeSsh(CompleterTask):
+    def __init__(self, hostname, qemuname, local_key, command=None,
                   boot_state="boot", expected=True, dry_run=False):
         self.hostname = hostname
         self.qemuname = qemuname
@@ -19,54 +22,54 @@ class CompleterTaskNodeSsh (CompleterTask):
         self.command = command if command is not None else "hostname;uname -a"
         self.expected = expected
         self.dry_run = dry_run
-        self.test_ssh =  TestSsh (self.hostname, key=self.local_key)
-    def run (self, silent):
+        self.test_ssh =  TestSsh(self.hostname, key=self.local_key)
+    def run(self, silent):
         command = self.test_ssh.actual_command(self.command)
-        retcod = utils.system (command, silent=silent, dry_run=self.dry_run)
+        retcod = utils.system(command, silent=silent, dry_run=self.dry_run)
         if self.expected:
             return retcod == 0
         else:
             return retcod != 0
-    def failure_epilogue (self):
-        print "Cannot reach {} in {} mode".format(self.hostname, self.boot_state)
+    def failure_epilogue(self):
+        print("Cannot reach {} in {} mode".format(self.hostname, self.boot_state))
 
 class TestNode:
 
-    def __init__ (self, test_plc, test_site, node_spec):
-       self.test_plc = test_plc
-       self.test_site = test_site
-       self.node_spec = node_spec
+    def __init__(self, test_plc, test_site, node_spec):
+        self.test_plc = test_plc
+        self.test_site = test_site
+        self.node_spec = node_spec
         
     def name(self):
         return self.node_spec['node_fields']['hostname']
     
-    def dry_run (self):
+    def dry_run(self):
         return self.test_plc.options.dry_run
 
     @staticmethod
-    def is_qemu_model (model):
+    def is_qemu_model(model):
         return model.find("qemu") >= 0
-    def is_qemu (self):
+    def is_qemu(self):
         return TestNode.is_qemu_model(self.node_spec['node_fields']['model'])
 
     @staticmethod
-    def is_real_model (model):
+    def is_real_model(model):
         return not TestNode.is_qemu_model(model)
-    def is_real (self):
-        return TestNode.is_real_model (self.node_spec['node_fields']['model'])
+    def is_real(self):
+        return TestNode.is_real_model(self.node_spec['node_fields']['model'])
 
     def buildname(self):
         return self.test_plc.options.buildname
         
-    def nodedir (self):
+    def nodedir(self):
         if self.is_qemu():
             return "qemu-{}".format(self.name())
         else:
             return "real-{}".format(self.name())
 
     # this returns a hostname
-    def host_box (self):
-        if self.is_real ():
+    def host_box(self):
+        if self.is_real():
             return 'localhost'
         else:
             try:
@@ -76,14 +79,14 @@ class TestNode:
                 return 'localhost'
 
     # this returns a TestBoxQemu instance - cached in .test_box_value
-    def test_box (self):
+    def test_box(self):
         try:
             return self.test_box_value
         except:
-            self.test_box_value = TestBoxQemu (self.host_box(),self.buildname())
+            self.test_box_value = TestBoxQemu(self.host_box(),self.buildname())
             return self.test_box_value
 
-    def create_node (self):
+    def create_node(self):
         ownername = self.node_spec['owner']
         user_spec = self.test_site.locate_user(ownername)
         test_user = TestUser(self.test_plc,self.test_site,user_spec)
@@ -108,19 +111,19 @@ class TestNode:
         else:
 #            print 'USING NEW INTERFACE with separate ip addresses'
             # this is for setting the 'dns' stuff that now goes with the node
-            server.UpdateNode (userauth, self.name(), self.node_spec['node_fields_nint'])
-            interface_id = server.AddInterface (userauth, self.name(),self.node_spec['interface_fields_nint'])
-            server.AddIpAddress (userauth, interface_id, self.node_spec['ipaddress_fields'])
+            server.UpdateNode(userauth, self.name(), self.node_spec['node_fields_nint'])
+            interface_id = server.AddInterface(userauth, self.name(),self.node_spec['interface_fields_nint'])
+            server.AddIpAddress(userauth, interface_id, self.node_spec['ipaddress_fields'])
             route_fields = self.node_spec['route_fields']
             route_fields['interface_id'] = interface_id
-            server.AddRoute (userauth, node_id, self.node_spec['route_fields'])
+            server.AddRoute(userauth, node_id, self.node_spec['route_fields'])
             pass
         # populate network interfaces - others
-        if self.node_spec.has_key('extra_interfaces'):
+        if 'extra_interfaces' in self.node_spec:
             for interface in self.node_spec['extra_interfaces']:
                 server.AddInterface(userauth,self.name(), interface['interface_fields'])
-                if interface.has_key('settings'):
-                    for (attribute,value) in interface['settings'].iteritems():
+                if 'settings' in interface:
+                    for attribute, value in interface['settings'].items():
                         # locate node network
                         interface = server.GetInterfaces(userauth,{'ip':interface['interface_fields']['ip']})[0]
                         interface_id = interface['interface_id']
@@ -133,7 +136,7 @@ class TestNode:
                         # attach value
                         server.AddInterfaceTag(userauth,interface_id,attribute,value)
 
-    def delete_node (self):
+    def delete_node(self):
         # uses the right auth as far as poss.
         try:
             ownername = self.node_spec['owner']
@@ -157,6 +160,7 @@ class TestNode:
     def bootcd(self):
         "all nodes: invoke GetBootMedium and store result locally"
         utils.header("Calling GetBootMedium for {}".format(self.name()))
+        # this would clearly belong in the config but, well ..
         options = []
         if self.is_qemu():
             options.append('serial')
@@ -164,43 +168,52 @@ class TestNode:
             options.append('systemd-debug')
         encoded = self.test_plc.apiserver.GetBootMedium(self.test_plc.auth_root(), 
                                                         self.name(), 'node-iso', '', options)
-        if (encoded == ''):
-            raise Exception, 'GetBootmedium failed'
+        if encoded == '':
+            raise Exception('GetBootmedium failed')
 
         filename = "{}/{}.iso".format(self.nodedir(), self.name())
         utils.header('Storing boot medium into {}'.format(filename))
+
+        # xxx discovered with python3, but a long stading issue:
+        # encoded at this point is a str instead of a bytes
+        # Quick & dirty : we convert this explicitly to a bytearray
+        # Longer run : clearly it seems like the plcapi server side should
+        # tag its result with <base64></base64> rather than as a string
+        bencoded = str.encode(encoded)
         if self.dry_run():
-            print "Dry_run: skipped writing of iso image"
+            print("Dry_run: skipped writing of iso image")
             return True
         else:
-            file(filename,'w').write(base64.b64decode(encoded))
+            # with python3 we need to call decodestring here
+            with open(filename,'wb') as storage:
+                storage.write(base64.decodestring(bencoded))
             return True
 
-    def nodestate_reinstall (self):
+    def nodestate_reinstall(self):
         "all nodes: mark PLCAPI boot_state as reinstall"
         self.test_plc.apiserver.UpdateNode(self.test_plc.auth_root(),
                                            self.name(),{'boot_state':'reinstall'})
         return True
     
-    def nodestate_safeboot (self):
+    def nodestate_safeboot(self):
         "all nodes: mark PLCAPI boot_state as safeboot"
         self.test_plc.apiserver.UpdateNode(self.test_plc.auth_root(),
                                            self.name(),{'boot_state':'safeboot'})
         return True
     
-    def nodestate_boot (self):
+    def nodestate_boot(self):
         "all nodes: mark PLCAPI boot_state as boot"
         self.test_plc.apiserver.UpdateNode(self.test_plc.auth_root(),
                                            self.name(),{'boot_state':'boot'})
         return True
 
-    def nodestate_show (self):
+    def nodestate_show(self):
         "all nodes: show PLCAPI boot_state"
         if self.dry_run():
-            print "Dry_run: skipped getting current node state"
+            print("Dry_run: skipped getting current node state")
             return True
         state = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(), self.name(), ['boot_state'])[0]['boot_state']
-        print self.name(),':',state
+        print(self.name(),':',state)
         return True
     
     def qemu_local_config(self):
@@ -214,7 +227,7 @@ class TestNode:
         target_arch = self.test_plc.apiserver.GetPlcRelease(auth)['build']['target-arch']
         conf_filename = "{}/qemu.conf".format(self.nodedir())
         if self.dry_run():
-            print "dry_run: skipped actual storage of qemu.conf"
+            print("dry_run: skipped actual storage of qemu.conf")
             return True
         utils.header('Storing qemu config for {} in {}'.format(self.name(), conf_filename))
         with open(conf_filename,'w') as file:
@@ -225,26 +238,26 @@ class TestNode:
             file.write('TARGET_ARCH={}\n'.format(target_arch))
         return True
 
-    def qemu_clean (self):
+    def qemu_clean(self):
         utils.header("Cleaning up qemu for host {} on box {}"\
                      .format(self.name(),self.test_box().hostname()))
         dry_run = self.dry_run()
         self.test_box().rmdir(self.nodedir(), dry_run=dry_run)
         return True
 
-    def qemu_export (self):
+    def qemu_export(self):
         "all nodes: push local node-dep directory on the qemu box"
         # if relevant, push the qemu area onto the host box
         if self.test_box().is_local():
             return True
         dry_run = self.dry_run()
-        utils.header ("Cleaning any former sequel of {} on {}"\
-                      .format(self.name(), self.host_box()))
-        utils.header ("Transferring configuration files for node {} onto {}"\
-                      .format(self.name(), self.host_box()))
+        utils.header("Cleaning any former sequel of {} on {}"\
+                     .format(self.name(), self.host_box()))
+        utils.header("Transferring configuration files for node {} onto {}"\
+                     .format(self.name(), self.host_box()))
         return self.test_box().copy(self.nodedir(), recursive=True, dry_run=dry_run) == 0
             
-    def qemu_start (self):
+    def qemu_start(self):
         "all nodes: start the qemu instance (also runs qemu-bridge-init start)"
         model = self.node_spec['node_fields']['model']
         #starting the Qemu nodes before 
@@ -255,7 +268,7 @@ class TestNode:
                          .format(self.name(), model))
         return True
 
-    def qemu_timestamp (self):
+    def qemu_timestamp(self):
         "all nodes: start the qemu instance (also runs qemu-bridge-init start)"
         test_box = self.test_box()
         test_box.run_in_buildname("mkdir -p {}".format(self.nodedir()), dry_run=self.dry_run())
@@ -263,7 +276,7 @@ class TestNode:
         return test_box.run_in_buildname("echo {:d} > {}/timestamp"\
                                          .format(now, self.nodedir()), dry_run=self.dry_run()) == 0
 
-    def start_qemu (self):
+    def start_qemu(self):
         test_box = self.test_box()
         utils.header("Starting qemu node {} on {}".format(self.name(), test_box.hostname()))
 
@@ -274,14 +287,14 @@ class TestNode:
         test_box.run_in_buildname("{}/qemu-start-node 2>&1 >> {}/log.txt"\
                                   .format(self.nodedir(), self.nodedir()))
 
-    def list_qemu (self):
+    def list_qemu(self):
         utils.header("Listing qemu for host {} on box {}"\
                      .format(self.name(), self.test_box().hostname()))
         command = "{}/qemu-kill-node -l {}".format(self.nodedir(), self.name())
         self.test_box().run_in_buildname(command, dry_run=self.dry_run())
         return True
 
-    def kill_qemu (self):
+    def kill_qemu(self):
         #Prepare the log file before killing the nodes
         test_box = self.test_box()
         # kill the right processes 
@@ -291,14 +304,14 @@ class TestNode:
         self.test_box().run_in_buildname(command, dry_run=self.dry_run())
         return True
 
-    def gather_qemu_logs (self):
+    def gather_qemu_logs(self):
         if not self.is_qemu():
             return True
         remote_log = "{}/log.txt".format(self.nodedir())
         local_log = "logs/node.qemu.{}.txt".format(self.name())
         self.test_box().test_ssh.fetch(remote_log,local_log,dry_run=self.dry_run())
 
-    def keys_clear_known_hosts (self):
+    def keys_clear_known_hosts(self):
         "remove test nodes entries from the local known_hosts file"
         TestSsh(self.name()).clear_known_hosts()
         return True
@@ -312,36 +325,36 @@ class TestNode:
         key = "keys/key_admin.rsa"
         return TestSsh(self.name(), buildname=self.buildname(), key=key)
 
-    def check_hooks (self):
+    def check_hooks(self):
         extensions = [ 'py','pl','sh' ]
         path = 'hooks/node'
-        scripts = utils.locate_hooks_scripts ('node '+self.name(), path,extensions)
+        scripts = utils.locate_hooks_scripts('node '+self.name(), path,extensions)
         overall = True
         for script in scripts:
-            if not self.check_hooks_script (script):
+            if not self.check_hooks_script(script):
                 overall = False
         return overall
 
-    def check_hooks_script (self,local_script):
+    def check_hooks_script(self,local_script):
         # push the script on the node's root context
         script_name = os.path.basename(local_script)
-        utils.header ("NODE hook {} ({})".format(script_name, self.name()))
+        utils.header("NODE hook {} ({})".format(script_name, self.name()))
         test_ssh = self.create_test_ssh()
         test_ssh.copy_home(local_script)
         if test_ssh.run("./"+script_name) != 0:
-            utils.header ("WARNING: node hooks check script {} FAILED (ignored)"\
-                          .format(script_name))
+            utils.header("WARNING: node hooks check script {} FAILED (ignored)"\
+                         .format(script_name))
             #return False
             return True
         else:
-            utils.header ("SUCCESS: node hook {} OK".format(script_name))
+            utils.header("SUCCESS: node hook {} OK".format(script_name))
             return True
 
-    def has_libvirt (self):
+    def has_libvirt(self):
         test_ssh = self.create_test_ssh()
-        return test_ssh.run ("rpm -q --quiet libvirt-client") == 0
+        return test_ssh.run("rpm -q --quiet libvirt-client") == 0
 
-    def _check_system_slice (self, slicename, dry_run=False):
+    def _check_system_slice(self, slicename, dry_run=False):
         sitename = self.test_plc.plc_spec['settings']['PLC_SLICE_PREFIX']
         vservername = "{}_{}".format(sitename, slicename)
         test_ssh = self.create_test_ssh()
@@ -350,7 +363,7 @@ class TestNode:
             return test_ssh.run("virsh --connect lxc:// list | grep -q ' {} '".format(vservername),
                                 dry_run = dry_run) == 0
         else:
-            (retcod,output) = \
+            retcod, output = \
                     utils.output_of(test_ssh.actual_command("cat /vservers/{}/etc/slicefamily 2> /dev/null")\
                                     .format(vservername))
             # get last line only as ssh pollutes the output