2021-08-04 15:07:59 +00:00
|
|
|
from flask import current_app
|
2021-02-01 11:51:07 +00:00
|
|
|
from ..models import Corpus
|
2020-11-09 15:14:19 +00:00
|
|
|
import docker
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
|
2021-02-01 11:51:07 +00:00
|
|
|
class CheckCorporaMixin:
|
|
|
|
def check_corpora(self):
|
|
|
|
corpora = Corpus.query.all()
|
2021-11-16 14:23:57 +00:00
|
|
|
for corpus in (x for x in corpora if x.status == 'submitted'):
|
2021-02-01 11:51:07 +00:00
|
|
|
self.create_build_corpus_service(corpus)
|
2021-11-16 14:23:57 +00:00
|
|
|
for corpus in (x for x in corpora if x.status == 'queued' or x.status == 'running'): # noqa
|
2021-02-01 11:51:07 +00:00
|
|
|
self.checkout_build_corpus_service(corpus)
|
2021-11-16 14:23:57 +00:00
|
|
|
for corpus in (x for x in corpora if x.status == 'prepared' and x.num_analysis_sessions > 0): # noqa
|
|
|
|
corpus.status = 'start analysis'
|
|
|
|
for corpus in (x for x in corpora if x.status == 'analysing' and x.num_analysis_sessions == 0): # noqa
|
|
|
|
corpus.status = 'stop analysis'
|
|
|
|
for corpus in (x for x in corpora if x.status == 'analysing'):
|
2021-07-16 11:07:01 +00:00
|
|
|
self.checkout_analysing_corpus_container(corpus)
|
2021-11-16 14:23:57 +00:00
|
|
|
for corpus in (x for x in corpora if x.status == 'start analysis'):
|
|
|
|
self.create_cqpserver_container(corpus)
|
|
|
|
for corpus in (x for x in corpora if x.status == 'stop analysis'):
|
2021-02-01 11:51:07 +00:00
|
|
|
self.remove_cqpserver_container(corpus)
|
2020-11-09 15:14:19 +00:00
|
|
|
|
2021-02-01 11:51:07 +00:00
|
|
|
def create_build_corpus_service(self, corpus):
|
2021-08-04 15:07:59 +00:00
|
|
|
''' # Docker service settings # '''
|
|
|
|
''' ## Command ## '''
|
|
|
|
command = 'docker-entrypoint.sh build-corpus'
|
|
|
|
''' ## Constraints ## '''
|
|
|
|
constraints = ['node.role==worker']
|
|
|
|
''' ## Image ## '''
|
2021-11-16 14:23:57 +00:00
|
|
|
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cqpserver:r1674' # noqa
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ## Labels ## '''
|
|
|
|
labels = {
|
|
|
|
'origin': current_app.config['SERVER_NAME'],
|
|
|
|
'type': 'build-corpus',
|
|
|
|
'corpus_id': str(corpus.id)
|
2021-02-01 11:51:07 +00:00
|
|
|
}
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ## Mounts ## '''
|
|
|
|
''' ### Corpus file mount ### '''
|
|
|
|
corpus_file_source = os.path.join(corpus.path, 'merged', 'corpus.vrt')
|
|
|
|
corpus_file_target = '/root/files/corpus.vrt'
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus_file_mount = f'{corpus_file_source}:{corpus_file_target}:ro'
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ### Corpus data mount ### '''
|
|
|
|
corpus_data_source = os.path.join(corpus.path, 'data')
|
|
|
|
corpus_data_target = '/corpora/data'
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus_data_mount = f'{corpus_data_source}:{corpus_data_target}:rw'
|
2021-08-04 15:07:59 +00:00
|
|
|
# Make sure that their is no data in the corpus data directory
|
|
|
|
shutil.rmtree(corpus_data_source, ignore_errors=True)
|
|
|
|
os.mkdir(corpus_data_source)
|
|
|
|
''' ### Corpus registry mount ### '''
|
|
|
|
corpus_registry_source = os.path.join(corpus.path, 'registry')
|
|
|
|
corpus_registry_target = '/usr/local/share/cwb/registry'
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus_registry_mount = f'{corpus_registry_source}:{corpus_registry_target}:rw' # noqa
|
2021-08-04 15:07:59 +00:00
|
|
|
# Make sure that their is no data in the corpus registry directory
|
|
|
|
shutil.rmtree(corpus_registry_source, ignore_errors=True)
|
|
|
|
os.mkdir(corpus_registry_source)
|
|
|
|
mounts = [corpus_file_mount, corpus_data_mount, corpus_registry_mount]
|
|
|
|
''' ## Name ## '''
|
2021-11-16 14:23:57 +00:00
|
|
|
name = f'build-corpus_{corpus.id}'
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ## Restart policy ## '''
|
|
|
|
restart_policy = docker.types.RestartPolicy()
|
2021-02-01 11:51:07 +00:00
|
|
|
try:
|
2021-08-04 15:07:59 +00:00
|
|
|
self.docker.services.create(
|
|
|
|
image,
|
|
|
|
command=command,
|
|
|
|
constraints=constraints,
|
|
|
|
labels=labels,
|
|
|
|
mounts=mounts,
|
|
|
|
name=name,
|
|
|
|
restart_policy=restart_policy
|
|
|
|
)
|
2021-02-01 11:51:07 +00:00
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Create service "{name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
2021-11-16 14:23:57 +00:00
|
|
|
return
|
|
|
|
corpus.status = 'queued'
|
2021-02-01 11:51:07 +00:00
|
|
|
|
|
|
|
def checkout_build_corpus_service(self, corpus):
|
2021-11-16 14:23:57 +00:00
|
|
|
service_name = f'build-corpus_{corpus.id}'
|
2021-02-01 11:51:07 +00:00
|
|
|
try:
|
|
|
|
service = self.docker.services.get(service_name)
|
2021-11-16 14:23:57 +00:00
|
|
|
except docker.errors.NotFound as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get service "{service_name}" failed '
|
|
|
|
+ f'due to "docker.errors.NotFound": {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
|
|
|
corpus.status = 'failed'
|
2021-11-16 14:23:57 +00:00
|
|
|
return
|
2021-02-01 11:51:07 +00:00
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get service "{service_name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
2021-11-16 14:23:57 +00:00
|
|
|
service_tasks = service.tasks()
|
|
|
|
if not service_tasks:
|
|
|
|
return
|
|
|
|
task_state = service_tasks[0].get('Status').get('State')
|
|
|
|
if corpus.status == 'queued' and task_state != 'pending':
|
|
|
|
corpus.status = 'running'
|
|
|
|
return
|
|
|
|
elif corpus.status == 'running' and task_state == 'complete':
|
|
|
|
corpus.status = 'prepared'
|
|
|
|
elif corpus.status == 'running' and task_state == 'failed':
|
|
|
|
corpus.status = 'failed'
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
service.remove()
|
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Remove service "{service_name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
2020-11-09 15:14:19 +00:00
|
|
|
|
2021-02-01 11:51:07 +00:00
|
|
|
def create_cqpserver_container(self, corpus):
|
2021-08-04 15:07:59 +00:00
|
|
|
''' # Docker container settings # '''
|
|
|
|
''' ## Command ## '''
|
|
|
|
command = 'cqpserver'
|
|
|
|
''' ## Detach ## '''
|
|
|
|
detach = True
|
|
|
|
''' ## Image ## '''
|
2021-11-16 14:23:57 +00:00
|
|
|
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cqpserver:r1674' # noqa
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ## Name ## '''
|
2021-11-16 14:23:57 +00:00
|
|
|
name = f'cqpserver_{corpus.id}'
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ## Network ## '''
|
|
|
|
network = 'nopaque_default'
|
|
|
|
''' ## Volumes ## '''
|
|
|
|
''' ### Corpus data volume ### '''
|
|
|
|
corpus_data_source = os.path.join(corpus.path, 'data')
|
|
|
|
corpus_data_target = '/corpora/data'
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus_data_volume = f'{corpus_data_source}:{corpus_data_target}:rw'
|
2021-08-04 15:07:59 +00:00
|
|
|
''' ### Corpus registry volume ### '''
|
|
|
|
corpus_registry_source = os.path.join(corpus.path, 'registry')
|
|
|
|
corpus_registry_target = '/usr/local/share/cwb/registry'
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus_registry_volume = f'{corpus_registry_source}:{corpus_registry_target}:rw' # noqa
|
2021-08-04 15:07:59 +00:00
|
|
|
volumes = [corpus_data_volume, corpus_registry_volume]
|
2021-02-01 11:51:07 +00:00
|
|
|
# Check if a cqpserver container already exists. If this is the case,
|
|
|
|
# remove it and create a new one
|
|
|
|
try:
|
2021-08-04 15:07:59 +00:00
|
|
|
container = self.docker.containers.get(name)
|
2021-02-01 11:51:07 +00:00
|
|
|
except docker.errors.NotFound:
|
|
|
|
pass
|
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get container "{name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
2020-11-09 15:14:19 +00:00
|
|
|
return
|
2021-02-01 11:51:07 +00:00
|
|
|
else:
|
2020-11-13 09:01:51 +00:00
|
|
|
try:
|
2021-02-01 11:51:07 +00:00
|
|
|
container.remove(force=True)
|
2020-11-13 09:01:51 +00:00
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Remove container "{name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2020-11-19 11:31:29 +00:00
|
|
|
)
|
2020-11-13 09:01:51 +00:00
|
|
|
return
|
|
|
|
try:
|
2021-11-16 14:23:57 +00:00
|
|
|
self.docker.containers.run(
|
|
|
|
image,
|
|
|
|
command=command,
|
|
|
|
detach=detach,
|
|
|
|
volumes=volumes,
|
|
|
|
name=name,
|
|
|
|
network=network
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
2021-11-16 14:23:57 +00:00
|
|
|
except docker.errors.ImageNotFound as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Run container "{name}" failed '
|
|
|
|
+ f'due to "docker.errors.ImageNotFound" error: {e}'
|
2021-02-01 11:51:07 +00:00
|
|
|
)
|
|
|
|
corpus.status = 'failed'
|
2021-11-16 14:23:57 +00:00
|
|
|
return
|
2020-11-13 09:01:51 +00:00
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Run container "{name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError" error: {e}'
|
2020-11-19 11:31:29 +00:00
|
|
|
)
|
2021-11-16 14:23:57 +00:00
|
|
|
return
|
|
|
|
corpus.status = 'analysing'
|
2020-11-09 15:14:19 +00:00
|
|
|
|
2021-07-16 11:07:01 +00:00
|
|
|
def checkout_analysing_corpus_container(self, corpus):
|
2021-11-16 14:23:57 +00:00
|
|
|
container_name = f'cqpserver_{corpus.id}'
|
2021-07-16 11:07:01 +00:00
|
|
|
try:
|
|
|
|
self.docker.containers.get(container_name)
|
2021-11-16 14:23:57 +00:00
|
|
|
except docker.errors.NotFound as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get container "{container_name}" failed '
|
|
|
|
+ f'due to "docker.errors.NotFound": {e}'
|
2021-09-16 09:15:31 +00:00
|
|
|
)
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus.num_analysis_sessions = 0
|
2021-07-16 11:07:01 +00:00
|
|
|
corpus.status = 'prepared'
|
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get container "{container_name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2021-07-16 11:07:01 +00:00
|
|
|
)
|
|
|
|
|
2021-02-01 11:51:07 +00:00
|
|
|
def remove_cqpserver_container(self, corpus):
|
2021-11-16 14:23:57 +00:00
|
|
|
container_name = f'cqpserver_{corpus.id}'
|
2020-11-13 09:01:51 +00:00
|
|
|
try:
|
2021-02-01 11:51:07 +00:00
|
|
|
container = self.docker.containers.get(container_name)
|
|
|
|
except docker.errors.NotFound:
|
2021-11-16 14:23:57 +00:00
|
|
|
corpus.status = 'prepared'
|
|
|
|
return
|
2020-11-13 09:01:51 +00:00
|
|
|
except docker.errors.APIError as e:
|
2021-09-16 09:15:31 +00:00
|
|
|
current_app.logger.error(
|
2021-11-16 14:23:57 +00:00
|
|
|
f'Get container "{container_name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
2020-11-19 11:31:29 +00:00
|
|
|
)
|
2020-11-13 09:01:51 +00:00
|
|
|
return
|
2021-11-16 14:23:57 +00:00
|
|
|
try:
|
|
|
|
container.remove(force=True)
|
|
|
|
except docker.errors.APIError as e:
|
|
|
|
current_app.logger.error(
|
|
|
|
f'Remove container "{container_name}" failed '
|
|
|
|
+ f'due to "docker.errors.APIError": {e}'
|
|
|
|
)
|