Skip to content
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

Merged
merged 7 commits into from
Mar 26, 2021

Conversation

dbutenhof
Copy link
Member

@dbutenhof dbutenhof commented Dec 4, 2020

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 by X-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 header X-Forwarded-Host: pbench.perf.lab.eng.bos.redhat.com:8902, for example:

{
  "api": {
    "controllers_list": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/controllers/list",
    "controllers_months": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/controllers/months",
    "elasticsearch": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/elasticsearch",
    "endpoints": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/endpoints",
    "graphql": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/graphql",
    "host_info": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/host_info",
    "login": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/login",
    "logout": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/logout",
    "register": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/register",
    "upload_ctrl": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/upload/ctrl/",
    "user": "http://pbench.perf.lab.eng.bos.redhat.com:8902/api/v1/api/v1/user/"
  },
  "identification": "Pbench server 0.71.0-11gdb60e9279",
  "indices": {
    "result_data_index": "drb.v5.result-data.",
    "result_index": "drb.v5.result-data-sample.",
    "run_index": "drb.v6.run-data.",
    "run_toc_index": "drb.v6.run-toc."
  }
}

This requires that the dashboard do a fetch back through the origin 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> in document.ejs with a more dynamic version that amounts to effectively the same HTTP and Javascript operations performed to load the static endpoints.js used by the current code:

  <!-- runtime endpoints config -->
  <script type="text/javascript">
    var response = await fetch(window.origin + "/api/v1/endpoints");
    window.endpoints = await response.json();
  </script>

(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.

@dbutenhof

This comment has been minimized.

Copy link
Member

@portante portante left a 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?

@portante portante added enhancement Dashboard Of and relating to the Dashboard GUI Server labels Dec 7, 2020
@portante portante added this to the v0.71 milestone Dec 7, 2020
@dbutenhof
Copy link
Member Author

dbutenhof commented Dec 7, 2020

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.

@gurbirkalsi
Copy link
Collaborator

gurbirkalsi commented Dec 9, 2020

@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 config/endpoints.js before running yarn build and eventually moving it to the target server?

@portante

This comment has been minimized.

@gurbirkalsi
Copy link
Collaborator

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.

portante
portante previously approved these changes Jan 8, 2021
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
portante
portante previously approved these changes Jan 8, 2021
@dbutenhof dbutenhof requested review from portante and webbnh March 23, 2021 13:24
webbnh
webbnh previously approved these changes Mar 24, 2021
Copy link
Member

@webbnh webbnh left a 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.

lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
Comment on lines +122 to +128
# 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
Copy link
Member

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}".

Copy link
Member Author

@dbutenhof dbutenhof Mar 25, 2021

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
Copy link
Member

@portante portante left a 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.

lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
lib/pbench/server/api/resources/endpoint_configure.py Outdated Show resolved Hide resolved
Comment on lines +122 to +128
# 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
Copy link
Member

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.
@dbutenhof dbutenhof requested review from portante and webbnh March 26, 2021 13:15
Copy link
Member

@webbnh webbnh left a 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.

Comment on lines 1 to 10
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
Copy link
Member

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?

Copy link
Member

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 imports first, from next, where standard library are grouped, then external libraries, then project imports.

@portante portante merged commit 3912bb7 into distributed-system-analysis:main Mar 26, 2021
riya-17 pushed a commit to riya-17/pbench that referenced this pull request Jul 6, 2021
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Dashboard Of and relating to the Dashboard GUI enhancement Server
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a server endpoint to return configuration data for the Dashboard
4 participants