first working draft - migrate not implemented yet
authorthierry <thierry@41d37cc5-eb28-0410-a9bf-d37491348ade>
Tue, 30 Mar 2010 08:33:32 +0000 (08:33 +0000)
committerthierry <thierry@41d37cc5-eb28-0410-a9bf-d37491348ade>
Tue, 30 Mar 2010 08:33:32 +0000 (08:33 +0000)
scripts/Makefile
scripts/VSERVERS [new file with mode: 0644]
scripts/VSHOSTS [deleted file]
scripts/vs-check [moved from scripts/vs-netcheck with 100% similarity]
scripts/vs-show [moved from scripts/vs-probe with 100% similarity]
scripts/vserver_tools.py

index abd3d3f..b16ab47 100644 (file)
@@ -1,14 +1,14 @@
 # $Id$
 # $URL$
 
-vhosts:
-       cat VSHOSTS
+vcheck:
+       vs-check -f VSERVERS
 
-vprobe:
-       vs-probe -f VSHOSTS > VSERVERS
-       @echo 'refreshed VSERVERS'
-       cat VSERVERS
+vshow:
+       vs-show -l -f VSERVERS
 
-vcheck:
-       vs-netcheck -f VSERVERS
+vcache:
+       vs-show -f VSERVERS > VCACHE
 
+vshowcache:
+       vs-show -l -f VCACHE
diff --git a/scripts/VSERVERS b/scripts/VSERVERS
new file mode 100644 (file)
index 0000000..cbd4a3f
--- /dev/null
@@ -0,0 +1,11 @@
+#*@blitz.pl.sophia.inria.fr
+#*@liquid.pl.sophia.inria.fr
+#*@reed.pl.sophia.inria.fr
+#*@velvet.pl.sophia.inria.fr
+*@warhol.pl.sophia.inria.fr
+*@speedball.pl.sophia.inria.fr
+*@addntox.pl.sophia.inria.fr
+*@deathvegas.pl.sophia.inria.fr
+*@gorillaz.pl.sophia.inria.fr
+*@gotan.pl.sophia.inria.fr
+*@truckers.pl.sophia.inria.fr
diff --git a/scripts/VSHOSTS b/scripts/VSHOSTS
deleted file mode 100644 (file)
index b8f0fa1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#blitz.pl.sophia.inria.fr
-#liquid.pl.sophia.inria.fr
-#reed.pl.sophia.inria.fr
-#velvet.pl.sophia.inria.fr
-warhol.pl.sophia.inria.fr
-speedball.pl.sophia.inria.fr
-addntox.pl.sophia.inria.fr
-deathvegas.pl.sophia.inria.fr
-gorillaz.pl.sophia.inria.fr
-gotan.pl.sophia.inria.fr
-truckers.pl.sophia.inria.fr
-
-
-
similarity index 100%
rename from scripts/vs-netcheck
rename to scripts/vs-check
similarity index 100%
rename from scripts/vs-probe
rename to scripts/vs-show
index 27874c8..d1a8c83 100755 (executable)
@@ -33,7 +33,8 @@ class Logger:
     def __init__(self):
         pass
 
-    def log (self,msg):
+    def log (self,msg,level=0):
+        if level>0: print >> sys.stderr,level*4*'=',
         print >>sys.stderr, msg
 
 logger=Logger()
@@ -47,24 +48,36 @@ def b2e (b):
 def e2b (b):
     return b==0
 
-verbose=False
-verbose=True
 ########################################
 class Shell:
-    def __init__(self,dry_run=False):
+    def __init__(self,dry_run=False,verbose=False):
         self.dry_run=dry_run
+        self.verbose=verbose
 
     def header (self,message):
-        logger.log("==============="+message)
+        logger.log(message,level=1)
+
+    # convenience : if argv is a string, we just split it to get a list
+    # if args may contain spaces, it's safer to build the list yourself
+    def normalize (self, argv):
+        if isinstance (argv,list): return argv
+        if isinstance (argv,str): return argv.split()
+        else: raise Exception, "Unsupported command type in Shell - %r"%argv
 
     # return an exit code
+    # in general dry_run ... does not run
+    # there are cases where the command is harmless and required for the rest to proceed correctly
+    # in this case you can set force_dry_run
     def run (self, argv, trash_stdout=False, trash_stderr=False, message="", force_dry_run=False):
