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

[RFR][SVCS-336]MFR issue: cannot render (videos, PDFs, or PDBs) locally when dockerized #259

Closed
wants to merge 2 commits into from

Conversation

TomBaxter
Copy link
Member

@TomBaxter TomBaxter commented May 19, 2017

Purpose:

In local development, those MFR extensions that create javascript links to WB files, must use localhost instead of 192.168.168.167. This is because the request will come from the browser instead of the MFR docker instance.

Add catch for rendering in local development

Changes:

Add EXTENSION settings for LOCAL_HOST and DOCKER_LOCAL_HOST
Add mfr.extension.utils.get_local_export_url to ...
1. Switch DOCKER_LOCAL_HOST to LOCAL_HOST
2. Remove "mode" parameter
For renderers pdb, pdf, and video:
Check for DOCKER_LOCAL_HOST and call mfr.extension.utils.get_local_export_url

Side effects

None

Important

There is a related PR That adds EXTENSION_CONFIG_LOCAL_DEVELOPMENT=1 to .docker-compose.mfr.env . Would be nice if the were merged together, though nothing bad would happen if they weren't.

[#SVCS-336]

  • Create PR for OSF.io add this line to OSF.io .docker-compose.mfr.env EXTENSION_CONFIG_LOCAL_DEVELOPMENT=1

@@ -13,7 +14,14 @@ class VideoRenderer(extension.BaseRenderer):
]).get_template('viewer.mako')

def render(self):
return self.TEMPLATE.render(base=self.assets_url, url=self.url)
self_url = urlparse(self.url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem could more generally also affect other renderers that directly serve the file content, such as PDB files. Might be worth checking for similar edge case bugs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Yep, already looking at updating pdf renderer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, so it also affects a third filetype? Good find. Hope we can DRY it a bit.

@TomBaxter TomBaxter changed the title [RFR][SVCS-336]MFR issue: cannot render videos locally when dockerized [RFR][SVCS-336]MFR issue: cannot render (videos, PDFs, or PDBs) locally when dockerized May 19, 2017
Copy link
Contributor

@cslzchen cslzchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • works as expected with my local env
  • looks good except a few style improvements

I also have a general question. Why this only fails for the three types but not others. Is it worth it to make this generalized for all instead of by specific extension?

Back to Add'l Dev 🎆

@@ -13,7 +15,11 @@ class VideoRenderer(extension.BaseRenderer):
]).get_template('viewer.mako')

def render(self):
return self.TEMPLATE.render(base=self.assets_url, url=self.url)
self_url = urlparse(self.metadata.download_url)
# Catch for local developement.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "development" (3 occurrences)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complete

@@ -0,0 +1,6 @@
from mfr import settings

config = settings.child('EXTENTION_CONFIG')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: 'EXTENSTION_CONFIG' (1 occurence)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complete


def development_export_url(urlparsed_url):
query_dict = parse_qs(urlparsed_url.query, keep_blank_values=True)
query_dict.pop('mode', None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we pop mode?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been quite a while since I worked on this and don't recall off the top of my head. In brief testing I was not able to come up with a good answer other then that it isn't needed. I can remove the pop, but it's very possible I had a good reason that I no longer recall.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only affects local development. We can keep it there with the TODO note for you to recall the reason. :D

urlparsed_url = urlparsed_url._replace(query=urlencode(query_dict, doseq=True))
urlparsed_url = urlparsed_url._replace(netloc="{}:{}".format(settings.LOCAL_HOST,
urlparsed_url.port))
return urlparsed_url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style suggestions:

  • indent by 4
  • 2 empty lines after imports
  • naming
  • docs/comments

Here is my example:

from urllib.parse import parse_qs, urlencode

from mfr.extensions import settings


def get_local_export_url(parsed_url):
    """
    Replace the docker domain with localhost (local development only).

    :param parsed_url: the default export URL which is parsed by `urllib.parse.urlparse`
    :return: the tweaked export URL for local development
    """

    query_dict = parse_qs(parsed_url.query, keep_blank_values=True)
    # TODO: why do we pop `mode`?
    query_dict.pop('mode', None)
    parsed_url = parsed_url._replace(query=urlencode(query_dict, doseq=True))
    parsed_url = parsed_url._replace(
            netloc="{}:{}".format(settings.LOCAL_HOST, parsed_url.port)
    )

    return parsed_url

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complete

@@ -16,9 +19,14 @@ class PdbRenderer(extension.BaseRenderer):
]).get_template('viewer.mako')

def render(self):
self_url = urlparse(self.metadata.download_url)
# Catch for local developement.
if self_url.hostname == ext_settings.DOCKER_LOCAL_HOST:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this equation check a flag instead? For example, in mfr/extenstion/settings:

# only set to `true` for local development
DOCKER_LOCAL_HOST = config.get('DOCKER_LOCAL_HOST', false)

And we check:

