~james-page/charms/trusty/glance/wily

« back to all changes in this revision

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

  • Committer: Corey Bryant
  • Date: 2015-08-18 17:34:34 UTC
  • Revision ID: corey.bryant@canonical.com-20150818173434-b7yih2ltlmyjlsyj
[corey.bryant,r=trivial] Sync charm-helpers to pick up Liberty support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# You should have received a copy of the GNU Lesser General Public License
15
15
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
 
import amulet
18
 
import ConfigParser
19
 
import distro_info
20
17
import io
 
18
import json
21
19
import logging
22
20
import os
23
21
import re
24
 
import six
 
22
import subprocess
25
23
import sys
26
24
import time
27
 
import urlparse
 
25
 
 
26
import amulet
 
27
import distro_info
 
28
import six
 
29
from six.moves import configparser
 
30
if six.PY3:
 
31
    from urllib import parse as urlparse
 
32
else:
 
33
    import urlparse
28
34
 
29
35
 
30
36
class AmuletUtils(object):
142
148
 
143
149
            for service_name in services_list:
144
150
                if (self.ubuntu_releases.index(release) >= systemd_switch or
145
 
                        service_name == "rabbitmq-server"):
146
 
                    # init is systemd
 
151
                        service_name in ['rabbitmq-server', 'apache2']):
 
152
                    # init is systemd (or regular sysv)
147
153
                    cmd = 'sudo service {} status'.format(service_name)
 
154
                    output, code = sentry_unit.run(cmd)
 
155
                    service_running = code == 0
148
156
                elif self.ubuntu_releases.index(release) < systemd_switch:
149
157
                    # init is upstart
150
158
                    cmd = 'sudo status {}'.format(service_name)
 
159
                    output, code = sentry_unit.run(cmd)
 
160
                    service_running = code == 0 and "start/running" in output
151
161
 
152
 
                output, code = sentry_unit.run(cmd)
153
162
                self.log.debug('{} `{}` returned '
154
163
                               '{}'.format(sentry_unit.info['unit_name'],
155
164
                                           cmd, code))
156
 
                if code != 0:
157
 
                    return "command `{}` returned {}".format(cmd, str(code))
 
165
                if not service_running:
 
166
                    return u"command `{}` returned {} {}".format(
 
167
                        cmd, output, str(code))
158
168
        return None
159
169
 
160
170
    def _get_config(self, unit, filename):
164
174
        # NOTE(beisner):  by default, ConfigParser does not handle options
165
175
        # with no value, such as the flags used in the mysql my.cnf file.
166
176
        # https://bugs.python.org/issue7005
167
 
        config = ConfigParser.ConfigParser(allow_no_value=True)
 
177
        config = configparser.ConfigParser(allow_no_value=True)
168
178
        config.readfp(io.StringIO(file_contents))
169
179
        return config
170
180
 
450
460
                                        cmd, code, output))
451
461
        return None
452
462
 
453
 
    def get_process_id_list(self, sentry_unit, process_name):
 
463
    def get_process_id_list(self, sentry_unit, process_name,
 
464
                            expect_success=True):
454
465
        """Get a list of process ID(s) from a single sentry juju unit
455
466
        for a single process name.
456
467
 
457
 
        :param sentry_unit: Pointer to amulet sentry instance (juju unit)
 
468
        :param sentry_unit: Amulet sentry instance (juju unit)
458
469
        :param process_name: Process name
 
470
        :param expect_success: If False, expect the PID to be missing,
 
471
            raise if it is present.
459
472
        :returns: List of process IDs
460
473
        """
461
 
        cmd = 'pidof {}'.format(process_name)
 
474
        cmd = 'pidof -x {}'.format(process_name)
 
475
        if not expect_success:
 
476
            cmd += " || exit 0 && exit 1"
462
477
        output, code = sentry_unit.run(cmd)
463
478
        if code != 0:
