Skip to content

Commit 5e17d1a

Browse files
author
B. Thomas Goodwin
committed
Merge branch 'dev-gitlab-ci' into 'master'
Resolve "Migrate testing to gitlab-ci" Closes ESRA-119, #4, #5, and #6 See merge request geon/redhawk-ui/rest-python!8
2 parents 9eab5c3 + 3c57bbd commit 5e17d1a

15 files changed

+421
-185
lines changed

.gitlab-ci.yml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# This CI script builds Docker-REDHAWK's webserver image and deploys it to
2+
# dockerhub upon successful execution of its tests.
3+
variables:
4+
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
5+
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
6+
DOCKERHUB_IMAGE: geontech/redhawk-webserver
7+
8+
stages:
9+
- build
10+
- test
11+
- release
12+
- deploy
13+
- cleanup
14+
15+
.dind: &dind
16+
image: docker:latest
17+
18+
.container_registry: &container_registry
19+
<<: *dind
20+
before_script:
21+
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
22+
after_script:
23+
- docker logout $CI_REGISTRY
24+
25+
# Build the test image and push it to the registry, delete the runner-local
26+
# image to avoid cluttering the runner.
27+
build:
28+
stage: build
29+
<<: *container_registry
30+
script:
31+
- sed -i -E "s/(geontech.+\:)(.+)/\1"$REDHAWK_VERSION"/g" Dockerfile
32+
- docker build --pull --rm -t $CONTAINER_TEST_IMAGE .
33+
- docker push $CONTAINER_TEST_IMAGE
34+
- docker rmi $CONTAINER_TEST_IMAGE
35+
36+
test:
37+
stage: test
38+
<<: *container_registry
39+
script:
40+
# Install docker-compose
41+
- apk add --no-cache py2-pip && pip install docker-compose
42+
43+
# Pull the test image and start the stack without rebuilding it.
44+
- cd tests
45+
- docker pull ${CONTAINER_TEST_IMAGE}
46+
- docker-compose -p ${CI_COMMIT_REF_SLUG} up -d --no-build
47+
- docker-compose -p ${CI_COMMIT_REF_SLUG} exec -T rest
48+
bash -l -c 'yum install -y rh.SigGen rh.FileWriter'
49+
- docker-compose -p ${CI_COMMIT_REF_SLUG} exec -T rest
50+
bash -l -c './test.sh' || RESULT=$?
51+
52+
# Clean up and exit with the result
53+
- docker-compose -p ${CI_COMMIT_REF_SLUG} down
54+
- docker rmi ${CONTAINER_TEST_IMAGE}
55+
- exit ${RESULT}
56+
57+
# Move the test image to internal 'latest' and clean up the worker's images
58+
release:
59+
stage: release
60+
only:
61+
- master
62+
<<: *container_registry
63+
script:
64+
- docker pull $CONTAINER_TEST_IMAGE
65+
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
66+
- docker push $CONTAINER_RELEASE_IMAGE
67+
- docker rmi $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
68+
69+
# Clean up the test image from the internal registry.
70+
cleanup-ci:
71+
stage: cleanup
72+
when: always
73+
image: alpine:latest
74+
script:
75+
- apk add --no-cache git bash curl
76+
- git clone ${DOCKER_UTILS} docker-util
77+
- docker-util/delete-image.sh $CONTAINER_TEST_IMAGE
78+
79+
# Push the released image to DockerHub on tags
80+
deploy-image:
81+
stage: deploy
82+
only:
83+
- tags
84+
<<: *container_registry
85+
variables:
86+
dockerhub_image_tag: geontech/redhawk-webserver:$CI_COMMIT_TAG
87+
dockerhub_image_latest: geontech/redhawk-webserver:latest
88+
script:
89+
- docker pull $CONTAINER_RELEASE_IMAGE
90+
- docker logout
91+
- docker login -u ${DOCKERHUB_USER} -p ${DOCKERHUB_TOKEN}
92+
- docker tag $CONTAINER_RELEASE_IMAGE ${dockerhub_image_tag}
93+
- docker tag $CONTAINER_RELEASE_IMAGE ${dockerhub_image_latest}
94+
- docker push ${dockerhub_image_tag} ${dockerhub_image_latest}
95+
- docker rmi ${CONTAINER_RELEASE_IMAGE} ${dockerhub_image_tag} ${dockerhub_image_latest}
96+
97+
# Push to GitHub
98+
deploy-github:
99+
stage: deploy
100+
only:
101+
- tags
102+
image: alpine/git
103+
script:
104+
- eval $(ssh-agent -s)
105+
- echo "${GITHUB_TOKEN}" | ssh-add -
106+
- mkdir -p ~/.ssh
107+
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
108+
# Reconfigure git and push w/ tag(s)
109+
- git config user.name "${GITHUB_USER_NAME}"
110+
- git config user.email "${GITHUB_USER_EMAIL}"
111+
- git remote set-url origin ${GITHUB_REPO}
112+
- git push origin ${GITHUB_BRANCH} --follow-tags

