Merge lp:~termie/nova/mega_flags into lp:~hudson-openstack/nova/trunk

Proposed by termie
Status: Merged
Approved by: Eric Day
Approved revision: 201
Merge reported by: OpenStack Infra
Merged at revision: not available
Proposed branch: lp:~termie/nova/mega_flags
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 331 lines (+273/-10)
6 files modified
nova/flags.py (+138/-9)
nova/tests/declare_flags.py (+23/-0)
nova/tests/flags_unittest.py (+87/-0)
nova/tests/runtime_flags.py (+23/-0)
run_tests.py (+1/-0)
run_tests.sh (+1/-1)
To merge this branch: bzr merge lp:~termie/nova/mega_flags
Reviewer Review Type Date Requested Status
Eric Day (community) Approve
Joshua McKenty (community) Needs Fixing
Vish Ishaya (community) Approve
Devin Carlen Pending
Review via email: mp+31312@code.launchpad.net

Commit message

Add some useful features to our flags

* No longer dies if there are unknown flags.
* Allows you to declare that you will use a flag from another file
* Allows you to import new flags at runtime and reparses the original arguments to fill them once they are accessed.

This hopefully gets around the issues described by vish in this thread:

https://lists.launchpad.net/nova/msg00009.html

Description of the change

Add some useful features to our flags

* No longer dies if there are unknown flags.
* Allows you to declare that you will use a flag from another file
* Allows you to import new flags at runtime and reparses the original arguments to fill them once they are accessed.

This hopefully gets around the issues described by vish in this thread:

https://lists.launchpad.net/nova/msg00009.html

To post a comment you must log in.
lp:~termie/nova/mega_flags updated
199. By termie

strip out some useless imports

Revision history for this message
Vish Ishaya (vishvananda) wrote :

This stuff all makes me happy. I think I can continue to deal with flags with these changes. If we can create a best-practices for naming of flags, where they should go, and how they fit into conf files, I think I'm content to stick with gflags.

(copying the above comment to the mailing list)

review: Approve
lp:~termie/nova/mega_flags updated
200. By termie

add copyright headers

Revision history for this message
Joshua McKenty (joshua-mckenty) wrote :

Party wrapper might be an inappropriate name?
Missing copyright block on declare_flags.py.
I find the name of DECLARE confusing, since it's not a declaration, it's an IMPORT.
Can we call it gflags.IMPORT instead?

Would love to see a docstring on the FlagValues class at some point.

lgtm with nits.

review: Needs Fixing
lp:~termie/nova/mega_flags updated
201. By termie

updated doc string and wrapper

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Termie, any comment on IMPORT vs DECLARE. That was the last thing on josh's list of nits. I'd like to merge this.

Revision history for this message
termie (termie) wrote :

Oh, I had already hassled him about it, but I think maybe since he has been traveling he didn't have a chance to click accept yet.

DECLARE is the syntax used in the C version of gflags. It's meaning is that you are declaring that you will be using a given flag. It's syntax varies slightly from that of the C version in that C has everything in the same namespace and is generally all compiled together where as in python the flag may not already be loaded so it will make sure it is. This is also in line with the DECLARE_key_flag syntax.

Revision history for this message
Eric Day (eday) wrote :

