~brianlbaird/charms/trusty/pcrf/trunk

« back to all changes in this revision

Viewing changes to trusty/pcrf/lib/charms/layer/execd.py

  • Committer: brian baird
  • Date: 2016-08-24 18:05:17 UTC
  • Revision ID: brianlbaird@gmail.com-20160824180517-uyp6100mfwuj6les
dt demo

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)