-        if self.dry_run:
-            print 'DRY_RUN',message+'RUNNING '+" ".join(argv)
+        argv=self.normalize(argv)
+        if self.verbose and message: self.header(message),
+        if self.dry_run and not force_dry_run:
+            print 'WOULD RUN '+" ".join(argv)
             return 0
         else:
-            if message: self.header(message),
-            if verbose: logger.log ('RUNNING '+" ".join(argv))
+            helper='RUNNING'
+            if force_dry_run and self.verbose: helper += ' (forced)'
+            if self.verbose: logger.log (helper+' '+" ".join(argv))
             if trash_stdout and trash_stderr:
                 return subprocess.call(argv,stdout=file('/dev/null','w'),stderr=subprocess.STDOUT)
             elif trash_stdout and not trash_stderr:
@@ -74,25 +87,29 @@ class Shell:
             elif not trash_stdout and not trash_stderr:
                 return subprocess.call(argv)
             
-    # like shell's $(...) which used to be `...`
-    def backquote (self, argv, trash_stderr=False,message=""):
-        if self.dry_run or verbose:
-            if verbose: logger.log("WARNING: " + message + " backquote is actually running "+" ".join(argv))
+    # like shell's $(...) which used to be `...` - cannot trash stdout of course
+    # ditto for the dry_run mode
+    def backquote (self, argv, trash_stderr=False,message="", force_dry_run=False):
+        argv=self.normalize (argv)
+        if self.verbose and message: self.header(message),
+        if self.dry_run and not force_dry_run:
+            print 'WOULD RUN $('+" ".join(argv) + ')'
+            return ""
         if not trash_stderr:
             return subprocess.Popen(argv,stdout=subprocess.PIPE).communicate()[0]
         else:
             return subprocess.Popen(argv,stdout=subprocess.PIPE,stderr=file('/dev/null','w')).communicate()[0]
             
 ####################
-class Ssh:
+class Ssh (Shell):
     
     #################### the class
-    def __init__(self,hostname=None,key=None, username='root',dry_run=False):
+    def __init__(self,hostname=None,key=None, username='root',dry_run=False,verbose=False):
         self.hostname=hostname
         if not hostname: self.hostname='localhost'
         self.key=key
         self.username=username
-        self.dry_run=dry_run
+        Shell.__init__(self,dry_run=dry_run,verbose=verbose)
 
     # inserts a backslash before each occurence of the following chars
     # \ " ' < > & | ; ( ) $ * ~ 
@@ -125,7 +142,7 @@ class Ssh:
             ip=socket.gethostbyname(hostname)
             return socket.gethostbyaddr(ip)[0]
         except:
-            logger.log( 'Warning - could not find canonical name for host %s'%hostname)
+            logger.log( "WARNING - could not find canonical name for host %s"%hostname)
             return hostname
 
 
@@ -160,118 +177,124 @@ class Ssh:
         ssh_argv += argv
         return ssh_argv
 
-    # argv is a list
     def run(self, argv,*args, **kwds):
-        return Shell(self.dry_run).run(self.argv(argv),*args, **kwds)
-    # command is a string
-    def run_string(self, command, *args, **kwds):
-        return Shell(self.dry_run).run(self.argv(command.split()),*args, **kwds)
-
-    # argv is a list
+        return Shell.run(self,self.argv(self.normalize(argv)),*args, **kwds)
     def backquote(self, argv, *args, **kwds):
-        return Shell(self.dry_run).backquote(self.argv(argv),*args, **kwds)
-    # command is a string
-    def backquote_string(self, command, *args, **kwds):
-        return Shell(self.dry_run).backquote(self.argv(command.split()),*args, **kwds)
+        return Shell.backquote(self,self.argv(self.normalize(argv)),*args, **kwds)
 
 ############################################################
 class Vserver:
 
-    def __init__ (self, name, dry_run=False):
+    def __init__ (self, name, dry_run=False,verbose=False):
         try:
             (self.name,self.hostname)=name.split("@")
         except:
             (self.name,self.hostname)=(name,'localhost')
         self.dry_run=dry_run
