Merge lp:~corey.bryant/charms/trusty/cinder/action-managed-upgrade into lp:~openstack-charmers-archive/charms/trusty/cinder/next

Proposed by Corey Bryant
Status: Merged
Merged at revision: 124
Proposed branch: lp:~corey.bryant/charms/trusty/cinder/action-managed-upgrade
Merge into: lp:~openstack-charmers-archive/charms/trusty/cinder/next
Diff against target: 945 lines (+584/-164)
9 files modified
actions/openstack_upgrade.py (+13/-40)
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+20/-5)
hooks/charmhelpers/contrib/openstack/amulet/utils.py (+359/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+51/-0)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+2/-11)
hooks/charmhelpers/core/host.py (+32/-16)
hooks/charmhelpers/core/kernel.py (+68/-0)
tests/charmhelpers/contrib/amulet/utils.py (+9/-0)
unit_tests/test_actions_openstack_upgrade.py (+30/-92)
To merge this branch: bzr merge lp:~corey.bryant/charms/trusty/cinder/action-managed-upgrade
Reviewer Review Type Date Requested Status
Billy Olsen Approve
Review via email: mp+270999@code.launchpad.net
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #10051 cinder-next for corey.bryant mp270999
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/10051/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #9216 cinder-next for corey.bryant mp270999
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/9216/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6436 cinder-next for corey.bryant mp270999
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6436/

134. By Corey Bryant

Update unit tests

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #10056 cinder-next for corey.bryant mp270999
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.

Full lint test output: http://paste.ubuntu.com/12419474/
Build: http://10.245.162.77:8080/job/charm_lint_check/10056/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #9222 cinder-next for corey.bryant mp270999
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/9222/

135. By Corey Bryant

Fix unit test

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #10058 cinder-next for corey.bryant mp270999
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/10058/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #9226 cinder-next for corey.bryant mp270999
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/9226/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6443 cinder-next for corey.bryant mp270999
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/12420336/
Build: http://10.245.162.77:8080/job/charm_amulet_test/6443/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6445 cinder-next for corey.bryant mp270999
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6445/

Revision history for this message
Billy Olsen (billy-olsen) wrote :

