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

feat(compute,api,update-server): Move system configs out of Dockerfile #2073

Merged
merged 1 commit into from
Aug 21, 2018

Conversation

sfoster1
Copy link
Member

@sfoster1 sfoster1 commented Aug 16, 2018

overview

Overhaul of the container configuration and initialization to remove as much as
possible from the Dockerfile and the volatile storage, preferring instead to
live in files that are included in the api server wheel and unpacked to /data
during initialization.

Most configuration and setup scripts are no longer COPYd into /usr/local/bin.
Instead, they are packed into the api server’s wheel (opentrons) in a
/resources subdirectory. This resources subdirectory is recursive-included
into the manifest, so putting a file into the directory will include it in the
wheel.

Because wheels do not run arbitrary code when they are installed, there is some
external tooling to get the files out of wheel/resources and into their eventual
home in /data/system.

  • Provisioning

The compute/find_module_path.py is an executable python script that uses
importlib to find the location of the opentrons module as Python would do (so it
always gets the right one) without importing opentrons, which has significant
side effects.

The opentrons/api/opentrons/resources/scripts/provision-api-resources script
uses find_module_path to find the current opentrons module and copy everything
in /resources in the package to /data/system (the OT_CONFIG_DIR). This includes
all the system configurations and scripts that were previously in compute/.

The provision script is designed to be invoked during updates. This happens
during the first boot of the container (more on this later) and during a
software update via the update server.

In the case of the update-server driven update, because we assume a user is
never updating to 3.3 from 3.0 on a 3.0 Dockerfile, we can assume the provision
script is in /data/system/scripts and is therefore in the path. The update
server therefore invokes the provision script after installing a new api server,
the provision script finds the right (newly-installed) opentrons module, and
updates /data/resources with the new scripts.

In the case of the first container boot, we rely on the one piece of
initialization left in compute/. compute/container_setup.sh does the first-boot
check that setup.sh used to do, and in addition to removing old api server
installations and updating the cached container ID it runs the provision script

  • this time from the module installed in /usr/local/lib.
  • Container Initialization

The Dockerfile CMD has been slightly simplified by symlinking the environment
file into /etc/profile.d. This requires the use of bash’s -l flag to get a login
shell in the docker container.

Because the symlinks to the environment must be present when the shell starts,
the environment script (opentrons/api/opentrons/resources/ot-environ.sh) is
linked into /etc/profile.d by the Dockerfile (the rest of the configuration file
symlinks are in opentrons/api/opentrons/resources/scripts/setup.sh). It is also
linked twice - once from /data/system, which will be empty when the container
boots for the first time; and once from the system installation of the api
server in /usr/local/lib, as a fallback. The one-time environment variables
ensure it is only executed once.

Then, the container setup script runs. In most cases it doesn’t do much, but see
above for the behavior during the first container boot.

Since /data/system/scripts is in the path, we still call setup.sh and start.sh.
In addition to what it used to do, setup.sh now makes symlinks for most of the
configuration files we had been COPYing into Docker. These are now in
/data/system. start.sh is pretty similar to before.

  • Container Building

The Dockerfile is now parameterized to make building a local container slightly
easier. Invoking docker build with no arguments builds a container for the
pi (it has to, since this is what Resin does). Invoking docker build with the
arguments to change the base image and clear RUNNING_ON_PI will build a
container for local machines. To make this easier, there is a new top-level
Makefile target, api-local-container, that invokes docker build with the correct
arguments. Note that the local container still doesn’t 100% work; it needs more
love to get over things like not having a dbus socket on all hosts.

changelog

  • Move container configuration and initialization to opentrons/api/opentrons/resources as much as possible
  • Copy the data in /resources to /data/system on first container boot and during update (in the update server)
  • Sweet new motd
  • Removed some of the nmcli commands used for janitoring the static ipv6 routes
  • Removed the nginx root server block and deleted the static files it was
  • serving
  • hardcoded some ports to get away from infinite env vars we can’t trust
  • Fixed an issue in the update server where it was sending the repr() of
    tracebacks in 500 messages for /server/update. Now it sends the result of traceback.format_tb()

Note that Dockerfiles without these changes will not get the new configuration and startup, even if an api server is properly installed and provisioned, since the environment script won't be sourced.

review requests

Test:

  • A robot that is on 3.0.0 and has its resin-updates.lock file deleted successfully downloads this container from resin, boots, and has all its old config data from the usb driver
  • A robot that has this docker file, update server, and api server can successfully be updated (by run app) to a new version, including provisioning (usually check by modifying eg /data/system/ot-environ.sh and check that the modifications are reverted after update)
  • Unless specifically erased by updating, changes in /data/system should persist and take effect after a reboot. For instance, a change to /data/system/ot-environ.sh should be present even after a reboot. Note that the resin.io web shell doesn't actually use a login shell, so it may appear as if configurations are erased. To see the correct environment, do bash -l in the resin webshell (ssh and make term do this by default)
  • Also that everything still works:
    • the api and update server respond
    • Jupyter works
    • it connects to wifi
    • it connects via usb

