Skip to content

Commit

Permalink
Merge pull request #10 from lsst-ts/release/2.0.0
Browse files Browse the repository at this point in the history
Release/2.0.0
  • Loading branch information
sfehlandt authored Aug 18, 2020
2 parents f419128 + 6ad7d41 commit be8c355
Show file tree
Hide file tree
Showing 143 changed files with 23,366 additions and 25 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ virtualenv
*.cache
.vscode
**/__pycache__
**/.DS_Store
**/.DS_Store
**/*.log
pytest_*
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM lsstts/develop-env:b65
FROM lsstts/develop-env:b101

WORKDIR /usr/src/love/

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM lsstts/develop-env:b65
FROM lsstts/develop-env:b101

WORKDIR /usr/src/love/commander
COPY requirements-dev.txt .
Expand Down
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,65 @@
# LOVE-commander

LOVE service to send SAL commands from http endpoints using salobj

## Running tests

Disabling plugins that may throw errors due to not having write access is recommended:

## Running tests
Disabling plugins that may throw errors due to not having write access is recommended:
```
pytest -p no:cacheprovider -p no:pytest_session2file
```

## 1. Use as part of the LOVE system

In order to use the LOVE-commander as part of the LOVE system we recommend to use the docker-compose and configuration files provided in the [LOVE-integration-tools](https://github.com/lsst-ts/LOVE-integration-tools) repo. Please follow the instructions there.

## 2. Local load for development

We provide a docker image and a docker-compose file in order to load the LOVE-commander locally for development purposes, i.e. run tests and build documentation.

This docker-compose does not copy the code into the image, but instead it mounts the repository inside the image, this way you can edit the code from outside the docker container with no need to rebuild or restart.

### 2.1 Load and get into the docker image

Follow these instructions to run the application in a docker container and get into it:

1. Launch and get into the container:

```
docker-compose up -d
docker-exec commander bash
```

2. Inside the container:, load the setup and got to love folder

```
source .setup.sh
cd /usr/src/love
```

### 2.2 Run tests

Once inside the container and in the `love` folder you can run the tests. Disabling plugins that may throw errors due to not having write access is recommended:

```
pytest -p no:cacheprovider -p no:pytest_session2file
```

You may filter tests with the `-k <filter-substring>` flag, where `<filter-substring>` can be a file or a test suite name.
For example the following command:

```
pytest -p no:cacheprovider -p no:pytest_session2file -k metadata
```

will run the `test_metadata` test of the `test_salinfo.py` file.

### 2.3 Build documentation

Once inside the container and in the `love` folder you can build the documentation as follows:

```
cd docsrc
./create_docs.sh
```
22 changes: 18 additions & 4 deletions commander/app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
"""Main application, instantiates the aiohtttp app."""
import json
from aiohttp import web
from lsst.ts import salobj
from .commands import create_app as create_cmd_app
from .heartbeats import create_app as create_heartbeat_app
from .salinfo import create_app as create_salinfo_app


def create_app(*args, **kwargs):
app = web.Application(
middlewares=[web.normalize_path_middleware()])
async def create_app(*args, **kwargs):
"""Create the aaplication with its subapplications
app.add_subapp('/cmd/', create_cmd_app())
Returns
-------
object
the application
"""
app = web.Application(middlewares=[web.normalize_path_middleware()])

app.add_subapp("/cmd/", create_cmd_app())
app.add_subapp("/heartbeat/", create_heartbeat_app())
app.add_subapp(
"/salinfo/",
await create_salinfo_app(remotes_len_limit=kwargs.get("remotes_len_limit")),
)

return app
31 changes: 22 additions & 9 deletions commander/commands.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,60 @@
"""Define the Commands subapplication, which provides the endpoints to accept command requests."""
from aiohttp import web
from lsst.ts import salobj
import json


def create_app():
"""Create the Commands application
Returns
-------
object
The application instance
"""
remotes = {}

cmd = web.Application()

async def start_cmd(request):
data = await request.json()
try:
assert 'csc' in data
assert 'salindex' in data
assert 'cmd' in data
assert 'params' in data
assert "csc" in data
assert "salindex" in data
assert "cmd" in data
assert "params" in data
except AssertionError:
return web.json_response({"ack": f'Request must have JSON data with the following keys: csc, salindex, cmd_name, params. Received {json.dumps(data)}'}, status=400)
return web.json_response(
{
"ack": f"Request must have JSON data with the following keys: csc, salindex, cmd_name, params. Received {json.dumps(data)}"
},
status=400,
)

csc = data["csc"]
salindex = data["salindex"]
cmd_name = data["cmd"]
params = data["params"]
remote_name = f"{csc}.{salindex}"
if remote_name not in remotes:
remotes[remote_name] = salobj.Remote(
salobj.Domain(), csc, salindex)
remotes[remote_name] = salobj.Remote(salobj.Domain(), csc, salindex)
await remotes[remote_name].start_task

cmd = getattr(remotes[remote_name], cmd_name)
cmd.set(**params)

try:
cmd_result = await cmd.start(timeout=10)
return web.json_response({'ack': cmd_result.result})
return web.json_response({"ack": cmd_result.result})
except salobj.AckTimeoutError:
return web.json_response({"ack": "Command time out"}, status=504)

cmd.router.add_post('/', start_cmd)
cmd.router.add_post("/", start_cmd)

async def on_cleanup(cmd):
for remote_name in remotes:
await remotes[remote_name].close()

cmd.on_cleanup.append(on_cleanup)

return cmd
33 changes: 33 additions & 0 deletions commander/heartbeats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Define the Heartbeats subapplication, which provides the endpoints to request a heartbeat."""
from aiohttp import web
import json
from datetime import datetime


def create_app():
"""Create the Heartbeats application
Returns
-------
object
The application instance
"""
remotes = {}

heartbeat = web.Application()

async def start_heartbeat(request):
data = await request.read()
now = datetime.now()
timestamp = datetime.timestamp(now)
return web.json_response({"timestamp": timestamp})

heartbeat.router.add_get("/", start_heartbeat)

async def on_cleanup(heartbeat):
for remote_name in remotes:
await remotes[remote_name].close()

heartbeat.on_cleanup.append(on_cleanup)

return heartbeat
Loading

0 comments on commit be8c355

Please sign in to comment.