~quobyte/charms/trusty/quobyte-webconsole/trunk

« back to all changes in this revision

Viewing changes to hooks/quobyte_helpers.py

  • Committer: Bruno Ranieri
  • Date: 2016-07-13 14:50:01 UTC
  • Revision ID: bruno@quobyte.com-20160713145001-1h6cddu9sltlvx7w
Initial charm

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
from charmhelpers.core.hookenv import (
 
3
    log,
 
4
    ERROR,
 
5
    DEBUG,
 
6
    config,
 
7
    status_set,
 
8
    unit_get,
 
9
    related_units,
 
10
    relation_ids,
 
11
    relation_get,
 
12
)
 
13
from charmhelpers.fetch import (
 
14
    apt_install,
 
15
    apt_update,
 
16
    add_source,
 
17
    filter_installed_packages
 
18
)
 
19
from charmhelpers.core.host import (
 
20
    mkdir,
 
21
    mount,
 
22
    mounts,
 
23
    lsb_release
 
24
)
 
25
from charmhelpers.contrib.network.ip import (
 
26
    format_ipv6_addr
 
27
)
 
28
from tempfile import NamedTemporaryFile
 
29
from stat import S_ISBLK
 
30
import subprocess
 
31
import tempfile
 
32
import shutil
 
33
import os
 
34
 
 
35
# default ports
 
36
api_port_rpc = 7860
 
37
registry_port_rpc = 7861
 
38
metadata_port_rpc = 7862
 
39
data_port_rpc = 7863
 
40
 
 
41
web_gui_port = 8080
 
42
registry_port_http = 7871
 
43
 
 
44
 
 
45
# -- shell-commands -----------------------------
 
46
 
 
47
 
 
48
def call(cmd, level=DEBUG):
 
49
    try:
 
50
        result = subprocess.check_output(cmd)
 
51
    except OSError as e1:
 
52
        # cmd not found
 
53
        log('command not found: {}'.format(e1), ERROR)
 
54
        return False, ''
 
55
    except subprocess.CalledProcessError as e2:
 
56
        # cmd was not successful
 
57
        return False, '{} {}'.format(e2, e2.output)
 
58
    log(' '.join(cmd))
 
59
    for line in result.split('\n'):
 
60
        log(line, level)
 
61
    return True, result
 
62
 
 
63
 
 
64
def echo_pipe(text, cmd):
 
65
    """ wrapper for echo $text | $cmd """
 
66
    try:
 
67
        subprocess.check_call('echo '+text+' | '+cmd, shell=True)
 
68
    except OSError as e:
 
69
        log('Error: "echo {} | {}" failed:.\n{}'.format(text, cmd, e), ERROR)
 
70
        return False
 
71
    return True
 
72
 
 
73
# -- file-system --------------------------------
 
74
 
 
75
 
 
76
def is_empty(folder):
 
77
    return os.listdir(folder) in [[], ['lost+found']]
 
78
 
 
79
 
 
80
def is_block_device(filename):
 
81
    try:
 
82
        mode = os.lstat(filename).st_mode
 
83
    except OSError:
 
84
        return False
 
85
    else:
 
86
        return S_ISBLK(mode)
 
87
 
 
88
 
 
89
def is_mounted(device):
 
90
    # mounts yields [['/mount/point','/dev/path'],[...]]
 
91
    return device in map(lambda x, _: x[1], mounts(), [])
 
92
 
 
93
 
 
94
def is_quobyte(mount_point):
 
95
    return -1 < max([x.upper().find('QUOBYTE_DEV_SETUP') for x in os.listdir(mount_point)])
 
96
 
 
97
 
 
98
def create_filesystem(device, filesystem):
 
99
    if filesystem in ['ext4']:
 
100
        log('Create filesystem {} on {}'.format(filesystem, device))
 
101
        success, mkfs_log = call(['mkfs.%s' % filesystem, '-F', '-m0', device])
 
102
        if success:
 
103
            log('{} filesystem created on {}'.format(filesystem, device))
 
104
            return True
 
105
        else:
 
106
            log(mkfs_log, ERROR)
 
107
            return False
 
108
    else:
 