@andySigler Would you mind double-checking ot-environ.sh and the contents of api/opentrons/resources and making sure that the stuff you need for the factory is OK?

Everybody else Let me know if there's more testing I should be doing.

@sfoster1 sfoster1 added api Affects the `api` project ready for review refactor container Affects the robot's Docker container large update server labels Aug 16, 2018
@sfoster1 sfoster1 force-pushed the api_server-installs-config-files-merge branch from 06465c5 to 59e95e6 Compare August 16, 2018 19:43
Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

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

Not in the office so can't personally test until, but here are my thoughts on the code

Dockerfile Outdated
ENV AUDIO_FILES /etc/audio
ENV USER_DEFN_ROOT /data/user_storage/opentrons_data/labware

COPY compute/container_setup.sh /usr/local/bin/container_setup.sh
Copy link
Contributor

Choose a reason for hiding this comment

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

[Style nitpick] All the other paths in this file have a leading ./

Dockerfile Outdated
COPY ./compute/conf/inetd.conf /etc/
COPY ./compute/conf/nginx.conf /etc/nginx/nginx.conf
COPY ./compute/static /usr/share/nginx/html
# COPY ./compute/opentrons.asc .
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ok to remove these lines entirely?

@@ -1,6 +1,7 @@
import os
import sys
import json
import shutil
Copy link
Contributor

Choose a reason for hiding this comment

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

Unused import

if [[ -z "$RUNNING_ON_PI" ]]
then
echo '[ $0 ] Forcing virtual smoothie board'
export ENABLE_VIRTUAL_SMOOTHIE=1
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure we want to do this? Running on a machine that isn't a Pi that is still connected to a real smoothie board seems like a valid dev use-case

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure

if [ -z $OT_ENVIRON_SET_UP ]; then
echo "[ $0 ] Configuring environment"

arg_running_on_pi=$1
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable doesn't appear to be used

# if they are in fact still used
export OT_SETTINGS_DIR=""
export OT_SERVER_PORT=31950
export OT_SERVER_UNIX_SOCKET_PATH=/tmp/aiohttp.sock
Copy link
Contributor

Choose a reason for hiding this comment

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

This PR might not be the time, but should we consider either ditching the Unix socket or aligning everything to use Unix sockets? At the very least, this is a really unhelpful socket name that we've been using


echo "[ $0 ] API server setup beginning"

if [ ! -z $RUNNING_ON_PI ] ; then
Copy link
Contributor

Choose a reason for hiding this comment

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

[Nitpick, no change requested] Any reason to use ! -z $RUNNING_ON_PI here over -n "$RUNNING_ON_PI"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Slowly re-remembering bash conditionals, mostly.

echo "[ $0 ] API server starting"
# mdns announcement
if [ ! -z $RUNNING_ON_PI ]; then
echo "[ $0 ] MDNS beginning"
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, why disable mDNS advertisements on non-Pis?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of a category of things in the container that only work at the very least on linux hosts and quite possibly only on the pi. I haven't been consistent with the rest of them, I think what we should do is create an issue to actually get a container running well on (osx, windows) docker.


export ENABLE_NETWORKING_ENDPOINTS=true
echo "[ $0 ] Starting Opentrons API server"
python -m opentrons.server.main -U /tmp/aiohttp.sock opentrons.server.main:init
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably use the OT_SERVER_UNIX_SOCKET_PATH environment variable here

@@ -44,6 +44,10 @@ robot. The script must:
Scripts in that directory will be executed in alphabetical order. The most straight-forward way to guarantee exectution
order is to prefix file names with numbers (e.g.: `00-configure-wifi` and `05-do-something-else`).

### System Configuration and Initialization
Copy link
Contributor

Choose a reason for hiding this comment

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

[Nice to have] While we're at improving documentation, does the top-level file RESIN_README.md mean anything anymore? If not, might be nice to ditch it in this PR

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do

@sfoster1 sfoster1 force-pushed the api_server-installs-config-files-merge branch from 59e95e6 to 763f12a Compare August 16, 2018 22:17
@codecov
Copy link

codecov bot commented Aug 16, 2018

Codecov Report

Merging #2073 into edge will increase coverage by 2.98%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##             edge    #2073      +/-   ##
==========================================
+ Coverage   32.86%   35.84%   +2.98%     
==========================================
  Files         453      453              
  Lines        7312     7802     +490     
