Compiled charm published to cs:~nfv/vpe-router-1
[osm/devops.git] / builds / vpe-router / lib / charms / layer / execd.py
1 # Copyright 2014-2016 Canonical Limited.
2 #
3 # This file is part of layer-basic, the reactive base layer for Juju.
4 #
5 # charm-helpers is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License version 3 as
7 # published by the Free Software Foundation.
8 #
9 # charm-helpers is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17 # This module may only import from the Python standard library.
18 import os
19 import sys
20 import subprocess
21 import time
22
23 '''
24 execd/preinstall
25
26 It is often necessary to configure and reconfigure machines
27 after provisioning, but before attempting to run the charm.
28 Common examples are specialized network configuration, enabling
29 of custom hardware, non-standard disk partitioning and filesystems,
30 adding secrets and keys required for using a secured network.
31
32 The reactive framework's base layer invokes this mechanism as
33 early as possible, before any network access is made or dependencies
34 unpacked or non-standard modules imported (including the charms.reactive
35 framework itself).
36
37 Operators needing to use this functionality may branch a charm and
38 create an exec.d directory in it. The exec.d directory in turn contains
39 one or more subdirectories, each of which contains an executable called
40 charm-pre-install and any other required resources. The charm-pre-install
41 executables are run, and if successful, state saved so they will not be
42 run again.
43
44 $CHARM_DIR/exec.d/mynamespace/charm-pre-install
45
46 An alternative to branching a charm is to compose a new charm that contains
47 the exec.d directory, using the original charm as a layer,
48
49 A charm author could also abuse this mechanism to modify the charm
50 environment in unusual ways, but for most purposes it is saner to use
51 charmhelpers.core.hookenv.atstart().
52 '''
53
54
55 def default_execd_dir():
56 return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
57
58
59 def execd_module_paths(execd_dir=None):
60 """Generate a list of full paths to modules within execd_dir."""
61 if not execd_dir:
62 execd_dir = default_execd_dir()
63
64 if not os.path.exists(execd_dir):
65 return
66
67 for subpath in os.listdir(execd_dir):
68 module = os.path.join(execd_dir, subpath)
69 if os.path.isdir(module):
70 yield module
71
72
73 def execd_submodule_paths(command, execd_dir=None):
74 """Generate a list of full paths to the specified command within exec_dir.
75 """
76 for module_path in execd_module_paths(execd_dir):
77 path = os.path.join(module_path, command)
78 if os.access(path, os.X_OK) and os.path.isfile(path):
79 yield path
80
81
82 def execd_sentinel_path(submodule_path):
83 module_path = os.path.dirname(submodule_path)
84 execd_path = os.path.dirname(module_path)
85 module_name = os.path.basename(module_path)
86 submodule_name = os.path.basename(submodule_path)
87 return os.path.join(execd_path,
88 '.{}_{}.done'.format(module_name, submodule_name))
89
90
91 def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
92 """Run command for each module within execd_dir which defines it."""
93 if stderr is None:
94 stderr = sys.stdout
95 for submodule_path in execd_submodule_paths(command, execd_dir):
96 # Only run each execd once. We cannot simply run them in the
97 # install hook, as potentially storage hooks are run before that.
98 # We cannot rely on them being idempotent.
99 sentinel = execd_sentinel_path(submodule_path)
100 if os.path.exists(sentinel):
101 continue
102
103 try:
104 subprocess.check_call([submodule_path], stderr=stderr,
105 universal_newlines=True)
106 with open(sentinel, 'w') as f:
107 f.write('{} ran successfully {}\n'.format(submodule_path,
108 time.ctime()))
109 f.write('Removing this file will cause it to be run again\n')
110 except subprocess.CalledProcessError as e:
111 # Logs get the details. We can't use juju-log, as the
112 # output may be substantial and exceed command line
113 # length limits.
114 print("ERROR ({}) running {}".format(e.returncode, e.cmd),
115 file=stderr)
116 print("STDOUT<<EOM", file=stderr)
117 print(e.output, file=stderr)
118 print("EOM", file=stderr)
119
120 # Unit workload status gets a shorter fail message.
121 short_path = os.path.relpath(submodule_path)
122 block_msg = "Error ({}) running {}".format(e.returncode,
123 short_path)
124 try:
125 subprocess.check_call(['status-set', 'blocked', block_msg],
126 universal_newlines=True)
127 if stop_on_error:
128 sys.exit(0) # Leave unit in blocked state.
129 except Exception:
130 pass # We care about the exec.d/* failure, not status-set.
131
132 if stop_on_error:
133 sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
134
135
136 def execd_preinstall(execd_dir=None):
137 """Run charm-pre-install for each module within execd_dir."""
138 execd_run('charm-pre-install', execd_dir=execd_dir)