Merge lp:~nelson-crynwr/swift/refactor into lp:~hudson-openstack/swift/trunk

Proposed by RussNelson
Status: Merged
Approved by: Chuck Thier
Approved revision: 155
Merged at revision: 155
Proposed branch: lp:~nelson-crynwr/swift/refactor
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 476 lines (+180/-173)
1 file modified
bin/swift-ring-builder (+180/-173)
To merge this branch: bzr merge lp:~nelson-crynwr/swift/refactor
Reviewer Review Type Date Requested Status
Chuck Thier (community) Approve
gholt (community) Approve
Review via email: mp+45314@code.launchpad.net

This proposal supersedes a proposal from 2010-12-31.

Description of the change

At least in theory, the code is not functionally different. It's just physically restructured internally so that the help strings are now docstrings within each command/function. There are other improvements possible, such as including test code in the docstring. I love Python!

To post a comment you must log in.
Revision history for this message
RussNelson (nelson-crynwr) wrote : Posted in a previous version of this proposal

Oh, yes, another reason for coding it this way is that now those subroutines are available to anyone, just by importing swift-ring-builder. You'd need to have a symlink to it from /usr/bin/swift-ring-builder to /usr/lib/pymodules/python2.6/swift_ring_builder.py cuz Python is jealous of its .py extension.

Revision history for this message
gholt (gholt) wrote : Posted in a previous version of this proposal

Russ, did you see my proposed changes to this?

https://code.launchpad.net/~gholt/swift/refactor/+merge/45166

Revision history for this message
RussNelson (nelson-crynwr) wrote : Posted in a previous version of this proposal

> Russ, did you see my proposed changes to this?
>
> https://code.launchpad.net/~gholt/swift/refactor/+merge/45166

Yes, I just approved it.

Revision history for this message
gholt (gholt) wrote :

I'm good with this; but since I have code in here too now I'll just have chuck give it a once over too.

review: Approve
Revision history for this message
Chuck Thier (cthier) wrote :

