Skip to content

Commit

Permalink
Initial local UI+dsub docker compose
Browse files Browse the repository at this point in the history
  • Loading branch information
calbach authored and Bryan Crampton committed Sep 25, 2017
1 parent d8be8ac commit 19cae6a
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 43 deletions.
5 changes: 5 additions & 0 deletions Dockerfile.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM node

WORKDIR /app
ADD ui/package.json /app/
RUN npm install
2 changes: 1 addition & 1 deletion api/jobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ paths:
Query jobs by various filter criteria. Returned jobs are ordered from
newest to oldest submission time.
parameters:
- name: parameters
- name: body
required: true
in: body
schema:
Expand Down
23 changes: 22 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
version: '2'
services:
jobs:
ui:
build:
context: .
dockerfile: Dockerfile.ui
command: ["npm", "run-script", "ng", "--", "serve", "--host", "ui"]
volumes:
- ./ui:/app
# Hide this subdirectory from the above volume; just use what's already on
# the docker image so we don't need to reinstall node modules every time.
- /app/node_modules
dsub-server:
# dsub creates the local provider folder in /tmp/dsub-local which can only
# be written to by the owner. Unless dsub changes the permissions on this
# folder the docker container must be 'privileged'
Expand All @@ -16,6 +26,7 @@ services:
command: ["-b", ":8190"]
environment:
- PROVIDER_TYPE=local
- PATH_PREFIX=/api
# Avoid writing .pyc files back to the volume. Files generated this way
# have restricted permissions set which cause errors on subsequent docker
# builds.
Expand All @@ -28,3 +39,13 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8190:8190
jobs-proxy:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
links:
- ui
- dsub-server
ports:
- 4200:4200

25 changes: 25 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# nginx proxy over the UI and API job manager servers. For directive
# documentation, see http://nginx.org/en/docs/dirindex.html
# Required - just leave the defaults for now.
events {}
http {
# These host names are available in a docker-compose environment via
# docker linking.
upstream ui {
server ui:4200;
}
upstream api {
server dsub-server:8190;
}
server {
listen 4200;
# All API requests have a version prefix. Route everything else to
# the UI server.
location /api {
proxy_pass http://api;
}
location / {
proxy_pass http://ui;
}
}
}
25 changes: 15 additions & 10 deletions servers/dsub/jobs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@
from .encoder import JSONEncoder
from controllers.dsub_client import DSubClient

app = connexion.App(__name__, specification_dir='./swagger/', swagger_ui=False)
app.app.json_encoder = JSONEncoder
app.add_api('swagger.yaml')

# Log to stderr.
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
app.app.logger.addHandler(handler)
app.app.logger.setLevel(logging.INFO)

# gunicorn flags are passed via env variables, so we use these as the default
# values. These arguments will rarely be specified as flags directly, aside from
# occasional use during local debugging.
Expand All @@ -26,6 +16,11 @@
type=str,
help='The dsub provider type to use for monitoring jobs',
default=os.environ.get('PROVIDER_TYPE'))
parser.add_argument(
'--path_prefix',
type=str,
help='Path prefix, e.g. /api to serve from',
default=os.environ.get('PATH_PREFIX'))

if __name__ == '__main__':
parser.add_argument(
Expand All @@ -39,8 +34,18 @@
# gunicorn.
args, _ = parser.parse_known_args()

app = connexion.App(__name__, specification_dir='./swagger/', swagger_ui=False)
app.app.config['PROVIDER_TYPE'] = args.provider_type
app.app.config['CLIENT'] = DSubClient()

# Log to stderr.
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
app.app.logger.addHandler(handler)
app.app.logger.setLevel(logging.INFO)

app.app.json_encoder = JSONEncoder
app.add_api('swagger.yaml', base_path='/api')

if __name__ == '__main__':
app.run(host='0.0.0.0', port=args.port)
7 changes: 6 additions & 1 deletion servers/dsub/jobs/controllers/dsub_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ def query_jobs(self, provider, query):
# https://github.com/googlegenomics/dsub/issues/67
# Eventually, the pipelines API and dsub should support this query .

# TODO(https://github.com/googlegenomics/dsub/issues/69): Move this
# logic into dsub.
statuses = dstat_params['statuses']
if not statuses:
statuses = ['*']
jobs = dstat.dstat_job_producer(
provider=provider,
status_list=dstat_params['statuses'],
status_list=statuses,
create_time=dstat_params['create_time'],
job_name_list=dstat_params['job_name_list'],
full_output=True).next()
Expand Down
9 changes: 7 additions & 2 deletions servers/dsub/jobs/controllers/jobs_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def get_job(id):
provider = _get_provider(project_id)
job = client().get_job(provider, job_id, task_id)

# Note: dsub local provider does not yet update 'end-time'
# https://github.com/googlegenomics/dsub/issues/67
return JobMetadataResponse(
id=id,
status=job_statuses.dsub_to_api(job.get('status')),
Expand All @@ -58,17 +60,20 @@ def get_job(id):
labels=job.get('labels', {}))


def query_jobs():
def query_jobs(body):
"""
Query jobs by various filter criteria.
Args:
body (dict): The JSON request body.
Returns:
QueryJobsResponse: Response containing results from query
"""
if not connexion.request.is_json:
raise BadRequest("Request body is not JSON formatted")

query = QueryJobsRequest.from_dict(connexion.request.get_json())
query = QueryJobsRequest.from_dict(body)
jobs = client().query_jobs(_get_provider(query.parent_id), query)
results = [_query_result(j) for j in jobs]
return QueryJobsResponse(results=results)
Expand Down
2 changes: 1 addition & 1 deletion servers/dsub/jobs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ paths:
operationId: "query_jobs"
parameters:
- in: "body"
name: "parameters"
name: "body"
required: true
schema:
$ref: "#/definitions/QueryJobsRequest"
Expand Down
20 changes: 0 additions & 20 deletions ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@ import {
HttpModule, Http, BaseRequestOptions
} from '@angular/http';
import {BrowserModule} from '@angular/platform-browser';
import {MockBackend} from '@angular/http/testing';

import {AppRoutingModule} from './app-routing.module';
import { AppComponent } from './app.component';
import {JobMonitorService} from './job-monitor.service';
import {newDefaultMockJobMonitorService, MockJobMonitorService} from './mock-job-monitor.service';
import {ListJobsComponent} from './components/list-jobs/component';

export function httpFactory(backend: MockBackend, options: BaseRequestOptions,
service: MockJobMonitorService) {
service.subscribe(backend);
return new Http(backend, options);
}

@NgModule({
imports: [
AppRoutingModule,
Expand All @@ -31,18 +23,6 @@ export function httpFactory(backend: MockBackend, options: BaseRequestOptions,
],
providers: [
JobMonitorService,
MockBackend,
BaseRequestOptions,
{
provide: MockJobMonitorService,
useFactory: newDefaultMockJobMonitorService,
},
// TODO(alahwa): Support communication with real backends.
{
provide: Http,
useFactory: httpFactory,
deps: [MockBackend, BaseRequestOptions, MockJobMonitorService],
}
],
// This specifies the top-level component, to load first.
bootstrap: [AppComponent]
Expand Down
6 changes: 4 additions & 2 deletions ui/src/app/components/list-jobs/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ export class ListJobsComponent implements OnInit {
private jobMonitorService: JobMonitorService
) {}
ngOnInit(): void {
this.jobMonitorService.listAllJobs()
.then(response => this.jobs = response.results);
this.route.queryParams.subscribe((params: Params) => {
this.jobMonitorService.listAllJobs(params['parentId'])
.then(response => this.jobs = response.results);
});
}

toggleSelect(job: JobQueryResult): void {
Expand Down
16 changes: 11 additions & 5 deletions ui/src/app/job-monitor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@ import {JobAbortResponse} from './model/JobAbortResponse';
/** Service wrapper for accessing the job monitor API. */
@Injectable()
export class JobMonitorService {
private apiUrl = '/v1';
private apiUrl = '/api';
private headers = new Headers({'Content-Type': 'application/json'});

constructor(private http: Http) {}

listAllJobs(): Promise<JobQueryResponse> {
return this.http.get(`${this.apiUrl}/jobs`,
new RequestOptions({headers: this.headers}))
listAllJobs(parentId?: string): Promise<JobQueryResponse> {
return this.http.post(
`${this.apiUrl}/jobs/query`,
{
parentId: parentId,
},
new RequestOptions({
headers: this.headers,
}))
.toPromise()
.then(response => response.json() as JobQueryResponse)
.catch(this.handleError);
}

abortJob(id: string): Promise<JobAbortResponse> {
return this.http.get(`${this.apiUrl}/jobs/${id}/abort`,
return this.http.post(`${this.apiUrl}/jobs/${id}/abort`, {},
new RequestOptions({headers: this.headers}))
.toPromise()
.then(response => response.json() as JobAbortResponse)
Expand Down

0 comments on commit 19cae6a

Please sign in to comment.