-        self.ssh=Ssh(self.hostname,username='root',dry_run=self.dry_run)
-        self._is_running=None
+        self.verbose=verbose
+        self._running_state=None
+        self._xid=None
+        self._autostart=None
+        self.set_hostname(self.hostname)
+
+    def set_hostname (self, hostname):
+        self.hostname=hostname
+        self.ssh=Ssh(self.hostname,username='root',dry_run=self.dry_run,verbose=self.verbose)
+
+    def ok(self,message): 
+        if self.verbose: logger.log('OK %s: %s'%(self.printable(),message))
+    def warning(self,message): 
+        if self.verbose: logger.log( 'WARNING %s: %s'%(self.printable(),message))
+    def error(self,message): 
+        logger.log('ERROR %s: %s'%(self.printable(),message))
 
     # cache run mode
-    def is_running (self):
-        if self._is_running is not None: return self._is_running
-        self._is_running = e2b(self.ssh.run_string('vserver %s status'%self.name,
-                                                   trash_stdout=True,trash_stderr=True,
-                                                   message='retrieving status for %s'%self.name))
+    def running_state (self):
+        if self._running_state is not None: return self._running_state
+        status_retcod= self.ssh.run('vserver %s status'%self.name,
+                                    trash_stdout=True,trash_stderr=True,
+                                    message='retrieving status for %s'%self.name,
+                                    force_dry_run=True)
+        if status_retcod == 0:          self._running_state = 'start'
+        elif status_retcod == 3:        self._running_state = 'stop'
+        else:                           self._running_state = 'unknown'
+        return self._running_state
+
     def running_char (self):
-        if self._is_running is None:    return '?'
-        elif not self._is_running:      return '-'
-        else:                           return '+'
+        if self._running_state is None:         return '?'
+        elif self._running_state == 'unknown' : return '!'
+        elif self._running_state == 'stop':     return '-'
+        else:                                   return '+'
+
+    # cache xid
+    def xid (self):
+        if self._xid is not None: return self._xid
+        self._xid = self.ssh.backquote(['cat','/etc/vservers/%s/context'%self.name],
+                                       force_dry_run=True,trash_stderr=True,
+                                       message='Retrieving xid for %s'%self.name).strip()
+        return self._xid
+
+    def autostart (self):
+        if self._autostart is not None: return self._autostart
+        self._autostart = self.ssh.backquote(['cat','/etc/vservers/%s/apps/init/mark'%self.name],
+                                             force_dry_run=True,trash_stderr=True,
+                                             message='Retrieving autostart status for %s'%self.name).strip()=='default'
+        return self._autostart
 
     def stop (self):
         # uncache
-        self._is_running=None
-        return e2b(self.ssh.run_string("vserver %s stop"%self.name,message='Stopping %s'%self.name))
+        self._running_state=None
+        return e2b(self.ssh.run("vserver %s stop"%self.name,
+                                message='Stopping %s'%self.name))
 
     def start (self):
         # uncache
-        self._is_running=None
-        return e2b(self.ssh.run_string("vserver %s start"%self.name,message='Starting %s'%self.name))
+        self._running_state=None
+        return e2b(self.ssh.run("vserver %s start"%self.name,
+                                message='Starting %s'%self.name))
 
     def printable_simple(self): return self.name + '@' + self.hostname
-    def printable_dbg(self): return "%s@%s (%s)"%(self.name,self.hostname,self.running_char())
+    def printable_dbg(self): 
+        autostart_part='<*>'
+        if not self.autostart(): autostart_part='< >'
+        return autostart_part+" (%s) [%s] %s@%s"%(self.running_char(),self.xid(),self.name,self.hostname)
     def printable(self): 
-        # force fetch
-        self.is_running()
-        return "%s@%s (%s)"%(self.name,self.hostname,self.running_char())
+        # force fetch cache info
+        self.running_state()
+        self.xid()
+        self.autostart()
+        return self.printable_dbg()
 
     def is_local (self):
         return self.ssh.is_local()
 
