Skip to content

Commit fc92f8a

Browse files
committed
Run stop-registry-importer as last batch job step
1 parent eeea506 commit fc92f8a

11 files changed

Lines changed: 206 additions & 23 deletions

File tree

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ ARG APPINSIGHTS_VERSION=3.7.7
2222
# expose server port
2323
EXPOSE 8080
2424

25+
# install Python 3 + pip for the stop-registry importer script that is run as the
26+
# final step of the Spring Batch import job
27+
RUN apt-get update \
28+
&& apt-get install -y --no-install-recommends python3 python3-pip \
29+
&& rm -rf /var/lib/apt/lists/*
30+
31+
# install stop-registry importer Python dependencies and copy the script
32+
COPY ./stop-registry-importer/requirements.txt /opt/stop-registry-importer/requirements.txt
33+
RUN pip3 install --no-cache-dir --break-system-packages \
34+
-r /opt/stop-registry-importer/requirements.txt
35+
COPY --chmod=444 ./stop-registry-importer/importer.py /opt/stop-registry-importer/importer.py
36+
2537
# download script for reading Docker secrets
2638
ADD --chmod=555 https://raw.githubusercontent.com/HSLdevcom/jore4-tools/main/docker/read-secrets.sh /tmp/read-secrets.sh
2739

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -536,24 +536,29 @@ creates the source MSSQL database (_docker/mssql_init/populate.sql_) was changed
536536

537537
### Requirements
538538

539-
You need to have Docker installed on your system to run the script
539+
You need to have Docker installed on your system to run the script.
540+
541+
Also the python virtual environment must be set up to run the tests (´./developer.sh setup:python`).
540542
541543
### How to use
542544
543-
To run the stop registry importer script you need to have a Jore 3 database, a populated Jore 4 routes database and the `jore4-hasura` and `jore4-tiamat` microservices running.
545+
The script is automatically run as part of the import process.
546+
547+
To manually run the stop registry importer script you need to have a Jore 3 database, a populated Jore 4 routes database and the `jore4-hasura` and `jore4-tiamat` microservices running.
544548
545549
By default the script runs from the local Jore 3 `mssqltestdb` database and uses the base local `jore4-hasura` service as the target.
546550
You can change the source database and target Hasura instance by creating a `.env` file in the same directory as the script.
547551
548552
Set the values for variables you want to set:
549553
550554
```
551-
GRAPHQL_URL=
552-
GRAPHQL_SECRET=
553-
JORE3_USERNAME=
554-
JORE3_PASSWORD=
555-
JORE3_DATABASE_URL=
556-
JORE3_DATABASE_NAME=
555+
HASURA_API_URL=
556+
HASURA_ADMIN_SECRET=
557+
SOURCE_DB_USERNAME=
558+
SOURCE_DB_PASSWORD=
559+
SOURCE_DB_HOSTNAME=
560+
SOURCE_DB_PORT=
561+
SOURCE_DB_DATABASE=
557562
```
558563
559564
You should have run the base Jore 3 importer first which ensures the Jore 4 database has the required scheduled stop points. Then run the stop registry import script which will match scheduled stop points in the Jore 4 routes database with stops in the Jore 3 database and generate GraphQL mutations to Hasura/Tiamat according to the data. The script will also link the generated stop registry stops with the scheduled stop points by their NeTEx ID using Hasura for the mutation.

development.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ cd "$(dirname "$0")" # Setting the working directory as the script directory
1212
# variable.
1313
DOCKER_COMPOSE_BUNDLE_REF=${BUNDLE_REF:-main}
1414

15+
STOP_REGISTRY_IMPORTER_DIR="./stop-registry-importer"
16+
STOP_REGISTRY_VENV_DIR="${STOP_REGISTRY_IMPORTER_DIR}/.venv-stop-registry"
17+
STOP_REGISTRY_REQUIREMENTS_FILE="${STOP_REGISTRY_IMPORTER_DIR}/requirements.txt"
18+
1519
# Define a Docker Compose project name to distinguish
1620
# the docker environment of this project from others
1721
export COMPOSE_PROJECT_NAME=jore3-importer
@@ -55,6 +59,9 @@ print_usage() {
5559
generate:jooq
5660
Generate JOOQ classes.
5761
62+
python:setup
63+
Creates/updates Python virtualenv for stop-registry importer and installs dependencies.
64+
5865
stop
5966
Stop the dependencies and the dockerized application.
6067
@@ -202,7 +209,8 @@ seed_tram_infra_links() {
202209
}
203210

204211
start_all() {
205-
$DOCKER_COMPOSE_CMD up --build -d importer-jooq-database importer-test-database jore4-mssqltestdb jore4-hasura jore4-testdb jore4-jore3importer jore4-mapmatchingdb jore4-mapmatching jore4-tiamat jore4-auth jore4-idp
212+
start_deps
213+
$DOCKER_COMPOSE_CMD up --build -d jore4-jore3importer
206214
}
207215

208216
start_deps() {
@@ -251,6 +259,15 @@ upload_zones() {
251259
curl --silent --output /dev/null --show-error --fail -X POST -H"Content-Type: application/xml" -d @netex/hsl-zones-netex.xml localhost:3010/services/stop_places/netex
252260
}
253261

262+
setup_python() {
263+
if [ ! -d "$STOP_REGISTRY_VENV_DIR" ]; then
264+
python3 -m venv "$STOP_REGISTRY_VENV_DIR"
265+
fi
266+
267+
"$STOP_REGISTRY_VENV_DIR/bin/pip" install --upgrade pip
268+
"$STOP_REGISTRY_VENV_DIR/bin/pip" install -r "$STOP_REGISTRY_REQUIREMENTS_FILE"
269+
}
270+
254271
### Control flow
255272

256273
COMMAND=${1:-}
@@ -263,12 +280,14 @@ fi
263280
case $COMMAND in
264281
start)
265282
download_docker_compose_bundle
283+
setup_python
266284
start_all
267285
upload_zones
268286
;;
269287

270288
start:deps)
271289
download_docker_compose_bundle
290+
setup_python
272291
start_deps
273292
upload_zones
274293
;;
@@ -278,6 +297,10 @@ case $COMMAND in
278297
generate_jooq
279298
;;
280299

300+
python:setup)
301+
setup_python
302+
;;
303+
281304
stop)
282305
stop
283306
;;
@@ -288,6 +311,7 @@ case $COMMAND in
288311

289312
recreate)
290313
remove
314+
setup_python
291315
start_deps
292316
upload_zones
293317
;;

profiles/ci/config.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,9 @@ digiroad.stop.csv.file.url=
7373

7474
# The map matching API URL
7575
map.matching.api.baseUrl=http://localhost:3005
76+
77+
# Stop-registry importer execution (container paths)
78+
stop.registry.importer.python.command=python3
79+
stop.registry.importer.script.path=/opt/stop-registry-importer/importer.py
80+
stop.registry.importer.working.directory=/opt/stop-registry-importer
81+
stop.registry.importer.timeout.hours=2

profiles/dev/config.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ digiroad.stop.csv.file.url=https://stjore4dev001.blob.core.windows.net/jore4-dig
5858
# The base URL of the Map Matching API
5959
map.matching.api.baseUrl=http://localhost:3005
6060

61+
# Stop-registry importer execution (local workspace paths)
62+
stop.registry.importer.python.command=.venv-stop-registry/bin/python
63+
stop.registry.importer.script.path=importer.py
64+
stop.registry.importer.working.directory=stop-registry-importer
65+
stop.registry.importer.timeout.hours=2
66+
6167
#
6268
# TEST SETTINGS
6369
#

profiles/prod/config.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ digiroad.stop.csv.file.url=https://stjore4dev001.blob.core.windows.net/jore4-dig
4141

4242
# The base URL of the map matching API
4343
map.matching.api.baseUrl=
44+
45+
# Stop-registry importer execution (container paths)
46+
stop.registry.importer.python.command=python3
47+
stop.registry.importer.script.path=/opt/stop-registry-importer/importer.py
48+
stop.registry.importer.working.directory=/opt/stop-registry-importer
49+
stop.registry.importer.timeout.hours=2

src/main/java/fi/hsl/jore/importer/config/jobs/JobConfig.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import fi.hsl.jore.importer.feature.batch.stop_place.StopPlaceImportProcessor;
6868
import fi.hsl.jore.importer.feature.batch.stop_place.StopPlaceImportReader;
6969
import fi.hsl.jore.importer.feature.batch.stop_place.support.IStopPlaceImportRepository;
70+
import fi.hsl.jore.importer.feature.batch.stop_registry.RunStopRegistryImporterTasklet;
7071
import fi.hsl.jore.importer.feature.infrastructure.link.dto.Jore3Link;
7172
import fi.hsl.jore.importer.feature.infrastructure.link_shape.dto.Jore3LinkShape;
7273
import fi.hsl.jore.importer.feature.infrastructure.node.dto.Jore3Node;
@@ -112,14 +113,17 @@
112113
import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy;
113114
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
114115
import org.springframework.boot.batch.autoconfigure.BatchTransactionManager;
116+
import org.springframework.beans.factory.annotation.Value;
115117
import org.springframework.context.annotation.Bean;
116118
import org.springframework.context.annotation.ComponentScan;
117119
import org.springframework.context.annotation.Configuration;
120+
import org.springframework.context.annotation.PropertySource;
118121
import org.springframework.transaction.PlatformTransactionManager;
119122

120123
@Configuration
121124
@EnableAutoConfiguration
122125
@ComponentScan(basePackages = "fi.hsl.jore.importer.feature")
126+
@PropertySource("classpath:application.properties")
123127
public class JobConfig {
124128
public static final String JOB_NAME = "importJoreJob";
125129

@@ -148,7 +152,9 @@ public Job importJob(
148152
final Flow importScheduledStopPointsFlow,
149153
final Flow importStopPlacesFlow,
150154
// Export data from the importer staging DB to Jore 4 DB.
151-
final Flow jore4ExportFlow) {
155+
final Flow jore4ExportFlow,
156+
// Final step: run the external Python stop-registry importer.
157+
final Flow stopRegistryImportFlow) {
152158
return new JobBuilder(JOB_NAME, jobRepository)
153159
.start(importNodesFlow)
154160
.next(importLinksFlow)
@@ -162,10 +168,32 @@ public Job importJob(
162168
.next(importScheduledStopPointsFlow)
163169
.next(importStopPlacesFlow)
164170
.next(jore4ExportFlow)
171+
.next(stopRegistryImportFlow)
165172
.end()
166173
.build();
167174
}
168175

176+
@Bean
177+
public Step runStopRegistryImporterStep(
178+
@Value("${stop.registry.importer.python.command:python3}") final String pythonCommand,
179+
@Value("${stop.registry.importer.script.path:importer.py}") final String scriptPath,
180+
@Value("${stop.registry.importer.working.directory:stop-registry-importer}") final String workingDir,
181+
@Value("${stop.registry.importer.timeout.hours:2}") final long timeoutHours) {
182+
return new StepBuilder("runStopRegistryImporterStep", jobRepository)
183+
.allowStartIfComplete(true)
184+
.tasklet(
185+
new RunStopRegistryImporterTasklet(pythonCommand, scriptPath, workingDir, timeoutHours),
186+
transactionManager)
187+
.build();
188+
}
189+
190+
@Bean
191+
public Flow stopRegistryImportFlow(final Step runStopRegistryImporterStep) {
192+
return new FlowBuilder<SimpleFlow>("stopRegistryImportFlow")
193+
.start(runStopRegistryImporterStep)
194+
.build();
195+
}
196+
169197
@Bean
170198
public Flow importNodesFlow(final Step prepareNodesStep, final Step importNodesStep, final Step commitNodesStep) {
171199

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package fi.hsl.jore.importer.feature.batch.stop_registry;
2+
3+
import java.io.BufferedReader;
4+
import java.io.File;
5+
import java.io.InputStreamReader;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.Map;
8+
import java.util.List;
9+
import java.util.concurrent.TimeUnit;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.springframework.batch.core.scope.context.ChunkContext;
13+
import org.springframework.batch.core.step.StepContribution;
14+
import org.springframework.batch.core.step.tasklet.Tasklet;
15+
import org.springframework.batch.infrastructure.repeat.RepeatStatus;
16+
17+
/**
18+
* Runs the external Python "stop-registry importer" script as a Spring Batch tasklet, streaming the script's
19+
* stdout/stderr line-by-line through SLF4J so that its output is intermixed with the Java application's normal logs.
20+
*/
21+
public class RunStopRegistryImporterTasklet implements Tasklet {
22+
23+
private static final Logger LOG = LoggerFactory.getLogger("stop-registry-importer");
24+
25+
private final String pythonCommand;
26+
private final String scriptPath;
27+
private final String workingDir;
28+
private final long timeoutHours;
29+
30+
public RunStopRegistryImporterTasklet(
31+
final String pythonCommand,
32+
final String scriptPath,
33+
final String workingDir,
34+
final long timeoutHours) {
35+
this.pythonCommand = pythonCommand;
36+
this.scriptPath = scriptPath;
37+
this.workingDir = workingDir;
38+
this.timeoutHours = timeoutHours;
39+
}
40+
41+
@Override
42+
public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) throws Exception {
43+
44+
LOG.info("Starting stop-registry importer script: {} (cwd={}, command={})", scriptPath, workingDir, pythonCommand);
45+
46+
final ProcessBuilder pb = new ProcessBuilder(List.of(pythonCommand, "-u", scriptPath))
47+
.directory(new File(workingDir))
48+
.redirectErrorStream(true);
49+
// Force unbuffered output from Python so logs appear in near real time.
50+
final Map<String, String> environment = pb.environment();
51+
environment.put("PYTHONUNBUFFERED", "1");
52+
environment.put("STOP_REGISTRY_IMPORTER_USE_DOTENV", "0");
53+
54+
final Process process = pb.start();
55+
56+
try (BufferedReader reader =
57+
new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
58+
String line;
59+
while ((line = reader.readLine()) != null) {
60+
LOG.info(line);
61+
}
62+
}
63+
64+
final boolean finished = process.waitFor(timeoutHours, TimeUnit.HOURS);
65+
if (!finished) {
66+
process.destroyForcibly();
67+
throw new IllegalStateException("Stop-registry importer timed out after " + timeoutHours + " hour(s)");
68+
}
69+
final int exitCode = process.exitValue();
70+
LOG.info("Stop-registry importer exited with code {}", exitCode);
71+
if (exitCode != 0) {
72+
throw new IllegalStateException("Stop-registry importer failed, exit code " + exitCode);
73+
}
74+
return RepeatStatus.FINISHED;
75+
}
76+
}

src/main/resources/application.properties

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,10 @@ digiroad.stop.csv.file.url=@digiroad.stop.csv.file.url@
2525

2626
# The base url of the map matching API
2727
map.matching.api.baseUrl=@map.matching.api.baseUrl@
28+
29+
# External stop-registry importer script settings
30+
stop.registry.importer.python.command=@stop.registry.importer.python.command@
31+
stop.registry.importer.script.path=@stop.registry.importer.script.path@
32+
stop.registry.importer.working.directory=@stop.registry.importer.working.directory@
33+
stop.registry.importer.timeout.hours=@stop.registry.importer.timeout.hours@
34+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# Don't try to run all the jobs immediately
22
spring.batch.job.enabled=false
3+
4+
# Keep test context in sync with profile-specific stop-registry importer settings.
5+
stop.registry.importer.python.command=@stop.registry.importer.python.command@
6+
stop.registry.importer.script.path=@stop.registry.importer.script.path@
7+
stop.registry.importer.working.directory=@stop.registry.importer.working.directory@
8+
stop.registry.importer.timeout.hours=@stop.registry.importer.timeout.hours@
9+

0 commit comments

Comments
 (0)