-
Notifications
You must be signed in to change notification settings - Fork 54
Conversation
c62ca76
to
696aad8
Compare
Pull Request Test Coverage Report for Build 1797
💛 - Coveralls |
Officially the first PR which increases the coverage 💟 🍸 🤙 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few inlines comments.
Also, we've got a number of things where it might be nice to be more strict; we can probably check that URLs are syntactically valid URLs, and the check the format of the vlan pools, etc.
Down the line it might be nice to swap out some of the lambdas with Use
schema, and start using the result of validating the schema, so we don't have to parse out the values again separately.
hil/config.py
Outdated
}, | ||
Optional('devel'): { | ||
Optional('dry_run'): | ||
lambda s: string_is_bool(s), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lambda is superfluous here (and elsewhere); you can just do Optional('dry_run'): string_is_bool
. You'll have to move the definition of that function up though, so it's defined before core_schema.
hil/ext/switches/n3000.py
Outdated
lambda v: 0 < v and v <= 4093, | ||
schema.Use(str)), | ||
'dummy_vlan': And(Use(int), | ||
lambda v: 0 < v and v <= 4093, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're at it, this python supports a shorthand, so this can be 0 < v <= 4093
.
Also, is there a reason for 4093? The others are 4096, which makes more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, the Dell N3000 switch only showed that it's from 1 to 4093.
R5-PA-C01-U39#show vlan id ?
<1-4093> Enter VLAN ID.
R5-PA-C01-U39#show vlan id 4094
^
Value is out of range. The valid range is 1 to 4093.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. Would you mind adding a comment to that effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In network-drivers it's mentioned that we enable VLAN 2-4093, but it doesn't mention the reason. @Izhmash, can you put a comment in the code while you are at it and update the docs to make it more clear?
hil/cli.py
Outdated
@@ -853,5 +854,7 @@ def main(): | |||
sys.exit('Error: %s\n' % e.message) | |||
except BadArgumentError as e: | |||
sys.exit('Error: %s\n' % e.message) | |||
except SchemaError as e: | |||
sys.exit('Config error: %s\n' % e.message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be slighly more explicit here; Error in config file
.
hil/ext/switches/nexus.py
Outdated
lambda v: 0 <= v and v <= 4096, | ||
schema.Use(str)), | ||
'dummy_vlan': And(Use(int), | ||
lambda v: 0 <= v and v <= 4096, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while we are talking about vlan ranges, I am sure 0 shouldn't be included in this range.
haas-loaner# show vlan id ?
<1-3967,4048-4093> VLAN ID 1-4094 or range(s): 1-5, 10 or 2-5,7-19
>
haas-loaner# show vlan id 0
83db946
to
f8ecfb0
Compare
I believe I addressed the comments above. @zenhack for more in-depth config checks, I've added checks for malformed web/db urls, a check for the VLAN pool list, and a check for log_level. Naved and I discussed checking the headnode config options, and we decided they can be left as I considered checking |
Tests will be on the way as soon as we lock in which config checks should be there. |
hil/config.py
Outdated
@@ -89,3 +170,4 @@ def setup(filename='hil.cfg'): | |||
load(filename) | |||
configure_logging() | |||
load_extensions() | |||
validate_config() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be more clear, check for bad values of log_dir
in validate_config()
and call it before configure_logging()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably check this by catching the IOError and reporting the issue; conceptually doing it here is weird, because the problem is an absent or non-writable directory, not a malformed confg file. Slightly more pragmatically, there's a race condition where if the directory is deleted/permissions changed between the check and the use, you could still get the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zenhack I agree that conceptually it's strange to do it here as well. For the scope of this PR, I'm thinking about moving the config validation up and then only checking for a malformed path. The IO check can be done in another small PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of inline comments.
hil/config.py
Outdated
"""Check if a string is a valid list of VLANs""" | ||
for r in option.split(","): | ||
r = r.strip().split("-") | ||
if not all(s.isdigit() for s in r): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- you can actually just call
isdigit
once:
>>> '423'.isdigit()
True
- Why not actually check the range?
s.isdigit() and 0 < int(s) <= 4096
Or, for that matter, just lift the relevant bit of the schema from the dummy_vlan field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check the range here, good for a first safety check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also after I do the r = r.strip().split("-")
line, I sometimes end up with a list of strings, so I think I still need to loop through them with all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're right, I was thinking you were checking each character.
hil/config.py
Outdated
|
||
cfg = ConfigParser.RawConfigParser() | ||
cfg.optionxform = str | ||
|
||
|
||
def string_is_bool(option): | ||
"""Check if a string matches ConfigParser's definition of a bool""" | ||
return option.lower() in ['true', 'yes', 'on', '1', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could be making better use of the library; Use
, And
, and Or
are handy:
return And(Use(option.lower), Or('true', 'yes', 'on', ...))
This can be applied in a number of other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason, having this function like this instead just places whatever is in option
into my validation schema, causing any config string to pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works however if I put the And(...)
directly into the schema
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zenhack how partial are you to using more of these library functions? I've been hacking at it for a while, and returning in this style causes the validation to not happen. From debugging it looks like there's an issue with returning the validation from another function, since it does work when hard-coding into the Schema dictionary.
To use more of the library functions I could instead remove some of the functions (like string_is_bool
) and write the validation code right into the schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you probably would need to do something like:
return And(Use(str.lower), Or(...)).validate(option)
I don't feel strongly; if it makes things awkward you can leave them as they are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's what I was missing. That definitely works, but it comes down now to how ugly the error messages look:
Current code error example:
Error in config file: string_is_bool('Frue') should evaluate to True
New suggestion error example:
Error in config file: Or('true', 'yes', 'on', '1', 'false', 'no', 'off', '0') did not validate 'Frue'
'0' does not match 'Frue'
I'm not 100% sure why the errors look different now, perhaps because it's a separate validation happening before the main dictionary validation. I think the 2nd error option might expose a little too much code to the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could go either way re: the error message. They're both exposing implementation detail a bit, but the later has the virtue that it actually gives the user a good hint as to what the correct settnigs are.
hil/config.py
Outdated
def string_is_db_uri(option): | ||
"""Check if a string is a valid DB URI""" | ||
url = urlparse(option) | ||
if not ('postgres' in url.scheme or 'sqlite' in url.scheme): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to use in
here; they should be exactly equal.
Alternatively, you could do url.scheme in ('postgres', 'sqlite')
.
hil/config.py
Outdated
@@ -89,3 +170,4 @@ def setup(filename='hil.cfg'): | |||
load(filename) | |||
configure_logging() | |||
load_extensions() | |||
validate_config() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably check this by catching the IOError and reporting the issue; conceptually doing it here is weird, because the problem is an absent or non-writable directory, not a malformed confg file. Slightly more pragmatically, there's a race condition where if the directory is deleted/permissions changed between the check and the use, you could still get the error.
hil/ext/auth/keystone.py
Outdated
from hil.model import Project | ||
from hil import auth, rest | ||
import logging | ||
import sys | ||
|
||
logger = rest.ContextLogger(logging.getLogger(__name__), {}) | ||
|
||
core_schema[__name__] = { | ||
'auth_url': str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could probably check for a valid URL here.
Would be worth tightening the other fields here as well, where possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the only field I can check here is the auth_url
. I considered auth_protocol
at least, but the authentication plugins available can differ depending on what's installed.
hil/cli.py
Outdated
@@ -853,5 +854,7 @@ def main(): | |||
sys.exit('Error: %s\n' % e.message) | |||
except BadArgumentError as e: | |||
sys.exit('Error: %s\n' % e.message) | |||
except SchemaError as e: | |||
sys.exit('Error in config file: %s\n' % e.message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed during the weekly meeting. Probably we should just return the e
? Since e.message
might be `None?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to do that, yeah. I'm not sure if it's within the scope of this PR though.
hil/config.py
Outdated
"""Check if a string is a valid web URL""" | ||
url = urlparse(option) | ||
if url.scheme == '' or url.netloc == '': | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious whether we could just return url.scheme != '' and url.netloc != ''
.
I know you may wanna return the False
explicitly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that works fine.
0181369
to
3be3cae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a note for the e.message
may not exist.
Tests are in progress now. |
cce5da3
to
9f5d076
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple more things. Pretty close now.
hil/config.py
Outdated
log_file, when='D', interval=1)) | ||
except IOError: | ||
sys.exit("Error: log directory does not exist or user " | ||
"has insufficient permissions") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to distinguish between diferent errors:
import errno
...
except IOError as e:
if e.errno == errno.ENOENT:
# no such file or directory
elif e.errno == errno.EPERM:
...
hil/config.py
Outdated
|
||
cfg = ConfigParser.RawConfigParser() | ||
cfg.optionxform = str | ||
|
||
|
||
def string_is_bool(option): | ||
"""Check if a string matches ConfigParser's definition of a bool""" | ||
return option.lower() in ['true', 'yes', 'on', '1', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could go either way re: the error message. They're both exposing implementation detail a bit, but the later has the virtue that it actually gives the user a good hint as to what the correct settnigs are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy. Just address @zenhack's comments.
I've updated the functions to better use the schema library. I tried to not use lambdas, but I went with them since it seems to be a standard in the schema docs. I also fixed up the |
2924671
to
028b23b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing (caught by travis), but otherwise I'm happy.
tests/unit/config.py
Outdated
opts = ['frue', 'nes', 'yOn', '2', 'Salse', 'Go', 'bff', '3'] | ||
for s in opts: | ||
with pytest.raises(SchemaError): | ||
config.string_is_bool(opts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think this should config.string_is_bool(s)
. Yay for linters catching real bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah so that's where it was! I was having a hard time figuring that out last week.
* Minor fixes
* Now catching bad directory IOErrors and reporting them
*Changed config validation funtions to better use schema library *Updated tests to match the updated functions
Everything should be all set now. |
LGTM, merging. |
Addresses #45:
Note: I am going to start working on testing, but I wanted to get this out for review first since I'll be out next week.