From ead95b34bc5e1c641d35e07470bef3c6e6ef59a8 Mon Sep 17 00:00:00 2001 From: Ken Thomas Date: Mon, 12 Mar 2012 12:44:38 -0700 Subject: [PATCH] Added '--dryrun' option for install/uninstall Fixes issue #72 --- devstack/components/db.py | 6 +- devstack/components/glance.py | 3 +- devstack/components/keystone.py | 3 +- devstack/components/melange.py | 3 +- devstack/components/nova.py | 5 +- devstack/components/rabbit.py | 3 +- devstack/libvirt.py | 4 +- devstack/opts.py | 7 ++ devstack/shell.py | 119 ++++++++++++++++++++++++-------- stack | 4 ++ 10 files changed, 112 insertions(+), 45 deletions(-) diff --git a/devstack/components/db.py b/devstack/components/db.py index 300ff5fa..b07da1bf 100644 --- a/devstack/components/db.py +++ b/devstack/components/db.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -import time - from devstack import component as comp from devstack import exceptions as excp from devstack import log as logging @@ -248,7 +246,7 @@ class DBRuntime(comp.EmptyRuntime): run_as_root=True, check_exit_code=True) LOG.info("Please wait %s seconds while it starts up." % START_WAIT_TIME) - time.sleep(START_WAIT_TIME) + sh.sleep(START_WAIT_TIME) return 1 else: return 0 @@ -270,7 +268,7 @@ class DBRuntime(comp.EmptyRuntime): run_as_root=True, check_exit_code=True) LOG.info("Please wait %s seconds while it restarts." % START_WAIT_TIME) - time.sleep(START_WAIT_TIME) + sh.sleep(START_WAIT_TIME) return 1 def status(self): diff --git a/devstack/components/glance.py b/devstack/components/glance.py index eaf07526..3dc13875 100644 --- a/devstack/components/glance.py +++ b/devstack/components/glance.py @@ -15,7 +15,6 @@ # under the License. import io -import time from devstack import cfg from devstack import component as comp @@ -224,5 +223,5 @@ class GlanceRuntime(comp.PythonRuntime): #install any images that need activating... # TODO: make this less cheesy - need to wait till glance goes online LOG.info("Waiting %s seconds so that glance can start up before image install." % (WAIT_ONLINE_TO)) - time.sleep(WAIT_ONLINE_TO) + sh.sleep(WAIT_ONLINE_TO) creator.ImageCreationService(self.cfg).install() diff --git a/devstack/components/keystone.py b/devstack/components/keystone.py index 2ecee0fe..91050f63 100644 --- a/devstack/components/keystone.py +++ b/devstack/components/keystone.py @@ -15,7 +15,6 @@ # under the License. import io -import time from urlparse import urlunparse @@ -224,7 +223,7 @@ class KeystoneRuntime(comp.PythonRuntime): #these environment additions are important #in that they eventually affect how this script runs LOG.info("Waiting %s seconds so that keystone can start up before running first time init." % (WAIT_ONLINE_TO)) - time.sleep(WAIT_ONLINE_TO) + sh.sleep(WAIT_ONLINE_TO) env = dict() env['ENABLED_SERVICES'] = ",".join(self.instances.keys()) env['BIN_DIR'] = self.bindir diff --git a/devstack/components/melange.py b/devstack/components/melange.py index a7852140..fb62dca9 100644 --- a/devstack/components/melange.py +++ b/devstack/components/melange.py @@ -15,7 +15,6 @@ # under the License. import io -import time from devstack import cfg from devstack import component as comp @@ -169,7 +168,7 @@ class MelangeRuntime(comp.PythonRuntime): comp.PythonRuntime.post_start(self) if CREATE_CIDR in self.component_opts or not self.component_opts: LOG.info("Waiting %s seconds so that the melange server can start up before cidr range creation." % (WAIT_ONLINE_TO)) - time.sleep(WAIT_ONLINE_TO) + sh.sleep(WAIT_ONLINE_TO) mp = dict() mp['CIDR_RANGE'] = self.cfg.getdefaulted('melange', 'm_mac_range', DEF_CIDR_RANGE) utils.execute_template(*CIDR_CREATE_CMD, params=mp) diff --git a/devstack/components/nova.py b/devstack/components/nova.py index c25bba57..dcaea75c 100644 --- a/devstack/components/nova.py +++ b/devstack/components/nova.py @@ -17,7 +17,6 @@ from urlparse import urlunparse import os -import time from devstack import component as comp from devstack import date @@ -443,7 +442,7 @@ class NovaRuntime(comp.PythonRuntime): #in that they eventually affect how this script runs if utils.service_enabled(settings.QUANTUM, self.instances, False): LOG.info("Waiting %s seconds so that quantum can start up before running first time init." % (WAIT_ONLINE_TO)) - time.sleep(WAIT_ONLINE_TO) + sh.sleep(WAIT_ONLINE_TO) env = dict() env['ENABLED_SERVICES'] = ",".join(self.instances.keys()) setup_cmd = NET_INIT_CMD_ROOT + [tgt_fn] @@ -833,7 +832,7 @@ class NovaConfConfigurator(object): LOG.debug("Attempting to create instance directory: %s" % (instances_path)) self.tracewriter.dirs_made(*sh.mkdirslist(instances_path)) LOG.debug("Adjusting permissions of instance directory: %s" % (instances_path)) - os.chmod(instances_path, 0777) + sh.chmod(instances_path, 0777) def _configure_libvirt(self, virt_type, nova_conf): if virt_type == 'lxc': diff --git a/devstack/components/rabbit.py b/devstack/components/rabbit.py index 406a8627..34e5b1f8 100644 --- a/devstack/components/rabbit.py +++ b/devstack/components/rabbit.py @@ -15,7 +15,6 @@ # under the License. from tempfile import TemporaryFile -import time from devstack import component as comp from devstack import log as logging @@ -133,7 +132,7 @@ class RabbitRuntime(comp.EmptyRuntime): LOG.info("Restarting rabbit-mq.") self._run_cmd(RESTART_CMD) LOG.info("Please wait %s seconds while it starts up." % (WAIT_ON_TIME)) - time.sleep(WAIT_ON_TIME) + sh.sleep(WAIT_ON_TIME) return 1 def stop(self): diff --git a/devstack/libvirt.py b/devstack/libvirt.py index 14537834..0f4255e0 100644 --- a/devstack/libvirt.py +++ b/devstack/libvirt.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -import time - from devstack import exceptions as excp from devstack import log as logging from devstack import settings @@ -110,7 +108,7 @@ def restart(distro): mp['SERVICE'] = SV_NAME_MAP[distro] utils.execute_template(*cmds, params=mp) LOG.info("Restarting the libvirt service, please wait %s seconds until its started." % (WAIT_ALIVE_TIME)) - time.sleep(WAIT_ALIVE_TIME) + sh.sleep(WAIT_ALIVE_TIME) def virt_ok(virt_type, distro): diff --git a/devstack/opts.py b/devstack/opts.py index 0e6452c0..286dc6f4 100644 --- a/devstack/opts.py +++ b/devstack/opts.py @@ -49,6 +49,12 @@ def parse(): dest="verbosity", default=[1], help="increase the verbose level") + parser.add_option("", "--dryrun", + action="store_const", + const=1, + dest="dryrun", + default=0, + help="log actions without actually doing any of them") base_group = OptionGroup(parser, "Install & uninstall & start & stop specific options") base_group.add_option("-a", "--action", @@ -104,6 +110,7 @@ def parse(): output = dict() output['components'] = options.component or list() output['dir'] = options.dir or "" + output['dryrun'] = options.dryrun or False output['ref_components'] = options.ref_components or list() output['action'] = options.action or "" output['force'] = not options.force diff --git a/devstack/shell.py b/devstack/shell.py index 532f5bde..4110a3f4 100644 --- a/devstack/shell.py +++ b/devstack/shell.py @@ -24,6 +24,7 @@ import subprocess import sys import random import re +import time from devstack import env from devstack import exceptions as excp @@ -44,6 +45,13 @@ SHELL_QUOTE_REPLACERS = { SHELL_WRAPPER = "\"%s\"" ROOT_PATH = os.sep RANDOMIZER = random.SystemRandom() +DRYRUN = None + + +def set_dryrun(val): + global DRYRUN + LOG.info("Setting dryrun to:%s" % (val)) + DRYRUN = val #root context guard @@ -65,7 +73,6 @@ class Rooted(object): def execute(*cmd, **kwargs): - process_input = kwargs.pop('process_input', None) check_exit_code = kwargs.pop('check_exit_code', [0]) cwd = kwargs.pop('cwd', None) @@ -129,27 +136,32 @@ def execute(*cmd, **kwargs): rc = None result = None with Rooted(run_as_root): - try: - obj = subprocess.Popen(execute_cmd, - stdin=stdin_fh, - stdout=stdout_fh, - stderr=stderr_fh, - close_fds=close_file_descriptors, - cwd=cwd, - shell=shell, - env=process_env) - if process_input is not None: - result = obj.communicate(str(process_input)) - else: - result = obj.communicate() - except OSError as e: - error_description = "%s: [%s, %s]" % (e.message, e.errno, e.strerror) - raise excp.ProcessExecutionError(description=error_description, cmd=str_cmd) - if (stdin_fh != subprocess.PIPE - and obj.stdin and close_stdin): - obj.stdin.close() - rc = obj.returncode - LOG.debug('Cmd result had exit code: %s' % rc) + if DRYRUN: + LOG.info("DRYRUN:%s" % (execute_cmd)) + # Pretend it worked + rc = 0 + else: + try: + obj = subprocess.Popen(execute_cmd, + stdin=stdin_fh, + stdout=stdout_fh, + stderr=stderr_fh, + close_fds=close_file_descriptors, + cwd=cwd, + shell=shell, + env=process_env) + if process_input is not None: + result = obj.communicate(str(process_input)) + else: + result = obj.communicate() + except OSError as e: + error_description = "%s: [%s, %s]" % (e.message, e.errno, e.strerror) + raise excp.ProcessExecutionError(description=error_description, cmd=str_cmd) + if (stdin_fh != subprocess.PIPE + and obj.stdin and close_stdin): + obj.stdin.close() + rc = obj.returncode + LOG.debug('Cmd result had exit code: %s' % rc) if not result: result = ("", "") @@ -343,6 +355,10 @@ def mkdirslist(path): def append_file(fn, text, flush=True, quiet=False): + if DRYRUN: + LOG.info("DRYRUN append_file:%s" % (fn)) + LOG.info("%s" % (text)) + return fn if not quiet: LOG.debug("Appending to file %s (%d bytes)", fn, len(text)) with open(fn, "a") as f: @@ -353,6 +369,10 @@ def append_file(fn, text, flush=True, quiet=False): def write_file(fn, text, flush=True, quiet=False): + if DRYRUN: + LOG.info("DRYRUN write_file:%s" % (fn)) + LOG.info("%s" % (text)) + return fn if not quiet: LOG.debug("Writing to file %s (%d bytes)", fn, len(text)) with open(fn, "w") as f: @@ -363,6 +383,9 @@ def write_file(fn, text, flush=True, quiet=False): def touch_file(fn, die_if_there=True, quiet=False, file_size=0): + if DRYRUN: + LOG.info("DRYRUN touch_file:%s" % (fn)) + return fn if not isfile(fn): if not quiet: LOG.debug("Touching and truncating file %s (%s)", fn, file_size) @@ -376,16 +399,30 @@ def touch_file(fn, die_if_there=True, quiet=False, file_size=0): def load_file(fn, quiet=False): + LOG.info("load_file:%s" % (fn)) if not quiet: LOG.debug("Loading data from file %s", fn) - with open(fn, "r") as f: - data = f.read() + try: + with open(fn, "r") as f: + data = f.read() + except IOError as e: + # If there was an error, then check if we're doing a dryrun. If + # yes, then return no data, otherwise the the error bubble on up + if DRYRUN: + LOG.info("DRYRUN: return no data for load_file:%s" % (fn)) + data = "" + else: + raise e + if not quiet: LOG.debug("Loaded (%d) bytes from file %s", len(data), fn) return data def mkdir(path, recurse=True): + if DRYRUN: + LOG.info("DRYRUN mkdir:%s" % (path)) + return if not isdir(path): if recurse: LOG.debug("Recursively creating directory \"%s\"" % (path)) @@ -396,6 +433,9 @@ def mkdir(path, recurse=True): def deldir(path, run_as_root=False): + if DRYRUN: + LOG.info("DRYRUN deldir:%s" % (path)) + return with Rooted(run_as_root): if isdir(path): LOG.debug("Recursively deleting directory tree starting at \"%s\"" % (path)) @@ -403,6 +443,9 @@ def deldir(path, run_as_root=False): def rmdir(path, quiet=True, run_as_root=False): + if DRYRUN: + LOG.info("DRYRUN rmdir:%s" % (path)) + return if not isdir(path): return try: @@ -422,9 +465,12 @@ def symlink(source, link, force=True, run_as_root=True): LOG.debug("Creating symlink from %s => %s" % (link, source)) path = dirname(link) needed_pths = mkdirslist(path) - if force and (exists(link) or islink(link)): - unlink(link, True) - os.symlink(source, link) + if DRYRUN: + LOG.info("DRYRUN symlink from %s => %s" % (source, link)) + else: + if force and (exists(link) or islink(link)): + unlink(link, True) + os.symlink(source, link) return needed_pths @@ -534,6 +580,9 @@ def umount(dev_name, ignore_errors=True): def unlink(path, ignore_errors=True, run_as_root=False): + if DRYRUN: + LOG.info("DRYRUN unlink:%s" % (path)) + return try: LOG.debug("Unlinking (removing) %s" % (path)) with Rooted(run_as_root): @@ -546,10 +595,16 @@ def unlink(path, ignore_errors=True, run_as_root=False): def move(src, dst): + if DRYRUN: + LOG.info("DRYRUN move:%s" % (src, dst)) + return shutil.move(src, dst) def chmod(fname, mode): + if DRYRUN: + LOG.info("DRYRUN chmod:%s to %s" % (fname, mode)) + return os.chmod(fname, mode) @@ -567,6 +622,9 @@ def replace_in(fn, search, replace, run_as_root=False): def copy_replace_file(fsrc, fdst, linemap): files = mkdirslist(dirname(fdst)) + if DRYRUN: + LOG.info("DRYRUN copy_replace:%s" % (fsrc, fdst)) + return with open(fdst, 'w') as fh: for line in fileinput.input(fsrc): for (k, v) in linemap.items(): @@ -626,3 +684,10 @@ def geteuid(): def getegid(): return os.getegid() + + +def sleep(winks): + if DRYRUN: + LOG.info("DRYRUN, sleep for:%s" % (winks)) + else: + time.sleep(winks) diff --git a/stack b/stack index d234730e..efb51c14 100755 --- a/stack +++ b/stack @@ -93,6 +93,10 @@ def run(args): #here on out we should be using the logger (and not print) start_time = time.time() config = common.get_config() + # Stash the dryrun value (if any) into the global configuration + dryrun = args.get('dryrun') + if dryrun: + sh.set_dryrun(dryrun) pkg_manager = common.get_packager(distro, args.get('keep_old')) components = utils.parse_components(args.pop("components")) runner = actions.ActionRunner(distro, action, rootdir, config, pkg_manager, components=components, **args)