alright

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/swift-ring-builder'
2--- bin/swift-ring-builder 2011-01-04 23:34:43 +0000
3+++ bin/swift-ring-builder 2011-01-06 00:14:08 +0000
4@@ -20,20 +20,40 @@
5 from os import mkdir
6 from os.path import basename, dirname, exists, join as pathjoin
7 from sys import argv, exit
8+from textwrap import wrap
9 from time import time
10
11 from swift.common.ring import RingBuilder
12
13
14 MAJOR_VERSION = 1
15-MINOR_VERSION = 1
16+MINOR_VERSION = 2
17 EXIT_RING_CHANGED = 0
18 EXIT_RING_UNCHANGED = 1
19-EXIT_ERROR = 2
20+EXIT_ERROR = 2
21
22
23 def search_devs(builder, search_value):
24- # d<device_id>z<zone>-<ip>:<port>/<device_name>_<meta>
25+ """
26+The <search-value> can be of the form:
27+ d<device_id>z<zone>-<ip>:<port>/<device_name>_<meta>
28+ Any part is optional, but you must include at least one part.
29+ Examples:
30+ d74 Matches the device id 74
31+ z1 Matches devices in zone 1
32+ z1-1.2.3.4 Matches devices in zone 1 with the ip 1.2.3.4
33+ 1.2.3.4 Matches devices in any zone with the ip 1.2.3.4
34+ z1:5678 Matches devices in zone 1 using port 5678
35+ :5678 Matches devices that use port 5678
36+ /sdb1 Matches devices with the device name sdb1
37+ _shiny Matches devices with shiny in the meta data
38+ _"snet: 5.6.7.8" Matches devices with snet: 5.6.7.8 in the meta data
39+ Most specific example:
40+ d74z1-1.2.3.4:5678/sdb1_"snet: 5.6.7.8"
41+ Nerd explanation:
42+ All items require their single character prefix except the ip, in which
43+ case the - is optional unless the device id or zone is also included.
44+ """
45 orig_search_value = search_value
46 match = []
47 if search_value.startswith('d'):
48@@ -72,7 +92,8 @@
49 match.append(('meta', search_value[1:]))
50 search_value = ''
51 if search_value:
52- raise ValueError('Invalid <search-value>: %s' % repr(orig_search_value))
53+ raise ValueError('Invalid <search-value>: %s' %
54+ repr(orig_search_value))
55 devs = []
56 for dev in builder.devs:
57 if not dev:
58@@ -89,142 +110,22 @@
59 return devs
60
61
62-SEARCH_VALUE_HELP = '''
63- The <search-value> can be of the form:
64- d<device_id>z<zone>-<ip>:<port>/<device_name>_<meta>
65- Any part is optional, but you must include at least one part.
66- Examples:
67- d74 Matches the device id 74
68- z1 Matches devices in zone 1
69- z1-1.2.3.4 Matches devices in zone 1 with the ip 1.2.3.4
70- 1.2.3.4 Matches devices in any zone with the ip 1.2.3.4
71- z1:5678 Matches devices in zone 1 using port 5678
72- :5678 Matches devices that use port 5678
73- /sdb1 Matches devices with the device name sdb1
74- _shiny Matches devices with shiny in the meta data
75- _"snet: 5.6.7.8" Matches devices with snet: 5.6.7.8 in the meta data
76- Most specific example:
77- d74z1-1.2.3.4:5678/sdb1_"snet: 5.6.7.8"
78- Nerd explanation:
79- All items require their single character prefix except the ip, in which
80- case the - is optional unless the device id or zone is also included.
81-'''.strip()
82-
83-CREATE_HELP = '''
84-swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours>
85+class Commands:
86+
87+ def unknown():
88+ print 'Unknown command: %s' % argv[2]
89+ exit(EXIT_ERROR)
90+
91+ def create():
92+ """
93+swift-ring-builder <builder_file> create <part_power> <replicas>
94+ <min_part_hours>
95 Creates <builder_file> with 2^<part_power> partitions and <replicas>.
96 <min_part_hours> is number of hours to restrict moving a partition more
97 than once.
98-'''.strip()
99-
100-SEARCH_HELP = '''
101-swift-ring-builder <builder_file> search <search-value>
102- Shows information about matching devices.
103-
104- %(SEARCH_VALUE_HELP)s
105-'''.strip() % globals()
106-
107-ADD_HELP = '''
108-swift-ring-builder <builder_file> add z<zone>-<ip>:<port>/<device_name>_<meta> <wght>
109- Adds a device to the ring with the given information. No partitions will be
110- assigned to the new device until after running 'rebalance'. This is so you
111- can make multiple device changes and rebalance them all just once.
112-'''.strip()
113-
114-SET_WEIGHT_HELP = '''
115-swift-ring-builder <builder_file> set_weight <search-value> <weight>
116- Resets the device's weight. No partitions will be reassigned to or from the
117- device until after running 'rebalance'. This is so you can make multiple
118- device changes and rebalance them all just once.
119-
120- %(SEARCH_VALUE_HELP)s
121-'''.strip() % globals()
122-
123-SET_INFO_HELP = '''
124-swift-ring-builder <builder_file> set_info <search-value>
125- <ip>:<port>/<device_name>_<meta>
126- Resets the device's information. This information isn't used to assign
127- partitions, so you can use 'write_ring' afterward to rewrite the current
128- ring with the newer device information. Any of the parts are optional
129- in the final <ip>:<port>/<device_name>_<meta> parameter; just give what you
130- want to change. For instance set_info d74 _"snet: 5.6.7.8" would just
131- update the meta data for device id 74.
132-
133- %(SEARCH_VALUE_HELP)s
134-'''.strip() % globals()
135-
136-REMOVE_HELP = '''
137-swift-ring-builder <builder_file> remove <search-value>
138- Removes the device(s) from the ring. This should normally just be used for
139- a device that has failed. For a device you wish to decommission, it's best
140- to set its weight to 0, wait for it to drain all its data, then use this
141- remove command. This will not take effect until after running 'rebalance'.
142- This is so you can make multiple device changes and rebalance them all just
143- once.
144-
145- %(SEARCH_VALUE_HELP)s
146-'''.strip() % globals()
147-
148-SET_MIN_PART_HOURS_HELP = '''
149-swift-ring-builder <builder_file> set_min_part_hours <hours>
150- Changes the <min_part_hours> to the given <hours>. This should be set to
151- however long a full replication/update cycle takes. We're working on a way
152- to determine this more easily than scanning logs.
153-'''.strip()
154-
155-
156-if __name__ == '__main__':
157- if len(argv) < 2:
158- print '''
159-swift-ring-builder %(MAJOR_VERSION)s.%(MINOR_VERSION)s
160-
161-%(CREATE_HELP)s
162-
163-swift-ring-builder <builder_file>
164- Shows information about the ring and the devices within.
165-
166-%(SEARCH_HELP)s
167-
168-%(ADD_HELP)s
169-
170-%(SET_WEIGHT_HELP)s
171-
172-%(SET_INFO_HELP)s
173-
174-%(REMOVE_HELP)s
175-
176-swift-ring-builder <builder_file> rebalance
177- Attempts to rebalance the ring by reassigning partitions that haven't been
178- recently reassigned.
179-
180-swift-ring-builder <builder_file> validate
181- Just runs the validation routines on the ring.
182-
183-swift-ring-builder <builder_file> write_ring
184- Just rewrites the distributable ring file. This is done automatically after
185- a successful rebalance, so really this is only useful after one or more
186- 'set_info' calls when no rebalance is needed but you want to send out the
187- new device information.
188-
189-%(SET_MIN_PART_HOURS_HELP)s
190-
191-Quick list: create search add set_weight set_info remove rebalance write_ring
192- set_min_part_hours
193-Exit codes: 0 = ring changed, 1 = ring did not change, 2 = error
194-'''.strip() % globals()
195- exit(EXIT_RING_UNCHANGED)
196-
197- if exists(argv[1]):
198- builder = pickle.load(open(argv[1], 'rb'))
199- for dev in builder.devs:
200- if dev and 'meta' not in dev:
201- dev['meta'] = ''
202- elif len(argv) < 3 or argv[2] != 'create':
203- print 'Ring Builder file does not exist: %s' % argv[1]
204- exit(EXIT_ERROR)
205- elif argv[2] == 'create':
206+ """
207 if len(argv) < 6:
208- print CREATE_HELP
209+ print Commands.create.__doc__.strip()
210 exit(EXIT_RING_UNCHANGED)
211 builder = RingBuilder(int(argv[3]), int(argv[4]), int(argv[5]))
212 backup_dir = pathjoin(dirname(argv[1]), 'backups')
213@@ -238,19 +139,11 @@
214 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
215 exit(EXIT_RING_CHANGED)
216
217- backup_dir = pathjoin(dirname(argv[1]), 'backups')
218- try:
219- mkdir(backup_dir)
220- except OSError, err:
221- if err.errno != EEXIST:
222- raise
223-
224- ring_file = argv[1]
225- if ring_file.endswith('.builder'):
226- ring_file = ring_file[:-len('.builder')]
227- ring_file += '.ring.gz'
228-
229- if len(argv) == 2:
230+ def default():
231+ """
232+swift-ring-builder <builder_file>
233+ Shows information about the ring and the devices within.
234+ """
235 print '%s, build version %d' % (argv[1], builder.version)
236 zones = 0
237 balance = 0
238@@ -284,9 +177,15 @@
239 dev['meta'])
240 exit(EXIT_RING_UNCHANGED)
241
242- if argv[2] == 'search':
243+ def search():
244+ """
245+swift-ring-builder <builder_file> search <search-value>
246+ Shows information about matching devices.
247+ """
248 if len(argv) < 4:
249- print SEARCH_HELP
250+ print Commands.search.__doc__.strip()
251+ print
252+ print search_devs.__doc__.strip()
253 exit(EXIT_RING_UNCHANGED)
254 devs = search_devs(builder, argv[3])
255 if not devs:
256@@ -311,10 +210,16 @@
257 dev['meta'])
258 exit(EXIT_RING_UNCHANGED)
259
260- elif argv[2] == 'add':
261- # add z<zone>-<ip>:<port>/<device_name>_<meta> <wght>
262+ def add():
263+ """
264+swift-ring-builder <builder_file> add z<zone>-<ip>:<port>/<device_name>_<meta>
265+ <wght>
266+ Adds a device to the ring with the given information. No partitions will be
267+ assigned to the new device until after running 'rebalance'. This is so you
268+ can make multiple device changes and rebalance them all just once.
269+ """
270 if len(argv) < 5:
271- print ADD_HELP
272+ print Commands.add.__doc__.strip()
273 exit(EXIT_RING_UNCHANGED)
274
275 if not argv[3].startswith('z'):
276@@ -379,9 +284,17 @@
277 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
278 exit(EXIT_RING_UNCHANGED)
279
280- elif argv[2] == 'set_weight':
281+ def set_weight():
282+ """
283+swift-ring-builder <builder_file> set_weight <search-value> <weight>
284+ Resets the device's weight. No partitions will be reassigned to or from the
285+ device until after running 'rebalance'. This is so you can make multiple
286+ device changes and rebalance them all just once.
287+ """
288 if len(argv) != 5:
289- print SET_WEIGHT_HELP
290+ print Commands.set_weight.__doc__.strip()
291+ print
292+ print search_devs.__doc__.strip()
293 exit(EXIT_RING_UNCHANGED)
294 devs = search_devs(builder, argv[3])
295 weight = float(argv[4])
296@@ -404,9 +317,21 @@
297 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
298 exit(EXIT_RING_UNCHANGED)
299
300- elif argv[2] == 'set_info':
301+ def set_info():
302+ """
303+swift-ring-builder <builder_file> set_info <search-value>
304+ <ip>:<port>/<device_name>_<meta>
305+ Resets the device's information. This information isn't used to assign
306+ partitions, so you can use 'write_ring' afterward to rewrite the current
307+ ring with the newer device information. Any of the parts are optional
308+ in the final <ip>:<port>/<device_name>_<meta> parameter; just give what you
309+ want to change. For instance set_info d74 _"snet: 5.6.7.8" would just
310+ update the meta data for device id 74.
311+ """
312 if len(argv) != 5:
313- print SET_INFO_HELP
314+ print Commands.set_info.__doc__.strip()
315+ print
316+ print search_devs.__doc__.strip()
317 exit(EXIT_RING_UNCHANGED)
318 devs = search_devs(builder, argv[3])
319 change_value = argv[4]
320@@ -471,9 +396,20 @@
321 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
322 exit(EXIT_RING_UNCHANGED)
323
324- elif argv[2] == 'remove':
325+ def remove():
326+ """
327+swift-ring-builder <builder_file> remove <search-value>
328+ Removes the device(s) from the ring. This should normally just be used for
329+ a device that has failed. For a device you wish to decommission, it's best
330+ to set its weight to 0, wait for it to drain all its data, then use this
331+ remove command. This will not take effect until after running 'rebalance'.
332+ This is so you can make multiple device changes and rebalance them all just
333+ once.
334+ """
335 if len(argv) < 4:
336- print REMOVE_HELP
337+ print Commands.remove.__doc__.strip()
338+ print
339+ print search_devs.__doc__.strip()
340 exit(EXIT_RING_UNCHANGED)
341 devs = search_devs(builder, argv[3])
342 if not devs:
343@@ -491,11 +427,17 @@
344 for dev in devs:
345 builder.remove_dev(dev['id'])
346 print 'd%(id)sz%(zone)s-%(ip)s:%(port)s/%(device)s_"%(meta)s" ' \
347- 'marked for removal and will be removed next rebalance.' % dev
348+ 'marked for removal and will be removed next rebalance.' \
349+ % dev
350 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
351 exit(EXIT_RING_UNCHANGED)
352
353- elif argv[2] == 'rebalance':
354+ def rebalance():
355+ """
356+swift-ring-builder <builder_file> rebalance
357+ Attempts to rebalance the ring by reassigning partitions that haven't been
358+ recently reassigned.
359+ """
360 devs_changed = builder.devs_changed
361 last_balance = builder.get_balance()
362 parts, balance = builder.rebalance()
363@@ -528,31 +470,50 @@
364 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
365 exit(EXIT_RING_CHANGED)
366
367- elif argv[2] == 'validate':
368+ def validate():
369+ """
370+swift-ring-builder <builder_file> validate
371+ Just runs the validation routines on the ring.
372+ """
373 builder.validate()
374 exit(EXIT_RING_UNCHANGED)
375
376- elif argv[2] == 'write_ring':
377+ def write_ring():
378+ """
379+swift-ring-builder <builder_file> write_ring
380+ Just rewrites the distributable ring file. This is done automatically after
381+ a successful rebalance, so really this is only useful after one or more
382+ 'set_info' calls when no rebalance is needed but you want to send out the
383+ new device information.
384+ """
385 ring_data = builder.get_ring()
386 if not ring_data._replica2part2dev_id:
387- if ring_data.devs:
388- print 'Warning: Writing a ring with no partition assignments but with devices; did you forget to run "rebalance"?'
389- else:
390- print 'Warning: Writing an empty ring'
391+ if ring_data.devs:
392+ print 'Warning: Writing a ring with no partition ' \
393+ 'assignments but with devices; did you forget to run ' \
394+ '"rebalance"?'
395+ else:
396+ print 'Warning: Writing an empty ring'
397 pickle.dump(ring_data,
398 GzipFile(pathjoin(backup_dir, '%d.' % time() +
399 basename(ring_file)), 'wb'), protocol=2)
400 pickle.dump(ring_data, GzipFile(ring_file, 'wb'), protocol=2)
401 exit(EXIT_RING_CHANGED)
402
403- elif argv[2] == 'pretend_min_part_hours_passed':
404+ def pretend_min_part_hours_passed():
405 builder.pretend_min_part_hours_passed()
406 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
407 exit(EXIT_RING_UNCHANGED)
408
409- elif argv[2] == 'set_min_part_hours':
410+ def set_min_part_hours():
411+ """
412+swift-ring-builder <builder_file> set_min_part_hours <hours>
413+ Changes the <min_part_hours> to the given <hours>. This should be set to
414+ however long a full replication/update cycle takes. We're working on a way
415+ to determine this more easily than scanning logs.
416+ """
417 if len(argv) < 4:
418- print SET_MIN_PART_HOURS_HELP
419+ print Commands.set_min_part_hours.__doc__.strip()
420 exit(EXIT_RING_UNCHANGED)
421 builder.change_min_part_hours(int(argv[3]))
422 print 'The minimum number of hours before a partition can be ' \
423@@ -560,5 +521,51 @@
424 pickle.dump(builder, open(argv[1], 'wb'), protocol=2)
425 exit(EXIT_RING_UNCHANGED)
426
427- print 'Unknown command: %s' % argv[2]
428- exit(EXIT_ERROR)
429+
430+if __name__ == '__main__':
431+ if len(argv) < 2:
432+ print "swift-ring-builder %(MAJOR_VERSION)s.%(MINOR_VERSION)s\n" % \
433+ globals()
434+ print Commands.default.__doc__.strip()
435+ print
436+ cmds = [c for c, f in Commands.__dict__.iteritems()
437+ if f.__doc__ and c[0] != '_' and c != 'default']
438+ cmds.sort()
439+ for cmd in cmds:
440+ print Commands.__dict__[cmd].__doc__.strip()
441+ print
442+ print search_devs.__doc__.strip()
443+ print
444+ for line in wrap(' '.join(cmds), 79, initial_indent='Quick list: ',
445+ subsequent_indent=' '):
446+ print line
447+ print 'Exit codes: 0 = ring changed, 1 = ring did not change, ' \
448+ '2 = error'
449+ exit(EXIT_RING_UNCHANGED)
450+
451+ if exists(argv[1]):
452+ builder = pickle.load(open(argv[1], 'rb'))
453+ for dev in builder.devs:
454+ if dev and 'meta' not in dev:
455+ dev['meta'] = ''
456+ elif len(argv) < 3 or argv[2] != 'create':
457+ print 'Ring Builder file does not exist: %s' % argv[1]
458+ exit(EXIT_ERROR)
459+
460+ backup_dir = pathjoin(dirname(argv[1]), 'backups')
461+ try:
462+ mkdir(backup_dir)
463+ except OSError, err:
464+ if err.errno != EEXIST:
465+ raise
466+
467+ ring_file = argv[1]
468+ if ring_file.endswith('.builder'):
469+ ring_file = ring_file[:-len('.builder')]
470+ ring_file += '.ring.gz'
471+
472+ if len(argv) == 2:
473+ command = "default"
474+ else:
475+ command = argv[2]
476+ Commands.__dict__.get(command, Commands.unknown)()