Merge lp:~hopem/charms/trusty/glance/lp1499643 into lp:~openstack-charmers-archive/charms/trusty/glance/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 143
Proposed branch: lp:~hopem/charms/trusty/glance/lp1499643
Merge into: lp:~openstack-charmers-archive/charms/trusty/glance/next
Diff against target: 1408 lines (+874/-79)
14 files modified
charmhelpers/contrib/network/ip.py (+5/-3)
charmhelpers/contrib/openstack/amulet/deployment.py (+23/-9)
charmhelpers/contrib/openstack/amulet/utils.py (+359/-0)
charmhelpers/contrib/openstack/context.py (+52/-7)
charmhelpers/contrib/openstack/templating.py (+30/-2)
charmhelpers/contrib/openstack/utils.py (+232/-2)
charmhelpers/contrib/storage/linux/ceph.py (+2/-11)
charmhelpers/core/hookenv.py (+32/-0)
charmhelpers/core/host.py (+32/-16)
charmhelpers/core/hugepage.py (+8/-1)
charmhelpers/core/strutils.py (+30/-0)
tests/charmhelpers/contrib/amulet/deployment.py (+4/-2)
tests/charmhelpers/contrib/amulet/utils.py (+56/-16)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+9/-10)
To merge this branch: bzr merge lp:~hopem/charms/trusty/glance/lp1499643
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+272409@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 #10762 glance-next for hopem mp272409
    LINT OK: passed

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

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

charm_unit_test #9942 glance-next for hopem mp272409
    UNIT FAIL: unit-test failed

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

Full unit test output: http://paste.ubuntu.com/12555285/
Build: http://10.245.162.77:8080/job/charm_unit_test/9942/

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

charm_amulet_test #6771 glance-next for hopem mp272409
    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/12555788/
Build: http://10.245.162.77:8080/job/charm_amulet_test/6771/

Revision history for this message
Liam Young (gnuoy) wrote :

