Skip to content

Commit

Permalink
Add ‘charon rollback’ operation
Browse files Browse the repository at this point in the history
Example usage:

$ charon list-generations
   1   2012-08-21 16:56:33
   ...
   9   2012-08-22 17:49:41
  10   2012-08-22 17:52:16   (current)

$ charon rollback 9
switching from generation 10 to 9
machine> copying closure...
machine> updating GRUB 2 menu...
...

Note that a rollback fails if machines in the old configuration have
been destroyed in the meantime, and it may silently fail if deployment
parameters such as IP addressed have changed.  So this rollback
capability is no general substitute for having the deployment
specification under version management control.
  • Loading branch information
edolstra committed Aug 22, 2012
1 parent 4eb805c commit 1ebd58a
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 7 deletions.
50 changes: 48 additions & 2 deletions charon/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,48 @@ def worker(m):
allow_reboot=allow_reboot, check=check)


def rollback(self, generation, include=[], exclude=[], check=False,
allow_reboot=False, max_concurrent_copy=5):
if not self.enable_rollback:
raise Exception("rollback is not enabled for this network; please set ‘network.enableRollback’ to ‘true’ and redeploy"
)
profile = self.get_profile()
if subprocess.call(["nix-env", "-p", profile, "--switch-generation", str(generation)]) != 0:
raise Exception("nix-env --switch-generation failed")

self.configs_path = os.path.realpath(profile)
assert os.path.isdir(self.configs_path)
self.write_state()

names = set()
for filename in os.listdir(self.configs_path):
if not os.path.islink(self.configs_path + "/" + filename): continue
if should_do_n(filename, include, exclude) and filename not in self.machines:
raise Exception("cannot roll back machine ‘{0}’ which no longer exists".format(filename))
names.add(filename)

# Update the set of active machines.
self.active = {}
for m in self.machines.values():
if m.name in names:
self.active[m.name] = m
if m.obsolete:
self.log("machine ‘{0}’ is no longer obsolete".format(m.name))
m.obsolete = False
m.write()
else:
self.log("machine ‘{0}’ is obsolete".format(m.name))
if not m.obsolete:
m.obsolete = True
m.write()

self.copy_closures(self.configs_path, include=include, exclude=exclude,
max_concurrent_copy=max_concurrent_copy)

self.activate_configs(self.configs_path, include=include, exclude=exclude,
allow_reboot=allow_reboot, check=check)


def destroy_vms(self, include=[], exclude=[]):
"""Destroy all active or obsolete VMs."""

Expand Down Expand Up @@ -640,9 +682,13 @@ class NixEvalError(Exception):


def should_do(m, include, exclude):
if m.name in exclude: return False
return should_do_n(m.name, include, exclude)

def should_do_n(name, include, exclude):
if name in exclude: return False
if include == []: return True
return m.name in include
return name in include



def _abs_nix_path(x):
Expand Down
27 changes: 22 additions & 5 deletions scripts/charon
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ def op_list_generations():
raise Exception("nix-env --list-generations failed")


def op_rollback():
depl = deployment.Deployment(args.state_file)
with depl:
depl.rollback(args.generation,
include=args.include or [], exclude=args.exclude or [],
check=args.check, allow_reboot=args.allow_reboot,
max_concurrent_copy=args.max_concurrent_copy)


# Set up the parser.
parser = argparse.ArgumentParser(description='NixOS cloud deployment tool')

Expand All @@ -259,19 +268,22 @@ subparser.add_argument('--no-eval', action='store_true', help='do not evaluate t
subparser = subparsers.add_parser('check', help='check the state of the machines in the network')
subparser.set_defaults(op=op_check)

def add_common_deployment_options(subparser):
subparser.add_argument('--include', nargs='+', metavar='MACHINE-NAME', help='perform deployment actions on the specified machines only')
subparser.add_argument('--exclude', nargs='+', metavar='MACHINE-NAME', help='do not perform deployment actions on the specified machines')
subparser.add_argument('--check', action='store_true', help='do not assume that the recorded state is correct')
subparser.add_argument('--allow-reboot', action='store_true', help='reboot machines if necessary')
subparser.add_argument('--max-concurrent-copy', type=int, default=5, metavar='N', help='maximum number of concurrent nix-copy-closure processes')

subparser = subparsers.add_parser('deploy', help='deploy the network configuration')
subparser.set_defaults(op=op_deploy)
subparser.add_argument('--kill-obsolete', '-k', action='store_true', help='kill obsolete virtual machines')
subparser.add_argument('--dry-run', action='store_true', help='show what would be done')
subparser.add_argument('--build-only', action='store_true', help='build only; do not perform deployment actions')
subparser.add_argument('--create-only', action='store_true', help='exit after creating missing machines')
subparser.add_argument('--copy-only', action='store_true', help='exit after copying closures')
subparser.add_argument('--include', nargs='+', metavar='MACHINE-NAME', help='perform deployment actions on the specified machines only')
subparser.add_argument('--exclude', nargs='+', metavar='MACHINE-NAME', help='do not perform deployment actions on the specified machines')
subparser.add_argument('--check', action='store_true', help='do not assume that the recorded state is correct')
subparser.add_argument('--allow-reboot', action='store_true', help='reboot machines if necessary')
subparser.add_argument('-I', nargs=1, action="append", dest="nix_path", metavar='PATH', help='append a directory to the Nix search path')
subparser.add_argument('--max-concurrent-copy', type=int, default=5, metavar='N', help='maximum number of concurrent nix-copy-closure processes')
add_common_deployment_options(subparser)

subparser = subparsers.add_parser('send-keys', help='send encryption keys')
subparser.set_defaults(op=op_send_keys)
Expand Down Expand Up @@ -348,6 +360,11 @@ subparser.add_argument('--xml', action='store_true', help='print the option valu
subparser = subparsers.add_parser('list-generations', help='list previous configurations to which you can roll back')
subparser.set_defaults(op=op_list_generations)

subparser = subparsers.add_parser('rollback', help='roll back to a previous configuration')
subparser.set_defaults(op=op_rollback)
subparser.add_argument('generation', type=int, metavar='GENERATION', help='number of the desired configuration (see ‘charon list-generations’)')
add_common_deployment_options(subparser)


# Parse the command line and execute the desired operation.
args = parser.parse_args()
Expand Down

0 comments on commit 1ebd58a

Please sign in to comment.