It sounds like all outstanding concerns are addressed, I'll mark accept for merge later today unless someone objects.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/flags.py'
2--- nova/flags.py 2010-07-28 23:11:02 +0000
3+++ nova/flags.py 2010-08-03 16:04:46 +0000
4@@ -21,16 +21,145 @@
5 where they're used.
6 """
7
8+import getopt
9 import socket
10-
11-
12-from gflags import *
13-
14-# This keeps pylint from barfing on the imports
15-FLAGS = FLAGS
16-DEFINE_string = DEFINE_string
17-DEFINE_integer = DEFINE_integer
18-DEFINE_bool = DEFINE_bool
19+import sys
20+
21+import gflags
22+
23+
24+class FlagValues(gflags.FlagValues):
25+ """Extension of gflags.FlagValues that allows undefined and runtime flags.
26+
27+ Unknown flags will be ignored when parsing the command line, but the
28+ command line will be kept so that it can be replayed if new flags are
29+ defined after the initial parsing.
30+
31+ """
32+
33+ def __init__(self):
34+ gflags.FlagValues.__init__(self)
35+ self.__dict__['__dirty'] = []
36+ self.__dict__['__was_already_parsed'] = False
37+ self.__dict__['__stored_argv'] = []
38+
39+ def __call__(self, argv):
40+ # We're doing some hacky stuff here so that we don't have to copy
41+ # out all the code of the original verbatim and then tweak a few lines.
42+ # We're hijacking the output of getopt so we can still return the
43+ # leftover args at the end
44+ sneaky_unparsed_args = {"value": None}
45+ original_argv = list(argv)
46+
47+ if self.IsGnuGetOpt():
48+ orig_getopt = getattr(getopt, 'gnu_getopt')
49+ orig_name = 'gnu_getopt'
50+ else:
51+ orig_getopt = getattr(getopt, 'getopt')
52+ orig_name = 'getopt'
53+
54+ def _sneaky(*args, **kw):
55+ optlist, unparsed_args = orig_getopt(*args, **kw)
56+ sneaky_unparsed_args['value'] = unparsed_args
57+ return optlist, unparsed_args
58+
59+ try:
60+ setattr(getopt, orig_name, _sneaky)
61+ args = gflags.FlagValues.__call__(self, argv)
62+ except gflags.UnrecognizedFlagError:
63+ # Undefined args were found, for now we don't care so just
64+ # act like everything went well
65+ # (these three lines are copied pretty much verbatim from the end
66+ # of the __call__ function we are wrapping)
67+ unparsed_args = sneaky_unparsed_args['value']
68+ if unparsed_args:
69+ if self.IsGnuGetOpt():
70+ args = argv[:1] + unparsed
71+ else:
72+ args = argv[:1] + original_argv[-len(unparsed_args):]
73+ else:
74+ args = argv[:1]
75+ finally:
76+ setattr(getopt, orig_name, orig_getopt)
77+
78+ # Store the arguments for later, we'll need them for new flags
79+ # added at runtime
80+ self.__dict__['__stored_argv'] = original_argv
81+ self.__dict__['__was_already_parsed'] = True
82+ self.ClearDirty()
83+ return args
84+
85+ def SetDirty(self, name):
86+ """Mark a flag as dirty so that accessing it will case a reparse."""
87+ self.__dict__['__dirty'].append(name)
88+
89+ def IsDirty(self, name):
90+ return name in self.__dict__['__dirty']
91+
92+ def ClearDirty(self):
93+ self.__dict__['__is_dirty'] = []
94+
95+ def WasAlreadyParsed(self):
96+ return self.__dict__['__was_already_parsed']
97+
98+ def ParseNewFlags(self):
99+ if '__stored_argv' not in self.__dict__:
100+ return
101+ new_flags = FlagValues()
102+ for k in self.__dict__['__dirty']:
103+ new_flags[k] = gflags.FlagValues.__getitem__(self, k)
104+
105+ new_flags(self.__dict__['__stored_argv'])
106+ for k in self.__dict__['__dirty']:
107+ setattr(self, k, getattr(new_flags, k))
108+ self.ClearDirty()
109+
110+ def __setitem__(self, name, flag):
111+ gflags.FlagValues.__setitem__(self, name, flag)
112+ if self.WasAlreadyParsed():
113+ self.SetDirty(name)
114+
115+ def __getitem__(self, name):
116+ if self.IsDirty(name):
117+ self.ParseNewFlags()
118+ return gflags.FlagValues.__getitem__(self, name)
119+
120+ def __getattr__(self, name):
121+ if self.IsDirty(name):
122+ self.ParseNewFlags()
123+ return gflags.FlagValues.__getattr__(self, name)
124+
125+
126+FLAGS = FlagValues()
127+
128+
129+def _wrapper(func):
130+ def _wrapped(*args, **kw):
131+ kw.setdefault('flag_values', FLAGS)
132+ func(*args, **kw)
133+ _wrapped.func_name = func.func_name
134+ return _wrapped
135+
136+
137+DEFINE_string = _wrapper(gflags.DEFINE_string)
138+DEFINE_integer = _wrapper(gflags.DEFINE_integer)
139+DEFINE_bool = _wrapper(gflags.DEFINE_bool)
140+DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
141+DEFINE_float = _wrapper(gflags.DEFINE_float)
142+DEFINE_enum = _wrapper(gflags.DEFINE_enum)
143+DEFINE_list = _wrapper(gflags.DEFINE_list)
144+DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
145+DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
146+DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
147+
148+
149+def DECLARE(name, module_string, flag_values=FLAGS):
150+ if module_string not in sys.modules:
151+ __import__(module_string, globals(), locals())
152+ if name not in flag_values:
153+ raise gflags.UnrecognizedFlag(
154+ "%s not defined by %s" % (name, module_string))
155+
156
157 # __GLOBAL FLAGS ONLY__
158 # Define any app-specific flags in their own files, docs at:
159
160=== added file 'nova/tests/declare_flags.py'
161--- nova/tests/declare_flags.py 1970-01-01 00:00:00 +0000
162+++ nova/tests/declare_flags.py 2010-08-03 16:04:46 +0000
163@@ -0,0 +1,23 @@
164+# vim: tabstop=4 shiftwidth=4 softtabstop=4
165+
166+# Copyright 2010 United States Government as represented by the
167+# Administrator of the National Aeronautics and Space Administration.
168+# All Rights Reserved.
169+#
170+# Licensed under the Apache License, Version 2.0 (the "License"); you may
171+# not use this file except in compliance with the License. You may obtain
172+# a copy of the License at
173+#
174+# http://www.apache.org/licenses/LICENSE-2.0
175+#
176+# Unless required by applicable law or agreed to in writing, software
177+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
178+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
179+# License for the specific language governing permissions and limitations
180+# under the License.
181+
182+from nova import flags
183+
184+FLAGS = flags.FLAGS
185+
186+flags.DEFINE_integer('answer', 42, 'test flag')
187
188=== added file 'nova/tests/flags_unittest.py'
189--- nova/tests/flags_unittest.py 1970-01-01 00:00:00 +0000
190+++ nova/tests/flags_unittest.py 2010-08-03 16:04:46 +0000
191@@ -0,0 +1,87 @@
192+# vim: tabstop=4 shiftwidth=4 softtabstop=4
193+
194+# Copyright 2010 United States Government as represented by the
195+# Administrator of the National Aeronautics and Space Administration.
196+# All Rights Reserved.
197+#
198+# Licensed under the Apache License, Version 2.0 (the "License"); you may
199+# not use this file except in compliance with the License. You may obtain
200+# a copy of the License at
201+#
202+# http://www.apache.org/licenses/LICENSE-2.0
203+#
204+# Unless required by applicable law or agreed to in writing, software
205+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
206+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
207+# License for the specific language governing permissions and limitations
208+# under the License.
209+
210+from nova import exception
211+from nova import flags
212+from nova import test
213+
214+
215+class FlagsTestCase(test.TrialTestCase):
216+ def setUp(self):
217+ super(FlagsTestCase, self).setUp()
218+ self.FLAGS = flags.FlagValues()
219+ self.global_FLAGS = flags.FLAGS
220+
221+ def test_define(self):
222+ self.assert_('string' not in self.FLAGS)
223+ self.assert_('int' not in self.FLAGS)
224+ self.assert_('false' not in self.FLAGS)
225+ self.assert_('true' not in self.FLAGS)
226+
227+ flags.DEFINE_string('string', 'default', 'desc', flag_values=self.FLAGS)
228+ flags.DEFINE_integer('int', 1, 'desc', flag_values=self.FLAGS)
229+ flags.DEFINE_bool('false', False, 'desc', flag_values=self.FLAGS)
230+ flags.DEFINE_bool('true', True, 'desc', flag_values=self.FLAGS)
231+
232+ self.assert_(self.FLAGS['string'])
233+ self.assert_(self.FLAGS['int'])
234+ self.assert_(self.FLAGS['false'])
235+ self.assert_(self.FLAGS['true'])
236+ self.assertEqual(self.FLAGS.string, 'default')
237+ self.assertEqual(self.FLAGS.int, 1)
238+ self.assertEqual(self.FLAGS.false, False)
239+ self.assertEqual(self.FLAGS.true, True)
240+
241+ argv = ['flags_test',
242+ '--string', 'foo',
243+ '--int', '2',
244+ '--false',
245+ '--notrue']
246+
247+ self.FLAGS(argv)
248+ self.assertEqual(self.FLAGS.string, 'foo')
249+ self.assertEqual(self.FLAGS.int, 2)
250+ self.assertEqual(self.FLAGS.false, True)
251+ self.assertEqual(self.FLAGS.true, False)
252+
253+ def test_declare(self):
254+ self.assert_('answer' not in self.global_FLAGS)
255+ flags.DECLARE('answer', 'nova.tests.declare_flags')
256+ self.assert_('answer' in self.global_FLAGS)
257+ self.assertEqual(self.global_FLAGS.answer, 42)
258+
259+ # Make sure we don't overwrite anything
260+ self.global_FLAGS.answer = 256
261+ self.assertEqual(self.global_FLAGS.answer, 256)
262+ flags.DECLARE('answer', 'nova.tests.declare_flags')
263+ self.assertEqual(self.global_FLAGS.answer, 256)
264+
265+ def test_runtime_and_unknown_flags(self):
266+ self.assert_('runtime_answer' not in self.global_FLAGS)
267+
268+ argv = ['flags_test', '--runtime_answer=60', 'extra_arg']
269+ args = self.global_FLAGS(argv)
270+ self.assertEqual(len(args), 2)
271+ self.assertEqual(args[1], 'extra_arg')
272+
273+ self.assert_('runtime_answer' not in self.global_FLAGS)
274+
275+ import nova.tests.runtime_flags
276+
277+ self.assert_('runtime_answer' in self.global_FLAGS)
278+ self.assertEqual(self.global_FLAGS.runtime_answer, 60)
279
280=== added file 'nova/tests/runtime_flags.py'
281--- nova/tests/runtime_flags.py 1970-01-01 00:00:00 +0000
282+++ nova/tests/runtime_flags.py 2010-08-03 16:04:46 +0000
283@@ -0,0 +1,23 @@
284+# vim: tabstop=4 shiftwidth=4 softtabstop=4
285+
286+# Copyright 2010 United States Government as represented by the
287+# Administrator of the National Aeronautics and Space Administration.
288+# All Rights Reserved.
289+#
290+# Licensed under the Apache License, Version 2.0 (the "License"); you may
291+# not use this file except in compliance with the License. You may obtain
292+# a copy of the License at
293+#
294+# http://www.apache.org/licenses/LICENSE-2.0
295+#
296+# Unless required by applicable law or agreed to in writing, software
297+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
298+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
299+# License for the specific language governing permissions and limitations
300+# under the License.
301+
302+from nova import flags
303+
304+FLAGS = flags.FLAGS
305+
306+flags.DEFINE_integer('runtime_answer', 54, 'test flag')
307
308=== modified file 'run_tests.py'
309--- run_tests.py 2010-07-27 20:08:22 +0000
310+++ run_tests.py 2010-08-03 16:04:46 +0000
311@@ -54,6 +54,7 @@
312 from nova.tests.api_unittest import *
313 from nova.tests.cloud_unittest import *
314 from nova.tests.compute_unittest import *
315+from nova.tests.flags_unittest import *
316 from nova.tests.model_unittest import *
317 from nova.tests.network_unittest import *
318 from nova.tests.objectstore_unittest import *
319
320=== modified file 'run_tests.sh'
321--- run_tests.sh 2010-07-28 04:39:58 +0000
322+++ run_tests.sh 2010-08-03 16:04:46 +0000
323@@ -4,7 +4,7 @@
324 with_venv=tools/with_venv.sh
325
326 if [ -e ${venv} ]; then
327- ${with_venv} python run_tests.py
328+ ${with_venv} python run_tests.py $@
329 else
330 echo "You need to install the Nova virtualenv before you can run this."
331 echo ""