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/>.
29
from six.moves import configparser
31
from urllib import parse as urlparse
30
36
class AmuletUtils(object):
143
149
for service_name in services_list:
144
150
if (self.ubuntu_releases.index(release) >= systemd_switch or
145
service_name == "rabbitmq-server"):
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
152
output, code = sentry_unit.run(cmd)
153
162
self.log.debug('{} `{}` returned '
154
163
'{}'.format(sentry_unit.info['unit_name'],
157
return "command `{}` returned {}".format(cmd, str(code))
165
if not service_running:
166
return u"command `{}` returned {} {}".format(
167
cmd, output, str(code))
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))
450
460
cmd, code, output))
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.
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
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)
464
479
msg = ('{} `{}` returned {} '
467
482
amulet.raise_status(amulet.FAIL, msg=msg)
468
483
return str(output).split()
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
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.
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})
488
512
return ('Unit count mismatch. expected, actual: {}, '
489
513
'{} '.format(len(expected), len(actual)))
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))
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,
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:
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:
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:
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'
571
def run_action(self, unit_sentry, action,
572
_check_output=subprocess.check_output):
573
"""Run the named action on a given unit sentry.
575
_check_output parameter is used for dependency injection.
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']
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.
590
_check_output parameter is used for dependency injection.
592
command = ["juju", "action", "fetch", "--format=json", "--wait=0",
594
output = _check_output(command, universal_newlines=True)
595
data = json.loads(output)
596
return data.get(u"status") == "completed"