Unrelated Amulet fail. I'll tweak the unit test and merge time. Approved.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmhelpers/contrib/network/ip.py'
--- charmhelpers/contrib/network/ip.py 2015-09-03 09:41:01 +0000
+++ charmhelpers/contrib/network/ip.py 2015-09-25 14:42:27 +0000
@@ -23,7 +23,7 @@
23from functools import partial23from functools import partial
2424
25from charmhelpers.core.hookenv import unit_get25from charmhelpers.core.hookenv import unit_get
26from charmhelpers.fetch import apt_install26from charmhelpers.fetch import apt_install, apt_update
27from charmhelpers.core.hookenv import (27from charmhelpers.core.hookenv import (
28 log,28 log,
29 WARNING,29 WARNING,
@@ -32,13 +32,15 @@
32try:32try:
33 import netifaces33 import netifaces
34except ImportError:34except ImportError:
35 apt_install('python-netifaces')35 apt_update(fatal=True)
36 apt_install('python-netifaces', fatal=True)
36 import netifaces37 import netifaces
3738
38try:39try:
39 import netaddr40 import netaddr
40except ImportError:41except ImportError:
41 apt_install('python-netaddr')42 apt_update(fatal=True)
43 apt_install('python-netaddr', fatal=True)
42 import netaddr44 import netaddr
4345
4446
4547
=== modified file 'charmhelpers/contrib/openstack/amulet/deployment.py'
--- charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-18 17:34:34 +0000
+++ charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-25 14:42:27 +0000
@@ -44,20 +44,31 @@
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:
52 base_series = self.current_next59 base_series = self.current_next
5360
54 if self.stable:61 for svc in other_services:
55 for svc in other_services:62 if svc['name'] in force_series_current:
63 base_series = self.current_next
64 # If a location has been explicitly set, use it
65 if svc.get('location'):
66 continue
67 if self.stable:
56 temp = 'lp:charms/{}/{}'68 temp = 'lp:charms/{}/{}'
57 svc['location'] = temp.format(base_series,69 svc['location'] = temp.format(base_series,
58 svc['name'])70 svc['name'])
59 else:71 else:
60 for svc in other_services:
61 if svc['name'] in base_charms:72 if svc['name'] in base_charms:
62 temp = 'lp:charms/{}/{}'73 temp = 'lp:charms/{}/{}'
63 svc['location'] = temp.format(base_series,74 svc['location'] = temp.format(base_series,
@@ -66,6 +77,7 @@
66 temp = 'lp:~openstack-charmers/charms/{}/{}/next'77 temp = 'lp:~openstack-charmers/charms/{}/{}/next'
67 svc['location'] = temp.format(self.current_next,78 svc['location'] = temp.format(self.current_next,
68 svc['name'])79 svc['name'])
80
69 return other_services81 return other_services
7082
71 def _add_services(self, this_service, other_services):83 def _add_services(self, this_service, other_services):
@@ -77,21 +89,23 @@
7789
78 services = other_services90 services = other_services
79 services.append(this_service)91 services.append(this_service)
92
93 # Charms which should use the source config option
80 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',94 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
81 'ceph-osd', 'ceph-radosgw']95 'ceph-osd', 'ceph-radosgw']
82 # Most OpenStack subordinate charms do not expose an origin option96
83 # as that is controlled by the principle.97 # Charms which can not use openstack-origin, ie. many subordinates
84 ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']98 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']
8599
86 if self.openstack:100 if self.openstack:
87 for svc in services:101 for svc in services:
88 if svc['name'] not in use_source + ignore:102 if svc['name'] not in use_source + no_origin:
89 config = {'openstack-origin': self.openstack}103 config = {'openstack-origin': self.openstack}
90 self.d.configure(svc['name'], config)104 self.d.configure(svc['name'], config)
91105
92 if self.source:106 if self.source:
93 for svc in services:107 for svc in services:
94 if svc['name'] in use_source and svc['name'] not in ignore:108 if svc['name'] in use_source and svc['name'] not in no_origin:
95 config = {'source': self.source}109 config = {'source': self.source}
96 self.d.configure(svc['name'], config)110 self.d.configure(svc['name'], config)
97111
98112
=== modified file 'charmhelpers/contrib/openstack/amulet/utils.py'
--- charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:17:23 +0000
+++ charmhelpers/contrib/openstack/amulet/utils.py 2015-09-25 14:42:27 +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 'charmhelpers/contrib/openstack/context.py'
--- charmhelpers/contrib/openstack/context.py 2015-09-12 06:27:17 +0000
+++ charmhelpers/contrib/openstack/context.py 2015-09-25 14:42:27 +0000
@@ -194,10 +194,50 @@
194class OSContextGenerator(object):194class OSContextGenerator(object):
195 """Base class for all context generators."""195 """Base class for all context generators."""
196 interfaces = []196 interfaces = []
197 related = False
198 complete = False
199 missing_data = []
197200
198 def __call__(self):201 def __call__(self):
199 raise NotImplementedError202 raise NotImplementedError
200203
204 def context_complete(self, ctxt):
205 """Check for missing data for the required context data.
206 Set self.missing_data if it exists and return False.
207 Set self.complete if no missing data and return True.
208 """
209 # Fresh start
210 self.complete = False
211 self.missing_data = []
212 for k, v in six.iteritems(ctxt):
213 if v is None or v == '':
214 if k not in self.missing_data:
215 self.missing_data.append(k)
216
217 if self.missing_data:
218 self.complete = False
219 log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO)
220 else:
221 self.complete = True
222 return self.complete
223
224 def get_related(self):
225 """Check if any of the context interfaces have relation ids.
226 Set self.related and return True if one of the interfaces
227 has relation ids.
228 """
229 # Fresh start
230 self.related = False
231 try:
232 for interface in self.interfaces:
233 if relation_ids(interface):
234 self.related = True
235 return self.related
236 except AttributeError as e:
237 log("{} {}"
238 "".format(self, e), 'INFO')
239 return self.related
240
201241
202class SharedDBContext(OSContextGenerator):242class SharedDBContext(OSContextGenerator):
203 interfaces = ['shared-db']243 interfaces = ['shared-db']
@@ -213,6 +253,7 @@
213 self.database = database253 self.database = database
214 self.user = user254 self.user = user
215 self.ssl_dir = ssl_dir255 self.ssl_dir = ssl_dir
256 self.rel_name = self.interfaces[0]
216257
217 def __call__(self):258 def __call__(self):
218 self.database = self.database or config('database')259 self.database = self.database or config('database')
@@ -246,6 +287,7 @@
246 password_setting = self.relation_prefix + '_password'287 password_setting = self.relation_prefix + '_password'
247288
248 for rid in relation_ids(self.interfaces[0]):289 for rid in relation_ids(self.interfaces[0]):
290 self.related = True
249 for unit in related_units(rid):291 for unit in related_units(rid):
250 rdata = relation_get(rid=rid, unit=unit)292 rdata = relation_get(rid=rid, unit=unit)
251 host = rdata.get('db_host')293 host = rdata.get('db_host')
@@ -257,7 +299,7 @@
257 'database_password': rdata.get(password_setting),299 'database_password': rdata.get(password_setting),
258 'database_type': 'mysql'300 'database_type': 'mysql'
259 }301 }
260 if context_complete(ctxt):302 if self.context_complete(ctxt):
261 db_ssl(rdata, ctxt, self.ssl_dir)303 db_ssl(rdata, ctxt, self.ssl_dir)
262 return ctxt304 return ctxt
263 return {}305 return {}
@@ -278,6 +320,7 @@
278320
279 ctxt = {}321 ctxt = {}
280 for rid in relation_ids(self.interfaces[0]):322 for rid in relation_ids(self.interfaces[0]):
323 self.related = True
281 for unit in related_units(rid):324 for unit in related_units(rid):
282 rel_host = relation_get('host', rid=rid, unit=unit)325 rel_host = relation_get('host', rid=rid, unit=unit)
283 rel_user = relation_get('user', rid=rid, unit=unit)326 rel_user = relation_get('user', rid=rid, unit=unit)
@@ -287,7 +330,7 @@
287 'database_user': rel_user,330 'database_user': rel_user,
288 'database_password': rel_passwd,331 'database_password': rel_passwd,
289 'database_type': 'postgresql'}332 'database_type': 'postgresql'}
290 if context_complete(ctxt):333 if self.context_complete(ctxt):
291 return ctxt334 return ctxt
292335
293 return {}336 return {}
@@ -348,6 +391,7 @@
348 ctxt['signing_dir'] = cachedir391 ctxt['signing_dir'] = cachedir
349392
350 for rid in relation_ids(self.rel_name):393 for rid in relation_ids(self.rel_name):
394 self.related = True
351 for unit in related_units(rid):395 for unit in related_units(rid):
352 rdata = relation_get(rid=rid, unit=unit)396 rdata = relation_get(rid=rid, unit=unit)
353 serv_host = rdata.get('service_host')397 serv_host = rdata.get('service_host')
@@ -366,7 +410,7 @@
366 'service_protocol': svc_protocol,410 'service_protocol': svc_protocol,
367 'auth_protocol': auth_protocol})411 'auth_protocol': auth_protocol})
368412
369 if context_complete(ctxt):413 if self.context_complete(ctxt):
370 # NOTE(jamespage) this is required for >= icehouse414 # NOTE(jamespage) this is required for >= icehouse
371 # so a missing value just indicates keystone needs415 # so a missing value just indicates keystone needs
372 # upgrading416 # upgrading
@@ -405,6 +449,7 @@
405 ctxt = {}449 ctxt = {}
406 for rid in relation_ids(self.rel_name):450 for rid in relation_ids(self.rel_name):
407 ha_vip_only = False451 ha_vip_only = False
452 self.related = True
408 for unit in related_units(rid):453 for unit in related_units(rid):
409 if relation_get('clustered', rid=rid, unit=unit):454 if relation_get('clustered', rid=rid, unit=unit):
410 ctxt['clustered'] = True455 ctxt['clustered'] = True
@@ -437,7 +482,7 @@
437 ha_vip_only = relation_get('ha-vip-only',482 ha_vip_only = relation_get('ha-vip-only',
438 rid=rid, unit=unit) is not None483 rid=rid, unit=unit) is not None
439484
440 if context_complete(ctxt):485 if self.context_complete(ctxt):
441 if 'rabbit_ssl_ca' in ctxt:486 if 'rabbit_ssl_ca' in ctxt:
442 if not self.ssl_dir:487 if not self.ssl_dir:
443 log("Charm not setup for ssl support but ssl ca "488 log("Charm not setup for ssl support but ssl ca "
@@ -469,7 +514,7 @@
469 ctxt['oslo_messaging_flags'] = config_flags_parser(514 ctxt['oslo_messaging_flags'] = config_flags_parser(
470 oslo_messaging_flags)515 oslo_messaging_flags)
471516
472 if not context_complete(ctxt):517 if not self.complete:
473 return {}518 return {}
474519
475 return ctxt520 return ctxt
@@ -507,7 +552,7 @@
507 if not os.path.isdir('/etc/ceph'):552 if not os.path.isdir('/etc/ceph'):
508 os.mkdir('/etc/ceph')553 os.mkdir('/etc/ceph')
509554
510 if not context_complete(ctxt):555 if not self.context_complete(ctxt):
511 return {}556 return {}
512557
513 ensure_packages(['ceph-common'])558 ensure_packages(['ceph-common'])
@@ -1366,6 +1411,6 @@
1366 'auth_protocol':1411 'auth_protocol':
1367 rdata.get('auth_protocol') or 'http',1412 rdata.get('auth_protocol') or 'http',
1368 }1413 }
1369 if context_complete(ctxt):1414 if self.context_complete(ctxt):
1370 return ctxt1415 return ctxt
1371 return {}1416 return {}
13721417
=== modified file 'charmhelpers/contrib/openstack/templating.py'
--- charmhelpers/contrib/openstack/templating.py 2015-07-29 10:47:33 +0000
+++ charmhelpers/contrib/openstack/templating.py 2015-09-25 14:42:27 +0000
@@ -18,7 +18,7 @@
1818
19import six19import six
2020
21from charmhelpers.fetch import apt_install21from charmhelpers.fetch import apt_install, apt_update
22from charmhelpers.core.hookenv import (22from charmhelpers.core.hookenv import (
23 log,23 log,
24 ERROR,24 ERROR,
@@ -29,6 +29,7 @@
29try:29try:
30 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions30 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
31except ImportError:31except ImportError:
32 apt_update(fatal=True)
32 apt_install('python-jinja2', fatal=True)33 apt_install('python-jinja2', fatal=True)
33 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions34 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
3435
@@ -112,7 +113,7 @@
112113
113 def complete_contexts(self):114 def complete_contexts(self):
114 '''115 '''
115 Return a list of interfaces that have atisfied contexts.116 Return a list of interfaces that have satisfied contexts.
116 '''117 '''
117 if self._complete_contexts:118 if self._complete_contexts:
118 return self._complete_contexts119 return self._complete_contexts
@@ -293,3 +294,30 @@
293 [interfaces.extend(i.complete_contexts())294 [interfaces.extend(i.complete_contexts())
294 for i in six.itervalues(self.templates)]295 for i in six.itervalues(self.templates)]
295 return interfaces296 return interfaces
297
298 def get_incomplete_context_data(self, interfaces):
299 '''
300 Return dictionary of relation status of interfaces and any missing
301 required context data. Example:
302 {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True},
303 'zeromq-configuration': {'related': False}}
304 '''
305 incomplete_context_data = {}
306
307 for i in six.itervalues(self.templates):
308 for context in i.contexts:
309 for interface in interfaces:
310 related = False
311 if interface in context.interfaces:
312 related = context.get_related()
313 missing_data = context.missing_data
314 if missing_data:
315 incomplete_context_data[interface] = {'missing_data': missing_data}
316 if related:
317 if incomplete_context_data.get(interface):
318 incomplete_context_data[interface].update({'related': True})
319 else:
320 incomplete_context_data[interface] = {'related': True}
321 else:
322 incomplete_context_data[interface] = {'related': False}
323 return incomplete_context_data
296324
=== modified file 'charmhelpers/contrib/openstack/utils.py'
--- charmhelpers/contrib/openstack/utils.py 2015-09-03 09:41:01 +0000
+++ charmhelpers/contrib/openstack/utils.py 2015-09-25 14:42:27 +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,12 +35,16 @@
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,
40 INFO,43 INFO,
41 relation_ids,44 relation_ids,
42 relation_set45 relation_set,
46 status_set,
47 hook_name
43)48)
4449
45from charmhelpers.contrib.storage.linux.lvm import (50from charmhelpers.contrib.storage.linux.lvm import (
@@ -49,7 +54,8 @@
49)54)
5055
51from charmhelpers.contrib.network.ip import (56from charmhelpers.contrib.network.ip import (
52 get_ipv6_addr57 get_ipv6_addr,
58 is_ipv6,
53)59)
5460
55from charmhelpers.contrib.python.packages import (61from charmhelpers.contrib.python.packages import (
@@ -114,6 +120,7 @@
114 ('2.2.1', 'kilo'),120 ('2.2.1', 'kilo'),
115 ('2.2.2', 'kilo'),121 ('2.2.2', 'kilo'),
116 ('2.3.0', 'liberty'),122 ('2.3.0', 'liberty'),
123 ('2.4.0', 'liberty'),
117])124])
118125
119# >= Liberty version->codename mapping126# >= Liberty version->codename mapping
@@ -142,6 +149,9 @@
142 'glance-common': OrderedDict([149 'glance-common': OrderedDict([
143 ('11.0.0', 'liberty'),150 ('11.0.0', 'liberty'),
144 ]),151 ]),
152 'openstack-dashboard': OrderedDict([
153 ('8.0.0', 'liberty'),
154 ]),
145}155}
146156
147DEFAULT_LOOPBACK_SIZE = '5G'157DEFAULT_LOOPBACK_SIZE = '5G'
@@ -510,6 +520,12 @@
510 relation_prefix=None):520 relation_prefix=None):
511 hosts = get_ipv6_addr(dynamic_only=False)521 hosts = get_ipv6_addr(dynamic_only=False)
512522
523 if config('vip'):
524 vips = config('vip').split()
525 for vip in vips:
526 if vip and is_ipv6(vip):
527 hosts.append(vip)
528
513 kwargs = {'database': database,529 kwargs = {'database': database,
514 'username': database_user,530 'username': database_user,
515 'hostname': json.dumps(hosts)}531 'hostname': json.dumps(hosts)}
@@ -745,3 +761,217 @@
745 return projects[key]761 return projects[key]
746762
747 return None763 return None
764
765
766def os_workload_status(configs, required_interfaces, charm_func=None):
767 """
768 Decorator to set workload status based on complete contexts
769 """
770 def wrap(f):
771 @wraps(f)
772 def wrapped_f(*args, **kwargs):
773 # Run the original function first
774 f(*args, **kwargs)
775 # Set workload status now that contexts have been
776 # acted on
777 set_os_workload_status(configs, required_interfaces, charm_func)
778 return wrapped_f
779 return wrap
780
781
782def set_os_workload_status(configs, required_interfaces, charm_func=None):
783 """
784 Set workload status based on complete contexts.
785 status-set missing or incomplete contexts
786 and juju-log details of missing required data.
787 charm_func is a charm specific function to run checking
788 for charm specific requirements such as a VIP setting.
789 """
790 incomplete_rel_data = incomplete_relation_data(configs, required_interfaces)
791 state = 'active'
792 missing_relations = []
793 incomplete_relations = []
794 message = None
795 charm_state = None
796 charm_message = None
797
798 for generic_interface in incomplete_rel_data.keys():
799 related_interface = None
800 missing_data = {}
801 # Related or not?
802 for interface in incomplete_rel_data[generic_interface]:
803 if incomplete_rel_data[generic_interface][interface].get('related'):
804 related_interface = interface
805 missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data')
806 # No relation ID for the generic_interface
807 if not related_interface:
808 juju_log("{} relation is missing and must be related for "
809 "functionality. ".format(generic_interface), 'WARN')
810 state = 'blocked'
811 if generic_interface not in missing_relations:
812 missing_relations.append(generic_interface)
813 else:
814 # Relation ID exists but no related unit
815 if not missing_data:
816 # Edge case relation ID exists but departing
817 if ('departed' in hook_name() or 'broken' in hook_name()) \
818 and related_interface in hook_name():
819 state = 'blocked'
820 if generic_interface not in missing_relations:
821 missing_relations.append(generic_interface)
822 juju_log("{} relation's interface, {}, "
823 "relationship is departed or broken "
824 "and is required for functionality."
825 "".format(generic_interface, related_interface), "WARN")
826 # Normal case relation ID exists but no related unit
827 # (joining)
828 else:
829 juju_log("{} relations's interface, {}, is related but has "
830 "no units in the relation."
831 "".format(generic_interface, related_interface), "INFO")
832 # Related unit exists and data missing on the relation
833 else:
834 juju_log("{} relation's interface, {}, is related awaiting "
835 "the following data from the relationship: {}. "
836 "".format(generic_interface, related_interface,
837 ", ".join(missing_data)), "INFO")
838 if state != 'blocked':
839 state = 'waiting'
840 if generic_interface not in incomplete_relations \
841 and generic_interface not in missing_relations:
842 incomplete_relations.append(generic_interface)
843
844 if missing_relations:
845 message = "Missing relations: {}".format(", ".join(missing_relations))
846 if incomplete_relations:
847 message += "; incomplete relations: {}" \
848 "".format(", ".join(incomplete_relations))
849 state = 'blocked'
850 elif incomplete_relations:
851 message = "Incomplete relations: {}" \
852 "".format(", ".join(incomplete_relations))
853 state = 'waiting'
854
855 # Run charm specific checks
856 if charm_func:
857 charm_state, charm_message = charm_func(configs)
858 if charm_state != 'active' and charm_state != 'unknown':
859 state = workload_state_compare(state, charm_state)
860 if message:
861 message = "{} {}".format(message, charm_message)
862 else:
863 message = charm_message
864
865 # Set to active if all requirements have been met
866 if state == 'active':
867 message = "Unit is ready"
868 juju_log(message, "INFO")
869
870 status_set(state, message)
871
872
873def workload_state_compare(current_workload_state, workload_state):
874 """ Return highest priority of two states"""
875 hierarchy = {'unknown': -1,
876 'active': 0,
877 'maintenance': 1,
878 'waiting': 2,
879 'blocked': 3,
880 }
881
882 if hierarchy.get(workload_state) is None:
883 workload_state = 'unknown'
884 if hierarchy.get(current_workload_state) is None:
885 current_workload_state = 'unknown'
886
887 # Set workload_state based on hierarchy of statuses
888 if hierarchy.get(current_workload_state) > hierarchy.get(workload_state):
889 return current_workload_state
890 else:
891 return workload_state
892
893
894def incomplete_relation_data(configs, required_interfaces):
895 """
896 Check complete contexts against required_interfaces
897 Return dictionary of incomplete relation data.
898
899 configs is an OSConfigRenderer object with configs registered
900
901 required_interfaces is a dictionary of required general interfaces
902 with dictionary values of possible specific interfaces.
903 Example:
904 required_interfaces = {'database': ['shared-db', 'pgsql-db']}
905
906 The interface is said to be satisfied if anyone of the interfaces in the
907 list has a complete context.
908
909 Return dictionary of incomplete or missing required contexts with relation
910 status of interfaces and any missing data points. Example:
911 {'message':
912 {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True},
913 'zeromq-configuration': {'related': False}},
914 'identity':
915 {'identity-service': {'related': False}},
916 'database':
917 {'pgsql-db': {'related': False},
918 'shared-db': {'related': True}}}
919 """
920 complete_ctxts = configs.complete_contexts()
921 incomplete_relations = []
922 for svc_type in required_interfaces.keys():
923 # Avoid duplicates
924 found_ctxt = False
925 for interface in required_interfaces[svc_type]:
926 if interface in complete_ctxts:
927 found_ctxt = True
928 if not found_ctxt:
929 incomplete_relations.append(svc_type)
930 incomplete_context_data = {}
931 for i in incomplete_relations:
932 incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i])
933 return incomplete_context_data
934
935
936def do_action_openstack_upgrade(package, upgrade_callback, configs):
937 """Perform action-managed OpenStack upgrade.
938
939 Upgrades packages to the configured openstack-origin version and sets
940 the corresponding action status as a result.
941
942 If the charm was installed from source we cannot upgrade it.
943 For backwards compatibility a config flag (action-managed-upgrade) must
944 be set for this code to run, otherwise a full service level upgrade will
945 fire on config-changed.
946
947 @param package: package name for determining if upgrade available
948 @param upgrade_callback: function callback to charm's upgrade function
949 @param configs: templating object derived from OSConfigRenderer class
950
951 @return: True if upgrade successful; False if upgrade failed or skipped
952 """
953 ret = False
954
955 if git_install_requested():
956 action_set({'outcome': 'installed from source, skipped upgrade.'})
957 else:
958 if openstack_upgrade_available(package):
959 if config('action-managed-upgrade'):
960 juju_log('Upgrading OpenStack release')
961
962 try:
963 upgrade_callback(configs=configs)
964 action_set({'outcome': 'success, upgrade completed.'})
965 ret = True
966 except:
967 action_set({'outcome': 'upgrade failed, see traceback.'})
968 action_set({'traceback': traceback.format_exc()})
969 action_fail('do_openstack_upgrade resulted in an '
970 'unexpected error')
971 else:
972 action_set({'outcome': 'action-managed-upgrade config is '
973 'False, skipped upgrade.'})
974 else:
975 action_set({'outcome': 'no upgrade available.'})
976
977 return ret
748978
=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
--- charmhelpers/contrib/storage/linux/ceph.py 2015-09-10 09:30:59 +0000
+++ charmhelpers/contrib/storage/linux/ceph.py 2015-09-25 14:42:27 +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 'charmhelpers/core/hookenv.py'
--- charmhelpers/core/hookenv.py 2015-09-03 09:41:01 +0000
+++ charmhelpers/core/hookenv.py 2015-09-25 14:42:27 +0000
@@ -623,6 +623,38 @@
623 return unit_get('private-address')623 return unit_get('private-address')
624624
625625
626@cached
627def storage_get(attribute="", storage_id=""):
628 """Get storage attributes"""
629 _args = ['storage-get', '--format=json']
630 if storage_id:
631 _args.extend(('-s', storage_id))
632 if attribute:
633 _args.append(attribute)
634 try:
635 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
636 except ValueError:
637 return None
638
639
640@cached
641def storage_list(storage_name=""):
642 """List the storage IDs for the unit"""
643 _args = ['storage-list', '--format=json']
644 if storage_name:
645 _args.append(storage_name)
646 try:
647 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
648 except ValueError:
649 return None
650 except OSError as e:
651 import errno
652 if e.errno == errno.ENOENT:
653 # storage-list does not exist
654 return []
655 raise
656
657
626class UnregisteredHookError(Exception):658class UnregisteredHookError(Exception):
627 """Raised when an undefined hook is called"""659 """Raised when an undefined hook is called"""
628 pass660 pass
629661
=== modified file 'charmhelpers/core/host.py'
--- charmhelpers/core/host.py 2015-08-19 13:49:22 +0000
+++ charmhelpers/core/host.py 2015-09-25 14:42:27 +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
=== modified file 'charmhelpers/core/hugepage.py'
--- charmhelpers/core/hugepage.py 2015-08-19 13:49:22 +0000
+++ charmhelpers/core/hugepage.py 2015-09-25 14:42:27 +0000
@@ -25,11 +25,13 @@
25 fstab_mount,25 fstab_mount,
26 mkdir,26 mkdir,
27)27)
28from charmhelpers.core.strutils import bytes_from_string
29from subprocess import check_output
2830
2931
30def hugepage_support(user, group='hugetlb', nr_hugepages=256,32def hugepage_support(user, group='hugetlb', nr_hugepages=256,
31 max_map_count=65536, mnt_point='/run/hugepages/kvm',33 max_map_count=65536, mnt_point='/run/hugepages/kvm',
32 pagesize='2MB', mount=True):34 pagesize='2MB', mount=True, set_shmmax=False):
33 """Enable hugepages on system.35 """Enable hugepages on system.
3436
35 Args:37 Args:
@@ -49,6 +51,11 @@
49 'vm.max_map_count': max_map_count,51 'vm.max_map_count': max_map_count,
50 'vm.hugetlb_shm_group': gid,52 'vm.hugetlb_shm_group': gid,
51 }53 }
54 if set_shmmax:
55 shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax']))
56 shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages
57 if shmmax_minsize > shmmax_current:
58 sysctl_settings['kernel.shmmax'] = shmmax_minsize
52 sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')59 sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')
53 mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)60 mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)
54 lfstab = fstab.Fstab()61 lfstab = fstab.Fstab()
5562
=== modified file 'charmhelpers/core/strutils.py'
--- charmhelpers/core/strutils.py 2015-04-16 19:53:49 +0000
+++ charmhelpers/core/strutils.py 2015-09-25 14:42:27 +0000
@@ -18,6 +18,7 @@
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1919
20import six20import six
21import re
2122
2223
23def bool_from_string(value):24def bool_from_string(value):
@@ -40,3 +41,32 @@
4041
41 msg = "Unable to interpret string value '%s' as boolean" % (value)42 msg = "Unable to interpret string value '%s' as boolean" % (value)
42 raise ValueError(msg)43 raise ValueError(msg)
44
45
46def bytes_from_string(value):
47 """Interpret human readable string value as bytes.
48
49 Returns int
50 """
51 BYTE_POWER = {
52 'K': 1,
53 'KB': 1,
54 'M': 2,
55 'MB': 2,
56 'G': 3,
57 'GB': 3,
58 'T': 4,
59 'TB': 4,
60 'P': 5,
61 'PB': 5,
62 }
63 if isinstance(value, six.string_types):
64 value = six.text_type(value)
65 else:
66 msg = "Unable to interpret non-string value '%s' as boolean" % (value)
67 raise ValueError(msg)
68 matches = re.match("([0-9]+)([a-zA-Z]+)", value)
69 if not matches:
70 msg = "Unable to interpret string value '%s' as bytes" % (value)
71 raise ValueError(msg)
72 return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
4373
=== modified file 'tests/charmhelpers/contrib/amulet/deployment.py'
--- tests/charmhelpers/contrib/amulet/deployment.py 2015-03-20 17:15:02 +0000
+++ tests/charmhelpers/contrib/amulet/deployment.py 2015-09-25 14:42:27 +0000
@@ -51,7 +51,8 @@
51 if 'units' not in this_service:51 if 'units' not in this_service:
52 this_service['units'] = 152 this_service['units'] = 1
5353
54 self.d.add(this_service['name'], units=this_service['units'])54 self.d.add(this_service['name'], units=this_service['units'],
55 constraints=this_service.get('constraints'))
5556
56 for svc in other_services:57 for svc in other_services:
57 if 'location' in svc:58 if 'location' in svc:
@@ -64,7 +65,8 @@
64 if 'units' not in svc:65 if 'units' not in svc:
65 svc['units'] = 166 svc['units'] = 1
6667
67 self.d.add(svc['name'], charm=branch_location, units=svc['units'])68 self.d.add(svc['name'], charm=branch_location, units=svc['units'],
69 constraints=svc.get('constraints'))
6870
69 def _add_relations(self, relations):71 def _add_relations(self, relations):
70 """Add all of the relations for the services."""72 """Add all of the relations for the services."""
7173
=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
--- tests/charmhelpers/contrib/amulet/utils.py 2015-09-10 09:30:59 +0000
+++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-25 14:42:27 +0000
@@ -326,7 +326,7 @@
326326
327 def service_restarted_since(self, sentry_unit, mtime, service,327 def service_restarted_since(self, sentry_unit, mtime, service,
328 pgrep_full=None, sleep_time=20,328 pgrep_full=None, sleep_time=20,
329 retry_count=2, retry_sleep_time=30):329 retry_count=30, retry_sleep_time=10):
330 """Check if service was been started after a given time.330 """Check if service was been started after a given time.
331331
332 Args:332 Args:
@@ -334,8 +334,9 @@
334 mtime (float): The epoch time to check against334 mtime (float): The epoch time to check against
335 service (string): service name to look for in process table335 service (string): service name to look for in process table
336 pgrep_full: [Deprecated] Use full command line search mode with pgrep336 pgrep_full: [Deprecated] Use full command line search mode with pgrep
337 sleep_time (int): Seconds to sleep before looking for process337 sleep_time (int): Initial sleep time (s) before looking for file
338 retry_count (int): If service is not found, how many times to retry338 retry_sleep_time (int): Time (s) to sleep between retries
339 retry_count (int): If file is not found, how many times to retry
339340
340 Returns:341 Returns:
341 bool: True if service found and its start time it newer than mtime,342 bool: True if service found and its start time it newer than mtime,
@@ -359,11 +360,12 @@
359 pgrep_full)360 pgrep_full)
360 self.log.debug('Attempt {} to get {} proc start time on {} '361 self.log.debug('Attempt {} to get {} proc start time on {} '
361 'OK'.format(tries, service, unit_name))362 'OK'.format(tries, service, unit_name))
362 except IOError:363 except IOError as e:
363 # NOTE(beisner) - race avoidance, proc may not exist yet.364 # NOTE(beisner) - race avoidance, proc may not exist yet.
364 # https://bugs.launchpad.net/charm-helpers/+bug/1474030365 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
365 self.log.debug('Attempt {} to get {} proc start time on {} '366 self.log.debug('Attempt {} to get {} proc start time on {} '
366 'failed'.format(tries, service, unit_name))367 'failed\n{}'.format(tries, service,
368 unit_name, e))
367 time.sleep(retry_sleep_time)369 time.sleep(retry_sleep_time)
368 tries += 1370 tries += 1
369371
@@ -383,35 +385,62 @@
383 return False385 return False
384386
385 def config_updated_since(self, sentry_unit, filename, mtime,387 def config_updated_since(self, sentry_unit, filename, mtime,
386 sleep_time=20):388 sleep_time=20, retry_count=30,
389 retry_sleep_time=10):
387 """Check if file was modified after a given time.390 """Check if file was modified after a given time.
388391
389 Args:392 Args:
390 sentry_unit (sentry): The sentry unit to check the file mtime on393 sentry_unit (sentry): The sentry unit to check the file mtime on
391 filename (string): The file to check mtime of394 filename (string): The file to check mtime of
392 mtime (float): The epoch time to check against395 mtime (float): The epoch time to check against
393 sleep_time (int): Seconds to sleep before looking for process396 sleep_time (int): Initial sleep time (s) before looking for file
397 retry_sleep_time (int): Time (s) to sleep between retries
398 retry_count (int): If file is not found, how many times to retry
394399
395 Returns:400 Returns:
396 bool: True if file was modified more recently than mtime, False if401 bool: True if file was modified more recently than mtime, False if
397 file was modified before mtime,402 file was modified before mtime, or if file not found.
398 """403 """
399 self.log.debug('Checking %s updated since %s' % (filename, mtime))404 unit_name = sentry_unit.info['unit_name']
405 self.log.debug('Checking that %s updated since %s on '
406 '%s' % (filename, mtime, unit_name))
400 time.sleep(sleep_time)407 time.sleep(sleep_time)
401 file_mtime = self._get_file_mtime(sentry_unit, filename)408 file_mtime = None
409 tries = 0
410 while tries <= retry_count and not file_mtime:
411 try:
412 file_mtime = self._get_file_mtime(sentry_unit, filename)
413 self.log.debug('Attempt {} to get {} file mtime on {} '
414 'OK'.format(tries, filename, unit_name))
415 except IOError as e:
416 # NOTE(beisner) - race avoidance, file may not exist yet.
417 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
418 self.log.debug('Attempt {} to get {} file mtime on {} '
419 'failed\n{}'.format(tries, filename,
420 unit_name, e))
421 time.sleep(retry_sleep_time)
422 tries += 1
423
424 if not file_mtime:
425 self.log.warn('Could not determine file mtime, assuming '
426 'file does not exist')
427 return False
428
402 if file_mtime >= mtime:429 if file_mtime >= mtime:
403 self.log.debug('File mtime is newer than provided mtime '430 self.log.debug('File mtime is newer than provided mtime '
404 '(%s >= %s)' % (file_mtime, mtime))431 '(%s >= %s) on %s (OK)' % (file_mtime,
432 mtime, unit_name))
405 return True433 return True
406 else:434 else:
407 self.log.warn('File mtime %s is older than provided mtime %s'435 self.log.warn('File mtime is older than provided mtime'
408 % (file_mtime, mtime))436 '(%s < on %s) on %s' % (file_mtime,
437 mtime, unit_name))
409 return False438 return False
410439
411 def validate_service_config_changed(self, sentry_unit, mtime, service,440 def validate_service_config_changed(self, sentry_unit, mtime, service,
412 filename, pgrep_full=None,441 filename, pgrep_full=None,
413 sleep_time=20, retry_count=2,442 sleep_time=20, retry_count=30,
414 retry_sleep_time=30):443 retry_sleep_time=10):
415 """Check service and file were updated after mtime444 """Check service and file were updated after mtime
416445
417 Args:446 Args:
@@ -456,7 +485,9 @@
456 sentry_unit,485 sentry_unit,
457 filename,486 filename,
458 mtime,487 mtime,
459 sleep_time=0)488 sleep_time=sleep_time,
489 retry_count=retry_count,
490 retry_sleep_time=retry_sleep_time)
460491
461 return service_restart and config_update492 return service_restart and config_update
462493
@@ -776,3 +807,12 @@
776 output = _check_output(command, universal_newlines=True)807 output = _check_output(command, universal_newlines=True)
777 data = json.loads(output)808 data = json.loads(output)
778 return data.get(u"status") == "completed"809 return data.get(u"status") == "completed"
810
811 def status_get(self, unit):
812 """Return the current service status of this unit."""
813 raw_status, return_code = unit.run(
814 "status-get --format=json --include-data")
815 if return_code != 0:
816 return ("unknown", "")
817 status = json.loads(raw_status)
818 return (status["status"], status["message"])
779819
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-10 09:30:59 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-25 14:42:27 +0000
@@ -58,19 +58,17 @@
58 else:58 else:
59 base_series = self.current_next59 base_series = self.current_next
6060
61 if self.stable:61 for svc in other_services:
62 for svc in other_services:62 if svc['name'] in force_series_current:
63 if svc['name'] in force_series_current:63 base_series = self.current_next
64 base_series = self.current_next64 # If a location has been explicitly set, use it
6565 if svc.get('location'):
66 continue
67 if self.stable:
66 temp = 'lp:charms/{}/{}'68 temp = 'lp:charms/{}/{}'
67 svc['location'] = temp.format(base_series,69 svc['location'] = temp.format(base_series,
68 svc['name'])70 svc['name'])
69 else:71 else:
70 for svc in other_services:
71 if svc['name'] in force_series_current:
72 base_series = self.current_next
73
74 if svc['name'] in base_charms:72 if svc['name'] in base_charms:
75 temp = 'lp:charms/{}/{}'73 temp = 'lp:charms/{}/{}'
76 svc['location'] = temp.format(base_series,74 svc['location'] = temp.format(base_series,
@@ -79,6 +77,7 @@
79 temp = 'lp:~openstack-charmers/charms/{}/{}/next'77 temp = 'lp:~openstack-charmers/charms/{}/{}/next'
80 svc['location'] = temp.format(self.current_next,78 svc['location'] = temp.format(self.current_next,
81 svc['name'])79 svc['name'])
80
82 return other_services81 return other_services
8382
84 def _add_services(self, this_service, other_services):83 def _add_services(self, this_service, other_services):

Subscribers

People subscribed via source and target branches