if ext_settings.DOCKER_LOCAL_HOST:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. However, I like the explicit DOCKER_LOCAL_HOST setting. I think it's less confusing. When someone sees the 192.168.168.167 address, they know just what we're talking about. Also, I like the explicitness of specifying the IP we will change, and no other. I'll split the difference with you and add another config LOCAL_DEVELOPMENT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. What you have looks good! 👍
And probably no need for the flag I suggested. It leads to confusion and developers need to update local configuration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOH! too late already did it, though I can undo it if you don't like it. You will now need to either add LOCAL_DEVELOPMENT=1 to your ~.cos file for MFR or...
add this line to your OSF.io .docker-compose.mfr.env...
EXTENSION_CONFIG_LOCAL_DEVELOPMENT=1
I'll make a coresponding OSF.io PR when this PR is finalized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried not to disturb the PR too much. It is better to have both in place (and it is) to make sure this only works for local docker environment.

return self.TEMPLATE.render(base=self.assets_url, url=self.metadata.download_url)
self_url = urlparse(self.metadata.download_url)
# Catch for local developement.
if self_url.hostname == settings.DOCKER_LOCAL_HOST:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as mentioned above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as mentioned above.

@TomBaxter
Copy link
Member Author

RE: general question
I updated the PR Purpose: and code doc with...

In local development, those MFR extensions that create links to WB files in TEMPLATE, must use localhost instead of 192.168.168.167. This is because the request will come from the browser instead of the MFR docker instance.

Copy link
Contributor

@cslzchen cslzchen 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 🔥 . Move to PCR (Passed Code Review).
@felliott Please double check before moving to RTM.
@TomBaxter Please add a dependency for the OSF ticket you mentioned.

@TomBaxter
Copy link
Member Author

related PR created.

Copy link
Member

@felliott felliott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some refactorings needed, and a few questions about the implementation of the url munger.

@@ -16,9 +19,14 @@ class PdbRenderer(extension.BaseRenderer):
]).get_template('viewer.mako')