Dockerfile

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# This file is protected by Copyright. Please refer to the COPYRIGHT file
2+
# distributed with this source distribution.
3+
#
4+
# This file is part of Geon's REST-Python.
5+
#
6+
# REST-Python is free software: you can redistribute it and/or modify it under
7+
# the terms of the GNU Lesser General Public License as published by the Free
8+
# Software Foundation, either version 3 of the License, or (at your option) any
9+
# later version.
10+
#
11+
# REST-Python is distributed in the hope that it will be useful, but WITHOUT
12+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14+
# details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with this program. If not, see http://www.gnu.org/licenses/.
18+
#
19+
20+
FROM geontech/redhawk-runtime:2.0.8
21+
LABEL name="REST-Python Web Server" \
22+
description="Geon's Fork of REST-Python" \
23+
maintainer="Thomas Goodwin <btgoodwin@geontech.com>"
24+
25+
# Build-time configurable variables
26+
ARG REST_PYTHON_PORT=8080
27+
28+
# Runtime variables
29+
ENV REST_PYTHON_PORT=${REST_PYTHON_PORT}
30+
31+
# Expose the configured default port.
32+
EXPOSE ${REST_PYTHON_PORT}
33+
34+
RUN yum install -y \
35+
git \
36+
gcc \
37+
python-dev \
38+
curl \
39+
python-virtualenv && \
40+
yum clean all -y
41+
42+
# Install and update pip
43+
RUN curl https://bootstrap.pypa.io/get-pip.py | python && \
44+
pip install -U pip
45+
46+
# Install the rest-python server requirements
47+
COPY ./setup.sh ./pyvenv ./requirements.txt /opt/rest-python/
48+
WORKDIR /opt/rest-python
49+
RUN ./setup.sh install && pip install -r requirements.txt
50+
51+
# Install the rest of the rest-python server and launcher
52+
COPY . /opt/rest-python
53+
RUN cp docker/rest-python.conf /etc/supervisor.d/rest-python.conf && \
54+
cp docker/kill_supervisor.py /usr/bin/kill_supervisor.py && \
55+
chmod u+x /usr/bin/kill_supervisor.py
56+
57+
# Mount point for end-user apps
58+
VOLUME /opt/rest-python/apps
59+
60+
CMD ["/usr/bin/supervisord"]

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,32 @@ Once running the REST Interface can be tested at `http://localhost:<desired_port
3939
## Deploying Applications
4040

4141
You can either install your application in `apps` for REST-Python to serve them, or deploy them with a separate server (e.g., NodeJS). REST-Python supports cross-domain responses to REST and Websocket requests to facilitate dual- or multi-server configurations to completely decouple the REDHAWK environment from the web application environment. (See [Docker-REDHAWK's](http://github.com/GeonTech/docker-redhawk) `geontech/redhawk-webserver` image.)
42+
43+
## Testing
44+
45+
If you would like to test REST-Python changes in a functional domain, download Docker-CE and Docker Compose for your operating system. The process takes 3 parts:
46+
47+
1. Your locally-built instance of the webserver image
48+
2. Running the `tests/docker-compose.yml` stack with your webserver image
49+
3. Using `docker-compose exec` to start the tests script.
50+
51+
> Note: Verify the Dockerfile version of the parent image (`FROM...`) and the compose file vs. the version of REDHAWK you want to test against. You can export `REDHAWK_VERSION` set to that version prior to running `docker-compose` to simplify changing versions of the infrastructure.
52+
53+
The process is shown here:
54+
55+
```
56+
cd tests
57+
export TEST_IMAGE=test-webserver
58+
docker-compose up -d --build
59+
docker-compose exec -T rest \
60+
bash -l -c 'yum install -y rh.FileWriter rh.SigGen && ./test.sh'
61+
```
62+
63+
If you make further changes and want to update the webserver image and container (the service is `rest` in the compose file), you can recreate the container without tearing down the entire compose by doing the following:
64+
65+
```
66+
docker-compose stop rest
67+
docker-compose up --build -d rest
68+
```
69+
70+
You would then repeat the `exec` command from above.

docker-compose.yml

Lines changed: 0 additions & 68 deletions
This file was deleted.

docker/kill_supervisor.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python
2+
# Special thanks to the example at:
3+
# https://blog.zhaw.ch/icclab/process-management-in-docker-containers/
4+
import sys
5+
import os
6+
import signal
7+
8+
9+
def write_stdout(s):
10+
sys.stdout.write(s)
11+
sys.stdout.flush()
12+
13+
14+
def write_stderr(s):
15+
sys.stderr.write(s)
16+
sys.stderr.flush()
17+
18+
19+
def main():
20+
while 1:
21+
write_stdout('READY\n')
22+
line = sys.stdin.readline()
23+
write_stdout('This line kills supervisor: ' + line)
24+
try:
25+
pidfile = open('/var/run/supervisord.pid', 'r')
26+
pid = int(pidfile.readline())
27+
os.kill(pid, signal.SIGQUIT)
28+
except Exception as e:
29+
write_stdout('Could not kill supervisor: ' + e.strerror + '\n')
30+
write_stdout('RESULT 2\nOK')
31+
32+
33+
if __name__ == '__main__':
34+
main()
35+
import sys

docker/rest-python.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[program:rest-python]
2+
autostart=true
3+
redirect_stderr=true
4+
command=/bin/bash -lc "/opt/rest-python/pyrest.py --port=%(ENV_REST_PYTHON_PORT)s"
5+
startsecs=5
6+
priority=10
7+
8+
[eventlistener:rest-python_exit]
9+
command=/usr/bin/kill_supervisor.py
10+
process_name=rest-python
11+
events=PROCESS_STATE_EXITED

pyrest.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import tornado.httpserver
3838
import tornado.web
3939
import tornado.websocket
40-
from tornado import ioloop, gen
40+
from tornado import ioloop, gen, concurrent
4141

4242
from model.redhawk import Redhawk
4343

@@ -74,6 +74,7 @@ class Application(tornado.web.Application):
7474
def __init__(self, *args, **kwargs):
7575
# explicit _ioloop for unit testing
7676
_ioloop = kwargs.get('_ioloop', None)
77+
_ws_close_future = kwargs.get('close_future', concurrent.Future())
7778
cwd = os.path.abspath(os.path.dirname(__import__(__name__).__file__))
7879

7980
# REDHAWK Service
@@ -118,7 +119,7 @@ def __init__(self, *args, **kwargs):
118119
(_APPLICATION_PATH + _ID + _PORT_PATH + _ID, PortHandler,
119120
dict(redhawk=redhawk, kind='application')),
120121
(_APPLICATION_PATH + _ID + _BULKIO_PATH, BulkIOWebsocketHandler,
121-
dict(redhawk=redhawk, kind='application', _ioloop=_ioloop)),
122+
dict(redhawk=redhawk, kind='application', _ioloop=_ioloop, close_future=_ws_close_future)),
122123
(_APPLICATION_PATH + _ID + _PROPERTIES_PATH + _LIST, ApplicationProperties,
123124
dict(redhawk=redhawk)),
124125
(_APPLICATION_PATH + _ID + _PROPERTIES_PATH + _ID, ApplicationProperties,
@@ -136,7 +137,7 @@ def __init__(self, *args, **kwargs):
136137
(_COMPONENT_PATH + _ID + _PORT_PATH + _ID, PortHandler,
137138
dict(redhawk=redhawk, kind='component')),
138139
(_COMPONENT_PATH + _ID + _BULKIO_PATH, BulkIOWebsocketHandler,
139-
dict(redhawk=redhawk, kind='component', _ioloop=_ioloop)),
140+
dict(redhawk=redhawk, kind='component', _ioloop=_ioloop, close_future=_ws_close_future)),
140141

141142
# Device Managers
142143
(_DEVICE_MGR_PATH + _LIST, DeviceManagers, dict(redhawk=redhawk)),
@@ -160,7 +161,8 @@ def __init__(self, *args, **kwargs):
160161
(_DEVICE_PATH + _ID + _PORT_PATH + _FEI_NAVDATA_ID + _LIST, FEINavDataHandler, dict(redhawk=redhawk)),
161162
(_DEVICE_PATH + _ID + _PORT_PATH + _FEI_NAVDATA_ID + _ID, FEINavDataHandler, dict(redhawk=redhawk)),
162163
(_DEVICE_PATH + _ID + _PORT_PATH + _ID, PortHandler, dict(redhawk=redhawk, kind='device')), # Default port handler
163-
(_DEVICE_PATH + _ID + _BULKIO_PATH, BulkIOWebsocketHandler, dict(redhawk=redhawk, kind='device', _ioloop=_ioloop)),
164+
(_DEVICE_PATH + _ID + _BULKIO_PATH, BulkIOWebsocketHandler,
165+
dict(redhawk=redhawk, kind='device', _ioloop=_ioloop, close_future=_ws_close_future)),
164166

165167
# Filesystem
166168
(_FS_PATH, FileSystem, dict(redhawk=redhawk))

rest/bulkio_handler.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
from omniORB import CORBA
2727

2828
# third party imports
29-
from tornado import ioloop, gen
30-
from tornado import websocket
29+
from tornado import ioloop, gen, websocket
3130

3231
import time
3332
import json
@@ -48,7 +47,8 @@ def enum(**enums):
4847

4948

5049
class BulkIOWebsocketHandler(CrossDomainSockets):
51-
def initialize(self, kind, redhawk=None, _ioloop=None):
50+
def initialize(self, close_future, kind, redhawk=None, _ioloop=None):
51+
self.close_future = close_future
5252
self.kind = kind
5353
self.redhawk = redhawk
5454
if not _ioloop:
@@ -240,6 +240,7 @@ def on_message(self, message):
240240

241241
def on_close(self):
242242
logging.debug('Stream CLOSE')
243+
self.close_future.set_result((self.close_code, self.close_reason))
243244
try:
244245
self.port.ref.disconnectPort(self._connectionId)
245246
logging.info("Closed websocket to %s, %s", self.port, self._connectionId)

0 commit comments

Comments
 (0)