-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(update-server): Fix issues with 3.2 api on 3.3 system (#2097)
This fixes issues with updating to a 3.2 api on a 3.3 system, and also issues with running 3.2 on a 3.3 system. API server wheels from before 3.3.0 do not have resource subdirectories and should not be provisioned. The update server now inspects the version (from the filename) of the api wheel it is installing and will not provision wheels whose version is prior to 3.3.0. In addition, once the 3.2 api is uploaded, it will not have established the proper setup scripts. This commit changes the container to properly fall back on the scripts established in /usr/local/lib.
- Loading branch information
Showing
7 changed files
with
236 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#!/usr/bin/env python | ||
""" Simple python script to find the most salient opentrons resources directory. | ||
The first thing we try is just using find_python_module.py to find whatever | ||
opentrons module takes priority. However, if somebody installed a 3.2 api on the | ||
system, that module may not have a /resources subdirectory. In this case, we | ||
fall back to what we know will be there in /usr/local/lib. | ||
""" | ||
import importlib | ||
import os | ||
import sys | ||
import find_python_module_path | ||
|
||
def find_resources_dir(): | ||
ideal = find_python_module_path.find_module('opentrons') | ||
if not os.path.exists(os.path.join(ideal, 'resources')): | ||
new_path = [] | ||
for item in sys.path: | ||
if '/data' not in item: | ||
new_path += item | ||
sys.path = new_path | ||
fallback = find_python_module_path.find_module('opentrons') | ||
# Here we return whatever we get and trust the caller to fail | ||
return os.path.join(fallback, 'resources') | ||
else: | ||
return os.path.join(ideal, 'resources') | ||
|
||
if __name__ == '__main__': | ||
print(find_resources_dir()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,63 @@ | ||
import asyncio | ||
import os | ||
import logging | ||
from time import sleep | ||
import aiohttp | ||
from aiohttp import web | ||
from threading import Thread | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def do_restart(): | ||
""" This is the (somewhat) synchronous method to use to do a restart. | ||
It actually starts a thread that does the restart. `__wait_and_restart`, | ||
on the other hand, should not be called directly, because it will block | ||
until the system restarts. | ||
""" | ||
Thread(target=__wait_and_restart).start() | ||
|
||
|
||
def __wait_and_restart(): | ||
""" Delay and then execute the restart. Do not call directly. Instead, call | ||
`do_restart()`. | ||
""" | ||
log.info('Restarting server') | ||
sleep(1) | ||
os.system('kill 1') | ||
# We can use the default event loop here because this | ||
# is actually running in a thread. We use aiohttp here because urllib is | ||
# painful and we don’t have `requests`. | ||
loop = asyncio.new_event_loop() | ||
loop.run_until_complete(_resin_supervisor_restart()) | ||
|
||
|
||
async def _resin_supervisor_restart(): | ||
""" Execute a container restart by requesting it from the supervisor. | ||
Note that failures here are returned but most likely will not be | ||
sent back to the caller, since this is run in a separate workthread. | ||
If the system is not responding, look for these log messages. | ||
""" | ||
supervisor = os.environ.get('RESIN_SUPERVISOR_ADDRESS', | ||
'https://127.0.0.1:48484') | ||
restart_url = supervisor + '/v1/restart' | ||
api = os.environ.get('RESIN_SUPERVISOR_API_KEY', 'unknown') | ||
app_id = os.environ.get('RESIN_APP_ID', 'unknown') | ||
async with aiohttp.ClientSession() as session: | ||
async with session.post(restart_url, | ||
params={'apikey': api}, | ||
json={'appId': app_id, | ||
'force': True}) as resp: | ||
body = await resp.read() | ||
if resp.status != 202: | ||
log.error("Could not shut down: {}: {}" | ||
.format(resp.status, body)) | ||
|
||
|
||
async def restart(request): | ||
""" | ||
Returns OK, then waits approximately 1 second and restarts container | ||
""" | ||
Thread(target=__wait_and_restart).start() | ||
do_restart() | ||
return web.json_response({"message": "restarting"}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
""" Test file for updating the API. | ||
""" | ||
import os | ||
import subprocess | ||
import sys | ||
import tempfile | ||
|
||
import otupdate | ||
from otupdate import install | ||
|
||
|
||
def build_pkg(package_name, version, in_dir=None): | ||
""" Build a fake minor package and return its path """ | ||
if not in_dir: | ||
td = tempfile.mkdtemp() | ||
in_dir = os.path.join(td, package_name) | ||
os.mkdir(in_dir) | ||
test_setup = """ | ||
from setuptools import setup | ||
setup(name='{0}', | ||
version='{1}', | ||
description='Test package', | ||
url='https://github.com/Opentrons/opentrons', | ||
author='Opentrons', | ||
author_email='[email protected]', | ||
license='Apache 2.0', | ||
packages=['{0}'], | ||
zip_safe=False) | ||
""".format(package_name, version) | ||
test_setup_file = os.path.join(in_dir, 'setup.py') | ||
with open(test_setup_file, 'w') as tsf: | ||
tsf.write(test_setup) | ||
|
||
src_dir = os.path.join(in_dir, package_name) | ||
try: | ||
os.mkdir(src_dir) | ||
except FileExistsError: | ||
pass | ||
test_code = """ | ||
print("all ok")' | ||
""" | ||
test_file = os.path.join(src_dir, '__init__.py') | ||
|
||
with open(test_file, 'w') as tf: | ||
tf.write(test_code) | ||
|
||
cmd = '{} setup.py bdist_wheel'.format(sys.executable) | ||
subprocess.run(cmd, cwd=in_dir, shell=True) | ||
return os.path.join( | ||
in_dir, 'dist', | ||
'{}-{}-py3-none-any.whl'.format(package_name, version)) | ||
|
||
|
||
async def test_provision_version_gate(loop, monkeypatch, test_client): | ||
|
||
async def mock_install_py(executable, data, loop): | ||
return {'message': 'ok', 'filename': data.filename}, 0 | ||
|
||
monkeypatch.setattr(install, 'install_py', mock_install_py) | ||
|
||
container_provisioned = False | ||
|
||
async def mock_provision_container(executable, loop): | ||
nonlocal container_provisioned | ||
container_provisioned = True | ||
return {'message': 'ok', 'filename': '<provision>'}, 0 | ||
|
||
monkeypatch.setattr(install, '_provision_container', | ||
mock_provision_container) | ||
|
||
update_package = os.path.join(os.path.abspath( | ||
os.path.dirname(otupdate.__file__)), 'package.json') | ||
app = otupdate.get_app( | ||
api_package=None, | ||
update_package=update_package, | ||
smoothie_version='not available', | ||
loop=loop, | ||
test=False) | ||
cli = await loop.create_task(test_client(app)) | ||
|
||
pkg_name = 'opentrons' | ||
td = tempfile.mkdtemp() | ||
tmpd = os.path.join(td, pkg_name) | ||
os.mkdir(tmpd) | ||
|
||
# First test: API server with a version before 3.3.0 should not provision | ||
test_wheel = build_pkg('opentrons', '3.2.0a2', tmpd) | ||
|
||
resp = await cli.post( | ||
'/server/update', data={'whl': open(test_wheel, 'rb')}) | ||
|
||
assert resp.status == 200 | ||
assert not container_provisioned | ||
|
||
# Second test: An api server with a version == 3.3.0 (regardless of tag) | ||
# should provision | ||
test_wheel = build_pkg('opentrons', '3.3.0rc5', tmpd) | ||
resp = await cli.post( | ||
'/server/update', data={'whl': open(test_wheel, 'rb')}) | ||
|
||
assert resp.status == 200 | ||
assert container_provisioned | ||
|
||
# Third test: An api server with a version > 3.3.0 should provision | ||
container_provisioned = False | ||
test_wheel = build_pkg('opentrons', '3.4.0', tmpd) | ||
resp = await cli.post( | ||
'/server/update', data={'whl': open(test_wheel, 'rb')}) | ||
|
||
assert resp.status == 200 | ||
assert container_provisioned |