def render(self):
self_url = urlparse(self.metadata.download_url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here and in the render methods for the pdf and video exporters are almost identical. Could you move this into a decorator/utility method/base class/whatever to avoid the code duplication?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

def get_local_export_url(urlparsed_url):
"""
Replace the docker domain with localhost (for use in local development).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: show a simple example, like:

http://localhost:7777/foo/bar => http://192.168.168.167:7777/foo/bar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completed.

"""

query_dict = parse_qs(urlparsed_url.query, keep_blank_values=True)
# TODO: why do we pop `mode`?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??? Did this come from somewhere else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a question from @cslzchen that I couldn't remember the answer to.

"""
Replace the docker domain with localhost (for use in local development).

:param parsed_url: the default export URL which is parsed by `urllib.parse.urlparse`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your docstring is out-of-date.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed, I think?

urlparsed_url = urlparsed_url._replace(query=urlencode(query_dict, doseq=True))
urlparsed_url = urlparsed_url._replace(
netloc="{}:{}".format(settings.LOCAL_HOST, urlparsed_url.port)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the _replace method. What does it do, and why are you using a private method? Is there another way to do this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this can be done many other ways, but I was trying to do the least amount of converting. This way I am just manipulating the ParseResult, a namedtuple subclass. Dropping the "mode" query parameter and switching the netloc.

https://docs.python.org/3.6/library/collections.html#collections.somenamedtuple._replace

@coveralls
Copy link

coveralls commented Nov 13, 2017

Coverage Status

Coverage increased (+0.2%) to 66.567% when pulling 0fd79a7 on TomBaxter:feature/svcs-336 into dd5dd3e on CenterForOpenScience:develop.

@coveralls
Copy link

coveralls commented Nov 14, 2017

Coverage Status

Coverage increased (+0.2%) to 66.567% when pulling 7ca847c on TomBaxter:feature/svcs-336 into 9e9fc26 on CenterForOpenScience:develop.

Copy link
Member

@felliott felliott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very close, almost all nitpicks. Two minor issues:

  • The name of the decorator should be more descriptive.
  • The popping of the mode parameter should either be explained or removed. Please try it without and see what breaks.

Cheers,
@felliott

@@ -5,6 +5,7 @@
from mako.lookup import TemplateLookup

from mfr.core import extension
from mfr.extensions.utils import download_from_template
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: follow PEP8 import order, this should go after mfr.extensions.pdb

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

@@ -0,0 +1,22 @@
from urllib.parse import urlparse, parse_qs, urlencode
from mfr.extensions import settings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: leave one space between import stanzas

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

# TEMPLATE to render, when using local docker development environment. Call will be coming from
# browser instead of docker container.

LOCAL_DEVELOPMENT = config.get_bool('LOCAL_DEVELOPMENT', 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: 0 ==> False

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make this change and it will work fine. The reason I picked 0 instead is because the corresponding osf.io .docker-compose.mfr.env file can't set LOCAL_DEVELOPMENT to True, must be a string, therefor I chose "1" as that is covered by setting.get_bool. You still want I should change it to False?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything else looks good, so I'll take care of it. But generally the settings should follow the idiom they're defined in. This is a python config file, so True and False are appropriate. The .docker-compose.*.env files are env files, so 1 and 0 are appropriate for them.

from mfr.extensions import settings


def download_from_template(func):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decorator looks good to me, but I'd like to bikeshed the name, download_from_template is a little nonspecific. Maybe munge_url_for_localdev or something like that? Or even munge_download_url if this might end up doing more than just rewriting the localdev download url. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Completed.

download_url = urlparse(self.metadata.download_url)
if (settings.LOCAL_DEVELOPMENT and download_url.hostname == settings.DOCKER_LOCAL_HOST):
query_dict = parse_qs(download_url.query, keep_blank_values=True)
query_dict.pop('mode', None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this and see what breaks. If it's needed, add it back with a comment explaining why. If not, leave it out. If we can't figure out why we need it, we should leave it out until we know why.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't able to reproduce an issue, so removing. Completed.

query_dict = parse_qs(download_url.query, keep_blank_values=True)
query_dict.pop('mode', None)
download_url = download_url._replace(query=urlencode(query_dict, doseq=True))
download_url = download_url._replace(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, my bad: I thought the leading underscore meant this was a private method. Clearly I need to learn more python.

Nitpick: you can modify both fields of the named tuple in a single _replace command.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

query_dict.pop('mode', None)
download_url = download_url._replace(query=urlencode(query_dict, doseq=True))
download_url = download_url._replace(
netloc="{}:{}".format(settings.LOCAL_HOST, download_url.port)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super-nitpick: please use single quotes for literals.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

@@ -2,6 +2,7 @@

from mako.lookup import TemplateLookup

from mfr.extensions.utils import download_from_template
from mfr.core import extension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: import order

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completed.

def download_from_template(func):
"""
Check if LOCAL_DEVELOPMENT and if so
Replace the docker domain with localhost (for use in local development).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyedit: "If MFR is being run in a local development environment (i.e. LOCAL_DEVELOPMENT is True), we need to replace the external host (the one the user provides, default: "localhost") with the internal host (the one the backend services communicate on, default: 192.168.168.167)."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed. Note: I had to switch the to/from. I probably confused you with the bad example.

Copy link
Member Author

@TomBaxter TomBaxter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRR completed.

# TEMPLATE to render, when using local docker development environment. Call will be coming from
# browser instead of docker container.

LOCAL_DEVELOPMENT = config.get_bool('LOCAL_DEVELOPMENT', 0)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make this change and it will work fine. The reason I picked 0 instead is because the corresponding osf.io .docker-compose.mfr.env file can't set LOCAL_DEVELOPMENT to True, must be a string, therefor I chose "1" as that is covered by setting.get_bool. You still want I should change it to False?

@@ -5,6 +5,7 @@
from mako.lookup import TemplateLookup

from mfr.core import extension
from mfr.extensions.utils import download_from_template
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

@@ -0,0 +1,22 @@
from urllib.parse import urlparse, parse_qs, urlencode
from mfr.extensions import settings
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

from mfr.extensions import settings


def download_from_template(func):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Completed.

def download_from_template(func):
"""
Check if LOCAL_DEVELOPMENT and if so
Replace the docker domain with localhost (for use in local development).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed. Note: I had to switch the to/from. I probably confused you with the bad example.

query_dict = parse_qs(download_url.query, keep_blank_values=True)
query_dict.pop('mode', None)
download_url = download_url._replace(query=urlencode(query_dict, doseq=True))
download_url = download_url._replace(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

query_dict.pop('mode', None)
download_url = download_url._replace(query=urlencode(query_dict, doseq=True))
download_url = download_url._replace(
netloc="{}:{}".format(settings.LOCAL_HOST, download_url.port)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed.

@@ -2,6 +2,7 @@

from mako.lookup import TemplateLookup

from mfr.extensions.utils import download_from_template
from mfr.core import extension
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completed.

download_url = urlparse(self.metadata.download_url)
if (settings.LOCAL_DEVELOPMENT and download_url.hostname == settings.DOCKER_LOCAL_HOST):
query_dict = parse_qs(download_url.query, keep_blank_values=True)
query_dict.pop('mode', None)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't able to reproduce an issue, so removing. Completed.

Add EXTENSION settings for..
    LOCAL_HOST, DOCKER_LOCAL_HOST, and LOCAL_DEVELOPMENT
Add mfr.extension.utils.download_from_template decorator to...
    1. Switch DOCKER_LOCAL_HOST to LOCAL_HOST
    2. Remove "mode" parameter
For renderers pdb, pdf, and video:
    Add download_from_template decorator to render method
Fix test for video renderer

In local development, those MFR extensions that create links to WB files in TEMPLATES,
must use localhost instead of 192.168.168.167. This is because the request
will come from the browser instead of the MFR docker instance.
@coveralls
Copy link

coveralls commented Nov 20, 2017

Coverage Status

Coverage increased (+0.2%) to 68.242% when pulling d7b598d on TomBaxter:feature/svcs-336 into 8bb2dd4 on CenterForOpenScience:develop.

@felliott
Copy link
Member

A!W!E!S!O!M!E! Moving to RTM.

felliott added a commit that referenced this pull request Nov 20, 2017
@felliott felliott closed this in ec620af Jan 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants