summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Kavanagh <alex@ajkavanagh.co.uk>2017-10-30 11:59:57 +0000
committerAlex Kavanagh <alex@ajkavanagh.co.uk>2017-10-30 12:02:58 +0000
commit407d98b96a143b2325d7bb3c969e9009a4d18ad5 (patch)
tree318b33b8d1ef667b317f1f3729e7640ca6141cac
parent4b8255a41128319fe42bc232e8222e99fbfab193 (diff)
Make charms.ceph fully py2/py3 compatible
These changes make the charms.ceph library suitable for both the old-stable Python 2 charms and the (to be) updates Python 3 only charms. Avoided use of six by using str() with the decode('UTF-8') function to allow the library to be used with both Py2 and Py3. The str(...) coercions can be removed at a later date when the library no longer needs to be synced to Py2 versions of the ceph-* charms. Change-Id: I416053439444bf4cf8945d1fe96643f9ed0f05f4
Notes
Notes (review): Code-Review+2: James Page <james.page@canonical.com> Workflow+1: James Page <james.page@canonical.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Mon, 30 Oct 2017 13:51:20 +0000 Reviewed-on: https://review.openstack.org/516265 Project: openstack/charms.ceph Branch: refs/heads/master
-rw-r--r--ceph/broker.py34
-rw-r--r--ceph/crush_utils.py19
-rw-r--r--ceph/utils.py86
-rw-r--r--setup.py2
-rw-r--r--tox.ini7
-rw-r--r--unit_tests/test_utils.py14
6 files changed, 95 insertions, 67 deletions
diff --git a/ceph/broker.py b/ceph/broker.py
index b071b91..95ee779 100644
--- a/ceph/broker.py
+++ b/ceph/broker.py
@@ -12,6 +12,7 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15import collections
15import json 16import json
16import os 17import os
17 18
@@ -134,7 +135,7 @@ def process_requests(reqs):
134 log(msg, level=ERROR) 135 log(msg, level=ERROR)
135 return {'exit-code': 1, 'stderr': msg} 136 return {'exit-code': 1, 'stderr': msg}
136 137
137 msg = ("Missing or invalid api version (%s)" % version) 138 msg = ("Missing or invalid api version ({})".format(version))
138 resp = {'exit-code': 1, 'stderr': msg} 139 resp = {'exit-code': 1, 'stderr': msg}
139 if request_id: 140 if request_id:
140 resp['request-id'] = request_id 141 resp['request-id'] = request_id
@@ -231,7 +232,7 @@ def add_pool_to_group(pool, group, namespace=None):
231def pool_permission_list_for_service(service): 232def pool_permission_list_for_service(service):
232 """Build the permission string for Ceph for a given service""" 233 """Build the permission string for Ceph for a given service"""
233 permissions = [] 234 permissions = []
234 permission_types = {} 235 permission_types = collections.OrderedDict()
235 for permission, group in service["group_names"].items(): 236 for permission, group in service["group_names"].items():
236 if permission not in permission_types: 237 if permission not in permission_types:
237 permission_types[permission] = [] 238 permission_types[permission] = []
@@ -267,9 +268,7 @@ def get_service_groups(service, namespace=None):
267 key="cephx.services.{}".format(service)) 268 key="cephx.services.{}".format(service))
268 try: 269 try:
269 service = json.loads(service_json) 270 service = json.loads(service_json)
270 except TypeError: 271 except (TypeError, ValueError):
271 service = None
272 except ValueError:
273 service = None 272 service = None
274 if service: 273 if service:
275 service['groups'] = _build_service_groups(service, namespace) 274 service['groups'] = _build_service_groups(service, namespace)
@@ -296,7 +295,7 @@ def _build_service_groups(service, namespace=None):
296 } 295 }
297 """ 296 """
298 all_groups = {} 297 all_groups = {}
299 for _, groups in service['group_names'].items(): 298 for groups in service['group_names'].values():
300 for group in groups: 299 for group in groups:
301 name = group 300 name = group
302 if namespace: 301 if namespace:
@@ -316,9 +315,7 @@ def get_group(group_name):
316 group_json = monitor_key_get(service='admin', key=group_key) 315 group_json = monitor_key_get(service='admin', key=group_key)
317 try: 316 try:
318 group = json.loads(group_json) 317 group = json.loads(group_json)
319 except TypeError: 318 except (TypeError, ValueError):
320 group = None
321 except ValueError:
322 group = None 319 group = None
323 if not group: 320 if not group:
324 group = { 321 group = {
@@ -391,9 +388,8 @@ def handle_erasure_pool(request, service):
391 percent_data=weight) 388 percent_data=weight)
392 # Ok make the erasure pool 389 # Ok make the erasure pool
393 if not pool_exists(service=service, name=pool_name): 390 if not pool_exists(service=service, name=pool_name):
394 log("Creating pool '%s' (erasure_profile=%s)" % (pool.name, 391 log("Creating pool '{}' (erasure_profile={})"
395 erasure_profile), 392 .format(pool.name, erasure_profile), level=INFO)
396 level=INFO)
397 pool.create() 393 pool.create()
398 394
399 # Set a quota if requested 395 # Set a quota if requested
@@ -446,11 +442,11 @@ def handle_replicated_pool(request, service):
446 pool = ReplicatedPool(service=service, 442 pool = ReplicatedPool(service=service,
447 name=pool_name, **kwargs) 443 name=pool_name, **kwargs)
448 if not pool_exists(service=service, name=pool_name): 444 if not pool_exists(service=service, name=pool_name):
449 log("Creating pool '%s' (replicas=%s)" % (pool.name, replicas), 445 log("Creating pool '{}' (replicas={})".format(pool.name, replicas),
450 level=INFO) 446 level=INFO)
451 pool.create() 447 pool.create()
452 else: 448 else:
453 log("Pool '%s' already exists - skipping create" % pool.name, 449 log("Pool '{}' already exists - skipping create".format(pool.name),
454 level=DEBUG) 450 level=DEBUG)
455 451
456 # Set a quota if requested 452 # Set a quota if requested
@@ -519,7 +515,7 @@ def handle_set_pool_value(request, service):
519 'key': request.get('key'), 515 'key': request.get('key'),
520 'value': request.get('value')} 516 'value': request.get('value')}
521 if params['key'] not in POOL_KEYS: 517 if params['key'] not in POOL_KEYS:
522 msg = "Invalid key '%s'" % params['key'] 518 msg = "Invalid key '{}'".format(params['key'])
523 log(msg, level=ERROR) 519 log(msg, level=ERROR)
524 return {'exit-code': 1, 'stderr': msg} 520 return {'exit-code': 1, 'stderr': msg}
525 521
@@ -685,7 +681,7 @@ def handle_rgw_create_user(request, service):
685 ] 681 ]
686 ) 682 )
687 try: 683 try:
688 user_json = json.loads(create_output) 684 user_json = json.loads(str(create_output.decode('UTF-8')))
689 return {'exit-code': 0, 'user': user_json} 685 return {'exit-code': 0, 'user': user_json}
690 except ValueError as err: 686 except ValueError as err:
691 log(err, level=ERROR) 687 log(err, level=ERROR)
@@ -790,10 +786,10 @@ def process_requests_v1(reqs):
790 operation failed along with an explanation). 786 operation failed along with an explanation).
791 """ 787 """
792 ret = None 788 ret = None
793 log("Processing %s ceph broker requests" % (len(reqs)), level=INFO) 789 log("Processing {} ceph broker requests".format(len(reqs)), level=INFO)
794 for req in reqs: 790 for req in reqs:
795 op = req.get('op') 791 op = req.get('op')
796 log("Processing op='%s'" % op, level=DEBUG) 792 log("Processing op='{}'".format(op), level=DEBUG)
797 # Use admin client since we do not have other client key locations 793 # Use admin client since we do not have other client key locations
798 # setup to use them for these operations. 794 # setup to use them for these operations.
799 svc = 'admin' 795 svc = 'admin'
@@ -848,7 +844,7 @@ def process_requests_v1(reqs):
848 elif op == "add-permissions-to-key": 844 elif op == "add-permissions-to-key":
849 ret = handle_add_permissions_to_key(request=req, service=svc) 845 ret = handle_add_permissions_to_key(request=req, service=svc)
850 else: 846 else:
851 msg = "Unknown operation '%s'" % op 847 msg = "Unknown operation '{}'".format(op)
852 log(msg, level=ERROR) 848 log(msg, level=ERROR)
853 return {'exit-code': 1, 'stderr': msg} 849 return {'exit-code': 1, 'stderr': msg}
854 850
diff --git a/ceph/crush_utils.py b/ceph/crush_utils.py
index 1c777f3..8b6876c 100644
--- a/ceph/crush_utils.py
+++ b/ceph/crush_utils.py
@@ -60,7 +60,7 @@ class Crushmap(object):
60 ids = list(map( 60 ids = list(map(
61 lambda x: int(x), 61 lambda x: int(x),
62 re.findall(CRUSHMAP_ID_RE, self._crushmap))) 62 re.findall(CRUSHMAP_ID_RE, self._crushmap)))
63 ids.sort() 63 ids = sorted(ids)
64 if roots != []: 64 if roots != []:
65 for root in roots: 65 for root in roots:
66 buckets.append(CRUSHBucket(root[0], root[1], True)) 66 buckets.append(CRUSHBucket(root[0], root[1], True))
@@ -73,8 +73,11 @@ class Crushmap(object):
73 73
74 def load_crushmap(self): 74 def load_crushmap(self):
75 try: 75 try:
76 crush = check_output(['ceph', 'osd', 'getcrushmap']) 76 crush = str(check_output(['ceph', 'osd', 'getcrushmap'])
77 return check_output(['crushtool', '-d', '-'], stdin=crush.stdout) 77 .decode('UTF-8'))
78 return str(check_output(['crushtool', '-d', '-'],
79 stdin=crush.stdout)
80 .decode('UTF-8'))
78 except CalledProcessError as e: 81 except CalledProcessError as e:
79 log("Error occured while loading and decompiling CRUSH map:" 82 log("Error occured while loading and decompiling CRUSH map:"
80 "{}".format(e), ERROR) 83 "{}".format(e), ERROR)
@@ -99,10 +102,12 @@ class Crushmap(object):
99 """Persist Crushmap to Ceph""" 102 """Persist Crushmap to Ceph"""
100 try: 103 try:
101 crushmap = self.build_crushmap() 104 crushmap = self.build_crushmap()
102 compiled = check_output(['crushtool', '-c', '/dev/stdin', '-o', 105 compiled = str(check_output(['crushtool', '-c', '/dev/stdin', '-o',
103 '/dev/stdout'], stdin=crushmap) 106 '/dev/stdout'], stdin=crushmap)
104 ceph_output = check_output(['ceph', 'osd', 'setcrushmap', '-i', 107 .decode('UTF-8'))
105 '/dev/stdin'], stdin=compiled) 108 ceph_output = str(check_output(['ceph', 'osd', 'setcrushmap', '-i',
109 '/dev/stdin'], stdin=compiled)
110 .decode('UTF-8'))
106 return ceph_output 111 return ceph_output
107 except CalledProcessError as e: 112 except CalledProcessError as e:
108 log("save error: {}".format(e)) 113 log("save error: {}".format(e))
diff --git a/ceph/utils.py b/ceph/utils.py
index 343a759..e8f4607 100644
--- a/ceph/utils.py
+++ b/ceph/utils.py
@@ -381,8 +381,9 @@ def get_block_uuid(block_dev):
381 :returns: The UUID of the device or None on Error. 381 :returns: The UUID of the device or None on Error.
382 """ 382 """
383 try: 383 try:
384 block_info = subprocess.check_output( 384 block_info = str(subprocess
385 ['blkid', '-o', 'export', block_dev]) 385 .check_output(['blkid', '-o', 'export', block_dev])
386 .decode('UTF-8'))
386 for tag in block_info.split('\n'): 387 for tag in block_info.split('\n'):
387 parts = tag.split('=') 388 parts = tag.split('=')
388 if parts[0] == 'UUID': 389 if parts[0] == 'UUID':
@@ -533,8 +534,9 @@ def get_osd_weight(osd_id):
533 :raises: CalledProcessError if our ceph command fails. 534 :raises: CalledProcessError if our ceph command fails.
534 """ 535 """
535 try: 536 try:
536 tree = subprocess.check_output( 537 tree = str(subprocess
537 ['ceph', 'osd', 'tree', '--format=json']) 538 .check_output(['ceph', 'osd', 'tree', '--format=json'])
539 .decode('UTF-8'))
538 try: 540 try:
539 json_tree = json.loads(tree) 541 json_tree = json.loads(tree)
540 # Make sure children are present in the json 542 # Make sure children are present in the json
@@ -561,9 +563,10 @@ def get_osd_tree(service):
561 Also raises CalledProcessError if our ceph command fails 563 Also raises CalledProcessError if our ceph command fails
562 """ 564 """
563 try: 565 try:
564 tree = subprocess.check_output( 566 tree = str(subprocess
565 ['ceph', '--id', service, 567 .check_output(['ceph', '--id', service,
566 'osd', 'tree', '--format=json']) 568 'osd', 'tree', '--format=json'])
569 .decode('UTF-8'))
567 try: 570 try:
568 json_tree = json.loads(tree) 571 json_tree = json.loads(tree)
569 crush_list = [] 572 crush_list = []
@@ -628,7 +631,7 @@ def _get_osd_num_from_dirname(dirname):
628 """ 631 """
629 match = re.search('ceph-(?P<osd_id>\d+)', dirname) 632 match = re.search('ceph-(?P<osd_id>\d+)', dirname)
630 if not match: 633 if not match:
631 raise ValueError("dirname not in correct format: %s" % dirname) 634 raise ValueError("dirname not in correct format: {}".format(dirname))
632 635
633 return match.group('osd_id') 636 return match.group('osd_id')
634 637
@@ -718,7 +721,7 @@ def get_version():
718 721
719 722
720def error_out(msg): 723def error_out(msg):
721 log("FATAL ERROR: %s" % msg, 724 log("FATAL ERROR: {}".format(msg),
722 level=ERROR) 725 level=ERROR)
723 sys.exit(1) 726 sys.exit(1)
724 727
@@ -736,7 +739,9 @@ def is_quorum():
736 ] 739 ]
737 if os.path.exists(asok): 740 if os.path.exists(asok):
738 try: 741 try:
739 result = json.loads(subprocess.check_output(cmd)) 742 result = json.loads(str(subprocess
743 .check_output(cmd)
744 .decode('UTF-8')))
740 except subprocess.CalledProcessError: 745 except subprocess.CalledProcessError:
741 return False 746 return False
742 except ValueError: 747 except ValueError:
@@ -763,7 +768,9 @@ def is_leader():
763 ] 768 ]
764 if os.path.exists(asok): 769 if os.path.exists(asok):
765 try: 770 try:
766 result = json.loads(subprocess.check_output(cmd)) 771 result = json.loads(str(subprocess
772 .check_output(cmd)
773 .decode('UTF-8')))
767 except subprocess.CalledProcessError: 774 except subprocess.CalledProcessError:
768 return False 775 return False
769 except ValueError: 776 except ValueError:
@@ -955,8 +962,9 @@ def is_osd_disk(dev):
955 partitions = get_partition_list(dev) 962 partitions = get_partition_list(dev)
956 for partition in partitions: 963 for partition in partitions:
957 try: 964 try:
958 info = subprocess.check_output(['sgdisk', '-i', partition.number, 965 info = str(subprocess
959 dev]) 966 .check_output(['sgdisk', '-i', partition.number, dev])
967 .decode('UTF-8'))
960 info = info.split("\n") # IGNORE:E1103 968 info = info.split("\n") # IGNORE:E1103
961 for line in info: 969 for line in info:
962 for ptype in CEPH_PARTITIONS: 970 for ptype in CEPH_PARTITIONS:
@@ -1039,7 +1047,7 @@ def generate_monitor_secret():
1039 '--name=mon.', 1047 '--name=mon.',
1040 '--gen-key' 1048 '--gen-key'
1041 ] 1049 ]
1042 res = subprocess.check_output(cmd) 1050 res = str(subprocess.check_output(cmd).decode('UTF-8'))
1043 1051
1044 return "{}==".format(res.split('=')[1].strip()) 1052 return "{}==".format(res.split('=')[1].strip())
1045 1053
@@ -1188,7 +1196,10 @@ def create_named_keyring(entity, name, caps=None):
1188 for subsystem, subcaps in caps.items(): 1196 for subsystem, subcaps in caps.items():
1189 cmd.extend([subsystem, '; '.join(subcaps)]) 1197 cmd.extend([subsystem, '; '.join(subcaps)])
1190 log("Calling check_output: {}".format(cmd), level=DEBUG) 1198 log("Calling check_output: {}".format(cmd), level=DEBUG)
1191 return parse_key(subprocess.check_output(cmd).strip()) # IGNORE:E1103 1199 return (parse_key(str(subprocess
1200 .check_output(cmd)
1201 .decode('UTF-8'))
1202 .strip())) # IGNORE:E1103
1192 1203
1193 1204
1194def get_upgrade_key(): 1205def get_upgrade_key():
@@ -1205,7 +1216,7 @@ def get_named_key(name, caps=None, pool_list=None):
1205 """ 1216 """
1206 try: 1217 try:
1207 # Does the key already exist? 1218 # Does the key already exist?
1208 output = subprocess.check_output( 1219 output = str(subprocess.check_output(
1209 [ 1220 [
1210 'sudo', 1221 'sudo',
1211 '-u', ceph_user(), 1222 '-u', ceph_user(),
@@ -1218,7 +1229,7 @@ def get_named_key(name, caps=None, pool_list=None):
1218 'auth', 1229 'auth',
1219 'get', 1230 'get',
1220 'client.{}'.format(name), 1231 'client.{}'.format(name),
1221 ]).strip() 1232 ]).decode('UTF-8')).strip()
1222 return parse_key(output) 1233 return parse_key(output)
1223 except subprocess.CalledProcessError: 1234 except subprocess.CalledProcessError:
1224 # Couldn't get the key, time to create it! 1235 # Couldn't get the key, time to create it!
@@ -1247,7 +1258,10 @@ def get_named_key(name, caps=None, pool_list=None):
1247 cmd.extend([subsystem, '; '.join(subcaps)]) 1258 cmd.extend([subsystem, '; '.join(subcaps)])
1248 1259
1249 log("Calling check_output: {}".format(cmd), level=DEBUG) 1260 log("Calling check_output: {}".format(cmd), level=DEBUG)
1250 return parse_key(subprocess.check_output(cmd).strip()) # IGNORE:E1103 1261 return parse_key(str(subprocess
1262 .check_output(cmd)
1263 .decode('UTF-8'))
1264 .strip()) # IGNORE:E1103
1251 1265
1252 1266
1253def upgrade_key_caps(key, caps): 1267def upgrade_key_caps(key, caps):
@@ -1361,7 +1375,7 @@ def maybe_zap_journal(journal_dev):
1361def get_partitions(dev): 1375def get_partitions(dev):
1362 cmd = ['partx', '--raw', '--noheadings', dev] 1376 cmd = ['partx', '--raw', '--noheadings', dev]
1363 try: 1377 try:
1364 out = subprocess.check_output(cmd).splitlines() 1378 out = str(subprocess.check_output(cmd).decode('UTF-8')).splitlines()
1365 log("get partitions: {}".format(out), level=DEBUG) 1379 log("get partitions: {}".format(out), level=DEBUG)
1366 return out 1380 return out
1367 except subprocess.CalledProcessError as e: 1381 except subprocess.CalledProcessError as e:
@@ -1529,7 +1543,7 @@ def get_running_osds():
1529 """Returns a list of the pids of the current running OSD daemons""" 1543 """Returns a list of the pids of the current running OSD daemons"""
1530 cmd = ['pgrep', 'ceph-osd'] 1544 cmd = ['pgrep', 'ceph-osd']
1531 try: 1545 try:
1532 result = subprocess.check_output(cmd) 1546 result = str(subprocess.check_output(cmd).decode('UTF-8'))
1533 return result.split() 1547 return result.split()
1534 except subprocess.CalledProcessError: 1548 except subprocess.CalledProcessError:
1535 return [] 1549 return []
@@ -1545,7 +1559,9 @@ def get_cephfs(service):
1545 # This command wasn't introduced until 0.86 ceph 1559 # This command wasn't introduced until 0.86 ceph
1546 return [] 1560 return []
1547 try: 1561 try:
1548 output = subprocess.check_output(["ceph", '--id', service, "fs", "ls"]) 1562 output = str(subprocess
1563 .check_output(["ceph", '--id', service, "fs", "ls"])
1564 .decode('UTF-8'))
1549 if not output: 1565 if not output:
1550 return [] 1566 return []
1551 """ 1567 """
@@ -2079,7 +2095,9 @@ def list_pools(service):
2079 """ 2095 """
2080 try: 2096 try:
2081 pool_list = [] 2097 pool_list = []
2082 pools = subprocess.check_output(['rados', '--id', service, 'lspools']) 2098 pools = str(subprocess
2099 .check_output(['rados', '--id', service, 'lspools'])
2100 .decode('UTF-8'))
2083 for pool in pools.splitlines(): 2101 for pool in pools.splitlines():
2084 pool_list.append(pool) 2102 pool_list.append(pool)
2085 return pool_list 2103 return pool_list
@@ -2140,10 +2158,8 @@ UCA_CODENAME_MAP = {
2140 2158
2141def pretty_print_upgrade_paths(): 2159def pretty_print_upgrade_paths():
2142 """Pretty print supported upgrade paths for ceph""" 2160 """Pretty print supported upgrade paths for ceph"""
2143 lines = [] 2161 return ["{} -> {}".format(key, value)
2144 for key, value in UPGRADE_PATHS.iteritems(): 2162 for key, value in UPGRADE_PATHS.iteritems()]
2145 lines.append("{} -> {}".format(key, value))
2146 return lines
2147 2163
2148 2164
2149def resolve_ceph_version(source): 2165def resolve_ceph_version(source):
@@ -2163,7 +2179,9 @@ def get_ceph_pg_stat():
2163 :returns: dict 2179 :returns: dict
2164 """ 2180 """
2165 try: 2181 try:
2166 tree = subprocess.check_output(['ceph', 'pg', 'stat', '--format=json']) 2182 tree = str(subprocess
2183 .check_output(['ceph', 'pg', 'stat', '--format=json'])
2184 .decode('UTF-8'))
2167 try: 2185 try:
2168 json_tree = json.loads(tree) 2186 json_tree = json.loads(tree)
2169 if not json_tree['num_pg_by_state']: 2187 if not json_tree['num_pg_by_state']:
@@ -2187,8 +2205,9 @@ def get_ceph_health():
2187 status, use get_ceph_health()['overall_status']. 2205 status, use get_ceph_health()['overall_status'].
2188 """ 2206 """
2189 try: 2207 try:
2190 tree = subprocess.check_output( 2208 tree = str(subprocess
2191 ['ceph', 'status', '--format=json']) 2209 .check_output(['ceph', 'status', '--format=json'])
2210 .decode('UTF-8'))
2192 try: 2211 try:
2193 json_tree = json.loads(tree) 2212 json_tree = json.loads(tree)
2194 # Make sure children are present in the json 2213 # Make sure children are present in the json
@@ -2215,9 +2234,12 @@ def reweight_osd(osd_num, new_weight):
2215 :raises CalledProcessError: if an error occurs invoking the systemd cmd 2234 :raises CalledProcessError: if an error occurs invoking the systemd cmd
2216 """ 2235 """
2217 try: 2236 try:
2218 cmd_result = subprocess.check_output( 2237 cmd_result = str(subprocess
2219 ['ceph', 'osd', 'crush', 'reweight', "osd.{}".format(osd_num), 2238 .check_output(['ceph', 'osd', 'crush',
2220 new_weight], stderr=subprocess.STDOUT) 2239 'reweight', "osd.{}".format(osd_num),
2240 new_weight],
2241 stderr=subprocess.STDOUT)
2242 .decode('UTF-8'))
2221 expected_result = "reweighted item id {ID} name \'osd.{ID}\'".format( 2243 expected_result = "reweighted item id {ID} name \'osd.{ID}\'".format(
2222 ID=osd_num) + " to {}".format(new_weight) 2244 ID=osd_num) + " to {}".format(new_weight)
2223 log(cmd_result) 2245 log(cmd_result)
diff --git a/setup.py b/setup.py
index 139c80d..4458437 100644
--- a/setup.py
+++ b/setup.py
@@ -49,7 +49,7 @@ if sys.argv[-1] == 'publish':
49 49
50 50
51if sys.argv[-1] == 'tag': 51if sys.argv[-1] == 'tag':
52 os.system("git tag -a %s -m 'version %s'" % (version, version)) 52 os.system("git tag -a {0} -m 'version {0}'".format(version))
53 os.system("git push --tags") 53 os.system("git push --tags")
54 sys.exit() 54 sys.exit()
55 55
diff --git a/tox.ini b/tox.ini
index b51ed19..a4a0b6d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
1[tox] 1[tox]
2envlist = pep8,py27,py34,py35 2envlist = pep8,py27,py34,py35,py36
3skipsdist = True 3skipsdist = True
4skip_missing_interpreters = True 4skip_missing_interpreters = True
5 5
@@ -32,6 +32,11 @@ basepython = python3.5
32deps = -r{toxinidir}/requirements.txt 32deps = -r{toxinidir}/requirements.txt
33 -r{toxinidir}/test-requirements.txt 33 -r{toxinidir}/test-requirements.txt
34 34
35[testenv:py36]
36basepython = python3.6
37deps = -r{toxinidir}/requirements.txt
38 -r{toxinidir}/test-requirements.txt
39
35[testenv:pep8] 40[testenv:pep8]
36basepython = python2.7 41basepython = python2.7
37deps = -r{toxinidir}/requirements.txt 42deps = -r{toxinidir}/requirements.txt
diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py
index 7f0fa02..7463c75 100644
--- a/unit_tests/test_utils.py
+++ b/unit_tests/test_utils.py
@@ -84,7 +84,7 @@ class CephTestCase(unittest.TestCase):
84 @patch.object(utils.subprocess, 'check_output') 84 @patch.object(utils.subprocess, 'check_output')
85 def test_get_osd_weight(self, output): 85 def test_get_osd_weight(self, output):
86 """It gives an OSD's weight""" 86 """It gives an OSD's weight"""
87 output.return_value = """{ 87 output.return_value = b"""{
88 "nodes": [{ 88 "nodes": [{
89 "id": -1, 89 "id": -1,
90 "name": "default", 90 "name": "default",
@@ -152,7 +152,7 @@ class CephTestCase(unittest.TestCase):
152 @patch.object(utils, "ceph_user", lambda: "ceph") 152 @patch.object(utils, "ceph_user", lambda: "ceph")
153 @patch.object(utils.socket, "gethostname", lambda: "osd001") 153 @patch.object(utils.socket, "gethostname", lambda: "osd001")
154 def test_get_named_key_with_pool(self, mock_check_output): 154 def test_get_named_key_with_pool(self, mock_check_output):
155 mock_check_output.side_effect = [CalledProcessError(0, 0, 0), ""] 155 mock_check_output.side_effect = [CalledProcessError(0, 0, 0), b""]
156 utils.get_named_key(name="rgw001", pool_list=["rbd", "block"]) 156 utils.get_named_key(name="rgw001", pool_list=["rbd", "block"])
157 mock_check_output.assert_has_calls([ 157 mock_check_output.assert_has_calls([
158 call(['sudo', '-u', 'ceph', 'ceph', '--name', 158 call(['sudo', '-u', 'ceph', 'ceph', '--name',
@@ -170,7 +170,7 @@ class CephTestCase(unittest.TestCase):
170 @patch.object(utils, 'ceph_user', lambda: "ceph") 170 @patch.object(utils, 'ceph_user', lambda: "ceph")
171 @patch.object(utils.socket, "gethostname", lambda: "osd001") 171 @patch.object(utils.socket, "gethostname", lambda: "osd001")
172 def test_get_named_key(self, mock_check_output): 172 def test_get_named_key(self, mock_check_output):
173 mock_check_output.side_effect = [CalledProcessError(0, 0, 0), ""] 173 mock_check_output.side_effect = [CalledProcessError(0, 0, 0), b""]
174 utils.get_named_key(name="rgw001") 174 utils.get_named_key(name="rgw001")
175 mock_check_output.assert_has_calls([ 175 mock_check_output.assert_has_calls([
176 call(['sudo', '-u', 'ceph', 'ceph', '--name', 176 call(['sudo', '-u', 'ceph', 'ceph', '--name',
@@ -225,14 +225,14 @@ class CephTestCase(unittest.TestCase):
225 @patch.object(utils.subprocess, 'check_output') 225 @patch.object(utils.subprocess, 'check_output')
226 def test_get_partition_list(self, output): 226 def test_get_partition_list(self, output):
227 with open('unit_tests/partx_output', 'r') as partx_out: 227 with open('unit_tests/partx_output', 'r') as partx_out:
228 output.return_value = partx_out.read() 228 output.return_value = partx_out.read().encode('UTF-8')
229 partition_list = utils.get_partition_list('/dev/xvdb') 229 partition_list = utils.get_partition_list('/dev/xvdb')
230 self.assertEqual(len(partition_list), 2) 230 self.assertEqual(len(partition_list), 2)
231 231
232 @patch.object(utils.subprocess, 'check_output') 232 @patch.object(utils.subprocess, 'check_output')
233 def test_get_ceph_pg_stat(self, output): 233 def test_get_ceph_pg_stat(self, output):
234 """It returns the current PG stat""" 234 """It returns the current PG stat"""
235 output.return_value = """{ 235 output.return_value = b"""{
236 "num_pg_by_state": [ 236 "num_pg_by_state": [
237 { 237 {
238 "name": "active+clean", 238 "name": "active+clean",
@@ -252,7 +252,7 @@ class CephTestCase(unittest.TestCase):
252 @patch.object(utils.subprocess, 'check_output') 252 @patch.object(utils.subprocess, 'check_output')
253 def test_get_ceph_health(self, output): 253 def test_get_ceph_health(self, output):
254 """It gives the current Ceph health""" 254 """It gives the current Ceph health"""
255 output.return_value = """{ 255 output.return_value = b"""{
256 "health": { 256 "health": {
257 "health_services": [ 257 "health_services": [
258 { 258 {
@@ -344,7 +344,7 @@ class CephTestCase(unittest.TestCase):
344 @patch.object(utils.subprocess, 'check_output') 344 @patch.object(utils.subprocess, 'check_output')
345 def test_reweight_osd(self, mock_reweight): 345 def test_reweight_osd(self, mock_reweight):
346 """It changes the weight of an OSD""" 346 """It changes the weight of an OSD"""
347 mock_reweight.return_value = "reweighted item id 0 name 'osd.0' to 1" 347 mock_reweight.return_value = b"reweighted item id 0 name 'osd.0' to 1"
348 reweight_result = utils.reweight_osd('0', '1') 348 reweight_result = utils.reweight_osd('0', '1')
349 self.assertEqual(reweight_result, True) 349 self.assertEqual(reweight_result, True)
350 mock_reweight.assert_called_once_with( 350 mock_reweight.assert_called_once_with(