-    @staticmethod
-    def probe_host (hostname, dry_run=False):
-        canonical=Ssh.canonical_name(hostname)
-        ssh=Ssh(hostname,dry_run=dry_run)
-        ls=ssh.backquote_string("ls -d1 /vservers/*",trash_stderr=True)
-        return [ '%s@%s'%(path.replace('/vservers/',''),canonical) for path in ls.split()]
-
-    # renaming
-    def rename (self, newname):
-        target=Vserver(newname)
-        if self.hostname != target.hostname:
-            # make user's like easier, no need to mention hostname again
-            if target.is_local():
-                target.hostname=self.hostname
-            else:
-                logger.log('rename cannot rename (%s->%s) - must be on same box'%(self.hostname, target.hostname))
-                return False
-        logger.log("Renaming %s into %s"%(self.printable(),target.printable_simple()))
-
-        ssh=self.ssh
-        oldname=self.name
-        newname=target.name
-        xid=ssh.backquote(['cat','/etc/vservers/%(oldname)s/context'%locals()])
-        currently_running=self.is_running()
-        result=True
-        if currently_running:
-            result = result and self.stop()
-        result = result \
-            and e2b(ssh.run_string("mv /vservers/%(oldname)s /vservers/%(newname)s"%locals())) \
-            and e2b(ssh.run_string("mv /etc/vservers/%(oldname)s /etc/vservers/%(newname)s"%locals())) \
-            and e2b(ssh.run_string("rm /etc/vservers/%(newname)s/run"%locals())) \
-            and e2b(ssh.run_string("ln -s /var/run/vservers/%(newname)s /etc/vservers/%(newname)s/run"%locals())) \
-            and e2b(ssh.run_string("rm /etc/vservers/%(newname)s/vdir"%locals())) \
-            and e2b(ssh.run_string("ln -s /etc/vservers/.defaults/vdirbase/%(newname)s /etc/vservers/%(newname)s/vdir"%locals())) \
-            and e2b(ssh.run_string("rm /etc/vservers/%(newname)s/cache"%locals())) \
-            and e2b(ssh.run_string("ln -s /etc/vservers/.defaults/cachebase/%(newname)s /etc/vservers/%(newname)s/cache"%locals())) \
-            and e2b(ssh.run_string("rm /var/run/vservers.rev/%(xid)s"%locals())) \
-            and e2b(ssh.run_string("ln -s /etc/vserver/%(newname)s /var/run/vservers.rev/%(xid)s"%locals()))
-        if currently_running:
-            result = result and self.start()
-        return result
-            
-    def ok(self,message): logger.log('OK %s: %s'%(self.printable(),message))
-    def error(self,message): logger.log('ERROR %s: %s'%(self.printable(),message))
-    def warning(self,message): logger.log( 'WARNING %s: %s'%(self.printable(),message))
+    # does it have a pattern
+    def has_pattern (self):
+        return self.name.find('*') >= 0
+
+    # probe it
+    def probe_pattern (self):
+        pattern=self.name
+        ls=self.ssh.backquote("ls -d1 /vservers/%s"%pattern,
+                              trash_stderr=True,force_dry_run=True,
+                              message='scanning /vservers with pattern %s on %s'%(pattern,self.hostname))
+        return [ '%s@%s'%(path.replace('/vservers/',''),self.hostname) for path in ls.split()]
+
+    #################### toplevel methods for main
+    def show (self, long_format):
+        if long_format:
+            print self.printable()
+        else:
+            print self.printable_simple()
 
-    def netcheck(self):
+    def check(self):
         result=True
         ssh=self.ssh
         name=self.name
-        hostname=ssh.backquote_string("cat /etc/vservers/%(name)s/uts/nodename"%locals()).strip()
+        hostname=ssh.backquote("cat /etc/vservers/%(name)s/uts/nodename"%locals()).strip()
         if not hostname: 
-            self.error('no uts/nodename')
-            result=False
-        ip=ssh.backquote_string("cat /etc/vservers/%(name)s/interfaces/0/ip"%locals()).strip()
+            self.warning('no uts/nodename')
+        ip=ssh.backquote("cat /etc/vservers/%(name)s/interfaces/0/ip"%locals()).strip()
         if not ip: 
             self.error('no interfaces/0/ip')
             result=False
@@ -287,7 +310,7 @@ class Vserver:
             expected_ip='255.255.255.255'
             result=False
         mask_path="/etc/vservers/%(name)s/interfaces/mask"%locals()
-        mask_exists = e2b(ssh.run_string('ls %s'%mask_path,trash_stderr=True))
+        mask_exists = e2b(ssh.run('ls %s'%mask_path,trash_stderr=True))
         if mask_exists:
             self.error('found a mask file in %s'%(self.printable(),mask_path))
             result=False
@@ -295,74 +318,151 @@ class Vserver:
             self.ok('no mask file %s'%mask_path)
         return result
         
