~canonical-ci-engineering/charms/trusty/core-image-publisher/trunk

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/charmhelpers/__init__.py

  • Committer: Celso Providelo
  • Date: 2015-03-25 04:13:43 UTC
  • Revision ID: celso.providelo@canonical.com-20150325041343-jw05jaz6jscs3c8f
fork of core-image-watcher

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014-2015 Canonical Limited.
 
2
#
 
3
# This file is part of charm-helpers.
 
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
# Copyright 2012 Canonical Ltd.  This software is licensed under the
 
18
# GNU Affero General Public License version 3 (see the file LICENSE).
 
19
 
 
20
import warnings
 
21
warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning)  # noqa
 
22
 
 
23
import operator
 
24
import tempfile
 
25
import time
 
26
import yaml
 
27
import subprocess
 
28
 
 
29
import six
 
30
if six.PY3:
 
31
    from urllib.request import urlopen
 
32
    from urllib.error import (HTTPError, URLError)
 
33
else:
 
34
    from urllib2 import (urlopen, HTTPError, URLError)
 
35
 
 
36
"""Helper functions for writing Juju charms in Python."""
 
37
 
 
38
__metaclass__ = type
 
39
__all__ = [
 
40
    # 'get_config',             # core.hookenv.config()
 
41
    # 'log',                    # core.hookenv.log()
 
42
    # 'log_entry',              # core.hookenv.log()
 
43
    # 'log_exit',               # core.hookenv.log()
 
44
    # 'relation_get',           # core.hookenv.relation_get()
 
45
    # 'relation_set',           # core.hookenv.relation_set()
 
46
    # 'relation_ids',           # core.hookenv.relation_ids()
 
47
    # 'relation_list',          # core.hookenv.relation_units()
 
48
    # 'config_get',             # core.hookenv.config()
 
49
    # 'unit_get',               # core.hookenv.unit_get()
 
50
    # 'open_port',              # core.hookenv.open_port()
 
51
    # 'close_port',             # core.hookenv.close_port()
 
52
    # 'service_control',        # core.host.service()
 
53
    'unit_info',              # client-side, NOT IMPLEMENTED
 
54
    'wait_for_machine',       # client-side, NOT IMPLEMENTED
 
55
    'wait_for_page_contents',  # client-side, NOT IMPLEMENTED
 
56
    'wait_for_relation',      # client-side, NOT IMPLEMENTED
 
57
    'wait_for_unit',          # client-side, NOT IMPLEMENTED
 
58
]
 
59
 
 
60
 
 
61
SLEEP_AMOUNT = 0.1
 
62
 
 
63
 
 
64
# We create a juju_status Command here because it makes testing much,
 
65
# much easier.
 
66
def juju_status():
 
67
    subprocess.check_call(['juju', 'status'])
 
68
 
 
69
# re-implemented as charmhelpers.fetch.configure_sources()
 
70
# def configure_source(update=False):
 
71
#    source = config_get('source')
 
72
#    if ((source.startswith('ppa:') or
 
73
#         source.startswith('cloud:') or
 
74
#         source.startswith('http:'))):
 
75
#        run('add-apt-repository', source)
 
76
#    if source.startswith("http:"):
 
77
#        run('apt-key', 'import', config_get('key'))
 
78
#    if update:
 
79
#        run('apt-get', 'update')
 
80
 
 
81
 
 
82
# DEPRECATED: client-side only
 
83
def make_charm_config_file(charm_config):
 
84
    charm_config_file = tempfile.NamedTemporaryFile(mode='w+')
 
85
    charm_config_file.write(yaml.dump(charm_config))
 
86
    charm_config_file.flush()
 
87
    # The NamedTemporaryFile instance is returned instead of just the name
 
88
    # because we want to take advantage of garbage collection-triggered
 
89
    # deletion of the temp file when it goes out of scope in the caller.
 
90
    return charm_config_file
 
91
 
 
92
 
 
93
# DEPRECATED: client-side only
 
94
def unit_info(service_name, item_name, data=None, unit=None):
 
95
    if data is None:
 
96
        data = yaml.safe_load(juju_status())
 
97
    service = data['services'].get(service_name)
 
98
    if service is None:
 
99
        # XXX 2012-02-08 gmb:
 
100
        #     This allows us to cope with the race condition that we
 
101
        #     have between deploying a service and having it come up in
 
102
        #     `juju status`. We could probably do with cleaning it up so
 
103
        #     that it fails a bit more noisily after a while.
 
104
        return ''
 
105
    units = service['units']
 
106
    if unit is not None:
 
