-
Notifications
You must be signed in to change notification settings - Fork 108
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
Add endpoint configuration query #2024
Add endpoint configuration query #2024
Conversation
This comment has been minimized.
This comment has been minimized.
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.
Instead of a separate API endpoint, why not render the web page served to the browser with the information embedded in the rendered page?
One big reason is that this is part of the server, not the dashboard. It can be used by curl, or Postman, or any other client to learn the server configuration. Now, we still need the client to know how to do that first query to find the configuration, and that needs to be known externally somehow. That's where your suggestion might come in, on the dashboard side. It also occurs to me that a GET server:8001 probably should return this information rather than requiring the client to know server:8001/api/v1/endpoints. Then if we do a "v2" API the default unqualified endpoint would return the latest version info. Maybe it should even identify itself as "server": "Pbench server 0.71-xxx" or some such. |
@dbutenhof With the endpoint configuration being integrated into the dashboard binary, is this server endpoint going to be a starting point for the deployment process to populate |
This comment has been minimized.
This comment has been minimized.
I've addressed these requirements in distributed-system-analysis/pbench-dashboard#110. Please let me know if these changes meet the requirements for deploying the dashboard binary with a standalone 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.
Looks good, but I found a couple of nits, and I have a bunch of pointed questions.
# If the URI is parameterized with a Flask "<type:name>" | ||
# template string, we don't want to report it, so we remove | ||
# it from the URI. We derive an API name by converting the |
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.
Would it be more useful to report the template string with JS template arguments in it? E.g.,
param_template = re.compile(r"<(?P<type_name>\w+):(?P<param_name>\w+)>")
[...]
url = self.param_template.sub("${\g<param_name>}", url)
such that "/api/v1/foo/<string:name>/detail/<string:param>"
would be reported as ".../api/v1/foo/${name}/detail/${param}"
.
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 client has to remove/substitute the templates then and I figured that wasn't very generic. I didn't really want to make it so Javascript-specific, either, and Javascript will only do that sort of substitution in literal template strings (backticked), so far as I can tell; although it's just a .replace
away, it's not quite that neat.
Anyway, just letting it append the username to the end of the URI seemed easiest, at least for now.
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 second the idea of not making this Javascript specific.
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 guess I was proposing two things:
- make the URI available as a template
- make it easy for the client to fill in the template (which is why I offered JS syntax)
It doesn't have to be Javascript format, but I'll point out that that format also works for shell, so it's not exactly JS-specific. ;-) We could use %s
instead of ${...}
, but I opted for the latter because it provides semantic information.
It's true that the client will likely need to have some foreknowledge in order to fill in the template, but it will need to have even closer coupling to produce the URI if we just lop it off at the first parameter. Providing a template removes the need for some of that coupling.
Apparently there is a (proposed) IETF standard for URI templates.
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.
Let's track in issue #2154.
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 needs a rebase.
# If the URI is parameterized with a Flask "<type:name>" | ||
# template string, we don't want to report it, so we remove | ||
# it from the URI. We derive an API name by converting the |
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 second the idea of not making this Javascript specific.
This defines a new API which will supply a client (e.g., the UI dashboard) with all the information it needs to adapt to this particular server instance including full API URIs and metadata like index names.
This API was previously assuming direct access to the Pbench server and API port. While we have a default Apache reverse-proxy configuration in our Ansible config role, that means we expect the client to access our services through the http port (80) rather than the configured server listening port (e.g., 8001). However, with an external reverse proxy, we don't want to advertise access to the local host/port at all; instead we want to point clients to the configured reverse proxy host and port. This adds a new [pbench-server] config variable called proxy_host, which by default mirrors the default host name on port 80 but can be altered to direct the client to access the reverse-proxy port; for example, proxy_host=pbench.lab.example.com:8901 will report an endpoint configuration directing all endpoints through that host and port.
Note that the unit test remains as it was, as a check that we're generating the proper set; this will have to be maintained as our API expands, but that seems like a worthwhile validation.
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.
Looks good. I just have one question.
import re | ||
from flask.globals import current_app | ||
from logging import Logger | ||
|
||
from flask.globals import current_app | ||
from flask_restful import Resource, abort | ||
from flask import request, jsonify | ||
from urllib.parse import urljoin | ||
|
||
from pbench.server import PbenchServerConfig | ||
from pbench.server.api.resources.query_apis import get_index_prefix |
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.
What is our rubric for structuring import statements?
I get keeping the pbench
ones separate and having them follow the "system" ones, but what's the distinction which sets re
and logging
apart from flask
and urllib
?
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.
Typically what PEP 8 talks about, which is alphabetical order grouped by import
s first, from
next, where standard library are grouped, then external libraries, then project imports.
The Pbench server API listens on port 8001; we want to be able to access the API externally through normal HTTP/HTTPS. With an external reverse proxy gateway (e.g., NGINX) this can be accomplished by redirecting (e.g.) pbench.example.com:/api/ to the actual server at port 8001 and opening the 8001/tcp port in the server firewall. Our default Ansible deployment mechanism configures a local Apache which is directly accessed from outside. This PR adds an Apache reverse proxy to allow external API access on port 80. With these changes, the Pbench gunicorn wsgi server will still listen on port 8001, but this port won't need to be opened externally as Apache will proxy /api/ to port 8001. This means that all external accesses will go to port 80 (http) or (when we get there) 433 (https). In the short term this affects the pbench_server configuration in the dashboard endpoints.js. Once distributed-system-analysis#2024 (server-side endpoint configuration and metadata) is merged, I plan to change the dashboard to use that, simply fetch-ing that from window.origin and assigning it to window.endpoints within the launch html page, removing the need to manage a local static endpoints.js entirely.
The advertised endpoints are self-configuring based on reverse-proxy configuration as long as the reverse-proxy service advertises the configured external host address either via
Forward: [...];host=<name>[;...]
or byX-Forwarded-Host: <host>[,...]
(note that in the latter case, the first hostname given is presumed to be the first and most appropriate proxy).The following sample is generated by a
GET <host>/api/v1/endpoints
with the headerX-Forwarded-Host: pbench.perf.lab.eng.bos.redhat.com:8902
, for example:This requires that the dashboard do a
fetch
back through theorigin
address with suffix/api/v1/endpoints
, and stash the resulting JSON somewhere. I have a prototype dashboard branch where I've simply replaced the<script>
indocument.ejs
with a more dynamic version that amounts to effectively the same HTTP and Javascript operations performed to load the staticendpoints.js
used by the current code:(Though whether this is the "best" way to do it may be another question, it works... of course with appropriate changes to the endpoint references through the code, and I haven't yet gotten the e2e tests debugged enough to push a draft PR.)
NOTE
The
user
endpoint in Flask ends with a parameter template string; I decided to remove that from the endpoint, but leave the trailing/
so the dashboard can simply append the username on the URI. The simple parsing here won't work if we end up with a template string that's not at the end, but that's agile.