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
=== modified file 'nova/flags.py'
--- nova/flags.py 2010-07-28 23:11:02 +0000
+++ nova/flags.py 2010-08-03 16:04:46 +0000
@@ -21,16 +21,145 @@
21where they're used.21where they're used.
22"""22"""
2323
24import getopt
24import socket25import socket
2526import sys
2627
27from gflags import *28import gflags
2829
29# This keeps pylint from barfing on the imports30
30FLAGS = FLAGS31class FlagValues(gflags.FlagValues):
31DEFINE_string = DEFINE_string32 """Extension of gflags.FlagValues that allows undefined and runtime flags.
32DEFINE_integer = DEFINE_integer33
33DEFINE_bool = DEFINE_bool34 Unknown flags will be ignored when parsing the command line, but the
35 command line will be kept so that it can be replayed if new flags are
36 defined after the initial parsing.
37
38 """
39
40 def __init__(self):
41 gflags.FlagValues.__init__(self)
42 self.__dict__['__dirty'] = []
43 self.__dict__['__was_already_parsed'] = False
44 self.__dict__['__stored_argv'] = []
45
46 def __call__(self, argv):
47 # We're doing some hacky stuff here so that we don't have to copy
48 # out all the code of the original verbatim and then tweak a few lines.
49 # We're hijacking the output of getopt so we can still return the
50 # leftover args at the end
51 sneaky_unparsed_args = {"value": None}
52 original_argv = list(argv)
53
54 if self.IsGnuGetOpt():
55 orig_getopt = getattr(getopt, 'gnu_getopt')
56 orig_name = 'gnu_getopt'
57 else:
58 orig_getopt = getattr(getopt, 'getopt')
59 orig_name = 'getopt'
60
61 def _sneaky(*args, **kw):
62 optlist, unparsed_args = orig_getopt(*args, **kw)
63 sneaky_unparsed_args['value'] = unparsed_args
64 return optlist, unparsed_args
65
66 try:
67 setattr(getopt, orig_name, _sneaky)
68 args = gflags.FlagValues.__call__(self, argv)
69 except gflags.UnrecognizedFlagError:
70 # Undefined args were found, for now we don't care so just
71 # act like everything went well
72 # (these three lines are copied pretty much verbatim from the end
73 # of the __call__ function we are wrapping)
74 unparsed_args = sneaky_unparsed_args['value']
75 if unparsed_args:
76 if self.IsGnuGetOpt():
77 args = argv[:1] + unparsed
78 else:
79 args = argv[:1] + original_argv[-len(unparsed_args):]
80 else:
81 args = argv[:1]
82 finally:
83 setattr(getopt, orig_name, orig_getopt)
84
85 # Store the arguments for later, we'll need them for new flags
86 # added at runtime
87 self.__dict__['__stored_argv'] = original_argv
88 self.__dict__['__was_already_parsed'] = True
89 self.ClearDirty()
90 return args
91
92 def SetDirty(self, name):
93 """Mark a flag as dirty so that accessing it will case a reparse."""
94 self.__dict__['__dirty'].append(name)
95
96 def IsDirty(self, name):
97 return name in self.__dict__['__dirty']
98
99 def ClearDirty(self):
100 self.__dict__['__is_dirty'] = []
101
102 def WasAlreadyParsed(self):
103 return self.__dict__['__was_already_parsed']
104
105 def ParseNewFlags(self):
106 if '__stored_argv' not in self.__dict__:
107 return
108 new_flags = FlagValues()
109 for k in self.__dict__['__dirty']:
110 new_flags[k] = gflags.FlagValues.__getitem__(self, k)
111
112 new_flags(self.__dict__['__stored_argv'])
113 for k in self.__dict__['__dirty']:
114 setattr(self, k, getattr(new_flags, k))
115 self.ClearDirty()
116
117 def __setitem__(self, name, flag):
118 gflags.FlagValues.__setitem__(self, name, flag)
119 if self.WasAlreadyParsed():
120 self.SetDirty(name)
121
122 def __getitem__(self, name):
123 if self.IsDirty(name):
124 self.ParseNewFlags()
125 return gflags.FlagValues.__getitem__(self, name)
126
127 def __getattr__(self, name):
128 if self.IsDirty(name):
129 self.ParseNewFlags()
130 return gflags.FlagValues.__getattr__(self, name)
131
132
133FLAGS = FlagValues()
134
135
136def _wrapper(func):
137 def _wrapped(*args, **kw):
138 kw.setdefault('flag_values', FLAGS)
139 func(*args, **kw)
140 _wrapped.func_name = func.func_name
141 return _wrapped
142
143
144DEFINE_string = _wrapper(gflags.DEFINE_string)
145DEFINE_integer = _wrapper(gflags.DEFINE_integer)
146DEFINE_bool = _wrapper(gflags.DEFINE_bool)
147DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
148DEFINE_float = _wrapper(gflags.DEFINE_float)
149DEFINE_enum = _wrapper(gflags.DEFINE_enum)
150DEFINE_list = _wrapper(gflags.DEFINE_list)
151DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
152DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
153DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
154
155
156def DECLARE(name, module_string, flag_values=FLAGS):
157 if module_string not in sys.modules:
158 __import__(module_string, globals(), locals())
159 if name not in flag_values:
160 raise gflags.UnrecognizedFlag(
161 "%s not defined by %s" % (name, module_string))
162
34163
35# __GLOBAL FLAGS ONLY__164# __GLOBAL FLAGS ONLY__
36# Define any app-specific flags in their own files, docs at:165# Define any app-specific flags in their own files, docs at:
37166
=== added file 'nova/tests/declare_flags.py'
--- nova/tests/declare_flags.py 1970-01-01 00:00:00 +0000
+++ nova/tests/declare_flags.py 2010-08-03 16:04:46 +0000
@@ -0,0 +1,23 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19from nova import flags
20
21FLAGS = flags.FLAGS
22
23flags.DEFINE_integer('answer', 42, 'test flag')
024
=== added file 'nova/tests/flags_unittest.py'
--- nova/tests/flags_unittest.py 1970-01-01 00:00:00 +0000
+++ nova/tests/flags_unittest.py 2010-08-03 16:04:46 +0000
@@ -0,0 +1,87 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19from nova import exception
20from nova import flags
21from nova import test
22
23
24class FlagsTestCase(test.TrialTestCase):
25 def setUp(self):
26 super(FlagsTestCase, self).setUp()
27 self.FLAGS = flags.FlagValues()
28 self.global_FLAGS = flags.FLAGS
29
30 def test_define(self):
31 self.assert_('string' not in self.FLAGS)
32 self.assert_('int' not in self.FLAGS)
33 self.assert_('false' not in self.FLAGS)
34 self.assert_('true' not in self.FLAGS)
35
36 flags.DEFINE_string('string', 'default', 'desc', flag_values=self.FLAGS)
37 flags.DEFINE_integer('int', 1, 'desc', flag_values=self.FLAGS)
38 flags.DEFINE_bool('false', False, 'desc', flag_values=self.FLAGS)
39 flags.DEFINE_bool('true', True, 'desc', flag_values=self.FLAGS)
40
41 self.assert_(self.FLAGS['string'])
42 self.assert_(self.FLAGS['int'])
43 self.assert_(self.FLAGS['false'])
44 self.assert_(self.FLAGS['true'])
45 self.assertEqual(self.FLAGS.string, 'default')
46 self.assertEqual(self.FLAGS.int, 1)
47 self.assertEqual(self.FLAGS.false, False)
48 self.assertEqual(self.FLAGS.true, True)
49
50 argv = ['flags_test',
51 '--string', 'foo',
52 '--int', '2',
53 '--false',
54 '--notrue']
55
56 self.FLAGS(argv)
57 self.assertEqual(self.FLAGS.string, 'foo')
58 self.assertEqual(self.FLAGS.int, 2)
59 self.assertEqual(self.FLAGS.false, True)
60 self.assertEqual(self.FLAGS.true, False)
61
62 def test_declare(self):
63 self.assert_('answer' not in self.global_FLAGS)
64 flags.DECLARE('answer', 'nova.tests.declare_flags')
65 self.assert_('answer' in self.global_FLAGS)
66 self.assertEqual(self.global_FLAGS.answer, 42)
67
68 # Make sure we don't overwrite anything
69 self.global_FLAGS.answer = 256
70 self.assertEqual(self.global_FLAGS.answer, 256)
71 flags.DECLARE('answer', 'nova.tests.declare_flags')
72 self.assertEqual(self.global_FLAGS.answer, 256)
73
74 def test_runtime_and_unknown_flags(self):
75 self.assert_('runtime_answer' not in self.global_FLAGS)
76
77 argv = ['flags_test', '--runtime_answer=60', 'extra_arg']
78 args = self.global_FLAGS(argv)
79 self.assertEqual(len(args), 2)
80 self.assertEqual(args[1], 'extra_arg')
81
82 self.assert_('runtime_answer' not in self.global_FLAGS)
83
84 import nova.tests.runtime_flags
85
86 self.assert_('runtime_answer' in self.global_FLAGS)
87 self.assertEqual(self.global_FLAGS.runtime_answer, 60)
088
=== added file 'nova/tests/runtime_flags.py'
--- nova/tests/runtime_flags.py 1970-01-01 00:00:00 +0000
+++ nova/tests/runtime_flags.py 2010-08-03 16:04:46 +0000
@@ -0,0 +1,23 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19from nova import flags
20
21FLAGS = flags.FLAGS
22
23flags.DEFINE_integer('runtime_answer', 54, 'test flag')
024
=== modified file 'run_tests.py'
--- run_tests.py 2010-07-27 20:08:22 +0000
+++ run_tests.py 2010-08-03 16:04:46 +0000
@@ -54,6 +54,7 @@
54from nova.tests.api_unittest import *54from nova.tests.api_unittest import *
55from nova.tests.cloud_unittest import *55from nova.tests.cloud_unittest import *
56from nova.tests.compute_unittest import *56from nova.tests.compute_unittest import *
57from nova.tests.flags_unittest import *
57from nova.tests.model_unittest import *58from nova.tests.model_unittest import *
58from nova.tests.network_unittest import *59from nova.tests.network_unittest import *
59from nova.tests.objectstore_unittest import *60from nova.tests.objectstore_unittest import *
6061
=== modified file 'run_tests.sh'
--- run_tests.sh 2010-07-28 04:39:58 +0000
+++ run_tests.sh 2010-08-03 16:04:46 +0000
@@ -4,7 +4,7 @@
4with_venv=tools/with_venv.sh4with_venv=tools/with_venv.sh
55
6if [ -e ${venv} ]; then6if [ -e ${venv} ]; then
7 ${with_venv} python run_tests.py7 ${with_venv} python run_tests.py $@
8else 8else
9 echo "You need to install the Nova virtualenv before you can run this."9 echo "You need to install the Nova virtualenv before you can run this."
10 echo ""10 echo ""