-class Main:
+    # renaming
+    def rename (self, newname):
+        target=Vserver(newname)
+        if self.hostname != target.hostname:
+            # make user's like easier, no need to mention hostname again
+            if target.is_local():
+                target.hostname=self.hostname
+            else:
+                logger.log('ERROR:rename cannot rename (%s->%s) - must be on same box'%(self.hostname, target.hostname))
+                return False
+        logger.log("%s into %s"%(self.printable(),target.printable_simple()),level=2)
+        
+        ssh=self.ssh
+        oldname=self.name
+        newname=target.name
+        xid=self.xid()
+        currently_running=self.running_state()=='start'
+        result=True
+        if currently_running:
+            result = result and self.stop()
+        result = result \
+            and e2b(ssh.run("mv /vservers/%(oldname)s /vservers/%(newname)s"%locals(),
+                            message="tweaking /vservers/<>")) \
+            and e2b(ssh.run("mv /etc/vservers/%(oldname)s /etc/vservers/%(newname)s"%locals(),
+                            message="tweaking /etc/vservers/<>")) \
+            and e2b(ssh.run("rm /etc/vservers/%(newname)s/run"%locals(),
+                            message="cleaning /etc/vservers/<>/run")) \
+            and e2b(ssh.run("ln -s /var/run/vservers/%(newname)s /etc/vservers/%(newname)s/run"%locals(),
+                            message="adjusting /etc/vservers/<>/run")) \
+            and e2b(ssh.run("rm /etc/vservers/%(newname)s/vdir"%locals(),
+                            message="cleaning /etc/vservers/<>/vdir")) \
+            and e2b(ssh.run("ln -s /etc/vservers/.defaults/vdirbase/%(newname)s /etc/vservers/%(newname)s/vdir"%locals(),
+                            message="adjusting /etc/vservers/<>/vdir")) \
+            and e2b(ssh.run("rm /etc/vservers/%(newname)s/cache"%locals(),
+                            message="cleaning /etc/vservers/<>/cache")) \
+            and e2b(ssh.run("ln -s /etc/vservers/.defaults/cachebase/%(newname)s /etc/vservers/%(newname)s/cache"%locals(),
+                            message="adjusting /etc/vservers/<>/cache")) \
+            and e2b(ssh.run("rm /var/run/vservers.rev/%(xid)s"%locals(),
+                            message="cleaning /var/run/vservers.rev/<>")) \
+            and e2b(ssh.run("ln -s /etc/vserver/%(newname)s /var/run/vservers.rev/%(xid)s"%locals(),
+                            message="adjusting /var/run/vservers.rev/<>")) 
+#        # refreshing target instance
+        target=Vserver(newname,dry_run=self.dry_run,verbose=self.verbose)
+        target.set_hostname(self.hostname)
+        if currently_running and not self.dry_run: 
+            result = result and target.start()
+        print target.printable()
+        return result
+            
+class VserverTools:
 
     modes={ 
-        # expected arg numbers can be either
-        # negative int, e.g. -1 means at least one arg
-        # positive int, exactly that number of args
-        # list of ints : allowed number of args
-        'probe' : "hostname(s)\n\tprobes all hostnames and returns the found vservers",
-        'netcheck' : "vsname\n\tchecks the network config for a vserver",
+        'show' : "vsname(s)\n\tdisplay status & details",
+        'start': "vsname(s)\n\tstart vservers",
+        'stop' : "vsname(s)\n\tstop vservers",
+        'check' : "vsname(s)\n\tchecks the network config for a vserver",
         'rename' : "oldname newname\n\trename a vserver - both named must be local",
-        'migrate' : "oldname hostname@newname\n\tmigrate a vserver over to a new box; must be run on the current box\n\tnot implemented yet",
+        'migrate' : "oldname hostname@newname\n\tmigrate a vserver over to a new box\n\tnot implemented yet",
         }
 
-    def read_args_from_file (self,args_from_file):
-        if not args_from_file: return []
+    def __init__ (self):
+        self.parser=OptionParser()
+        self.args=None
+        self.options=None
+
+    def vserver(self, vsname):
+        return Vserver(vsname,dry_run=self.options.dry_run,verbose=self.options.verbose)
+
+    def probe_star_arg (self, arg):
+        vs=self.vserver(arg)
+        if vs.has_pattern():    return vs.probe_pattern()
+        else:                   return [arg,]
+
+    def read_args_from_file (self):
+        if not self.options.args_from_file: return []
         try:
-            raw=file(args_from_file).read().split()
-            return [x for x in raw if x.find('#')<0]
+            result=[]
+            raw_args=file(self.options.args_from_file).read().split()
+            for arg in raw_args:
+                result += self.probe_star_arg(arg)
+            return result
         except:
-            logger.log('ERROR: Could not open args file %s'%args_from_file)
+            logger.log('ERROR: Could not open args file %s'%self.options.args_from_file)
             return []
 
+    def check_usual_args(self):
+        if not self.options.args_from_file and len(self.args)==0: 
+            print ('no arg specified')
+            self.parser.print_help()
+            sys.exit(1)
+        result=[]
+        for arg in self.args: result += self.probe_star_arg(arg)
+        result += self.read_args_from_file()
+        result.sort()
+        return result
+        
+    # return exit code
     def run (self):
 
         mode=None
-        for function in Main.modes.keys():
+        for function in VserverTools.modes.keys():
             if sys.argv[0].find(function) >= 0:
                 mode = function
-                mode_desc = Main.modes[mode]
+                mode_desc = VserverTools.modes[mode]
                 break
         if not mode:
             print "Unsupported command",sys.argv[0]
-            print "Supported commands:" + " ".join(Main.modes.keys())
+            print "Supported commands:" + " ".join(VserverTools.modes.keys())
             sys.exit(1)
 
-        parser=OptionParser(usage="%prog " + mode_desc)
-        parser.add_option('-n','--dry-run',dest='dry_run',action='store_true',default=False,
+        self.parser.set_usage("%prog " + mode_desc)
+        self.parser.add_option('-n','--dry-run',dest='dry_run',action='store_true',default=False,
                           help="dry run")
-        if mode == 'probe' or mode == 'netcheck':
-            parser.add_option ('-f','--file',action='store',dest='args_from_file',default=None,
-                               help='read args from file')
-
-        (options,args) = parser.parse_args()
-        parser.set_usage("%prog " + mode_desc)
-
-        # check number of arguments
-        def bail_out (msg):
-            parser.print_help(msg)
-            sys.exit(1)
-
-        # 
-        if mode == 'probe':
-            names=[]
-            if not options.args_from_file and len(args)==0: bail_out('no arg specified')
-            for arg in args + self.read_args_from_file(options.args_from_file):
-                names += Vserver.probe_host(arg,options.dry_run)
-            names.sort()
-            for name in names: print name
-                
-        elif mode=='netcheck':
+        self.parser.add_option('-v','--verbose',dest='verbose',action='store_true',default=False,
+                          help="be verbose")
+        self.parser.add_option ('-l','--long',action='store_true', dest='long_format',default=False,
+                                help="show more detailed result")
+        ##########
+        if mode not in ['rename','migrate'] :
+            self.parser.add_option ('-f','--file',action='store',dest='args_from_file',default=None,
+                               help="read args from file")
+
+        (self.options,self.args) = self.parser.parse_args()
+
+        if mode=='show':
+            for arg in self.check_usual_args():
+                self.vserver(arg).show(self.options.long_format)
+        elif mode=='stop':
+            for arg in self.check_usual_args():
+                vs = self.vserver(arg)
+                vs.stop()
+                vs.show(True)
+        elif mode=='start':
+            for arg in self.check_usual_args():
+                vs = self.vserver(arg)
+                vs.start()
+                vs.show(True)
+        elif mode=='check':
             result=True
-            if not options.args_from_file and len(args)==0: bail_out('no arg specified')
-            for arg in args + self.read_args_from_file(options.args_from_file):
-                if not b2e(Vserver(arg,options.dry_run).netcheck()): result=False
+            for arg in self.check_usual_args():
+                if not b2e(self.vserver(arg).check()): result=False
             return result
         elif mode=='rename':
-            [x,y]=args
-            return b2e(Vserver(x,options.dry_run).rename(y))
+            [x,y]=self.args
+            return b2e(self.vserver(x).rename(y))
         elif mode=='migrate':
             print 'migrate not yet implemented'
             return 1
@@ -371,4 +471,4 @@ class Main:
             return b2e(False)
 
 if __name__ == '__main__':
-    sys.exit( Main().run() )
+    sys.exit( VserverTools().run() )