Giter Site home page Giter Site logo

fnndsc / chris_ultron_backend Goto Github PK

View Code? Open in Web Editor NEW
31.0 13.0 100.0 9.76 MB

Backend for ChRIS

Home Page: https://fnndsc.github.io/ChRIS_ultron_backEnd

License: MIT License

Python 93.98% Shell 5.58% Dockerfile 0.17% JavaScript 0.02% HTML 0.25%
django django-rest-framework docker openshift kubernetes python3 medical pipelines swift-storage

chris_ultron_backend's Introduction

ChRIS logo ChRIS_ultron_backEnd

Build License

ChRIS is an open-source platform for containerized medical compute. The ChRIS backend, a.k.a. ChRIS Ultron Backend or CUBE for short, is a component of the ChRIS system.

Architecture Diagram Architecture Diagram

The core backend service for the ChRIS distributed software platform, also known by the anacronym CUBE. Internally the service is implemented as a Django-PostgreSQL project offering a collection+json REST API. Important ancillary components include the pfcon and pman file transfer and remote process management microservices.

ChRIS development, testing and deployment

Abstract

ChRIS Ultron Back End (sometimes also ChRIS Underlying Back End) or simply CUBE is the main core of the ChRIS system. CUBE provides the main REST API to the ChRIS system, as well as maintaining an internal database of users, files, pipelines, and plugins. Currently CUBE has two separate compute paradigms depending on deployment context. In the case of development all components of CUBE use docker and docker swarm technologies. In the case of production technologies such as openshift and kubernetes are also supported.

Consult this page for instructions on starting CUBE in either development or production contexts. For documentation/overview/background, please see the documention.

Preconditions

Operating system support -- please read

Linux

Linux is the first class host platform for all things CUBE related. Linux distributions used by various core developers include Ubuntu, Arch, and Fedora. The development team is happy to provide help to folks trying / struggling to run CUBE on most any Linux distribution.

macOS

macOS is fully supported as a host platform for CUBE. Please note that you must update/tweak some things first. Most importantly, macOS is distributed with a deprecated version of the bash shell that will not work with our Makefile. If you want to host CUBE on macOS, you must update bash to a current version. Instructions are out of scope of this document, but we recommend homebrew as your friend here.

Windows

In a word, don't (ok, that's technically two words). CUBE is ideally meant to be deployed on Linux/*nix systems. Windows is not officially supported nor recommended as the host environment. If you insist on trying on Windows you can consult some unmaintained documentation on attempts to deploy CUBE using the Windows Subsystem for Linux (WSL) here. This probably will break. Note that currently no one on the core development uses Windows in much of any capacity so interest or knowledge to help questions about Windows support is low. Nonetheless, we would welcome any brave soul though who has the time and inclination to fully investigate CUBE on Windows deployment.

Install latest Docker and Docker Compose.

Currently tested platforms:

  • Docker 18.06.0+
  • Docker Compose 1.27.0+
  • Ubuntu 18.04+ and MAC OS X 10.14+

On a Linux machine make sure to add your computer user to the docker group

Consult this page https://docs.docker.com/engine/install/linux-postinstall/

TL;DR

If you read nothing else on this page, and just want to get an instance of the ChRIS backend services up and running with no mess, no fuss:

The real TL;DR

The all in one copy/paste line to drop into your terminal (assuming of course you are in the repo directory and have the preconditions met):

docker swarm leave --force && docker swarm init --advertise-addr 127.0.0.1 &&  \
./unmake.sh && sudo rm -fr CHRIS_REMOTE_FS && rm -fr CHRIS_REMOTE_FS &&        \
./make.sh -U -I -i

This will start a bare bones CUBE. This CUBE will NOT have any plugins installed. To install a set of plugins, do

./postscript.sh
Slightly longer but still short TL;DR

Start a local Docker Swarm cluster if not already started:

docker swarm init --advertise-addr 127.0.0.1

Get the source code from CUBE repo:

git clone https://github.com/FNNDSC/ChRIS_ultron_backend
cd ChRIS_ultron_backend

Run full CUBE instantiation with tests:

./unmake.sh ; sudo rm -fr CHRIS_REMOTE_FS; rm -fr CHRIS_REMOTE_FS; ./make.sh

Or skip unit and integration tests and the intro:

./unmake.sh ; sudo rm -fr CHRIS_REMOTE_FS; rm -fr CHRIS_REMOTE_FS; ./make.sh -U -I -s

Once the system is "up" you can add more compute plugins to the ecosystem:

./postscript.sh

The resulting CUBE instance uses the default Django development server and therefore is not suitable for production.

Production deployments

Please refer to https://github.com/FNNDSC/khris-helm

Development

Docker Swarm-based development environment:

Start a local Docker Swarm cluster if not already started:

docker swarm init --advertise-addr 127.0.0.1

Start CUBE from the repository source directory by running the make bash script

git clone https://github.com/FNNDSC/ChRIS_ultron_backEnd.git
cd ChRIS_ultron_backEnd
./make.sh

All the steps performed by the above script are properly documented in the script itself. After running this script all the automated tests should have successfully run and a Django development server should be running in interactive mode in this terminal.

Later you can stop and remove CUBE services and storage space by running the following bash script from the repository source directory:

./unmake.sh

Then remove the local Docker Swarm cluster if desired:

docker swarm leave --force

Kubernetes-based development environment:

Install single-node Kubernetes cluster. On MAC OS Docker Desktop includes a standalone Kubernetes server and client. Consult this page https://docs.docker.com/desktop/kubernetes/. On Linux there is a simple MicroK8s installation. Consult this page https://microk8s.io. Then create the required alias:

snap alias microk8s.kubectl kubectl
microk8s.kubectl config view --raw > $HOME/.kube/config

Start the Kubernetes cluster:

microk8s start

Start CUBE from the repository source directory by running the make bash script

git clone https://github.com/FNNDSC/ChRIS_ultron_backEnd.git
cd ChRIS_ultron_backEnd
export HOSTIP=<IP address of this machine>
./make.sh -O kubernetes

Later you can stop and remove CUBE services and storage space by running the following bash script from the repository source directory:

./unmake.sh -O kubernetes

Stop the Kubernetes cluster if desired:

microk8s stop

Rerun automated tests after modifying source code

Open another terminal and run the Unit and Integration tests within the container running the Django server:

To run only the Unit tests:

cd ChRIS_ultron_backEnd
docker compose -f docker-compose_dev.yml exec chris_dev python manage.py test --exclude-tag integration

To run only the Integration tests:

docker compose -f docker-compose_dev.yml exec chris_dev python manage.py test --tag integration

To run all the tests:

docker compose -f docker-compose_dev.yml exec chris_dev python manage.py test 

After running the Integration tests the ./CHRIS_REMOTE_FS directory must be empty otherwise it means some error has occurred and you should manually empty it.

Check code coverage of the automated tests

Make sure the chris_backend/ dir is world writable. Then type:

docker compose -f docker-compose_dev.yml exec chris_dev coverage run --source=feeds,plugins,userfiles,users manage.py test
docker compose -f docker-compose_dev.yml exec chris_dev coverage report

Using HTTPie client to play with the REST API

A simple GET request to retrieve the user-specific list of feeds:

http -a cube:cube1234 http://localhost:8000/api/v1/

A simple POST request to run the plugin with id 1:

http -a cube:cube1234 POST http://localhost:8000/api/v1/plugins/1/instances/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json template:='{"data":[{"name":"dir","value":"cube/"}]}'

Then keep making the following GET request until the "status" descriptor in the response becomes "finishedSuccessfully":

http -a cube:cube1234 http://localhost:8000/api/v1/plugins/instances/1/

Using swift client to list files in the users bucket

swift -A http://127.0.0.1:8080/auth/v1.0 -U chris:chris1234 -K testing list users

Documentation

REST API reference

Available here.

Install Sphinx and the http extension (useful to document the REST API)

pip install Sphinx
pip install sphinxcontrib-httpdomain

Build the html documentation

cd docs/
make html

ChRIS REST API design.

Available here.

ChRIS backend database design.

Available here.

Wiki.

Available here.

Learn More

If you are interested in contributing or joining us, Check here.

chris_ultron_backend's People

Contributors

aaishpra avatar anik120 avatar cornelia247 avatar danmcp avatar devbird007 avatar jbernal0019 avatar jdtzmn avatar jennydaman avatar junaruga avatar mamurak avatar nicolasrannou avatar pintogideon avatar qxprakash avatar ravisantoshgudimetla avatar rudolphpienaar avatar sandip117 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chris_ultron_backend's Issues

Allow GET + parameters queries

It will be important to let user query the API using parameters.
Is it supported already? If so, where can I see the list of supported parameters?

Use case:

GET Feed + favorite=true
GET Feeds + tag="grantX" + tag="topSecret"
Get Tags + username="nicolas"

POST, followed by immediate GET, can cause django server to hang.

Description:

When POST on feed instance to create a file, then immediatly trying to get the instance the new instance to inspect the status fails.

Expected:

It should return the instance with the proper status (started, success, etc.)

Reproduce:

(instance 63 is the instance that will be create by POST)
http -a chris:Chris1234 POST http://127.0.0.1:8001/api/v1/plugins/2/instances/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json && http -a chris:Chris1234 GET http://127.0.0.1:8001/api/v1/plugins/instances/63/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json

Result:

POST OK but then the GET hangs then timeouts.

HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/vnd.collection+json
Date: Wed, 26 Oct 2016 10:53:19 GMT
Location: http://127.0.0.1:8001/api/v1/plugins/instances/63/
Server: WSGIServer/0.2 CPython/3.5.2
Vary: Accept
X-Frame-Options: SAMEORIGIN

{
    "collection": {
        "href": "http://127.0.0.1:8001/api/v1/plugins/2/instances/", 
        "items": [
            {
                "data": [
                    {
                        "name": "id", 
                        "value": 63
                    }, 
                    {
                        "name": "plugin_name", 
                        "value": "pacsquery"
                    }, 
                    {
                        "name": "start_date", 
                        "value": "2016-10-26T10:53:19.638699Z"
                    }, 
                    {
                        "name": "end_date", 
                        "value": "2016-10-26T10:53:19.638742Z"
                    }, 
                    {
                        "name": "status", 
                        "value": "started"
                    }, 
                    {
                        "name": "owner", 
                        "value": "chris"
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/plugins/instances/63/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/63/", 
                        "rel": "feed"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/2/", 
                        "rel": "plugin"
                    }
                ]
            }
        ], 
        "links": [], 
        "version": "1.0"
    }
}


http: error: Request timed out (30s).

GET files return more data

currently:

                   {
                       "name": "fname",
                       "value": "~/users/jbernal/feed_35/simplefsapp_60/data/out.txt"
                   }
               ],

needed:

                   {
                       "name": "fname",
                       "value": "~/users/jbernal/feed_35/simplefsapp_60/data/out.txt"
                   },
                   {
                       "name": "plugin_instance",
                       "value": "60"
                   },
                   {
                       "name": "feed_id",
                       "value": "35"
                   }
               ],

Error handling when plugin added already exists

We might want to handle gracefully the case when we try to add a plugin that has already been added to the system:

(chris-ultron) ubuntu@chris-ultron:~/chris-ultron-backend/chris_backend/plugins/services$ python manager.py --add simpledsapp
(chris-ultron) ubuntu@chris-ultron:~/chris-ultron-backend/chris_backend/plugins/services$ python manager.py --add simpledsapp
Traceback (most recent call last):
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/mysql/base.py", line 110, in execute
    return self.cursor.execute(query, args)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 250, in execute
    self.errorhandler(self, exc, value)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/connections.py", line 42, in defaulterrorhandler
    raise errorvalue
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 247, in execute
    res = self._query(query)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 411, in _query
    rowcount = self._do_query(q)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 374, in _do_query
    db.query(q)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/connections.py", line 270, in query
    _mysql.connection.query(self, query)
_mysql_exceptions.IntegrityError: (1062, "Duplicate entry 'simpledsapp' for key 'plugins_plugin_name_2e574762_uniq'")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manager.py", line 186, in <module>
    manager.run()
  File "manager.py", line 65, in run
    self.add_plugin(options.name)
  File "manager.py", line 87, in add_plugin
    plugin.save()
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/base.py", line 796, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/base.py", line 824, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/base.py", line 908, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/base.py", line 947, in _do_insert
    using=using, raw=raw)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/query.py", line 1045, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 1054, in execute_sql
    cursor.execute(sql, params)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/django/db/backends/mysql/base.py", line 110, in execute
    return self.cursor.execute(query, args)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 250, in execute
    self.errorhandler(self, exc, value)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/connections.py", line 42, in defaulterrorhandler
    raise errorvalue
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 247, in execute
    res = self._query(query)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 411, in _query
    rowcount = self._do_query(q)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/cursors.py", line 374, in _do_query
    db.query(q)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/site-packages/MySQLdb/connections.py", line 270, in query
    _mysql.connection.query(self, query)
django.db.utils.IntegrityError: (1062, "Duplicate entry 'simpledsapp' for key 'plugins_plugin_name_2e574762_uniq'")

MEDIA_ROOT makes test fail

Right now, MEDIA_ROOT is defined as:

MEDIA_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR))) + '/users'

https://github.com/FNNDSC/ChRIS_ultron_backEnd/blob/master/chris_backend/config/settings/local.py#l30

If the MEDIA_ROOT directory doesn't exist on the file system, it makes the feed test_views fail because it tries to create test user in a directory that doesn't exist (/users).

2 possibilities:
-require the '/users' directory to be there (add a test for it to make sure there is a '/users' directory) - will also update vagrant/bootstrap.sh to create the /users directory.

or

-use makedirs instead of mkdir to recursively create the directories that do not exist.

thoughts @jbernal0019?

Think about global tags

Just a thought that came up during today's status meeting. See design ticket here:
FNNDSC/cube-design#15

This is a far out roadmap consideration and not of any urgency at all. It's not clear whether or not we'd want this right now either.

Plugins parameters handling

Some plugins require an <empty> string as input.
Right now, the following command is generated:

pacsquery --patientID --serverIP 123.123.23.32

instead of:

pacsquery --patientID "" --serverIP 123.123.23.32

In addition, some plugin might require space separated arguments, which could be a problem if not handled carefully:

pacsquery --patientID 1234 9845 --serverIP 123.123.23.32

instead of:

pacsquery --patientID 1234 9845 --serverIP 123.123.23.32

Finally, boolean argument should also work with the proposed solution:

pacsquery --patientID "1234 9845" --verbose --serverIP 123.123.23.32

For now, charms just appends all the arguments separated by a space:
https://github.com/FNNDSC/ChRIS_ultron_backEnd/blob/master/chris_backend/plugins/services/charm.py#L299

line 299 -> ' '.join(self.l_appArgs)

GET api root does not return all the feeds at once.

Description:

Get /api/v1/ returns the last 10 feeds and a link to another page that contains the next 10 feeds, and so on.

Expected:

Returns all the feeds at once.

Reproduce:

$> http -a chris:Chris1234 GET http://127.0.0.1:8001/api/v1/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json
nico@OSXLAP01821-2:~/work/gitroot/chris-ultron-backend/chris_backend (plugin_pacsquery)$ http -a chris:Chris1234 GET http://127.0.0.1:8001/api/v1/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/vnd.collection+json
Date: Wed, 26 Oct 2016 11:05:23 GMT
Server: WSGIServer/0.2 CPython/3.5.2
Vary: Accept
X-Frame-Options: SAMEORIGIN

{
    "collection": {
        "href": "http://127.0.0.1:8001/api/v1/", 
        "items": [
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/1/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note1/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/1/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/1/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/1/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/1/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/2/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note2/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/2/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/2/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/2/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/2/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/3/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note3/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/3/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/3/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/3/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/3/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/4/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note4/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/4/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/4/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/4/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/4/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/5/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note5/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/5/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/5/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/5/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/5/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/6/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note6/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/6/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/6/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/6/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/6/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/7/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note7/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/7/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/7/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/7/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/7/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/8/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note8/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/8/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/8/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/8/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/8/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/9/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note9/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/9/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/9/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/9/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/9/", 
                        "rel": "plugin_inst"
                    }
                ]
            }, 
            {
                "data": [
                    {
                        "name": "name", 
                        "value": ""
                    }
                ], 
                "href": "http://127.0.0.1:8001/api/v1/10/", 
                "links": [
                    {
                        "href": "http://127.0.0.1:8001/api/v1/users/1/", 
                        "rel": "owner"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/note10/", 
                        "rel": "note"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/10/tags/", 
                        "rel": "tags"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/10/comments/", 
                        "rel": "comments"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/10/files/", 
                        "rel": "files"
                    }, 
                    {
                        "href": "http://127.0.0.1:8001/api/v1/plugins/instances/10/", 
                        "rel": "plugin_inst"
                    }
                ]
            }
        ], 
        "links": [
            {
                "href": "http://127.0.0.1:8001/api/v1/plugins/", 
                "rel": "plugins"
            }, 
            {
                "href": "http://127.0.0.1:8001/api/v1/?page=2", 
                "rel": "next"
            }
        ], 
        "version": "1.0"
    }
}

Serializer validation function for GPU limit executed implicitly

Steps to reproduce error:

sudo ./docker-make-chris_dev.sh -UI
export HOST_IP=$(sudo ip route | grep -v docker | awk '{if(NF==11) print $9}')
pfurl --auth chris:chris1234 --verb POST --http ${HOST_IP}:8000/api/v1/plugins/8/instances/ \
--content-type application/vnd.collection+json \
--jsonwrapper 'template' --msg '{"data":[{"name":"dir","value":"/"},{"name":"sleepLength","value":"0"},{"name":"gpu_limit","value":"0"}]}' \
--quiet --jsonpprintindent 4

Initial Error:

validate_gpu_limit() missing 1 required positional argument: 'plugin'

Stack trace:

[23/Mar/2018 17:04:20] "POST /api/v1/plugins/8/instances/ HTTP/1.1" 201 746
  File "/usr/lib/python3.5/threading.py", line 882, in _bootstrap
    self._bootstrap_inner()
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.5/threading.py", line 862, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib/python3.5/socketserver.py", line 625, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.5/socketserver.py", line 354, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.5/socketserver.py", line 681, in __init__
    self.handle()
  File "/usr/local/lib/python3.5/dist-packages/django/core/servers/basehttp.py", line 174, in handle
    handler.run(self.server.get_app())
  File "/usr/lib/python3.5/wsgiref/handlers.py", line 137, in run
    self.result = application(self.environ, self.start_response)
  File "/usr/local/lib/python3.5/dist-packages/django/contrib/staticfiles/handlers.py", line 63, in __call__
    return self.application(environ, start_response)
  File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/wsgi.py", line 170, in __call__
    response = self.get_response(request)
  File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py", line 124, in get_response
    response = self._middleware_chain(request)
  File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/exception.py", line 39, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.5/dist-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/views.py", line 474, in dispatch
    response = handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/generics.py", line 243, in post
    return self.create(request, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/mixins.py", line 20, in create
    serializer.is_valid(raise_exception=True)
  File "/usr/src/chris_backend/collectionjson/services.py", line 72, in new_is_valid
    valid = is_valid_method(*args, **kwargs)
  File "/usr/src/chris_backend/plugins/serializers.py", line 68, in is_valid
    return super(PluginInstanceSerializer, self).is_valid(raise_exception=raise_exception)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py", line 236, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py", line 427, in run_validation
    value = self.to_internal_value(data)
  File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py", line 459, in to_internal_value
    validated_value = validate_method(validated_value)
  File "/usr/src/chris_backend/plugins/serializers.py", line 100, in validate_gpu_limit
    traceback.print_stack()

@ravisantoshgudimetla @jbernal0019 FYI

StringList parameter for plugins

Jorge Bernal Oh, If I understood your question then yes there is a TYPES list in models.py in the plugins Django app

[5:40 PM]
Nicolas Rannou yes that was that!

[5:40 PM]
Jorge Bernal The TYPES dictionary has been used in other modules like manager.py for example (edited)

[5:41 PM]
Nicolas Rannou OK

[5:41 PM]
do we also support β€œlist” of string for instance?

[5:42 PM]
the use case is for a tractography plugin for instance, user can choose the mode of reconstruction he wants to run

[5:44 PM]
Jorge Bernal Well i f you look at models.py so far we only support these parameter types in the DB: StringParameter, IntParameter, FloatParameter and BoolParameter

[5:44 PM]
Nicolas Rannou yes - I was wondering maybe more types were specified somewhere else

[5:47 PM]
Jorge Bernal If we want to implement a drop down-like list we probably will need to define another parameter type, something like StringListParameter which basically uses a dictionary similarly to TYPE_CHOICES in the same way as the Plugin model uses TYPE_CHOICES

[5:48 PM]
Another example is PluginParameter model using the dictionary PLUGIN_TYPE_CHOICES (edited)

[5:50 PM]
So basically you have to define the possible choices in a dictionary CHOICES ant then in the model doing including something like choices=CHOICES

[5:52 PM]
So probably the way of implementing this is a bit more complicated we can have another model like this:

    value = models.CharField(max_length=200, blank=True)
    plugin_inst = models.ForeignKey(PluginInstance, on_delete=models.CASCADE,
                                    related_name='string_param')
    plugin_param = models.ForeignKey(PluginParameter, on_delete=models.CASCADE,
                                     related_name='string_inst')

    def __str__(self):
        return self.value

setup Travis CI

We need to run the test on every commit to ensure we do not break things.

Setup script will probably look like vagrant/bootstrap.sh.

Default ChrisApp plugins properties

Is it possible to default the following arguments in the ChrisApp super class?

    SELFPATH        = os.path.dirname(__file__)
    SELFEXEC        = os.path.basename(__file__)
    EXECSHELL       = 'python3'

Right now, it also has to be defined at the plugin level:
https://github.com/FNNDSC/ChRIS_ultron_backEnd/blob/master/chris_backend/plugins/services/simplefsapp/simplefsapp.py#L28-L30

For instance, if we omit SELFPATH definition in the pacsretrieve.py file, the following error appear when trying to add the pacsretrieve plugin to the ChRIS API, even though the super class ChrisAPP defines it:

$> python manager.py --add pacsretrieve
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 665, in exec_module
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "../../plugins/services/pacsretrieve/pacsretrieve.py", line 19, in <module>
    class PacsRetrieveApp(ChrisApp):
  File "/home/ubuntu/chris-ultron-backend/chris_backend/plugins/services/base.py", line 48, in __init__
    attr))
ValueError: Class PacsRetrieveApp doesn't define SELFPATH class variable

Is it OK to rely on OS environment vars?

cc: @rudolphpienaar @jbernal0019

In a couple of places, (i.e. https://github.com/FNNDSC/ChRIS_ultron_backEnd/blob/master/chris_backend/plugins/services/charm.py#L26-L27), we rely directly on OS environment variable.

That doesn't feel 100% right to me.

What to you guys think if instead, we define "Global" or "Configuration" variable in our project.

Django could try to match those globals to OS environment vars but if it can not, it could assign it default values:

the following:

WORKON_HOME = os.environ['WORKON_HOME']
VIRTUAL_ENV = os.environ['VIRTUAL_ENV']

becomes:

WORKON_HOME = CHRIS.WORKON_HOME
VIRTUAL_ENV = CHRIS.VIRTUAL_ENV

and at some point in Django, wherever appropriate:

if os.environ['WORKON_HOME']:
  CHRIS.WORKON_HOME = os.environ['WORKON_HOME']
else:
  CHRIS.WORKON_HOME = 'current directory, etc...'

WDYT?

FYI: All the Travis setup @jbernal0019 did is now failing because it expects a environment variable to be defined. Of course it is easy to add in Travis but it got me to think that there might be a better more robust solution.
https://travis-ci.org/FNNDSC/ChRIS_ultron_backEnd

Error when running plugins test

Does it look familiar @jbernal0019 ?

(chris-ultron) ubuntu@chris-ultron:~/chris-ultron-backend/chris_backend$ python manage.py test
Creating test database for alias 'default'...
....................................................................................................EEEE......
======================================================================
ERROR: test_plugin_instance_create_failure_unauthenticated (plugins.tests.test_views.PluginInstanceListViewTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/ubuntu/chris-ultron-backend/chris_backend/plugins/services/manager.py", line 45, in _get_plugin_app_class
    plugin_app_module = import_module(plugin_app_module_name)
  File "/home/ubuntu/python-envs/chris-ultron/lib/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 944, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 956, in _find_and_load_unlocked
ImportError: No module named 'plugins.services.simpleapp'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ubuntu/chris-ultron-backend/chris_backend/plugins/tests/test_views.py", line 146, in setUp
    pl_manager.add_plugin('simpleapp')
  File "/home/ubuntu/chris-ultron-backend/chris_backend/plugins/services/manager.py", line 74, in add_plugin
    plugin_app_class = self._get_plugin_app_class(name)
  File "/home/ubuntu/chris-ultron-backend/chris_backend/plugins/services/manager.py", line 48, in _get_plugin_app_class
    plugin's app package was added." % plugin_app_module_name)
ImportError: Error: failed to import module plugins.services.simpleapp.simpleapp. Check if the                  plugin's app package was added.

Can not POST to FEED on HTTPS

On latest #master, doing a simple POST to create a new feed doesn't work:
POST /api/v1/plugins/3 (with appropriate payload)

returns:

Error:
[Mon Oct 24 06:11:21.831152 2016] [wsgi:error] [pid 9694:tid 139932718855936] [remote 10.0.2.2:49519] [Errno 2] No such file or directory: '/home/ubuntu/tmp/debug-charm.log'

Creating this directory manually bypasses the error but then get the following error:

[Mon Oct 24 06:18:56.341476 2016] [wsgi:error] [pid 10194:tid 140208603395840] [remote 10.0.2.2:50299]     if len(sys.argv) == 1: sys.exit(1)
[Mon Oct 24 06:18:56.341527 2016] [wsgi:error] [pid 10194:tid 140208603395840] [remote 10.0.2.2:50299] SystemExit: 1

https://github.com/FNNDSC/pman/blob/master/purl.py#L117

Commenting the line out, I get the following error:

[Mon Oct 24 06:21:06.444133 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] \x1b[1;33m---->
[Mon Oct 24 06:21:06.444159 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] 2016-10-24 06:21:06.444144 | http://10.0.2.15:5010/
[Mon Oct 24 06:21:06.444167 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540]  {'meta': {'echoBack': 'Hi there!', 'askAbout': 'sysinfo'}, 'action': 'hello'}
[Mon Oct 24 06:21:06.444173 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] 
[Mon Oct 24 06:21:06.444181 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] \x1b[1;33m---->
[Mon Oct 24 06:21:06.444847 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] \x1b[0m\x1b[0;35m2016-10-24 06:21:06.444830 | Sending data...
[Mon Oct 24 06:21:06.445172 2016] [wsgi:error] [pid 10336:tid 140208688183040] [remote 10.0.2.2:50540] \x1b[0m(7, 'Failed to connect to 10.0.2.15 port 5010: Connection refused')

Looking into this one now, not sure if it could be HTTPS related issue or I am just missing a pman server running (I thought in latest version there was no need to start pman manually)

cc @rudolphpienaar

Fix destroy

Fix the docker destroy script which has slightly different behavior on mac-vs-linux for container naming.

Build fails

Seems to be MySQL version issues in Travis

Define feed / files / plugin relationships in DB

Ref: https://www.gliffy.com/go/html5/10518181 (v25)

Option 1

Feed contains N plugins, each plugin contains N files.

Feed -> Plugins -> Files

Pros

  • Consistent
  • Keep track of plugin_fs that created the Feed
  • Feed to it simplest

Cons:

Option 2 (current DB structure) (to be clarified with Jorge)

Feed contains N plugins. Feed contains N files from all the plugins. Plugin doesn't contain anything.

Feed -> Files
     -> Plugins

Pros

  • Direct link Feed -> Files might be useful (use case?)

Cons:

  • Plugin should contain the files it created.

Option 3 (to be clarified with Rudolph)

Feed contains N files from the plugin_fs that created the Feed (or contains all the files?). Feed contains N plugins (including Plugin_fs?). Plugin contains N files.

Feed -> Files
     -> Plugins -> Files

Pros:

  • - if Files are Files from all plugins - Direct link Feed -> Files might be useful (use case?)
  • - if Plugins contains all plugins, including Plugin_fs that created the Feed- it is useful.

Cons:

  • If Feed only contains the files from the plugin_fs which is the use case? (related to next cons)
  • If Plugins doesn't contain the Plugin_FS that created the feed doesn't seem that there is a missing piece. Any reason this architecture (more complex) better than Option 1?

List index out of range after restarting PMAN

NOTE FROM RUDOLPH: These steps are not necessarily guaranteed to recreate the issue always. The key is killing pman before it saves its internal database. This is not always guaranteed. If pman has saved its internal database then the issue won't occur.

Steps to reproduce:

  1. Start PMAN
> pman --raw 1 --http  --port 5010 --listeners 12
  1. In another terminal start Django server
> python manage.py runserver
  1. POST to CHRIS to create a FS plugin
> http -a user:password POST http://127.0.0.1:8000/api/v1/plugins/1/instances/ Content-Type:application/vnd.collection+json Accept:application/vnd.collection+json template:='{"data":[{"name":"dir","value":"./"}]}'
  1. Stop PMAN
> <ctrl>c
  1. Restart PMAN
>  pman --raw 1 --http  --port 5010 --listeners 12
  1. GET to CHRIS to get the FS plugin instance that was just created
> http -a user:password http://127.0.0.1:8000/api/v1/plugins/instances/9/ 

Client message after 6.

HTTP/1.0 500 Internal Server Error
Content-Type: application/vnd.collection+json
Date: Wed, 15 Mar 2017 16:52:34 GMT
Server: WSGIServer/0.2 CPython/3.5.2
X-Frame-Options: SAMEORIGIN

{
    "collection": {
        "error": {
            "message": "Internal server error"
        },
        "href": "http://127.0.0.1:8000/api/v1/plugins/instances/9/",
        "version": "1.0"
    }
}

Server side message after 6.

list index out of range
[15/Mar/2017 12:52:34] "GET /api/v1/plugins/instances/9/ HTTP/1.1" 500 135

Can not register file name that is too long

When a filename (path + name) is too long, a file can not be registered.
https://github.com/FNNDSC/ChRIS_ultron_backEnd/blob/master/chris_backend/plugins/models.py#L116

For instance:

dirpath = `home/ubuntu/users/chris/feed_121/pacsquery_137/pacsretrieve_138/data/2175-Anonimo/CODO.LATERAL-20150820-1.2.392.200036.9125.2.1884891176246156.64786914937.231631/CODO.LATERAL-20150820-1.2.392.200036.9125.3.1884891176246156.64786914937.231635/` (edited)
and 
name = `series.info`

error:

(1406, "Data too long for column 'fname' at row 1")

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.