107
        item = units[unit][item_name]
 
108
    else:
 
109
        # It might seem odd to sort the units here, but we do it to
 
110
        # ensure that when no unit is specified, the first unit for the
 
111
        # service (or at least the one with the lowest number) is the
 
112
        # one whose data gets returned.
 
113
        sorted_unit_names = sorted(units.keys())
 
114
        item = units[sorted_unit_names[0]][item_name]
 
115
    return item
 
116
 
 
117
 
 
118
# DEPRECATED: client-side only
 
119
def get_machine_data():
 
120
    return yaml.safe_load(juju_status())['machines']
 
121
 
 
122
 
 
123
# DEPRECATED: client-side only
 
124
def wait_for_machine(num_machines=1, timeout=300):
 
125
    """Wait `timeout` seconds for `num_machines` machines to come up.
 
126
 
 
127
    This wait_for... function can be called by other wait_for functions
 
128
    whose timeouts might be too short in situations where only a bare
 
129
    Juju setup has been bootstrapped.
 
130
 
 
131
    :return: A tuple of (num_machines, time_taken). This is used for
 
132
             testing.
 
133
    """
 
134
    # You may think this is a hack, and you'd be right. The easiest way
 
135
    # to tell what environment we're working in (LXC vs EC2) is to check
 
136
    # the dns-name of the first machine. If it's localhost we're in LXC
 
137
    # and we can just return here.
 
138
    if get_machine_data()[0]['dns-name'] == 'localhost':
 
139
        return 1, 0
 
140
    start_time = time.time()
 
141
    while True:
 
142
        # Drop the first machine, since it's the Zookeeper and that's
 
143
        # not a machine that we need to wait for. This will only work
 
144
        # for EC2 environments, which is why we return early above if
 
145
        # we're in LXC.
 
146
        machine_data = get_machine_data()
 
147
        non_zookeeper_machines = [
 
148
            machine_data[key] for key in list(machine_data.keys())[1:]]
 
149
        if len(non_zookeeper_machines) >= num_machines:
 
150
            all_machines_running = True
 
151
            for machine in non_zookeeper_machines:
 
152
                if machine.get('instance-state') != 'running':
 
153
                    all_machines_running = False
 
154
                    break
 
155
            if all_machines_running:
 
156
                break
 
157
        if time.time() - start_time >= timeout:
 
158
            raise RuntimeError('timeout waiting for service to start')
 
159
        time.sleep(SLEEP_AMOUNT)
 
160
    return num_machines, time.time() - start_time
 
161
 
 
162
 
 
163
# DEPRECATED: client-side only
 
164
def wait_for_unit(service_name, timeout=480):
 
165
    """Wait `timeout` seconds for a given service name to come up."""
 
166
    wait_for_machine(num_machines=1)
 
167
    start_time = time.time()
 
168
    while True:
 
169
        state = unit_info(service_name, 'agent-state')
 
170
        if 'error' in state or state == 'started':
 
171
            break
 
172
        if time.time() - start_time >= timeout:
 
173
            raise RuntimeError('timeout waiting for service to start')
 
174
        time.sleep(SLEEP_AMOUNT)
 
175
    if state != 'started':
 
176
        raise RuntimeError('unit did not start, agent-state: ' + state)
 
177
 
 
178
 
 
179
# DEPRECATED: client-side only
 
180
def wait_for_relation(service_name, relation_name, timeout=120):
 
181
    """Wait `timeout` seconds for a given relation to come up."""
 
182
    start_time = time.time()
 
183
    while True:
 
184
        relation = unit_info(service_name, 'relations').get(relation_name)
 
185
        if relation is not None and relation['state'] == 'up':
 
186
            break
 
187
        if time.time() - start_time >= timeout:
 
188
            raise RuntimeError('timeout waiting for relation to be up')
 
189
        time.sleep(SLEEP_AMOUNT)
 
190
 
 
191
 
 
192
# DEPRECATED: client-side only
 
193
def wait_for_page_contents(url, contents, timeout=120, validate=None):
 
194
    if validate is None:
 
195
        validate = operator.contains
 
196
    start_time = time.time()
 
197
    while True:
 
198
        try:
 
199
            stream = urlopen(url)
 
200
        except (HTTPError, URLError):
 
201
            pass
 
202
        else:
 
203
            page = stream.read()
 
204
            if validate(page, contents):
 
205
                return page
 
206
        if time.time() - start_time >= timeout:
 
207
            raise RuntimeError('timeout waiting for contents of ' + url)
 
208
        time.sleep(SLEEP_AMOUNT)