LGTM. Approved.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'actions/openstack_upgrade.py'
--- actions/openstack_upgrade.py 2015-09-01 15:13:23 +0000
+++ actions/openstack_upgrade.py 2015-09-15 18:42:15 +0000
@@ -1,26 +1,20 @@
1#!/usr/bin/python1#!/usr/bin/python
2import sys2import sys
3import traceback
4import uuid3import uuid
54
6sys.path.append('hooks/')5sys.path.append('hooks/')
76
7from charmhelpers.contrib.openstack.utils import (
8 do_action_openstack_upgrade,
9)
10
8from charmhelpers.core.hookenv import (11from charmhelpers.core.hookenv import (
9 action_set,
10 action_fail,
11 config,
12 relation_ids,12 relation_ids,
13 relation_set13 relation_set,
14)14)
1515
16from cinder_hooks import config_changed16from cinder_hooks import config_changed
1717
18from charmhelpers.contrib.openstack.utils import (
19 juju_log,
20 git_install_requested,
21 openstack_upgrade_available
22)
23
24from cinder_utils import (18from cinder_utils import (
25 do_openstack_upgrade,19 do_openstack_upgrade,
26 register_configs20 register_configs
@@ -38,35 +32,14 @@
38 code to run, otherwise a full service level upgrade will fire32 code to run, otherwise a full service level upgrade will fire
39 on config-changed."""33 on config-changed."""
4034
41 if git_install_requested():35 if (do_action_openstack_upgrade('cinder-common',
42 action_set({'outcome': 'installed from source, skipped upgrade.'})36 do_openstack_upgrade,
43 else:37 CONFIGS)):
44 if openstack_upgrade_available('cinder-common'):38 # tell any storage-backends we just upgraded
45 if config('action-managed-upgrade'):39 for rid in relation_ids('storage-backend'):
46 juju_log('Upgrading OpenStack release')40 relation_set(relation_id=rid,
4741 upgrade_nonce=uuid.uuid4())
48 try:42 config_changed()
49 do_openstack_upgrade(configs=CONFIGS)
50
51 # NOTE(jamespage) tell any storage-backends we just
52 # upgraded
53 for rid in relation_ids('storage-backend'):
54 relation_set(relation_id=rid,
55 upgrade_nonce=uuid.uuid4())
56
57 action_set({'outcome': 'success, upgrade completed.'})
58 except:
59 action_set({'outcome': 'upgrade failed, see traceback.'})
60 action_set({'traceback': traceback.format_exc()})
61 action_fail('do_openstack_upgrade resulted in an '
62 'unexpected error')
63
64 config_changed()
65 else:
66 action_set({'outcome': 'action-managed-upgrade config is '
67 'False, skipped upgrade.'})
68 else:
69 action_set({'outcome': 'no upgrade available.'})
7043
71if __name__ == '__main__':44if __name__ == '__main__':
72 openstack_upgrade()45 openstack_upgrade()
7346
=== added directory 'bin'
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-18 17:34:33 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-15 18:42:15 +0000
@@ -44,8 +44,15 @@
44 Determine if the local branch being tested is derived from its44 Determine if the local branch being tested is derived from its
45 stable or next (dev) branch, and based on this, use the corresonding45 stable or next (dev) branch, and based on this, use the corresonding
46 stable or next branches for the other_services."""46 stable or next branches for the other_services."""
47
48 # Charms outside the lp:~openstack-charmers namespace
47 base_charms = ['mysql', 'mongodb', 'nrpe']49 base_charms = ['mysql', 'mongodb', 'nrpe']
4850
51 # Force these charms to current series even when using an older series.
52 # ie. Use trusty/nrpe even when series is precise, as the P charm
53 # does not possess the necessary external master config and hooks.
54 force_series_current = ['nrpe']
55
49 if self.series in ['precise', 'trusty']:56 if self.series in ['precise', 'trusty']:
50 base_series = self.series57 base_series = self.series
51 else:58 else:
@@ -53,11 +60,17 @@
5360
54 if self.stable:61 if self.stable:
55 for svc in other_services:62 for svc in other_services:
63 if svc['name'] in force_series_current:
64 base_series = self.current_next
65
56 temp = 'lp:charms/{}/{}'66 temp = 'lp:charms/{}/{}'
57 svc['location'] = temp.format(base_series,67 svc['location'] = temp.format(base_series,
58 svc['name'])68 svc['name'])
59 else:69 else:
60 for svc in other_services:70 for svc in other_services:
71 if svc['name'] in force_series_current:
72 base_series = self.current_next
73
61 if svc['name'] in base_charms:74 if svc['name'] in base_charms:
62 temp = 'lp:charms/{}/{}'75 temp = 'lp:charms/{}/{}'
63 svc['location'] = temp.format(base_series,76 svc['location'] = temp.format(base_series,
@@ -77,21 +90,23 @@
7790
78 services = other_services91 services = other_services
79 services.append(this_service)92 services.append(this_service)
93
94 # Charms which should use the source config option
80 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',95 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
81 'ceph-osd', 'ceph-radosgw']96 'ceph-osd', 'ceph-radosgw']
82 # Most OpenStack subordinate charms do not expose an origin option97
83 # as that is controlled by the principle.98 # Charms which can not use openstack-origin, ie. many subordinates
84 ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']99 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']
85100
86 if self.openstack:101 if self.openstack:
87 for svc in services:102 for svc in services:
88 if svc['name'] not in use_source + ignore:103 if svc['name'] not in use_source + no_origin:
89 config = {'openstack-origin': self.openstack}104 config = {'openstack-origin': self.openstack}
90 self.d.configure(svc['name'], config)105 self.d.configure(svc['name'], config)
91106
92 if self.source:107 if self.source:
93 for svc in services:108 for svc in services:
94 if svc['name'] in use_source and svc['name'] not in ignore:109 if svc['name'] in use_source and svc['name'] not in no_origin:
95 config = {'source': self.source}110 config = {'source': self.source}
96 self.d.configure(svc['name'], config)111 self.d.configure(svc['name'], config)
97112
98113
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py'
--- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:17:07 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-15 18:42:15 +0000
@@ -27,6 +27,7 @@
27import heatclient.v1.client as heat_client27import heatclient.v1.client as heat_client
28import keystoneclient.v2_0 as keystone_client28import keystoneclient.v2_0 as keystone_client
29import novaclient.v1_1.client as nova_client29import novaclient.v1_1.client as nova_client
30import pika
30import swiftclient31import swiftclient
3132
32from charmhelpers.contrib.amulet.utils import (33from charmhelpers.contrib.amulet.utils import (
@@ -602,3 +603,361 @@
602 self.log.debug('Ceph {} samples (OK): '603 self.log.debug('Ceph {} samples (OK): '
603 '{}'.format(sample_type, samples))604 '{}'.format(sample_type, samples))
604 return None605 return None
606
607# rabbitmq/amqp specific helpers:
608 def add_rmq_test_user(self, sentry_units,
609 username="testuser1", password="changeme"):
610 """Add a test user via the first rmq juju unit, check connection as
611 the new user against all sentry units.
612
613 :param sentry_units: list of sentry unit pointers
614 :param username: amqp user name, default to testuser1
615 :param password: amqp user password
616 :returns: None if successful. Raise on error.
617 """
618 self.log.debug('Adding rmq user ({})...'.format(username))
619
620 # Check that user does not already exist
621 cmd_user_list = 'rabbitmqctl list_users'
622 output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list)
623 if username in output:
624 self.log.warning('User ({}) already exists, returning '
625 'gracefully.'.format(username))
626 return
627
628 perms = '".*" ".*" ".*"'
629 cmds = ['rabbitmqctl add_user {} {}'.format(username, password),
630 'rabbitmqctl set_permissions {} {}'.format(username, perms)]
631
632 # Add user via first unit
633 for cmd in cmds:
634 output, _ = self.run_cmd_unit(sentry_units[0], cmd)
635
636 # Check connection against the other sentry_units
637 self.log.debug('Checking user connect against units...')
638 for sentry_unit in sentry_units:
639 connection = self.connect_amqp_by_unit(sentry_unit, ssl=False,
640 username=username,
641 password=password)
642 connection.close()
643
644 def delete_rmq_test_user(self, sentry_units, username="testuser1"):
645 """Delete a rabbitmq user via the first rmq juju unit.
646
647 :param sentry_units: list of sentry unit pointers
648 :param username: amqp user name, default to testuser1
649 :param password: amqp user password
650 :returns: None if successful or no such user.
651 """
652 self.log.debug('Deleting rmq user ({})...'.format(username))
653
654 # Check that the user exists
655 cmd_user_list = 'rabbitmqctl list_users'
656 output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list)
657
658 if username not in output:
659 self.log.warning('User ({}) does not exist, returning '
660 'gracefully.'.format(username))
661 return
662
663 # Delete the user
664 cmd_user_del = 'rabbitmqctl delete_user {}'.format(username)
665 output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del)
666
667 def get_rmq_cluster_status(self, sentry_unit):
668 """Execute rabbitmq cluster status command on a unit and return
669 the full output.
670
671 :param unit: sentry unit
672 :returns: String containing console output of cluster status command
673 """
674 cmd = 'rabbitmqctl cluster_status'
675 output, _ = self.run_cmd_unit(sentry_unit, cmd)
676 self.log.debug('{} cluster_status:\n{}'.format(
677 sentry_unit.info['unit_name'], output))
678 return str(output)
679
680 def get_rmq_cluster_running_nodes(self, sentry_unit):
681 """Parse rabbitmqctl cluster_status output string, return list of
682 running rabbitmq cluster nodes.
683
684 :param unit: sentry unit
685 :returns: List containing node names of running nodes
686 """
687 # NOTE(beisner): rabbitmqctl cluster_status output is not
688 # json-parsable, do string chop foo, then json.loads that.
689 str_stat = self.get_rmq_cluster_status(sentry_unit)
690 if 'running_nodes' in str_stat:
691 pos_start = str_stat.find("{running_nodes,") + 15
692 pos_end = str_stat.find("]},", pos_start) + 1
693 str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"')
694 run_nodes = json.loads(str_run_nodes)
695 return run_nodes
696 else:
697 return []
698
699 def validate_rmq_cluster_running_nodes(self, sentry_units):
700 """Check that all rmq unit hostnames are represented in the
701 cluster_status output of all units.
702
703 :param host_names: dict of juju unit names to host names
704 :param units: list of sentry unit pointers (all rmq units)
705 :returns: None if successful, otherwise return error message
706 """
707 host_names = self.get_unit_hostnames(sentry_units)
708 errors = []
709
710 # Query every unit for cluster_status running nodes
711 for query_unit in sentry_units:
712 query_unit_name = query_unit.info['unit_name']
713 running_nodes = self.get_rmq_cluster_running_nodes(query_unit)
714
715 # Confirm that every unit is represented in the queried unit's
716 # cluster_status running nodes output.
717 for validate_unit in sentry_units:
718 val_host_name = host_names[validate_unit.info['unit_name']]
719 val_node_name = 'rabbit@{}'.format(val_host_name)
720
721 if val_node_name not in running_nodes:
722 errors.append('Cluster member check failed on {}: {} not '
723 'in {}\n'.format(query_unit_name,
724 val_node_name,
725 running_nodes))
726 if errors:
727 return ''.join(errors)
728
729 def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None):
730 """Check a single juju rmq unit for ssl and port in the config file."""
731 host = sentry_unit.info['public-address']
732 unit_name = sentry_unit.info['unit_name']
733
734 conf_file = '/etc/rabbitmq/rabbitmq.config'
735 conf_contents = str(self.file_contents_safe(sentry_unit,
736 conf_file, max_wait=16))
737 # Checks
738 conf_ssl = 'ssl' in conf_contents
739 conf_port = str(port) in conf_contents
740
741 # Port explicitly checked in config
742 if port and conf_port and conf_ssl:
743 self.log.debug('SSL is enabled @{}:{} '
744 '({})'.format(host, port, unit_name))
745 return True
746 elif port and not conf_port and conf_ssl:
747 self.log.debug('SSL is enabled @{} but not on port {} '
748 '({})'.format(host, port, unit_name))
749 return False
750 # Port not checked (useful when checking that ssl is disabled)
751 elif not port and conf_ssl:
752 self.log.debug('SSL is enabled @{}:{} '
753 '({})'.format(host, port, unit_name))
754 return True
755 elif not port and not conf_ssl:
756 self.log.debug('SSL not enabled @{}:{} '
757 '({})'.format(host, port, unit_name))
758 return False
759 else:
760 msg = ('Unknown condition when checking SSL status @{}:{} '
761 '({})'.format(host, port, unit_name))
762 amulet.raise_status(amulet.FAIL, msg)
763
764 def validate_rmq_ssl_enabled_units(self, sentry_units, port=None):
765 """Check that ssl is enabled on rmq juju sentry units.
766
767 :param sentry_units: list of all rmq sentry units
768 :param port: optional ssl port override to validate
769 :returns: None if successful, otherwise return error message
770 """
771 for sentry_unit in sentry_units:
772 if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port):
773 return ('Unexpected condition: ssl is disabled on unit '
774 '({})'.format(sentry_unit.info['unit_name']))
775 return None
776
777 def validate_rmq_ssl_disabled_units(self, sentry_units):
778 """Check that ssl is enabled on listed rmq juju sentry units.
779
780 :param sentry_units: list of all rmq sentry units
781 :returns: True if successful. Raise on error.
782 """
783 for sentry_unit in sentry_units:
784 if self.rmq_ssl_is_enabled_on_unit(sentry_unit):
785 return ('Unexpected condition: ssl is enabled on unit '
786 '({})'.format(sentry_unit.info['unit_name']))
787 return None
788
789 def configure_rmq_ssl_on(self, sentry_units, deployment,
790 port=None, max_wait=60):
791 """Turn ssl charm config option on, with optional non-default
792 ssl port specification. Confirm that it is enabled on every
793 unit.
794
795 :param sentry_units: list of sentry units
796 :param deployment: amulet deployment object pointer
797 :param port: amqp port, use defaults if None
798 :param max_wait: maximum time to wait in seconds to confirm
799 :returns: None if successful. Raise on error.
800 """
801 self.log.debug('Setting ssl charm config option: on')
802
803 # Enable RMQ SSL
804 config = {'ssl': 'on'}
805 if port:
806 config['ssl_port'] = port
807
808 deployment.configure('rabbitmq-server', config)
809
810 # Confirm
811 tries = 0
812 ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port)
813 while ret and tries < (max_wait / 4):
814 time.sleep(4)
815 self.log.debug('Attempt {}: {}'.format(tries, ret))
816 ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port)
817 tries += 1
818
819 if ret:
820 amulet.raise_status(amulet.FAIL, ret)
821
822 def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60):
823 """Turn ssl charm config option off, confirm that it is disabled
824 on every unit.
825
826 :param sentry_units: list of sentry units
827 :param deployment: amulet deployment object pointer
828 :param max_wait: maximum time to wait in seconds to confirm
829 :returns: None if successful. Raise on error.
830 """
831 self.log.debug('Setting ssl charm config option: off')
832
833 # Disable RMQ SSL
834 config = {'ssl': 'off'}
835 deployment.configure('rabbitmq-server', config)
836
837 # Confirm
838 tries = 0
839 ret = self.validate_rmq_ssl_disabled_units(sentry_units)
840 while ret and tries < (max_wait / 4):
841 time.sleep(4)
842 self.log.debug('Attempt {}: {}'.format(tries, ret))
843 ret = self.validate_rmq_ssl_disabled_units(sentry_units)
844 tries += 1
845
846 if ret:
847 amulet.raise_status(amulet.FAIL, ret)
848
849 def connect_amqp_by_unit(self, sentry_unit, ssl=False,
850 port=None, fatal=True,
851 username="testuser1", password="changeme"):
852 """Establish and return a pika amqp connection to the rabbitmq service
853 running on a rmq juju unit.
854
855 :param sentry_unit: sentry unit pointer
856 :param ssl: boolean, default to False
857 :param port: amqp port, use defaults if None
858 :param fatal: boolean, default to True (raises on connect error)
859 :param username: amqp user name, default to testuser1
860 :param password: amqp user password
861 :returns: pika amqp connection pointer or None if failed and non-fatal
862 """
863 host = sentry_unit.info['public-address']
864 unit_name = sentry_unit.info['unit_name']
865
866 # Default port logic if port is not specified
867 if ssl and not port:
868 port = 5671
869 elif not ssl and not port:
870 port = 5672
871
872 self.log.debug('Connecting to amqp on {}:{} ({}) as '
873 '{}...'.format(host, port, unit_name, username))
874
875 try:
876 credentials = pika.PlainCredentials(username, password)
877 parameters = pika.ConnectionParameters(host=host, port=port,
878 credentials=credentials,
879 ssl=ssl,
880 connection_attempts=3,
881 retry_delay=5,
882 socket_timeout=1)
883 connection = pika.BlockingConnection(parameters)
884 assert connection.server_properties['product'] == 'RabbitMQ'
885 self.log.debug('Connect OK')
886 return connection
887 except Exception as e:
888 msg = ('amqp connection failed to {}:{} as '
889 '{} ({})'.format(host, port, username, str(e)))
890 if fatal:
891 amulet.raise_status(amulet.FAIL, msg)
892 else:
893 self.log.warn(msg)
894 return None
895
896 def publish_amqp_message_by_unit(self, sentry_unit, message,
897 queue="test", ssl=False,
898 username="testuser1",
899 password="changeme",
900 port=None):
901 """Publish an amqp message to a rmq juju unit.
902
903 :param sentry_unit: sentry unit pointer
904 :param message: amqp message string
905 :param queue: message queue, default to test
906 :param username: amqp user name, default to testuser1
907 :param password: amqp user password
908 :param ssl: boolean, default to False
909 :param port: amqp port, use defaults if None
910 :returns: None. Raises exception if publish failed.
911 """
912 self.log.debug('Publishing message to {} queue:\n{}'.format(queue,
913 message))
914 connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl,
915 port=port,
916 username=username,
917 password=password)
918
919 # NOTE(beisner): extra debug here re: pika hang potential:
920 # https://github.com/pika/pika/issues/297
921 # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw
922 self.log.debug('Defining channel...')
923 channel = connection.channel()
924 self.log.debug('Declaring queue...')
925 channel.queue_declare(queue=queue, auto_delete=False, durable=True)
926 self.log.debug('Publishing message...')
927 channel.basic_publish(exchange='', routing_key=queue, body=message)
928 self.log.debug('Closing channel...')
929 channel.close()
930 self.log.debug('Closing connection...')
931 connection.close()
932
933 def get_amqp_message_by_unit(self, sentry_unit, queue="test",
934 username="testuser1",
935 password="changeme",
936 ssl=False, port=None):
937 """Get an amqp message from a rmq juju unit.
938
939 :param sentry_unit: sentry unit pointer
940 :param queue: message queue, default to test
941 :param username: amqp user name, default to testuser1
942 :param password: amqp user password
943 :param ssl: boolean, default to False
944 :param port: amqp port, use defaults if None
945 :returns: amqp message body as string. Raise if get fails.
946 """
947 connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl,
948 port=port,
949 username=username,
950 password=password)
951 channel = connection.channel()
952 method_frame, _, body = channel.basic_get(queue)
953
954 if method_frame:
955 self.log.debug('Retreived message from {} queue:\n{}'.format(queue,
956 body))
957 channel.basic_ack(method_frame.delivery_tag)
958 channel.close()
959 connection.close()
960 return body
961 else:
962 msg = 'No message retrieved.'
963 amulet.raise_status(amulet.FAIL, msg)
605964
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2015-09-03 09:43:34 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-09-15 18:42:15 +0000
@@ -25,6 +25,7 @@
25import re25import re
2626
27import six27import six
28import traceback
28import yaml29import yaml
2930
30from charmhelpers.contrib.network import ip31from charmhelpers.contrib.network import ip
@@ -34,6 +35,8 @@
34)35)
3536
36from charmhelpers.core.hookenv import (37from charmhelpers.core.hookenv import (
38 action_fail,
39 action_set,
37 config,40 config,
38 log as juju_log,41 log as juju_log,
39 charm_dir,42 charm_dir,
@@ -114,6 +117,7 @@
114 ('2.2.1', 'kilo'),117 ('2.2.1', 'kilo'),
115 ('2.2.2', 'kilo'),118 ('2.2.2', 'kilo'),
116 ('2.3.0', 'liberty'),119 ('2.3.0', 'liberty'),
120 ('2.4.0', 'liberty'),
117])121])
118122
119# >= Liberty version->codename mapping123# >= Liberty version->codename mapping
@@ -142,6 +146,9 @@
142 'glance-common': OrderedDict([146 'glance-common': OrderedDict([
143 ('11.0.0', 'liberty'),147 ('11.0.0', 'liberty'),
144 ]),148 ]),
149 'openstack-dashboard': OrderedDict([
150 ('8.0.0', 'liberty'),
151 ]),
145}152}
146153
147DEFAULT_LOOPBACK_SIZE = '5G'154DEFAULT_LOOPBACK_SIZE = '5G'
@@ -745,3 +752,47 @@
745 return projects[key]752 return projects[key]
746753
747 return None754 return None
755
756
757def do_action_openstack_upgrade(package, upgrade_callback, configs):
758 """Perform action-managed OpenStack upgrade.
759
760 Upgrades packages to the configured openstack-origin version and sets
761 the corresponding action status as a result.
762
763 If the charm was installed from source we cannot upgrade it.
764 For backwards compatibility a config flag (action-managed-upgrade) must
765 be set for this code to run, otherwise a full service level upgrade will
766 fire on config-changed.
767
768 @param package: package name for determining if upgrade available
769 @param upgrade_callback: function callback to charm's upgrade function
770 @param configs: templating object derived from OSConfigRenderer class
771
772 @return: True if upgrade successful; False if upgrade failed or skipped
773 """
774 ret = False
775
776 if git_install_requested():
777 action_set({'outcome': 'installed from source, skipped upgrade.'})
778 else:
779 if openstack_upgrade_available(package):
780 if config('action-managed-upgrade'):
781 juju_log('Upgrading OpenStack release')
782
783 try:
784 upgrade_callback(configs=configs)
785 action_set({'outcome': 'success, upgrade completed.'})
786 ret = True
787 except:
788 action_set({'outcome': 'upgrade failed, see traceback.'})
789 action_set({'traceback': traceback.format_exc()})
790 action_fail('do_openstack_upgrade resulted in an '
791 'unexpected error')
792 else:
793 action_set({'outcome': 'action-managed-upgrade config is '
794 'False, skipped upgrade.'})
795 else:
796 action_set({'outcome': 'no upgrade available.'})
797
798 return ret
748799
=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-09-10 09:30:00 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-09-15 18:42:15 +0000
@@ -59,6 +59,8 @@
59 apt_install,59 apt_install,
60)60)
6161
62from charmhelpers.core.kernel import modprobe
63
62KEYRING = '/etc/ceph/ceph.client.{}.keyring'64KEYRING = '/etc/ceph/ceph.client.{}.keyring'
63KEYFILE = '/etc/ceph/ceph.client.{}.key'65KEYFILE = '/etc/ceph/ceph.client.{}.key'
6466
@@ -291,17 +293,6 @@
291 os.chown(data_src_dst, uid, gid)293 os.chown(data_src_dst, uid, gid)
292294
293295
294# TODO: re-use
295def modprobe(module):
296 """Load a kernel module and configure for auto-load on reboot."""
297 log('Loading kernel module', level=INFO)
298 cmd = ['modprobe', module]
299 check_call(cmd)
300 with open('/etc/modules', 'r+') as modules:
301 if module not in modules.read():
302 modules.write(module)
303
304
305def copy_files(src, dst, symlinks=False, ignore=None):296def copy_files(src, dst, symlinks=False, ignore=None):
306 """Copy files from src to dst."""297 """Copy files from src to dst."""
307 for item in os.listdir(src):298 for item in os.listdir(src):
308299
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-08-19 13:51:56 +0000
+++ hooks/charmhelpers/core/host.py 2015-09-15 18:42:15 +0000
@@ -63,32 +63,48 @@
63 return service_result63 return service_result
6464
6565
66def service_pause(service_name, init_dir=None):66def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
67 """Pause a system service.67 """Pause a system service.
6868
69 Stop it, and prevent it from starting again at boot."""69 Stop it, and prevent it from starting again at boot."""
70 if init_dir is None:
71 init_dir = "/etc/init"
72 stopped = service_stop(service_name)70 stopped = service_stop(service_name)
73 # XXX: Support systemd too71 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
74 override_path = os.path.join(72 sysv_file = os.path.join(initd_dir, service_name)
75 init_dir, '{}.override'.format(service_name))73 if os.path.exists(upstart_file):
76 with open(override_path, 'w') as fh:74 override_path = os.path.join(
77 fh.write("manual\n")75 init_dir, '{}.override'.format(service_name))
76 with open(override_path, 'w') as fh:
77 fh.write("manual\n")
78 elif os.path.exists(sysv_file):
79 subprocess.check_call(["update-rc.d", service_name, "disable"])
80 else:
81 # XXX: Support SystemD too
82 raise ValueError(
83 "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
84 service_name, upstart_file, sysv_file))
78 return stopped85 return stopped
7986
8087
81def service_resume(service_name, init_dir=None):88def service_resume(service_name, init_dir="/etc/init",
89 initd_dir="/etc/init.d"):
82 """Resume a system service.90 """Resume a system service.
8391
84 Reenable starting again at boot. Start the service"""92 Reenable starting again at boot. Start the service"""
85 # XXX: Support systemd too93 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
86 if init_dir is None:94 sysv_file = os.path.join(initd_dir, service_name)
87 init_dir = "/etc/init"95 if os.path.exists(upstart_file):
88 override_path = os.path.join(96 override_path = os.path.join(
89 init_dir, '{}.override'.format(service_name))97 init_dir, '{}.override'.format(service_name))
90 if os.path.exists(override_path):98 if os.path.exists(override_path):
91 os.unlink(override_path)99 os.unlink(override_path)
100 elif os.path.exists(sysv_file):
101 subprocess.check_call(["update-rc.d", service_name, "enable"])
102 else:
103 # XXX: Support SystemD too
104 raise ValueError(
105 "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
106 service_name, upstart_file, sysv_file))
107
92 started = service_start(service_name)108 started = service_start(service_name)
93 return started109 return started
94110
95111
=== added file 'hooks/charmhelpers/core/kernel.py'
--- hooks/charmhelpers/core/kernel.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/kernel.py 2015-09-15 18:42:15 +0000
@@ -0,0 +1,68 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Copyright 2014-2015 Canonical Limited.
5#
6# This file is part of charm-helpers.
7#
8# charm-helpers is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#
12# charm-helpers is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19
20__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
21
22from charmhelpers.core.hookenv import (
23 log,
24 INFO
25)
26
27from subprocess import check_call, check_output
28import re
29
30
31def modprobe(module, persist=True):
32 """Load a kernel module and configure for auto-load on reboot."""
33 cmd = ['modprobe', module]
34
35 log('Loading kernel module %s' % module, level=INFO)
36
37 check_call(cmd)
38 if persist:
39 with open('/etc/modules', 'r+') as modules:
40 if module not in modules.read():
41 modules.write(module)
42
43
44def rmmod(module, force=False):
45 """Remove a module from the linux kernel"""
46 cmd = ['rmmod']
47 if force:
48 cmd.append('-f')
49 cmd.append(module)
50 log('Removing kernel module %s' % module, level=INFO)
51 return check_call(cmd)
52
53
54def lsmod():
55 """Shows what kernel modules are currently loaded"""
56 return check_output(['lsmod'],
57 universal_newlines=True)
58
59
60def is_module_loaded(module):
61 """Checks if a kernel module is already loaded"""
62 matches = re.findall('^%s[ ]+' % module, lsmod(), re.M)
63 return len(matches) > 0
64
65
66def update_initramfs(version='all'):
67 """Updates an initramfs image"""
68 return check_call(["update-initramfs", "-k", version, "-u"])
069
=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
--- tests/charmhelpers/contrib/amulet/utils.py 2015-09-10 09:30:00 +0000
+++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-15 18:42:15 +0000
@@ -776,3 +776,12 @@
776 output = _check_output(command, universal_newlines=True)776 output = _check_output(command, universal_newlines=True)
777 data = json.loads(output)777 data = json.loads(output)
778 return data.get(u"status") == "completed"778 return data.get(u"status") == "completed"
779
780 def status_get(self, unit):
781 """Return the current service status of this unit."""
782 raw_status, return_code = unit.run(
783 "status-get --format=json --include-data")
784 if return_code != 0:
785 return ("unknown", "")
786 status = json.loads(raw_status)
787 return (status["status"], status["message"])
779788
=== modified file 'unit_tests/test_actions_openstack_upgrade.py'
--- unit_tests/test_actions_openstack_upgrade.py 2015-09-01 15:13:23 +0000
+++ unit_tests/test_actions_openstack_upgrade.py 2015-09-15 18:42:15 +0000
@@ -11,8 +11,9 @@
11)11)
1212
13TO_PATCH = [13TO_PATCH = [
14 'config',14 'config_changed',
15 'juju_log',15 'do_openstack_upgrade',
16 'register_configs',
16 'relation_set',17 'relation_set',
17 'relation_ids',18 'relation_ids',
18 'uuid'19 'uuid'
@@ -24,103 +25,40 @@
24 def setUp(self):25 def setUp(self):
25 super(TestCinderUpgradeActions, self).setUp(openstack_upgrade,26 super(TestCinderUpgradeActions, self).setUp(openstack_upgrade,
26 TO_PATCH)27 TO_PATCH)
27 self.config.side_effect = self.test_config.get
2828
29 @patch.object(openstack_upgrade, 'action_set')
30 @patch.object(openstack_upgrade, 'action_fail')
31 @patch.object(openstack_upgrade, 'do_openstack_upgrade')
32 @patch.object(openstack_upgrade, 'openstack_upgrade_available')
33 @patch.object(openstack_upgrade, 'config_changed')
34 @patch('charmhelpers.contrib.openstack.utils.config')29 @patch('charmhelpers.contrib.openstack.utils.config')
35 def test_openstack_upgrade(self, _config, config_changed,30 @patch('charmhelpers.contrib.openstack.utils.action_set')
36 openstack_upgrade_available,31 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')
37 do_openstack_upgrade, action_fail,32 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
38 action_set):33 def test_openstack_upgrade_true(self, upgrade_avail, git_requested,
39 _config.return_value = None34 action_set, config):
40 openstack_upgrade_available.return_value = True35 git_requested.return_value = False
36 upgrade_avail.return_value = True
37 config.return_value = True
41 self.relation_ids.return_value = ['relid1']38 self.relation_ids.return_value = ['relid1']
42 self.uuid.uuid4.return_value = 1234539 self.uuid.uuid4.return_value = 12345
4340
44 self.test_config.set('action-managed-upgrade', True)
45
46 openstack_upgrade.openstack_upgrade()41 openstack_upgrade.openstack_upgrade()
4742
48 self.assertTrue(do_openstack_upgrade.called)43 self.assertTrue(self.do_openstack_upgrade.called)
49 self.assertTrue(config_changed.called)
50 self.assertTrue(self.relation_ids.called)44 self.assertTrue(self.relation_ids.called)
51 self.relation_set.assert_called_with(relation_id='relid1',45 self.relation_set.assert_called_with(relation_id='relid1',
52 upgrade_nonce=12345)46 upgrade_nonce=12345)
53 self.assertFalse(action_fail.called)47 self.assertTrue(self.config_changed.called)
5448
55 @patch.object(openstack_upgrade, 'action_set')49 @patch('charmhelpers.contrib.openstack.utils.config')
56 @patch.object(openstack_upgrade, 'do_openstack_upgrade')50 @patch('charmhelpers.contrib.openstack.utils.action_set')
57 @patch.object(openstack_upgrade, 'openstack_upgrade_available')51 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')
58 @patch.object(openstack_upgrade, 'config_changed')52 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
59 @patch('charmhelpers.contrib.openstack.utils.config')53 def test_openstack_upgrade_false(self, upgrade_avail, git_requested,
60 def test_openstack_upgrade_not_configured(self, _config, config_changed,54 action_set, config):
61 openstack_upgrade_available,55 git_requested.return_value = False
62 do_openstack_upgrade,56 upgrade_avail.return_value = True
63 action_set):57 config.return_value = False
64 _config.return_value = None58
65 openstack_upgrade_available.return_value = True59 openstack_upgrade.openstack_upgrade()
6660
67 openstack_upgrade.openstack_upgrade()61 self.assertFalse(self.do_openstack_upgrade.called)
6862 self.assertFalse(self.relation_ids.called)
69 msg = ('action-managed-upgrade config is False, skipped upgrade.')63 self.assertFalse(self.relation_set.called)
7064 self.assertFalse(self.config_changed.called)
71 action_set.assert_called_with({'outcome': msg})
72 self.assertFalse(do_openstack_upgrade.called)
73
74 @patch.object(openstack_upgrade, 'action_set')
75 @patch.object(openstack_upgrade, 'do_openstack_upgrade')
76 @patch.object(openstack_upgrade, 'openstack_upgrade_available')
77 @patch.object(openstack_upgrade, 'config_changed')
78 @patch('charmhelpers.contrib.openstack.utils.config')
79 def test_openstack_upgrade_git_install(self, _config, config_changed,
80 openstack_upgrade_available,
81 do_openstack_upgrade,
82 action_set):
83
84 self.test_config.set('action-managed-upgrade', True)
85 self.test_config.set('openstack-origin-git', True)
86
87 openstack_upgrade.openstack_upgrade()
88
89 msg = ('installed from source, skipped upgrade.')
90 action_set.assert_called_with({'outcome': msg})
91 self.assertFalse(do_openstack_upgrade.called)
92
93 @patch.object(openstack_upgrade, 'action_set')
94 @patch.object(openstack_upgrade, 'action_fail')
95 @patch.object(openstack_upgrade, 'do_openstack_upgrade')
96 @patch.object(openstack_upgrade, 'openstack_upgrade_available')
97 @patch.object(openstack_upgrade, 'config_changed')
98 @patch('traceback.format_exc')
99 @patch('charmhelpers.contrib.openstack.utils.config')
100 def test_openstack_upgrade_exception(self, _config, format_exc,
101 config_changed,
102 openstack_upgrade_available,
103 do_openstack_upgrade,
104 action_fail, action_set):
105 _config.return_value = None
106 self.test_config.set('action-managed-upgrade', True)
107 openstack_upgrade_available.return_value = True
108
109 e = OSError('something bad happened')
110 do_openstack_upgrade.side_effect = e
111 traceback = (
112 "Traceback (most recent call last):\n"
113 " File \"actions/openstack_upgrade.py\", line 37, in openstack_upgrade\n" # noqa
114 " openstack_upgrade(config(\'openstack-origin-git\'))\n"
115 " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 964, in __call__\n" # noqa
116 " return _mock_self._mock_call(*args, **kwargs)\n"
117 " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 1019, in _mock_call\n" # noqa
118 " raise effect\n"
119 "OSError: something bad happened\n")
120 format_exc.return_value = traceback
121
122 openstack_upgrade.openstack_upgrade()
123
124 msg = 'do_openstack_upgrade resulted in an unexpected error'
125 action_fail.assert_called_with(msg)
126 action_set.assert_called_with({'traceback': traceback})

Subscribers

People subscribed via source and target branches