464
479
            msg = ('{} `{}` returned {} '
467
482
            amulet.raise_status(amulet.FAIL, msg=msg)
468
483
        return str(output).split()
469
484
 
470
 
    def get_unit_process_ids(self, unit_processes):
 
485
    def get_unit_process_ids(self, unit_processes, expect_success=True):
471
486
        """Construct a dict containing unit sentries, process names, and
472
 
        process IDs."""
 
487
        process IDs.
 
488
 
 
489
        :param unit_processes: A dictionary of Amulet sentry instance
 
490
            to list of process names.
 
491
        :param expect_success: if False expect the processes to not be
 
492
            running, raise if they are.
 
493
        :returns: Dictionary of Amulet sentry instance to dictionary
 
494
            of process names to PIDs.
 
495
        """
473
496
        pid_dict = {}
474
 
        for sentry_unit, process_list in unit_processes.iteritems():
 
497
        for sentry_unit, process_list in six.iteritems(unit_processes):
475
498
            pid_dict[sentry_unit] = {}
476
499
            for process in process_list:
477
 
                pids = self.get_process_id_list(sentry_unit, process)
 
500
                pids = self.get_process_id_list(
 
501
                    sentry_unit, process, expect_success=expect_success)
478
502
                pid_dict[sentry_unit].update({process: pids})
479
503
        return pid_dict
480
504
 
488
512
            return ('Unit count mismatch.  expected, actual: {}, '
489
513
                    '{} '.format(len(expected), len(actual)))
490
514
 
491
 
        for (e_sentry, e_proc_names) in expected.iteritems():
 
515
        for (e_sentry, e_proc_names) in six.iteritems(expected):
492
516
            e_sentry_name = e_sentry.info['unit_name']
493
517
            if e_sentry in actual.keys():
494
518
                a_proc_names = actual[e_sentry]
507
531
                            '{}'.format(e_proc_name, a_proc_name))
508
532
 
509
533
                a_pids_length = len(a_pids)
510
 
                if e_pids_length != a_pids_length:
511
 
                    return ('PID count mismatch. {} ({}) expected, actual: '
 
534
                fail_msg = ('PID count mismatch. {} ({}) expected, actual: '
512
535
                            '{}, {} ({})'.format(e_sentry_name, e_proc_name,
513
536
                                                 e_pids_length, a_pids_length,
514
537
                                                 a_pids))
 
538
 
 
539
                # If expected is not bool, ensure PID quantities match
 
540
                if not isinstance(e_pids_length, bool) and \
 
541
                        a_pids_length != e_pids_length:
 
542
                    return fail_msg
 
543
                # If expected is bool True, ensure 1 or more PIDs exist
 
544
                elif isinstance(e_pids_length, bool) and \
 
545
                        e_pids_length is True and a_pids_length < 1:
 
546
                    return fail_msg
 
547
                # If expected is bool False, ensure 0 PIDs exist
 
548
                elif isinstance(e_pids_length, bool) and \
 
549
                        e_pids_length is False and a_pids_length != 0:
 
550
                    return fail_msg
515
551
                else:
516
552
                    self.log.debug('PID check OK: {} {} {}: '
517
553
                                   '{}'.format(e_sentry_name, e_proc_name,
531
567
            return 'Dicts within list are not identical'
532
568
 
533
569
        return None
 
570
 
 
571
    def run_action(self, unit_sentry, action,
 
572
                   _check_output=subprocess.check_output):
 
573
        """Run the named action on a given unit sentry.
 
574
 
 
575
        _check_output parameter is used for dependency injection.
 
576
 
 
577
        @return action_id.
 
578
        """
 
579
        unit_id = unit_sentry.info["unit_name"]
 
580
        command = ["juju", "action", "do", "--format=json", unit_id, action]
 
581
        self.log.info("Running command: %s\n" % " ".join(command))
 
582
        output = _check_output(command, universal_newlines=True)
 
583
        data = json.loads(output)
 
584
        action_id = data[u'Action queued with id']
 
585
        return action_id
 
586
 
 
587
    def wait_on_action(self, action_id, _check_output=subprocess.check_output):
 
588
        """Wait for a given action, returning if it completed or not.
 
589
 
 
590
        _check_output parameter is used for dependency injection.
 
591
        """
 
592
        command = ["juju", "action", "fetch", "--format=json", "--wait=0",
 
593
                   action_id]
 
594
        output = _check_output(command, universal_newlines=True)
 
595
        data = json.loads(output)
 
596
        return data.get(u"status") == "completed"