-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor suite runtime server API logic #2373
Refactor suite runtime server API logic #2373
Conversation
8efedbb
to
80e24d4
Compare
@matthewrmshin - from your description, this looks great. Could you just clarify a few things though:
(Just want to make sure I understand all of this!) |
The main rationale is to reduce complexity. This is achieved here by combining the API into a simple set of methods under a single façade, all visible under the same roof. A side effect of this pattern is that it can potentially allows us to turn the relationship between the web server and the suite upside down. E.g. it will be possible to start up a web server on its own and then mount multiple suites to it! I cannot really see any case of alternate communication methods to HTTPS at the moment. The current approach has been a bit awkward - with cherrypy logic leaked back into the general network package. (The original approach made sense when we were doing remote object calls in Pyro, but it has become awkward now that we are supposed to have a Restful interface.) This new approach should be very easy to change into a different set of implementation should there be a case to do so. (But now that you have said it, I think I'll rename the modules to make it clearer that they are HTTPS client, and HTTPS server and service façade based on cherrypy.) |
Profiling by running the complex suite shows minor improvement to Elapsed/CPU time against master but very little change to memory: 1st run:
2nd run:
where:
|
Re profiling results - is that pretty much what you expected? |
I have expected a mainly neutral result. The small performance gain is a bonus. |
excellent :-) |
ca7160c
to
a031738
Compare
@matthewrmshin you remark "I cannot really see any case of alternate communication methods to HTTPS at the moment." I have a few things that may require an alternate communication method, but haven't started yet on implementing them. Only reason to bring it up here is to serve as a reminder that there are other options that others are thinking about (and has nothing to do with this PR). |
To reassure, this change should make it easier to add other forms of communication protocols rather than making it harder. Logic unrelated to the network have been moved to where they belong. The API is now in one place and simplified. There are lots more to do to really complete the refactor, but I do feel that this change is taking us towards the right direction for the future. |
a031738
to
9b07160
Compare
@matthewrmshin thanks for your feedback - lots of this looked simplified, but upon further review what set of my alarm bells turned out to be me mis-reading something! This looks very helpful toward what I'm thinking about. |
35b8877
to
124a51e
Compare
124a51e
to
34427c2
Compare
Conflict resolved. Branch re-based. |
Adding my profiling results:
|
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.
That certainly tidied up a bunch of stuff. Nice work. I didn't find any real problems.
bin/cylc-validate
Outdated
) | ||
'WARNING, %s:' | ||
' not explicitly defined in dependency graphs (deprecated)' | ||
) % name |
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 think this code block can be removed? - implicit-cycling is now obsolete, not just deprecated. (I'm slightly confused though, as I can't trigger this block with implicit cycling even back at d2c77 where it was first introduced 2 years ago).
lib/cylc/broadcast_mgr.py
Outdated
return (list(cancel_keys) in | ||
[prune[2:] for prune in prunes if prune[2:]]) | ||
def _settings_to_keys_list(broadcasts): | ||
"""Return a list containing each setting dict keys as a list. |
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.
(don't need "as a list" on the end of that sentence?)
lib/cylc/mp_pool.py
Outdated
try: | ||
self.pool.join() | ||
except AssertionError: | ||
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.
Why do we need to catch AssertionError here?
lib/cylc/scheduler.py
Outdated
# is set to tell process_tasks() that task processing is required. | ||
broadcast_mgr = self.task_events_mgr.broadcast_mgr | ||
broadcast_mgr.add_ext_triggers( | ||
self.ext_trigger_queue) |
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.
(no need for a line break here)
34427c2
to
91e07ff
Compare
Branch rebased. Comments addressed. |
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.
All looks good to me, some comments:
bin/cylc-ext-trigger
Outdated
|
||
sent = False | ||
i_try = 0 | ||
while not sent and i_try < max_n_tries: |
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 this be:
for i_try in range(0, max_n_tries):
try:
...
except _:
...
else:
...
break
else:
sys.exit('ERROR: send failed')
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.
Done with similar logic.
lib/cylc/network/httpserver.py
Outdated
self._report() | ||
self._check_access_priv('full-read') | ||
if isinstance(pruned, basestring): | ||
pruned = ast.literal_eval(pruned) |
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.
Should these ast.literal_eval
calls be wrapped in try/except statements?
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.
Now wrapped by a static method - raise 400 with a useful error message on exception.
self.clients[uuid] = time() | ||
self._housekeep() | ||
|
||
def _report_id_requests(self): |
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.
Nice!
lib/cylc/suite_logging.py
Outdated
except (IOError, OSError): | ||
size = 0 | ||
if size == prev_size: | ||
return [], prev_size |
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.
return '', prev_size
?
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.
Done.
lib/cylc/unicode_util.py
Outdated
|
||
if METHOD in ["https", "http"]: | ||
from cylc.network.https.daemon import CommsDaemon | ||
def unicode_encode(data): |
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.
This doesn't encode non-unicode strings, purposeful?
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.
This function comes from https://github.com/cylc/cylc/blob/master/lib/cylc/network/https/util.py#L65 and I have not made any logical change to it, so not sure.
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.
This line confuses me {unicode_encode(key): unicode_encode(value)}
.
Why would we try to unicode_encode
the key if unicode_encode
doesn't encode strings.
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.
Perhaps the purpose of this logic is to ensure any unicode conforms to UTF-8 rather than to encode strings to unicode. Perhaps a name change could alleviate this confusion.
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.
Yes - a unicode
data type is not encoded. u'my string'.encode()
converts the unicode string into bytes. So if it's already a str
(which is already bytes) it is skipped, otherwise if it's a unicode
string it gets encoded to UTF-8.
https://docs.python.org/2/howto/unicode.html#python-2-x-s-unicode-support
https://stackoverflow.com/questions/10288016/usage-of-unicode-and-encode-functions-in-python
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.
Will rename this utf8_enforce
.
@cherrypy.expose | ||
@cherrypy.tools.json_in() | ||
@cherrypy.tools.json_out() | ||
def clear_broadcast( |
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.
Some thoughts on these methods:
- The privilege levels (e.g.
'full-control'
) could be constants. - The
self._check_access_priv(<privlevel>)
method could be a decorator (e.g.@privilege_full_control
) - A
self._report()
call is present in every method? Could this be factored out?
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.
Added constants.
self._check_access_priv(<privlevel>)
and self._report()
now combined into a single method call. Not done via decorator, but it is now a single line per method.
Combine multiple client classes into a single one. Single HTTPS call from GUI on idle suite. Combine all server classes into a single service facade class. Move non-network related logic from the `cylc.network` package. Remove `cylc.network.https` package. Flatten API method serve points. Remove singleton suite config logic.
Remove deprecated test in validate. Improve doc string in broadcast manager. Improve comment for AssertionError on joining multiprocessing pool. Remove unnecessary new line in schduler. Connect scheduler to server after config is loaded to prevent rare traceback from scan.
Improve retry message send loop in `cylc ext-trigger`. Constants for access privilege levels. Wrap `ast.literal_eval` in httpserver. * return 400 with useful error message on failure. Check access privilege and report are now done by single method call. Fix bad return in `cylc.suite_logging`. Rename `unicode_encode` to `utf8_enforce`.
91e07ff
to
f4f6648
Compare
@matthewrmshin - did you consider backward compatibility (for client-server interaction) with this change. (We don't have it, but maybe you considered it!) |
I considered it. I have put in compatibility with scan for obvious reasons. The other methods are not compatible at the moment. I can put something in if you think it is desirable to do so. |
I'm reasonably happy to tell users to deal with it, but you have a lot more users - so I'll go with you on this. |
"""Start quick web service.""" | ||
# cherrypy.config["tools.encode.on"] = True | ||
# cherrypy.config["tools.encode.encoding"] = "utf-8" | ||
cherrypy.config["server.socket_host"] = get_suite_host() |
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.
Note: This was changed from '0.0.0.0' in lib/cylc/network/https/daemon.py
since because Codacy was complaining about possible binding to all interfaces.
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.
From Bandit documentation (the security analyzer run by Codacy):
Binding to all network interfaces can potentially open up a service to traffic on unintended interfaces, that may not be properly documented or secured. This plugin test looks for a string pattern “0.0.0.0” that may indicate a hardcoded binding to all network interfaces.
Highlights:
https://host:port/command/hold_suite
=>https://host:port/hold_suite
https://host:port/broadcast/get
=>https://host:port/get_broadcast
cylc.network
package.SuiteConfig
, are now normal object instances.cylc.broadcast_mgr
module.cylc.flags.pflag
is now an attribute of theTaskEventsMgr
.cylc.state_summary_mgr
module.cylc.network.https
package.