Skip to content

Commit 7286502

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Add more specific error messages to swift-ring-builder"
2 parents 1681bcc + 2f50a07 commit 7286502

File tree

4 files changed

+80
-19
lines changed

4 files changed

+80
-19
lines changed

bin/swift-ring-builder

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ from sys import argv, exit, modules
2424
from textwrap import wrap
2525
from time import time
2626

27+
from swift.common import exceptions
2728
from swift.common.ring import RingBuilder
2829

2930

@@ -494,7 +495,21 @@ swift-ring-builder <builder_file> remove <search-value>
494495
print 'Aborting device removals'
495496
exit(EXIT_ERROR)
496497
for dev in devs:
497-
builder.remove_dev(dev['id'])
498+
try:
499+
builder.remove_dev(dev['id'])
500+
except exceptions.RingBuilderError, e:
501+
print '-' * 79
502+
print ("An error occurred while removing device with id %d\n"
503+
"This usually means that you attempted to remove the\n"
504+
"last device in a ring. If this is the case, consider\n"
505+
"creating a new ring instead.\n"
506+
"The on-disk ring builder is unchanged\n"
507+
"Original exception message: %s" %
508+
(dev['id'], e.message)
509+
)
510+
print '-' * 79
511+
exit(EXIT_ERROR)
512+
498513
print 'd%(id)sz%(zone)s-%(ip)s:%(port)s/%(device)s_"%(meta)s" ' \
499514
'marked for removal and will be removed next rebalance.' \
500515
% dev
@@ -508,8 +523,18 @@ swift-ring-builder <builder_file> rebalance
508523
recently reassigned.
509524
"""
510525
devs_changed = builder.devs_changed
511-
last_balance = builder.get_balance()
512-
parts, balance = builder.rebalance()
526+
try:
527+
last_balance = builder.get_balance()
528+
parts, balance = builder.rebalance()
529+
except exceptions.RingBuilderError, e:
530+
print '-' * 79
531+
print ("An error has occurred during ring validation. Common\n"
532+
"causes of failure are rings that are empty or do not\n"
533+
"have enough devices to accommodate the replica count.\n"
534+
"Original exception message:\n %s" % e.message
535+
)
536+
print '-' * 79
537+
exit(EXIT_ERROR)
513538
if not parts:
514539
print 'No partitions could be reassigned.'
515540
print 'Either none need to be or none can be due to ' \
@@ -519,7 +544,17 @@ swift-ring-builder <builder_file> rebalance
519544
print 'Cowardly refusing to save rebalance as it did not change ' \
520545
'at least 1%.'
521546
exit(EXIT_WARNING)
522-
builder.validate()
547+
try:
548+
builder.validate()
549+
except exceptions.RingValidationError, e:
550+
print '-' * 79
551+
print ("An error has occurred during ring validation. Common\n"
552+
"causes of failure are rings that are empty or do not\n"
553+
"have enough devices to accommodate the replica count.\n"
554+
"Original exception message:\n %s" % e.message
555+
)
556+
print '-' * 79
557+
exit(EXIT_ERROR)
523558
print 'Reassigned %d (%.02f%%) partitions. Balance is now %.02f.' % \
524559
(parts, 100.0 * parts / builder.parts, balance)
525560
status = EXIT_SUCCESS

swift/common/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,19 @@ class DriveNotMounted(Exception):
6060

6161
class LockTimeout(MessageTimeout):
6262
pass
63+
64+
65+
class RingBuilderError(Exception):
66+
pass
67+
68+
69+
class RingValidationError(RingBuilderError):
70+
pass
71+
72+
73+
class EmptyRingError(RingBuilderError):
74+
pass
75+
76+
77+
class DuplicateDeviceError(RingBuilderError):
78+
pass

swift/common/ring/builder.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from random import randint, shuffle
1818
from time import time
1919

20+
from swift.common import exceptions
2021
from swift.common.ring import RingData
2122

2223

@@ -69,6 +70,15 @@ def __init__(self, part_power, replicas, min_part_hours):
6970
self._remove_devs = []
7071
self._ring = None
7172

73+
def weighted_parts(self):
74+
try:
75+
return self.parts * self.replicas / \
76+
sum(d['weight'] for d in self.devs if d is not None)
77+
except ZeroDivisionError:
78+
raise exceptions.EmptyRingError('There are no devices in this '
79+
'ring, or all devices have been '
80+
'deleted')
81+
7282
def copy_from(self, builder):
7383
if hasattr(builder, 'devs'):
7484
self.part_power = builder.part_power
@@ -177,7 +187,8 @@ def add_dev(self, dev):
177187
:param dev: device dict
178188
"""
179189
if dev['id'] < len(self.devs) and self.devs[dev['id']] is not None:
180-
raise Exception('Duplicate device id: %d' % dev['id'])
190+
raise exceptions.DuplicateDeviceError(
191+
'Duplicate device id: %d' % dev['id'])
181192
while dev['id'] >= len(self.devs):
182193
self.devs.append(None)
183194
dev['weight'] = float(dev['weight'])
@@ -273,11 +284,11 @@ def validate(self, stats=False):
273284
:param stats: if True, check distribution of partitions across devices
274285
:returns: if stats is True, a tuple of (device usage, worst stat), else
275286
(None, None)
276-
:raises Exception: problem was found with the ring.
287+
:raises RingValidationError: problem was found with the ring.
277288
"""
278289
if sum(d['parts'] for d in self.devs if d is not None) != \
279290
self.parts * self.replicas:
280-
raise Exception(
291+
raise exceptions.RingValidationError(
281292
'All partitions are not double accounted for: %d != %d' %
282293
(sum(d['parts'] for d in self.devs if d is not None),
283294
self.parts * self.replicas))
@@ -291,16 +302,15 @@ def validate(self, stats=False):
291302
dev_usage[dev_id] += 1
292303
zone = self.devs[dev_id]['zone']
293304
if zone in zones:
294-
raise Exception(
305+
raise exceptions.RingValidationError(
295306
'Partition %d not in %d distinct zones. ' \
296307
'Zones were: %s' %
297308
(part, self.replicas,
298309
[self.devs[self._replica2part2dev[r][part]]['zone']
299310
for r in xrange(self.replicas)]))
300311
zones[zone] = True
301312
if stats:
302-
weighted_parts = self.parts * self.replicas / \
303-
sum(d['weight'] for d in self.devs if d is not None)
313+
weighted_parts = self.weighted_parts()
304314
worst = 0
305315
for dev in self.devs:
306316
if dev is None:
@@ -328,9 +338,8 @@ def get_balance(self):
328338
329339
:returns: balance of the ring
330340
"""
331-
weighted_parts = self.parts * self.replicas / \
332-
sum(d['weight'] for d in self.devs if d is not None)
333341
balance = 0
342+
weighted_parts = self.weighted_parts()
334343
for dev in self.devs:
335344
if dev is None:
336345
continue
@@ -370,8 +379,8 @@ def _set_parts_wanted(self):
370379
used to sort the devices according to "most wanted" during rebalancing
371380
to best distribute partitions.
372381
"""
373-
weighted_parts = self.parts * self.replicas / \
374-
sum(d['weight'] for d in self.devs if d is not None)
382+
weighted_parts = self.weighted_parts()
383+
375384
for dev in self.devs:
376385
if dev is not None:
377386
if not dev['weight']:

test/unit/common/ring/test_builder.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
import unittest
1818
from shutil import rmtree
1919

20-
from swift.common.ring import RingBuilder, RingData
20+
from swift.common import exceptions
2121
from swift.common import ring
22+
from swift.common.ring import RingBuilder, RingData
2223

2324
class TestRingBuilder(unittest.TestCase):
2425

@@ -68,7 +69,7 @@ def test_add_dev(self):
6869
dev = \
6970
{'id': 0, 'zone': 0, 'weight': 1, 'ip': '127.0.0.1', 'port': 10000}
7071
rb.add_dev(dev)
71-
self.assertRaises(Exception, rb.add_dev, dev)
72+
self.assertRaises(exceptions.DuplicateDeviceError, rb.add_dev, dev)
7273

7374
def test_set_dev_weight(self):
7475
rb = ring.RingBuilder(8, 3, 1)
@@ -252,13 +253,13 @@ def test_validate(self):
252253

253254
# Test not all partitions doubly accounted for
254255
rb.devs[1]['parts'] -= 1
255-
self.assertRaises(Exception, rb.validate)
256+
self.assertRaises(exceptions.RingValidationError, rb.validate)
256257
rb.devs[1]['parts'] += 1
257258

258259
# Test duplicate device for partition
259260
orig_dev_id = rb._replica2part2dev[0][0]
260261
rb._replica2part2dev[0][0] = rb._replica2part2dev[1][0]
261-
self.assertRaises(Exception, rb.validate)
262+
self.assertRaises(exceptions.RingValidationError, rb.validate)
262263
rb._replica2part2dev[0][0] = orig_dev_id
263264

264265
# Test duplicate zone for partition
@@ -282,7 +283,7 @@ def test_validate(self):
282283
break
283284
if orig_replica is not None:
284285
break
285-
self.assertRaises(Exception, rb.validate)
286+
self.assertRaises(exceptions.RingValidationError, rb.validate)
286287
rb._replica2part2dev[orig_replica][orig_partition] = orig_device
287288

288289
# Tests that validate can handle 'holes' in .devs

0 commit comments

Comments
 (0)