~charmers/charms/precise/solr-jetty/trunk

« back to all changes in this revision

Viewing changes to lib/charmhelpers/core/host.py

  • Committer: Matthew Wedgwood
  • Date: 2013-07-19 16:34:13 UTC
  • mfrom: (17.1.8 solr-jetty)
  • Revision ID: matthew.wedgwood@canonical.com-20130719163413-2vk3szo8xj7vwfr4
[thedac] Use current charm-helpers (revno 38) inline

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Tools for working with the host system"""
 
2
# Copyright 2012 Canonical Ltd.
 
3
#
 
4
# Authors:
 
5
#  Nick Moffitt <nick.moffitt@canonical.com>
 
6
#  Matthew Wedgwood <matthew.wedgwood@canonical.com>
 
7
 
 
8
import apt_pkg
 
9
import os
 
10
import pwd
 
11
import grp
 
12
import subprocess
 
13
import hashlib
 
14
 
 
15
from collections import OrderedDict
 
16
 
 
17
from hookenv import log, execution_environment
 
18
 
 
19
 
 
20
def service_start(service_name):
 
21
    service('start', service_name)
 
22
 
 
23
 
 
24
def service_stop(service_name):
 
25
    service('stop', service_name)
 
26
 
 
27
 
 
28
def service_restart(service_name):
 
29
    service('restart', service_name)
 
30
 
 
31
 
 
32
def service_reload(service_name, restart_on_failure=False):
 
33
    if not service('reload', service_name) and restart_on_failure:
 
34
        service('restart', service_name)
 
35
 
 
36
 
 
37
def service(action, service_name):
 
38
    cmd = ['service', service_name, action]
 
39
    return subprocess.call(cmd) == 0
 
40
 
 
41
 
 
42
def adduser(username, password=None, shell='/bin/bash', system_user=False):
 
43
    """Add a user"""
 
44
    try:
 
45
        user_info = pwd.getpwnam(username)
 
46
        log('user {0} already exists!'.format(username))
 
47
    except KeyError:
 
48
        log('creating user {0}'.format(username))
 
49
        cmd = ['useradd']
 
50
        if system_user or password is None:
 
51
           cmd.append('--system')
 
52
        else:
 
53
           cmd.extend([
 
54
               '--create-home',
 
55
               '--shell', shell,
 
56
               '--password', password,
 
57
           ])
 
58
        cmd.append(username)
 
59
        subprocess.check_call(cmd)
 
60
        user_info = pwd.getpwnam(username)
 
61
    return user_info
 
62
 
 
63
 
 
64
def add_user_to_group(username, group):
 
65
    """Add a user to a group"""
 
66
    cmd = [
 
67
        'gpasswd', '-a',
 
68
        username,
 
69
        group
 
70
    ]
 
71
    log("Adding user {} to group {}".format(username, group))
 
72
    subprocess.check_call(cmd)
 
73
 
 
74
 
 
75
def rsync(from_path, to_path, flags='-r', options=None):
 
76
    """Replicate the contents of a path"""
 
77
    context = execution_environment()
 
78
    options = options or ['--delete', '--executability']
 
79
    cmd = ['/usr/bin/rsync', flags]
 
80
    cmd.extend(options)
 
81
    cmd.append(from_path.format(**context))
 
82
    cmd.append(to_path.format(**context))
 
83
    log(" ".join(cmd))
 
84
    return subprocess.check_output(cmd).strip()
 
85
 
 
86
 
 
87
def symlink(source, destination):
 
88
    """Create a symbolic link"""
 
89
    context = execution_environment()
 
90
    log("Symlinking {} as {}".format(source, destination))
 
91
    cmd = [
 
92
        'ln',
 
93
        '-sf',
 
94
        source.format(**context),
 
95
        destination.format(**context)
 
96
    ]
 
97
    subprocess.check_call(cmd)
 
98
 
 
99
 
 
100
def mkdir(path, owner='root', group='root', perms=0555, force=False):
 
101
    """Create a directory"""
 
102
    context = execution_environment()
 
103
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
 
104
                                          perms))
 
105
    uid = pwd.getpwnam(owner.format(**context)).pw_uid
 
106
    gid = grp.getgrnam(group.format(**context)).gr_gid
 
107
    realpath = os.path.abspath(path)
 
108
    if os.path.exists(realpath):
 
109
        if force and not os.path.isdir(realpath):
 
110
            log("Removing non-directory file {} prior to mkdir()".format(path))
 
111
            os.unlink(realpath)
 
112
    else:
 
113
        os.makedirs(realpath, perms)
 
114
    os.chown(realpath, uid, gid)
 
115
 
 
116
 
 
117
def write_file(path, fmtstr, owner='root', group='root', perms=0444, **kwargs):
 
118
    """Create or overwrite a file with the contents of a string"""
 
119
    context = execution_environment()
 
120
    context.update(kwargs)
 
121
    log("Writing file {} {}:{} {:o}".format(path, owner, group,
 
122
        perms))
 
123
    uid = pwd.getpwnam(owner.format(**context)).pw_uid
 
124
    gid = grp.getgrnam(group.format(**context)).gr_gid
 
125
    with open(path.format(**context), 'w') as target:
 
126
        os.fchown(target.fileno(), uid, gid)
 
127
        os.fchmod(target.fileno(), perms)
 
128
        target.write(fmtstr.format(**context))
 
129
 
 
130
 
 
131
def render_template_file(source, destination, **kwargs):
 
132
    """Create or overwrite a file using a template"""
 
133
    log("Rendering template {} for {}".format(source,
 
134
        destination))
 
135
    context = execution_environment()
 
136
    with open(source.format(**context), 'r') as template:
 
137
        write_file(destination.format(**context), template.read(),
 
138
                   **kwargs)
 
139
 
 
140
 
 
141
def filter_installed_packages(packages):
 
142
    """Returns a list of packages that require installation"""
 
143
    apt_pkg.init()
 
144
    cache = apt_pkg.Cache()
 
145
    _pkgs = []
 
146
    for package in packages:
 
147
        try:
 
148
            p = cache[package]
 
149
            p.current_ver or _pkgs.append(package)
 
150
        except KeyError:
 
151
            log('Package {} has no installation candidate.'.format(package),
 
152
                level='WARNING')
 
153
            _pkgs.append(package)
 
154
    return _pkgs
 
155
 
 
156
 
 
157
def apt_install(packages, options=None, fatal=False):
 
158
    """Install one or more packages"""
 
159
    options = options or []
 
160
    cmd = ['apt-get', '-y']
 
161
    cmd.extend(options)
 
162
    cmd.append('install')
 
163
    if isinstance(packages, basestring):
 
164
        cmd.append(packages)
 
165
    else:
 
166
        cmd.extend(packages)
 
167
    log("Installing {} with options: {}".format(packages,
 
168
                                                options))
 
169
    if fatal:
 
170
        subprocess.check_call(cmd)
 
171
    else:
 
172
        subprocess.call(cmd)
 
173
 
 
174
 
 
175
def apt_update(fatal=False):
 
176
    """Update local apt cache"""
 
177
    cmd = ['apt-get', 'update']
 
178
    if fatal:
 
179
        subprocess.check_call(cmd)
 
180
    else:
 
181
        subprocess.call(cmd)
 
182
 
 
183
 
 
184
def mount(device, mountpoint, options=None, persist=False):
 
185
    '''Mount a filesystem'''
 
186
    cmd_args = ['mount']
 
187
    if options is not None:
 
188
        cmd_args.extend(['-o', options])
 
189
    cmd_args.extend([device, mountpoint])
 
190
    try:
 
191
        subprocess.check_output(cmd_args)
 
192
    except subprocess.CalledProcessError, e:
 
193
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
 
194
        return False
 
195
    if persist:
 
196
        # TODO: update fstab
 
197
        pass
 
198
    return True
 
199
 
 
200
 
 
201
def umount(mountpoint, persist=False):
 
202
    '''Unmount a filesystem'''
 
203
    cmd_args = ['umount', mountpoint]
 
204
    try:
 
205
        subprocess.check_output(cmd_args)
 
206
    except subprocess.CalledProcessError, e:
 
207
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
208
        return False
 
209
    if persist:
 
210
        # TODO: update fstab
 
211
        pass
 
212
    return True
 
213
 
 
214
 
 
215
def mounts():
 
216
    '''List of all mounted volumes as [[mountpoint,device],[...]]'''
 
217
    with open('/proc/mounts') as f:
 
218
        # [['/mount/point','/dev/path'],[...]]
 
219
        system_mounts = [m[1::-1] for m in [l.strip().split()
 
220
                                            for l in f.readlines()]]
 
221
    return system_mounts
 
222
 
 
223
 
 
224
def file_hash(path):
 
225
    ''' Generate a md5 hash of the contents of 'path' or None if not found '''
 
226
    if os.path.exists(path):
 
227
        h = hashlib.md5()
 
228
        with open(path, 'r') as source:
 
229
            h.update(source.read())  # IGNORE:E1101 - it does have update
 
230
        return h.hexdigest()
 
231
    else:
 
232
        return None
 
233
 
 
234
 
 
235
def restart_on_change(restart_map):
 
236
    ''' Restart services based on configuration files changing
 
237
 
 
238
    This function is used a decorator, for example
 
239
 
 
240
        @restart_on_change({
 
241
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
242
            })
 
243
        def ceph_client_changed():
 
244
            ...
 
245
 
 
246
    In this example, the cinder-api and cinder-volume services
 
247
    would be restarted if /etc/ceph/ceph.conf is changed by the
 
248
    ceph_client_changed function.
 
249
    '''
 
250
    def wrap(f):
 
251
        def wrapped_f(*args):
 
252
            checksums = {}
 
253
            for path in restart_map:
 
254
                checksums[path] = file_hash(path)
 
255
            f(*args)
 
256
            restarts = []
 
257
            for path in restart_map:
 
258
                if checksums[path] != file_hash(path):
 
259
                    restarts += restart_map[path]
 
260
            for service_name in list(OrderedDict.fromkeys(restarts)):
 
261
                service('restart', service_name)
 
262
        return wrapped_f
 
263
    return wrap