==========================================
+ Hits         2403     2797     +394     
- Misses       4909     5005      +96
Impacted Files Coverage Δ
app-shell/src/discovery.js 88.88% <0%> (-1.12%) ⬇️
discovery-client/src/service.js 100% <0%> (ø) ⬆️
discovery-client/src/cli.js 0% <0%> (ø) ⬆️
components/src/deck/Labware.js 0% <0%> (ø) ⬆️
...rotocol-designer/src/components/SelectablePlate.js 0% <0%> (ø) ⬆️
app/src/components/LostConnectionAlert/index.js 0% <0%> (ø) ⬆️
...rotocol-designer/src/containers/SelectablePlate.js 0% <0%> (ø) ⬆️
components/src/deck/Tip.js 0% <0%> (ø) ⬆️
app/src/rpc/message-types.js 100% <0%> (ø) ⬆️
discovery-client/src/index.js 99.29% <0%> (+0.37%) ⬆️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f42ae1b...ee3872c. Read the comment docs.

@sfoster1 sfoster1 force-pushed the api_server-installs-config-files-merge branch 2 times, most recently from 16cc3a5 to c0fa188 Compare August 20, 2018 18:39
@btmorr btmorr changed the title feat(compute,api,update-server): Move system configs out of Dockerfile feat(*): Move system configs out of Dockerfile Aug 21, 2018
Copy link
Contributor

@btmorr btmorr left a comment

Choose a reason for hiding this comment

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

🤖 Code revision looks great, and the Run App works as expected on both the old Dockerfile and the one from this PR. Only issue observed during testing was the bug in troughs that is being addressed by #2023

@btmorr btmorr changed the title feat(*): Move system configs out of Dockerfile feat(compute,api,update-server): Move system configs out of Dockerfile Aug 21, 2018
Overhaul the container configuration and initialization to remove as much as
possible from the Dockerfile and the volatile storage, preferring instead to
live in files that are included in the api server wheel and unpacked to /data
during initialization.

Most configuration and setup scripts are no longer COPYd into /usr/local/bin.
Instead, they are packed into the api server’s wheel (opentrons) in a
/resources subdirectory. This resources subdirectory is recursive-included
into the manifest, so putting a file into the directory will include it in the
wheel.

Because wheels do not run arbitrary code when they are installed, there is some
external tooling to get the files out of wheel/resources and into their eventual
home in /data/system.

* Provisioning

The compute/find_module_path.py is an executable python script that uses
importlib to find the location of the opentrons module as Python would do (so it
always gets the right one) without importing opentrons, which has significant
side effects.

The opentrons/api/opentrons/resources/scripts/provision-api-resources script
uses find_module_path to find the current opentrons module and copy everything
in /resources in the package to /data/system (the OT_CONFIG_DIR). This includes
all the system configurations and scripts that were previously in compute/.

The provision script is designed to be invoked during updates. This happens
during the first boot of the container (more on this later) and during a
software update via the update server.

In the case of the update-server driven update, because we assume a user is
never updating to 3.3 from 3.0 on a 3.0 Dockerfile, we can assume the provision
script is in /data/system/scripts and is therefore in the path. The update
server therefore invokes the provision script after installing a new api server,
the provision script finds the right (newly-installed) opentrons module, and
updates /data/resources with the new scripts.

In the case of the first container boot, we rely on the one piece of
initialization left in compute/. compute/container_setup.sh does the first-boot
check that setup.sh used to do, and in addition to removing old api server
installations and updating the cached container ID it runs the provision script
- this time from the module installed in /usr/local/lib.

* Container Initialization

The Dockerfile CMD has been slightly simplified by symlinking the environment
file into /etc/profile.d. This requires the use of bash’s -l flag to get a login
shell in the docker container.

Because the symlinks to the environment must be present when the shell starts,
the environment script (opentrons/api/opentrons/resources/ot-environ.sh) is
linked into /etc/profile.d by the Dockerfile (the rest of the configuration file
symlinks are in opentrons/api/opentrons/resources/scripts/setup.sh). It is also
linked twice - once from /data/system, which will be empty when the container
boots for the first time; and once from the system installation of the api
server in /usr/local/lib, as a fallback. The one-time environment variables
ensure it is only executed once.

Then, the container setup script runs. In most cases it doesn’t do much, but see
above for the behavior during the first container boot.

Since /data/system/scripts is in the path, we still call setup.sh and start.sh.
In addition to what it used to do, setup.sh now makes symlinks for most of the
configuration files we had been COPYing into Docker. These are now in
/data/system. start.sh is pretty similar to before.

* Container Building

The Dockerfile is now parameterized to make building a local container slightly
easier. Invoking docker build with no arguments builds a container for the
pi (it has to, since this is what Resin does). Invoking docker build with the
arguments to change the base image and clear RUNNING_ON_PI will build a
container for local machines. To make this easier, there is a new top-level
Makefile target, api-local-container, that invokes docker build with the correct
arguments. Note that the local container still doesn’t 100% work; it needs more
love to get over things like not having a dbus socket on all hosts.