109
        log('Given parameter filesystem={} is not supported.'.format(filesystem), ERROR)
 
110
        return False
 
111
 
 
112
 
 
113
def mount_device(device, mount_point, overwrite=True, filesystem="ext4"):
 
114
    if os.path.exists(mount_point):
 
115
        if os.path.isdir(mount_point):
 
116
            mount(device, mount_point, persist=True, filesystem=filesystem)
 
117
            return True
 
118
        else:
 
119
            if overwrite:
 
120
                os.unlink(mount_point)
 
121
                mount(device, mount_point, persist=True, filesystem=filesystem)
 
122
                return True
 
123
            else:
 
124
                # error ~ mount_point is a file
 
125
                return False
 
126
    else:
 
127
        mkdir(mount_point)
 
128
        mount(device, mount_point, persist=True)
 
129
        return True
 
130
 
 
131
 
 
132
# -- package-system -----------------------------
 
133
 
 
134
 
 
135
def fetch_key_from_repository():
 
136
    key, keyid = '', ''
 
137
    #  fetch key
 
138
    release = lsb_release()['DISTRIB_RELEASE']
 
139
    repo_id = config('repo_id')
 
140
    if release in ['12.04', '12.10', '13.10', '14.04', '14.10', '15.04']:
 
141
        url_key = 'https://support.quobyte.com/repo/2/{}/xUbuntu_{}/Release.key'.format(repo_id, release)
 
142
    else:
 
143
        msg = 'Ubuntu Release {} is not supported'.format(release)
 
144
        log(msg, ERROR)
 
145
        status_set('blocked', msg)
 
146
        return '', ''
 
147
 
 
148
    cmd_fetch = ['wget', '-q', '-O', '-', url_key]
 
149
    try:
 
150
        key = subprocess.check_output(cmd_fetch)
 
151
    except subprocess.CalledProcessError as error:
 
152
        msg = 'Unable to fetch key: {}'.format(error)
 
153
        log(msg, ERROR)
 
154
        status_set('blocked', msg)
 
155
        return '', ''
 
156
    cmd_extract = ['gpg', '--dry-run', '--list-packets']
 
157
    p2 = subprocess.Popen(cmd_extract, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
 
158
    p2.stdin.writelines(key)
 
159
    p2.stdin.close()
 
160
    key_info = p2.stdout.readlines()
 
161
    for line in key_info:
 
162
        if line.strip().startswith('keyid'):
 
163
            keyid = line.split(':')[1].strip()
 
164
            break
 
165
    return key, keyid
 
166
 
 
167
 
 
168
def update_config_file(key, new_value, filename):
 
169
    """ Look for 'key=some_value' in a a given file and replaces some_value with new_value
 
170
    :param key: key to lookup
 
171
    :param new_value: new value
 
172
    :param filename: file to manipulate
 
173
    :return: 0 if successful, > 0 otherwise
 
174
    """
 
175
    try:
 
176
        out_file_name = tempfile.mkdtemp()
 
177
        out_file_name = os.path.join(out_file_name, os.path.basename(filename))
 
178
        out_file = open(out_file_name, 'w')
 
179
    except IOError as e:
 
180
        log('Cannot open a temp. file: %r' % e, ERROR)
 
181
        return 1
 
182
 
 
183
    key_found = False
 
184
    try:
 
185
        with open(filename, 'r') as in_file:
 
186
            for line in in_file:
 
187
                token = line.strip().split('=')
 
188
                if token[0].strip() == key:
 
189
                    key_found = True
 
190
                    new_line = key+'='+new_value
 
191
                    if len(token[1].split('#')):
 
192
                        # preserve given comments
 
193
                        new_line += ' '+''.join(token[1].split('#')[1:])
 
194
                    new_line += '\n'
 
195
                else:
 
196
                    new_line = line
 
197
                out_file.write(new_line)
 
198
            if not key_found:
 
199
                new_line = '{}={}'.format(key, new_value)
 
200
                out_file.write(new_line)
 
201
    except IOError as e:
 
202
        log('Cannot read from file %r: %r' % (filename, e), ERROR)
 
203
        return 1
 
204
 
 
205
    out_file.close()
 
206
    in_file.close()
 
207
    os.remove(filename)
 
208
    shutil.move(out_file_name, filename)
 
209
    return 0
 
210
 
 
211
 
 
212
def is_installed(packages=None):
 
213
    if packages is None:
 
214
        packages = ['quobyte-client', 'quobyte-server',
 
215
                    'oracle-java8-installer', 'ntp']
 
216
    return len(filter_installed_packages(packages)) is 0
 
217
 
 
218
 
 
219
def install_quobyte():
 
220
 
 
221
    if is_installed():
 
222
        log('install_quobyte called, but nothing to do', DEBUG)
 
223
        return True
 
224
 
 
225
    repo_id = config('repo_id')
 
226
    oracle_java_license_accepted = config('oracle-java-license-accepted')
 
227
    quobyte_license_accepted = config('quobyte-license-accepted')
 
228
    if repo_id is None or len(repo_id) == 0:
 
229
        status_set('blocked', 'please provide customer repository id using Juju config')
 
230
        return False
 
231
    if not oracle_java_license_accepted:
 
232
        status_set('blocked', 'please accept Oracle Java License using Juju config')
 
233
        return False
 
234
    if not quobyte_license_accepted:
 
235
        status_set('blocked', 'please accept Quobyte License using Juju config')
 
236
        return False
 
237
    else:
 
238
        release = lsb_release()['DISTRIB_RELEASE']
 
239
        if release in ['12.04', '14.04', '15.04', '15.10']:
 
240
            qb_deb = 'deb https://support.quobyte.com/repo/2/{}/xUbuntu_{} ./'.format(repo_id, release)
 
241
            if release in ['12.04']:
 
242
                add_source('ppa:ubuntu-toolchain-r/test')
 
243
                apt_update(fatal=True)
 
244
                apt_install(packages=['python-software-properties'])
 
245
        else:
 
246
            msg = 'Ubuntu Release {} is not supported'.format(release)
 
247
            log(msg, ERROR)
 
248
            status_set('blocked', msg)
 
249
            return False
 
250
 
 
251
    log('Installing Quobyte-Data')
 
252
    status_set('maintenance', 'Installing software')
 
253
 
 
254
    # Java
 
255
    status_set('maintenance', 'Fetching Java')
 
256
    add_source('ppa:webupd8team/java')
 
257
    # accept Oracle Jave License
 
258
    if oracle_java_license_accepted:
 
259
        echo_pipe('oracle-java8-installer shared/accepted-oracle-license-v1-1 select true',
 
260
                  'debconf-set-selections')
 
261
    else:
 
262
        return False
 
263
 
 
264
    # Quobyte
 
265
    status_set('maintenance', 'Fetching Quobyte')
 
266
    key, keyid = fetch_key_from_repository()
 
267
    log('add key with keyid "%s" to apt' % keyid)
 
268
    add_source(qb_deb, key)
 
269
 
 
270
    # Install
 
271
    status_set('maintenance', 'Installing Software')
 
272
    apt_update(fatal=True)
 
273
    apt_install(['oracle-java8-installer'])
 
274
    apt_install(packages=['quobyte-client', 'quobyte-server', 'ntp'])
 
275
 
 
276
    update_config_file('constants.automation.manage_registry_replicas', 'true', r'/etc/quobyte/host.cfg')
 
277
 
 
278
    if not is_installed():
 
279
        status_set('blocked', 'Installing failed')
 
280
        return False
 
281
 
 
282
    status_set('maintenance', 'Software is installed')
 
283
    return True
 
284
 
 
285
# -- Juju-Relations -----------------------------
 
286
 
 
287
 
 
288
def get_relation_hosts(relation):
 
289
    hosts = []
 
290
    address = unit_get('private-address')
 
291
    # add self
 
292
    hosts.append('{}'.format(format_ipv6_addr(address) or address))
 
293
    log('Hosts (local): {}'.format(hosts), DEBUG)
 
294
    # add relations
 
295
    for rel_id in relation_ids(relation):
 
296
        for unit in related_units(rel_id):
 
297
            address = relation_get('host', unit, rel_id)
 
298
            if address is not None:
 
299
                hosts.append('{}'.format(
 
300
                    format_ipv6_addr(address) or address))
 
301
 
 
302
    hosts.sort()
 
303
    log('Hosts (all-known)(relation): {} ({})'.format(hosts, relation), DEBUG)
 
304
    return hosts
 
305
 
 
306
 
 
307
def do_update_replica_hosts(hosts):
 
308
    with_ports = ['{}:{}'.format(host, registry_port_rpc) for host in hosts]
 
309
    hosts = ','.join(with_ports)
 
310
    success = 0 is update_config_file('registry', hosts, r'/etc/quobyte/host.cfg')
 
311
    return success, hosts
 
312
 
 
313
 
 
314
# -- quobyte-system -----------------------------
 
315
 
 
316
 
 
317
def update_volume_config(key, new_value, configuration_name='BASE'):
 
318
    current_file = NamedTemporaryFile()
 
319
    new_file = NamedTemporaryFile()
 
320
    success, output = call(['qmgmt', 'volume', 'config', 'export', configuration_name, current_file.name])
 
321
    if not success:
 
322
        log('Error:\n{}'.format(output), ERROR)
 
323
    for line in current_file:
 
324
        if line.find(':') >= 0:
 
325
            current_key, value = line.split(':')
 
326
            if key == current_key.strip():
 
327
                new_file.write('{}: {}\n'.format(key, new_value))
 
328
            else:
 
329
                new_file.write(line)
 
330
        else:
 
331
            new_file.write(line)
 
332
    current_file.close()
 
333
    new_file.seek(0)
 
334
    success, output = call(['qmgmt', 'volume', 'config', 'import', configuration_name, new_file.name])
 
335
    if not success:
 
336
        log('Error:\n{}'.format(output), ERROR)
 
337
    new_file.close()
 
338
 
 
339
 
 
340
def fetch_registry_replica_devices(ip_api_service):
 
341
    registered_devices = []
 
342
    # cmd = ['qmgmt', '--force', '-u',
 
343
    cmd = ['qmgmt', '-u',
 
344
           'http://{}:{}'.format(ip_api_service, api_port_rpc),
 
345
           'registry', 'list']
 
346
    success, result = call(cmd)
 
347
    if success:
 
348
        for line in result:
 
349
            if line.strip().isdigit():
 
350
                registered_devices.append(line.strip())
 
351
        return True, registered_devices
 
352
    else:
 
353
        log(result, ERROR)
 
354
        return False, []
 
355
 
 
356
 
 
357
def add_registry_replica(device_id, ip_api_service):
 
358
    # cmd = ['qmgmt', '--force', '-u',
 
359
    cmd = ['qmgmt', '-u',
 
360
           'http://{}:{}'.format(ip_api_service, api_port_rpc),
 
361
           'registry', 'add', '{}'.format(device_id)]
 
362
    success, result = call(cmd)
 
363
    if success:
 
364
        log(result)
 
365
    else:
 
366
        log(result, ERROR)
 
367
    return success
 
368
 
 
369
 
 
370
def fetch_devices(ip_api_service, service_type=None):
 
371
    devices = []
 
372
    # cmd = ['qmgmt', '--force', '-u',
 
373
    cmd = ['qmgmt', '-u',
 
374
           'http://{}:{}'.format(ip_api_service, api_port_rpc),
 
375
           'device', 'list']
 
376
    success, result = call(cmd)
 
377
    if success:
 
378
        for line in result:
 
379
            token = line.split()
 
380
            if (token[0].isdigit() and
 
381
                    (service_type is token[-1] or service_type is None)):
 
382
                devices.append(token[0])
 
383
        return True, devices
 
384
    else:
 
385
        log(result, ERROR)
 
386
        return False, []
 
387
 
 
388
 
 
389
def get_device_id(mount_point):
 
390
    try:
 
391
        with open(os.path.join(mount_point, 'QUOBYTE_DEV_ID'), 'r') as fd:
 
392
            for line in fd:
 
393
                if line.strip().startswith('device.id'):
 
394
                    return line.split('=')[-1].strip()
 
395
    except IOError as e:
 
396
        log('failed to get quobyte device id from {}: {}'.format(mount_point, e))
 
397
        return None