~canonical-ci-engineering/charms/trusty/core-result-checker/test-with-relations

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/amulet/utils.py

  • Committer: Celso Providelo
  • Date: 2015-03-25 04:54:08 UTC
  • Revision ID: celso.providelo@canonical.com-20150325045408-q4knk469ig0ddg4u
forking 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
import ConfigParser
 
18
import io
 
19
import logging
 
20
import re
 
21
import sys
 
22
import time
 
23
 
 
24
import six
 
25
 
 
26
 
 
27
class AmuletUtils(object):
 
28
    """Amulet utilities.
 
29
 
 
30
       This class provides common utility functions that are used by Amulet
 
31
       tests.
 
32
       """
 
33
 
 
34
    def __init__(self, log_level=logging.ERROR):
 
35
        self.log = self.get_logger(level=log_level)
 
36
 
 
37
    def get_logger(self, name="amulet-logger", level=logging.DEBUG):
 
38
        """Get a logger object that will log to stdout."""
 
39
        log = logging
 
40
        logger = log.getLogger(name)
 
41
        fmt = log.Formatter("%(asctime)s %(funcName)s "
 
42
                            "%(levelname)s: %(message)s")
 
43
 
 
44
        handler = log.StreamHandler(stream=sys.stdout)
 
45
        handler.setLevel(level)
 
46
        handler.setFormatter(fmt)
 
47
 
 
48
        logger.addHandler(handler)
 
49
        logger.setLevel(level)
 
50
 
 
51
        return logger
 
52
 
 
53
    def valid_ip(self, ip):
 
54
        if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip):
 
55
            return True
 
56
        else:
 
57
            return False
 
58
 
 
59
    def valid_url(self, url):
 
60
        p = re.compile(
 
61
            r'^(?:http|ftp)s?://'
 
62
            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # noqa
 
63
            r'localhost|'
 
64
            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
 
65
            r'(?::\d+)?'
 
66
            r'(?:/?|[/?]\S+)$',
 
67
            re.IGNORECASE)
 
68
        if p.match(url):
 
69
            return True
 
70
        else:
 
71
            return False
 
72
 
 
73
    def validate_services(self, commands):
 
74
        """Validate services.
 
75
 
 
76
           Verify the specified services are running on the corresponding
 
77
           service units.
 
78
           """
 
79
        for k, v in six.iteritems(commands):
 
80
            for cmd in v:
 
81
                output, code = k.run(cmd)
 
82
                if code != 0:
 
83
                    return "command `{}` returned {}".format(cmd, str(code))
 
84
        return None
 
85
 
 
86
    def _get_config(self, unit, filename):
 
87
        """Get a ConfigParser object for parsing a unit's config file."""
 
88
        file_contents = unit.file_contents(filename)
 
89
        config = ConfigParser.ConfigParser()
 
90
        config.readfp(io.StringIO(file_contents))
 
91
        return config
 
92
 
 
93
    def validate_config_data(self, sentry_unit, config_file, section,
 
94
                             expected):
 
95
        """Validate config file data.
 
96
 
 
97
           Verify that the specified section of the config file contains
 
98
           the expected option key:value pairs.
 
99
           """
 
100
        config = self._get_config(sentry_unit, config_file)
 
101
 
 
102
        if section != 'DEFAULT' and not config.has_section(section):
 
103
            return "section [{}] does not exist".format(section)
 
104
 
 
105
        for k in expected.keys():
 
106
            if not config.has_option(section, k):
 
107
                return "section [{}] is missing option {}".format(section, k)
 
108
            if config.get(section, k) != expected[k]:
 
109
                return "section [{}] {}:{} != expected {}:{}".format(
 
110
                       section, k, config.get(section, k), k, expected[k])
 
111
        return None
 
112
 
 
113
    def _validate_dict_data(self, expected, actual):
 
114
        """Validate dictionary data.
 
115
 
 
116
           Compare expected dictionary data vs actual dictionary data.
 
117
           The values in the 'expected' dictionary can be strings, bools, ints,
 
118
           longs, or can be a function that evaluate a variable and returns a
 
119
           bool.
 
120
           """
 
121
        for k, v in six.iteritems(expected):
 
122
            if k in actual:
 
123
                if (isinstance(v, six.string_types) or
 
124
                        isinstance(v, bool) or
 
125
                        isinstance(v, six.integer_types)):
 
126
                    if v != actual[k]:
 
127
                        return "{}:{}".format(k, actual[k])
 
128
                elif not v(actual[k]):
 
129
                    return "{}:{}".format(k, actual[k])
 
130
            else:
 
131
                return "key '{}' does not exist".format(k)
 
132
        return None
 
133
 
 
134
    def validate_relation_data(self, sentry_unit, relation, expected):
 
135
        """Validate actual relation data based on expected relation data."""
 
136
        actual = sentry_unit.relation(relation[0], relation[1])
 
137
        self.log.debug('actual: {}'.format(repr(actual)))
 
138
        return self._validate_dict_data(expected, actual)
 
139
 
 
140
    def _validate_list_data(self, expected, actual):
 
141
        """Compare expected list vs actual list data."""
 
142
        for e in expected:
 
143
            if e not in actual:
 
144
                return "expected item {} not found in actual list".format(e)
 
145
        return None
 
146
 
 
147
    def not_null(self, string):
 
148
        if string is not None:
 
149
            return True
 
150
        else:
 
151
            return False
 
152
 
 
153
    def _get_file_mtime(self, sentry_unit, filename):
 
154
        """Get last modification time of file."""
 
155
        return sentry_unit.file_stat(filename)['mtime']
 
156
 
 
157
    def _get_dir_mtime(self, sentry_unit, directory):
 
158
        """Get last modification time of directory."""
 
159
        return sentry_unit.directory_stat(directory)['mtime']
 
160
 
 
161
    def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False):
 
162
        """Get process' start time.
 
163
 
 
164
           Determine start time of the process based on the last modification
 
165
           time of the /proc/pid directory. If pgrep_full is True, the process
 
166
           name is matched against the full command line.
 
167
           """
 
168
        if pgrep_full:
 
169
            cmd = 'pgrep -o -f {}'.format(service)
 
170
        else:
 
171
            cmd = 'pgrep -o {}'.format(service)
 
172
        proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip())
 
173
        return self._get_dir_mtime(sentry_unit, proc_dir)
 
174
 
 
175
    def service_restarted(self, sentry_unit, service, filename,
 
176
                          pgrep_full=False, sleep_time=20):
 
177
        """Check if service was restarted.
 
178
 
 
179
           Compare a service's start time vs a file's last modification time
 
180
           (such as a config file for that service) to determine if the service
 
181
           has been restarted.
 
182
           """
 
183
        time.sleep(sleep_time)
 
184
        if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >=
 
185
                self._get_file_mtime(sentry_unit, filename)):
 
186
            return True
 
187
        else:
 
188
            return False
 
189
 
 
190
    def relation_error(self, name, data):
 
191
        return 'unexpected relation data in {} - {}'.format(name, data)
 
192
 
 
193
    def endpoint_error(self, name, data):
 
194
        return 'unexpected endpoint data in {} - {}'.format(name, data)