* Misc

- Sweet new motd
- Removed some of the nmcli commands used for janitoring the static ipv6 routes
- Removed the nginx root server block and deleted the static files it was
- serving
- hardcoded some ports to get away from infinite env vars we can’t trust
- Fixed an issue in the update server where it was sending the repr() of
  tracebacks in 500 messages for /server/update. Now it sends the result of traceback.format_tb()

Closes #1114
@sfoster1 sfoster1 force-pushed the api_server-installs-config-files-merge branch from c0fa188 to ee3872c Compare August 21, 2018 17:36
@sfoster1 sfoster1 merged commit 354c740 into edge Aug 21, 2018
@sfoster1 sfoster1 deleted the api_server-installs-config-files-merge branch August 21, 2018 18:04
b-cooper pushed a commit that referenced this pull request Aug 29, 2018
#2073)

Overhaul the container configuration and initialization to remove as much as
possible from the Dockerfile and the volatile storage, preferring instead to
live in files that are included in the api server wheel and unpacked to /data
during initialization.

Most configuration and setup scripts are no longer COPYd into /usr/local/bin.
Instead, they are packed into the api server’s wheel (opentrons) in a
/resources subdirectory. This resources subdirectory is recursive-included
into the manifest, so putting a file into the directory will include it in the
wheel.

Because wheels do not run arbitrary code when they are installed, there is some
external tooling to get the files out of wheel/resources and into their eventual
home in /data/system.

* Provisioning

The compute/find_module_path.py is an executable python script that uses
importlib to find the location of the opentrons module as Python would do (so it
always gets the right one) without importing opentrons, which has significant
side effects.

The opentrons/api/opentrons/resources/scripts/provision-api-resources script
uses find_module_path to find the current opentrons module and copy everything
in /resources in the package to /data/system (the OT_CONFIG_DIR). This includes
all the system configurations and scripts that were previously in compute/.

The provision script is designed to be invoked during updates. This happens
during the first boot of the container (more on this later) and during a
software update via the update server.

In the case of the update-server driven update, because we assume a user is
never updating to 3.3 from 3.0 on a 3.0 Dockerfile, we can assume the provision
script is in /data/system/scripts and is therefore in the path. The update
server therefore invokes the provision script after installing a new api server,
the provision script finds the right (newly-installed) opentrons module, and
updates /data/resources with the new scripts.

In the case of the first container boot, we rely on the one piece of
initialization left in compute/. compute/container_setup.sh does the first-boot
check that setup.sh used to do, and in addition to removing old api server
installations and updating the cached container ID it runs the provision script
- this time from the module installed in /usr/local/lib.

* Container Initialization

The Dockerfile CMD has been slightly simplified by symlinking the environment
file into /etc/profile.d. This requires the use of bash’s -l flag to get a login
shell in the docker container.

Because the symlinks to the environment must be present when the shell starts,
the environment script (opentrons/api/opentrons/resources/ot-environ.sh) is
linked into /etc/profile.d by the Dockerfile (the rest of the configuration file
symlinks are in opentrons/api/opentrons/resources/scripts/setup.sh). It is also
linked twice - once from /data/system, which will be empty when the container
boots for the first time; and once from the system installation of the api
server in /usr/local/lib, as a fallback. The one-time environment variables
ensure it is only executed once.

Then, the container setup script runs. In most cases it doesn’t do much, but see
above for the behavior during the first container boot.

Since /data/system/scripts is in the path, we still call setup.sh and start.sh.
In addition to what it used to do, setup.sh now makes symlinks for most of the
configuration files we had been COPYing into Docker. These are now in
/data/system. start.sh is pretty similar to before.

* Container Building

The Dockerfile is now parameterized to make building a local container slightly
easier. Invoking docker build with no arguments builds a container for the
pi (it has to, since this is what Resin does). Invoking docker build with the
arguments to change the base image and clear RUNNING_ON_PI will build a
container for local machines. To make this easier, there is a new top-level
Makefile target, api-local-container, that invokes docker build with the correct
arguments. Note that the local container still doesn’t 100% work; it needs more
love to get over things like not having a dbus socket on all hosts.

* Misc

- Sweet new motd
- Removed some of the nmcli commands used for janitoring the static ipv6 routes
- Removed the nginx root server block and deleted the static files it was
- serving
- hardcoded some ports to get away from infinite env vars we can’t trust
- Fixed an issue in the update server where it was sending the repr() of
  tracebacks in 500 messages for /server/update. Now it sends the result of traceback.format_tb()

Closes #1114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Affects the `api` project container Affects the robot's Docker container large refactor update server
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants