From 7e71b5d38322e74c209ee438b67398ebbddadb98 Mon Sep 17 00:00:00 2001 From: Maaike Date: Tue, 2 Apr 2024 16:46:15 +0200 Subject: [PATCH] improve metadata detection and publication (#642) * detect MCF or WCMP2 for metadata publishing * move distribution link generation to a function * test for empty links * fix * add generated by * fix for data from dict * metadata publish fixes * add arg to detect how distribution links are generated (mcf or wcmp2) * protection for missing data-mappings * improved publish checks, update version * add force and flush data capabilities * fix ref * attempt to test mqtt-connect, move refresh data-mappings * fix/debug GH test * wait longer * reduce sleep, update test-count * sleep longer * test messages in order of CI * reduce test cases temporarily * revert * fix unpublish * cleanup and update testing * fix unpublish workflow, broker publish URL * prune romania * reduce sleep, reduce romania count * improved testing, set NotHandledError on STORAGE_PUBLIC * fix channels, fix stations count * sleep longer * sleep even longer * prune romania data some more * update minio: log errors + doc-strings * remove pub-conn-check, add refresh_data_mappings * remove time from mqtt * simplify wis2box-create-config, update docs * updated docs, improve local SSL certificates support * fix WCMP2 MQTT outbound link --------- Co-authored-by: Tom Kralidis --- .github/workflows/tests-docker.yml | 54 +- config-templates/countries.json | 1481 ----------------- config-templates/data-mappings.yml.tmpl | 59 - config-templates/metadata-synop.yml.tmpl | 61 - config-templates/metadata-temp.yml.tmpl | 62 - config-templates/station_list_example.csv | 1 - .../_static/wis2box-webapp-dataset_editor.png | Bin 0 -> 108295 bytes docs/source/user/public-services-setup.rst | 22 +- docs/source/user/setup.rst | 57 +- grafana/dashboards/home.json | 6 +- nginx/nginx-ssl.conf | 3 - nginx/nginx.conf | 3 - .../metadata/discovery/int-wmo-test-buoy.yml | 5 - .../metadata/discovery/int-wmo-test-ship.yml | 5 - .../discovery/int-wmo-test-wind_profiler.yml | 5 - .../ro-synoptic-weather-observations.yml | 3 - tests/data/metadata/station/station_list.csv | 47 +- ...1200CCA_C_EDZW_20230117174401_51649529.txt | 5 - ...1200CCB_C_EDZW_20230118094300_52396633.txt | 5 - ...1800CCB_C_EDZW_20230118055302_52230688.txt | 6 - ...K180000_C_EDZW_20230118000502_51936144.txt | 76 - ...K180600_C_EDZW_20230118060404_52242453.txt | 77 - ...K181200_C_EDZW_20230118120404_52514693.txt | 72 - ...K171200_C_EDZW_20230117125200_51396856.txt | 72 - ...1800CCA_C_EDZW_20230117184900_51697747.txt | 5 - ...K171800_C_EDZW_20230117181403_51669400.txt | 53 - ...0000CCA_C_EDZW_20230118004301_51967254.txt | 12 - ...K180000_C_EDZW_20230118001801_51945941.txt | 56 - ...O151752_C_EDZW_20240215180405_26717559.bin | Bin 7085 -> 0 bytes ...O160610_C_EDZW_20240216062204_27366803.bin | Bin 3319 -> 0 bytes tests/integration/test_workflow.py | 12 +- tests/test.env | 2 +- wis2box-create-config.py | 77 +- wis2box-ctl.py | 20 +- wis2box-management/wis2box/__init__.py | 2 +- wis2box-management/wis2box/api/__init__.py | 36 +- .../wis2box/api/backend/base.py | 11 + .../wis2box/api/backend/elastic.py | 11 + .../wis2box/api/config/pygeoapi.py | 5 +- wis2box-management/wis2box/data/__init__.py | 17 +- wis2box-management/wis2box/data/base.py | 25 +- wis2box-management/wis2box/data_mappings.py | 23 +- wis2box-management/wis2box/dataset.py | 11 +- wis2box-management/wis2box/env.py | 3 +- wis2box-management/wis2box/handler.py | 16 +- .../wis2box/metadata/discovery.py | 173 +- wis2box-management/wis2box/pubsub/message.py | 4 +- wis2box-management/wis2box/pubsub/mqtt.py | 15 +- .../wis2box/pubsub/subscribe.py | 20 +- wis2box-management/wis2box/storage/minio.py | 200 ++- 50 files changed, 511 insertions(+), 2485 deletions(-) delete mode 100644 config-templates/countries.json delete mode 100644 config-templates/data-mappings.yml.tmpl delete mode 100644 config-templates/metadata-synop.yml.tmpl delete mode 100644 config-templates/metadata-temp.yml.tmpl delete mode 100644 config-templates/station_list_example.csv create mode 100644 docs/source/_static/wis2box-webapp-dataset_editor.png delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK171200CCA_C_EDZW_20230117174401_51649529.txt delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK171200CCB_C_EDZW_20230118094300_52396633.txt delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK171800CCB_C_EDZW_20230118055302_52230688.txt delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK180000_C_EDZW_20230118000502_51936144.txt delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK180600_C_EDZW_20230118060404_52242453.txt delete mode 100644 tests/data/observations/romania/A_SMRO01YRBK181200_C_EDZW_20230118120404_52514693.txt delete mode 100644 tests/data/observations/romania_update/A_SMRO01YRBK171200_C_EDZW_20230117125200_51396856.txt delete mode 100644 tests/data/observations/romania_update/A_SMRO01YRBK171800CCA_C_EDZW_20230117184900_51697747.txt delete mode 100644 tests/data/observations/romania_update/A_SMRO01YRBK171800_C_EDZW_20230117181403_51669400.txt delete mode 100644 tests/data/observations/romania_update/A_SMRO01YRBK180000CCA_C_EDZW_20230118004301_51967254.txt delete mode 100644 tests/data/observations/romania_update/A_SMRO01YRBK180000_C_EDZW_20230118001801_51945941.txt delete mode 100644 tests/data/observations/wmo/buoy/A_IOBX18CWAO151752_C_EDZW_20240215180405_26717559.bin delete mode 100644 tests/data/observations/wmo/buoy/A_IOBX18CWAO160610_C_EDZW_20240216062204_27366803.bin diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml index 53a455e77..b11f762d9 100644 --- a/.github/workflows/tests-docker.yml +++ b/.github/workflows/tests-docker.yml @@ -42,7 +42,7 @@ jobs: docker logs wis2box-management - name: setup wis2box-management ⚙️ run: | - sleep 30 + sleep 20 python3 wis2box-ctl.py execute wis2box environment show - name: populate stations from CSV 📡 run: | @@ -50,94 +50,118 @@ jobs: - name: add Malawi synop data (csv2bufr synop_bufr template) 🇲🇼 env: TOPIC_HIERARCHY: mw-mw_met_centre.data.core.weather.surface-based-observations.synop + CHANNEL: origin/a/wis2/mw-mw_met_centre/data/core/weather/surface-based-observations/synop + TERRITORY: MWI DISCOVERY_METADATA: /data/wis2box/metadata/discovery/mw-surface-weather-observations.yml DISCOVERY_METADATA_ID: urn:wmo:md:mw-mw_met_centre:surface-weather-observations TEST_DATA: /data/wis2box/observations/malawi TEST_DATA_UPDATE: /data/wis2box/observations/malawi_update run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --territory-name $TERRITORY $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA_UPDATE - name: add Italy synop data (bufr2bufr) 🇮🇹 env: TOPIC_HIERARCHY: it-roma_met_centre.data.core.weather.surface-based-observations.synop + CHANNEL: origin/a/wis2/it-roma_met_centre/data/core/weather/surface-based-observations/synop + TERRITORY: ITA DISCOVERY_METADATA: /data/wis2box/metadata/discovery/it-surface-weather-observations.yml DISCOVERY_METADATA_ID: urn:wmo:md:it-roma_met_centre:surface-weather-observations TEST_DATA: /data/wis2box/observations/italy run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --territory-name $TERRITORY $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add Algeria synop data (bufr2bufr) 🇩🇿 env: TOPIC_HIERARCHY: dz-alger_met_centre.data.core.weather.surface-based-observations.synop + CHANNEL: origin/a/wis2/dz-alger_met_centre/data/core/weather/surface-based-observations/synop + TERRITORY: DZA DISCOVERY_METADATA: /data/wis2box/metadata/discovery/dz-surface-weather-observations.yml DISCOVERY_METADATA_ID: urn:wmo:md:dz-alger_met_centre:surface-weather-observations TEST_DATA: /data/wis2box/observations/algeria run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --territory-name $TERRITORY $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add Romania synop data (synop2bufr and csv2bufr aws-template) 🇷🇴 env: TOPIC_HIERARCHY: ro-rnimh.data.core.weather.surface-based-observations.synop + CHANNEL: origin/a/wis2/ro-rnimh/data/core/weather/surface-based-observations/synop + TERRITORY: ROU DISCOVERY_METADATA: /data/wis2box/metadata/discovery/ro-synoptic-weather-observations.yml DISCOVERY_METADATA_ID: urn:wmo:md:ro-rnimh:synoptic-weather-observations TEST_DATA: /data/wis2box/observations/romania run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --territory-name $TERRITORY $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add Congo synop data (synop2bufr) 🇨🇩 env: TOPIC_HIERARCHY: cd-brazza_met_centre.data.core.weather.surface-based-observations.synop + CHANNEL: origin/a/wis2/cd-brazza_met_centre/data/core/weather/surface-based-observations/synop + TERRITORY: COD DISCOVERY_METADATA: /data/wis2box/metadata/discovery/cd-surface-weather-observations.yml DISCOVERY_METADATA_ID: urn:wmo:md:cd-brazza_met_centre:surface-weather-observations TEST_DATA: /data/wis2box/observations/congo run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --territory-name $TERRITORY $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID - + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add example ship data (bufr2bufr) WMO env: TOPIC_HIERARCHY: int-wmo-test.data.core.weather.surface-based-observations.ship + CHANNEL: origin/a/wis2/int-wmo-test/data/core/weather/surface-based-observations/ship DISCOVERY_METADATA: /data/wis2box/metadata/discovery/int-wmo-test-ship.yml DISCOVERY_METADATA_ID: urn:wmo:md:int-wmo-test:surface-weather-observations:ship TEST_DATA: /data/wis2box/observations/wmo/ship run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-3WL23XC $CHANNEL + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-4EEWQMW $CHANNEL + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-4SGNZUJ $CHANNEL + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-EUCDE09 $CHANNEL + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-EUCDE34 $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID - check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add example buoy data (bufr2bufr) WMO env: TOPIC_HIERARCHY: int-wmo-test.data.core.weather.surface-based-observations.buoy + CHANNEL: origin/a/wis2/int-wmo-test/data/core/weather/surface-based-observations/buoy DISCOVERY_METADATA: /data/wis2box/metadata/discovery/int-wmo-test-buoy.yml DISCOVERY_METADATA_ID: urn:wmo:md:int-wmo-test:surface-weather-observations:buoy TEST_DATA: /data/wis2box/observations/wmo/buoy run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python3 wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-22000-0-1400011 $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add example wind profiler data (bufr2bufr) WMO env: TOPIC_HIERARCHY: int-wmo-test.data.core.weather.surface-based-observations.wind_profiler + CHANNEL: origin/a/wis2/int-wmo-test/data/core/weather/surface-based-observations/wind_profiler DISCOVERY_METADATA: /data/wis2box/metadata/discovery/int-wmo-test-wind_profiler.yml DISCOVERY_METADATA_ID: urn:wmo:md:int-wmo-test:surface-weather-observations:wind_profiler TEST_DATA: /data/wis2box/observations/wmo/wind_profiler run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + python wis2box-ctl.py execute wis2box metadata station add-topic --wsi 0-702-0-48698 $CHANNEL curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA - name: add China GRIB2 data (universal pipeline) 🇨🇳 env: TOPIC_HIERARCHY: cn-cma.data.core.weather.prediction.forecast.medium-range.probabilistic.global @@ -146,14 +170,10 @@ jobs: TEST_DATA: /data/wis2box/observations/china run: | python3 wis2box-ctl.py execute wis2box dataset publish $DISCOVERY_METADATA - python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA curl -s http://localhost/oapi/collections/discovery-metadata/items/$DISCOVERY_METADATA_ID --output /tmp/$DISCOVERY_METADATA_ID check-jsonschema --schemafile /tmp/wcmp2-bundled.json /tmp/$DISCOVERY_METADATA_ID - - name: sync stations 🔄 - run: | - sleep 30 - python3 wis2box-ctl.py execute wis2box metadata station publish-collection - - name: run integration tests ⚙️ + python3 wis2box-ctl.py execute wis2box data ingest -th $TOPIC_HIERARCHY -p $TEST_DATA + - name: sleep 30 seconds then run integration tests ⚙️ run: | sleep 30 pytest -s tests/integration diff --git a/config-templates/countries.json b/config-templates/countries.json deleted file mode 100644 index 7153ee3f2..000000000 --- a/config-templates/countries.json +++ /dev/null @@ -1,1481 +0,0 @@ -{ - "countries": { - "int": { - "name": "Intergovernmental Organization", - "bbox": { - "minx": -180.0, - "miny": -90.0, - "maxx": 180.0, - "maxy": 90.0 - } - }, - "org": { - "name": "International Organization", - "bbox": { - "minx": -180.0, - "miny": -90.0, - "maxx": 180.0, - "maxy": 90.0 - } - }, - "fj": { - "name": "Fiji", - "bbox": { - "minx": -180.0, - "miny": -21.9434274, - "maxx": 180.0, - "maxy": -12.2613866 - } - }, - "tz": { - "name": "Tanzania", - "bbox": { - "minx": 29.3269773, - "miny": -11.761254, - "maxx": 40.6584071, - "maxy": -0.9854812 - } - }, - "ca": { - "name": "Canada", - "bbox": { - "minx": -141.00275, - "miny": 41.6765556, - "maxx": -52.3237664, - "maxy": 83.3362128 - } - }, - "us": { - "name": "United States of America", - "bbox": { - "minx": -180.0, - "miny": -14.7608358, - "maxx": 180.0, - "maxy": 71.5889534 - } - }, - "kz": { - "name": "Kazakhstan", - "bbox": { - "minx": 46.4932179, - "miny": 40.5686476, - "maxx": 87.3156336, - "maxy": 55.4421701 - } - }, - "uz": { - "name": "Uzbekistan", - "bbox": { - "minx": 55.9985531, - "miny": 37.1816004, - "maxx": 73.2147728, - "maxy": 45.5906784 - } - }, - "pg": { - "name": "Papua New Guinea", - "bbox": { - "minx": 140.8416553, - "miny": -11.8555739, - "maxx": 159.6934961, - "maxy": -0.5573576 - } - }, - "id": { - "name": "Indonesia", - "bbox": { - "minx": 94.7717124, - "miny": -11.2085669, - "maxx": 141.0194444, - "maxy": 6.2744496 - } - }, - "ar": { - "name": "Argentina", - "bbox": { - "minx": -73.5605371, - "miny": -55.1925709, - "maxx": -53.6374515, - "maxy": -21.7808568 - } - }, - "cl": { - "name": "Chile", - "bbox": { - "minx": -109.6795789, - "miny": -56.725, - "maxx": -66.0753474, - "maxy": -17.4983832 - } - }, - "so": { - "name": "Somalia", - "bbox": { - "minx": 40.98918, - "miny": -1.8031969, - "maxx": 51.6177696, - "maxy": 12.1889121 - } - }, - "ke": { - "name": "Kenya", - "bbox": { - "minx": 33.9096888, - "miny": -4.8995204, - "maxx": 41.9067502, - "maxy": 4.62 - } - }, - "sd": { - "name": "Sudan", - "bbox": { - "minx": 5.5, - "miny": 9.9, - "maxx": 7.5, - "maxy": 11.9 - } - }, - "td": { - "name": "Chad", - "bbox": { - "minx": 13.47348, - "miny": 7.44107, - "maxx": 24.0, - "maxy": 23.4507957 - } - }, - "ht": { - "name": "Haiti", - "bbox": { - "minx": -75.2384618, - "miny": 17.820859, - "maxx": -71.6217461, - "maxy": 20.2911852 - } - }, - "ru": { - "name": "Russia", - "bbox": { - "minx": -84.424049, - "miny": 40.226398, - "maxx": -84.388279, - "maxy": 40.241081 - } - }, - "bs": { - "name": "Bahamas", - "bbox": { - "minx": -80.7001941, - "miny": 20.7059846, - "maxx": -72.4477521, - "maxy": 27.4734551 - } - }, - "no": { - "name": "Norway", - "bbox": { - "minx": 4.0875274, - "miny": 57.7590052, - "maxx": 31.7615929, - "maxy": 71.3848787 - } - }, - "gl": { - "name": "Greenland", - "bbox": { - "minx": -73.0263201, - "miny": 59.9737205, - "maxx": -11.3511087, - "maxy": 83.6608453 - } - }, - "tl": { - "name": "Timor-Leste", - "bbox": { - "minx": 124.0339863, - "miny": -9.6096854, - "maxx": 127.5433663, - "maxy": -8.1161123 - } - }, - "za": { - "name": "South Africa", - "bbox": { - "minx": 16.3335213, - "miny": -47.1788335, - "maxx": 38.2898954, - "maxy": -22.1250301 - } - }, - "ls": { - "name": "Lesotho", - "bbox": { - "minx": 27.0113763, - "miny": -30.6772773, - "maxx": 29.4557099, - "maxy": -28.5705652 - } - }, - "mx": { - "name": "Mexico", - "bbox": { - "minx": -99.3649242, - "miny": 19.0487187, - "maxx": -98.9403028, - "maxy": 19.5927572 - } - }, - "uy": { - "name": "Uruguay", - "bbox": { - "minx": -58.4947729, - "miny": -35.7824481, - "maxx": -53.0755833, - "maxy": -30.0853962 - } - }, - "br": { - "name": "Brazil", - "bbox": { - "minx": -73.9830625, - "miny": -33.8694284, - "maxx": -28.6289646, - "maxy": 5.2695808 - } - }, - "bo": { - "name": "Bolivia", - "bbox": { - "minx": -69.6450073, - "miny": -22.8982742, - "maxx": -57.453, - "maxy": -9.6689438 - } - }, - "pe": { - "name": "Peru", - "bbox": { - "minx": -84.6356535, - "miny": -20.1984472, - "maxx": -68.6519906, - "maxy": -0.0392818 - } - }, - "co": { - "name": "Colombia", - "bbox": { - "minx": -82.1243611, - "miny": -4.2294028, - "maxx": -66.8490386, - "maxy": 16.0495518 - } - }, - "pa": { - "name": "Panama", - "bbox": { - "minx": -83.0517245, - "miny": 7.0027171, - "maxx": -77.1585751, - "maxy": 9.8469028 - } - }, - "cr": { - "name": "Costa Rica", - "bbox": { - "minx": -87.2951397, - "miny": 5.2980534, - "maxx": -82.4299043, - "maxy": 11.2195684 - } - }, - "ni": { - "name": "Nicaragua", - "bbox": { - "minx": -87.8958171, - "miny": 10.7073945, - "maxx": -82.4767247, - "maxy": 15.0902778 - } - }, - "hn": { - "name": "Honduras", - "bbox": { - "minx": -89.3568207, - "miny": 12.9808485, - "maxx": -82.18164, - "maxy": 17.619522 - } - }, - "sv": { - "name": "El Salvador", - "bbox": { - "minx": -90.2089183, - "miny": 12.9518017, - "maxx": -87.5971467, - "maxy": 14.4510488 - } - }, - "gt": { - "name": "Guatemala", - "bbox": { - "minx": -92.3743889, - "miny": 13.5577935, - "maxx": -88.2134425, - "maxy": 17.8165947 - } - }, - "bz": { - "name": "Belize", - "bbox": { - "minx": -89.2275905, - "miny": 15.8857286, - "maxx": -87.2765017, - "maxy": 18.4959143 - } - }, - "ve": { - "name": "Venezuela", - "bbox": { - "minx": -73.3529632, - "miny": 0.6473964, - "maxx": -59.7707163, - "maxy": 15.9158431 - } - }, - "gy": { - "name": "Guyana", - "bbox": { - "minx": -61.3827449, - "miny": 1.1710017, - "maxx": -56.4689543, - "maxy": 8.6038842 - } - }, - "sr": { - "name": "Suriname", - "bbox": { - "minx": -58.0711851, - "miny": 1.8312802, - "maxx": -53.8601382, - "maxy": 6.2160842 - } - }, - "fr": { - "name": "France", - "bbox": { - "minx": -178.3873749, - "miny": -50.2187169, - "maxx": 172.3057152, - "maxy": 51.3055721 - } - }, - "ec": { - "name": "Ecuador", - "bbox": { - "minx": -92.2072392, - "miny": -5.0159314, - "maxx": -75.192504, - "maxy": 1.8835964 - } - }, - "pr": { - "name": "Puerto Rico", - "bbox": { - "minx": -68.1109184, - "miny": 17.7306659, - "maxx": -65.1100908, - "maxy": 18.6663822 - } - }, - "jm": { - "name": "Jamaica", - "bbox": { - "minx": -78.5782366, - "miny": 16.5899443, - "maxx": -75.7541143, - "maxy": 18.7256394 - } - }, - "cu": { - "name": "Cuba", - "bbox": { - "minx": -85.1679702, - "miny": 19.6275294, - "maxx": -73.9190004, - "maxy": 23.4816972 - } - }, - "zw": { - "name": "Zimbabwe", - "bbox": { - "minx": 25.2373, - "miny": -22.4241096, - "maxx": 33.0683501, - "maxy": -15.6097033 - } - }, - "bw": { - "name": "Botswana", - "bbox": { - "minx": 19.9986486, - "miny": -26.9070073, - "maxx": 29.3738868, - "maxy": -17.778137 - } - }, - "na": { - "name": "Namibia", - "bbox": { - "minx": 11.5280384, - "miny": -28.96945, - "maxx": 25.2617292, - "maxy": -16.9635105 - } - }, - "sn": { - "name": "Senegal", - "bbox": { - "minx": -17.7498686, - "miny": 12.2402664, - "maxx": -11.3459503, - "maxy": 16.6919712 - } - }, - "ml": { - "name": "Mali", - "bbox": { - "minx": -12.2402835, - "miny": 10.147811, - "maxx": 4.2673828, - "maxy": 25.001084 - } - }, - "mr": { - "name": "Mauritania", - "bbox": { - "minx": -17.2380959, - "miny": 14.7209909, - "maxx": -4.8333344, - "maxy": 27.314942 - } - }, - "bj": { - "name": "Benin", - "bbox": { - "minx": 0.776667, - "miny": 6.0398696, - "maxx": 3.8451454, - "maxy": 12.4092028 - } - }, - "ng": { - "name": "Nigeria", - "bbox": { - "minx": 2.676932, - "miny": 4.0690959, - "maxx": 14.678014, - "maxy": 13.885645 - } - }, - "cm": { - "name": "Cameroon", - "bbox": { - "minx": 8.3822176, - "miny": 1.6517945, - "maxx": 16.1911011, - "maxy": 13.083333 - } - }, - "tg": { - "name": "Togo", - "bbox": { - "minx": -0.1439746, - "miny": 5.926547, - "maxx": 1.8087257, - "maxy": 11.1395102 - } - }, - "gh": { - "name": "Ghana", - "bbox": { - "minx": -3.260786, - "miny": 4.5392525, - "maxx": 1.2732942, - "maxy": 11.1748562 - } - }, - "ci": { - "name": "C\u00f4te d'Ivoire", - "bbox": { - "minx": -8.6014675, - "miny": 4.1621205, - "maxx": -2.4948836, - "maxy": 10.740197 - } - }, - "gn": { - "name": "Guinea", - "bbox": { - "minx": -15.5680508, - "miny": 7.1906045, - "maxx": -7.6379222, - "maxy": 12.67563 - } - }, - "gw": { - "name": "Guinea-Bissau", - "bbox": { - "minx": -16.897501, - "miny": 10.6514215, - "maxx": -13.6348777, - "maxy": 12.6862384 - } - }, - "lr": { - "name": "Liberia", - "bbox": { - "minx": -11.6080764, - "miny": 4.1555907, - "maxx": -7.367323, - "maxy": 8.5519251 - } - }, - "sl": { - "name": "Sierra Leone", - "bbox": { - "minx": -13.5003389, - "miny": 6.755, - "maxx": -10.271683, - "maxy": 9.999973 - } - }, - "bf": { - "name": "Burkina Faso", - "bbox": { - "minx": -5.513207, - "miny": 9.4104718, - "maxx": 2.4089717, - "maxy": 15.0840044 - } - }, - "cg": { - "name": "Congo", - "bbox": { - "minx": 12.039074, - "miny": -13.459035, - "maxx": 31.3056758, - "maxy": 5.3920026 - } - }, - "ga": { - "name": "Gabon", - "bbox": { - "minx": 8.5002246, - "miny": -4.126909, - "maxx": 14.539444, - "maxy": 2.3192896 - } - }, - "zm": { - "name": "Zambia", - "bbox": { - "minx": 21.9993509, - "miny": -18.0762145, - "maxx": 33.7088556, - "maxy": -8.2749338 - } - }, - "mw": { - "name": "Malawi", - "bbox": { - "minx": 32.6718589, - "miny": -17.1296031, - "maxx": 35.9185731, - "maxy": -9.368301 - } - }, - "mz": { - "name": "Mozambique", - "bbox": { - "minx": 30.2121663, - "miny": -26.9209427, - "maxx": 41.0545908, - "maxy": -10.3252149 - } - }, - "sz": { - "name": "eSwatini", - "bbox": { - "minx": 30.7908, - "miny": -27.3175201, - "maxx": 32.1349834, - "maxy": -25.71876 - } - }, - "ao": { - "name": "Angola", - "bbox": { - "minx": 11.4609793, - "miny": -18.0393809, - "maxx": 24.0876359, - "maxy": -4.3458006 - } - }, - "bi": { - "name": "Burundi", - "bbox": { - "minx": 29.000716, - "miny": -4.4693155, - "maxx": 30.8498462, - "maxy": -2.3096796 - } - }, - "il": { - "name": "Israel", - "bbox": { - "minx": 27.8594762, - "miny": 23.8124247, - "maxx": 41.8594762, - "maxy": 37.8124247 - } - }, - "lb": { - "name": "Lebanon", - "bbox": { - "minx": -76.6790862, - "miny": 40.1957824, - "maxx": -76.1514553, - "maxy": 40.555157 - } - }, - "mg": { - "name": "Madagascar", - "bbox": { - "minx": 42.9680076, - "miny": -25.784021, - "maxx": 50.6727307, - "maxy": -11.732889 - } - }, - "ps": { - "name": "Palestine", - "bbox": { - "minx": -95.7242641, - "miny": 31.6952698, - "maxx": -95.5677611, - "maxy": 31.8084308 - } - }, - "gm": { - "name": "Gambia", - "bbox": { - "minx": -17.0223778, - "miny": 13.0558333, - "maxx": -13.797778, - "maxy": 13.8253137 - } - }, - "tn": { - "name": "Tunisia", - "bbox": { - "minx": 10.0037899, - "miny": 36.6925111, - "maxx": 10.3548094, - "maxy": 36.9430196 - } - }, - "dz": { - "name": "Algeria", - "bbox": { - "minx": -8.668908, - "miny": 18.968147, - "maxx": 11.997337, - "maxy": 37.2962055 - } - }, - "jo": { - "name": "Jordan", - "bbox": { - "minx": 35.5189087, - "miny": 31.758258, - "maxx": 35.6309601, - "maxy": 33.1956046 - } - }, - "ae": { - "name": "United Arab Emirates", - "bbox": { - "minx": 51.4160714, - "miny": 22.6316214, - "maxx": 56.6024458, - "maxy": 26.1517219 - } - }, - "qa": { - "name": "Qatar", - "bbox": { - "minx": 47.4861085, - "miny": 39.0874426, - "maxx": 47.5261085, - "maxy": 39.1274426 - } - }, - "kw": { - "name": "Kuwait", - "bbox": { - "minx": 47.8134174, - "miny": 29.2196532, - "maxx": 48.1334174, - "maxy": 29.5396532 - } - }, - "iq": { - "name": "Iraq", - "bbox": { - "minx": 38.7926388, - "miny": 29.0585661, - "maxx": 49.1067706, - "maxy": 37.3806687 - } - }, - "om": { - "name": "Oman", - "bbox": { - "minx": 26.9046399, - "miny": 42.2479001, - "maxx": 26.9446399, - "maxy": 42.2879001 - } - }, - "vu": { - "name": "Vanuatu", - "bbox": { - "minx": 166.3355255, - "miny": -20.4627425, - "maxx": 170.449982, - "maxy": -12.8713777 - } - }, - "kh": { - "name": "Cambodia", - "bbox": { - "minx": -73.1593675, - "miny": 3.9667059, - "maxx": -73.1393675, - "maxy": 3.9867059 - } - }, - "th": { - "name": "Thailand", - "bbox": { - "minx": 97.3438072, - "miny": 5.612851, - "maxx": 105.636812, - "maxy": 20.4648337 - } - }, - "mm": { - "name": "Myanmar", - "bbox": { - "minx": 96.4514194, - "miny": 22.0451803, - "maxx": 96.4515194, - "maxy": 22.0452803 - } - }, - "vn": { - "name": "Vietnam", - "bbox": { - "minx": 102.1438643, - "miny": 7.6920852, - "maxx": 114.8572578, - "maxy": 23.3926918 - } - }, - "kp": { - "name": "North Korea", - "bbox": { - "minx": 124.092355, - "miny": 37.5833302, - "maxx": 130.8861177, - "maxy": 43.0089642 - } - }, - "kr": { - "name": "South Korea", - "bbox": { - "minx": 124.3727348, - "miny": 32.9104556, - "maxx": 132.1467806, - "maxy": 38.61772 - } - }, - "mn": { - "name": "Mongolia", - "bbox": { - "minx": -79.2435563, - "miny": 43.9182593, - "maxx": -79.2035563, - "maxy": 43.9582593 - } - }, - "in": { - "name": "India", - "bbox": { - "minx": 67.9544415, - "miny": 6.5531169, - "maxx": 97.395561, - "maxy": 35.6745457 - } - }, - "bd": { - "name": "Bangladesh", - "bbox": { - "minx": 36.0314231, - "miny": -0.3064982, - "maxx": 36.0714231, - "maxy": -0.2664982 - } - }, - "bt": { - "name": "Bhutan", - "bbox": { - "minx": 75.6258366, - "miny": 33.0004804, - "maxx": 75.6658366, - "maxy": 33.0404804 - } - }, - "np": { - "name": "Nepal", - "bbox": { - "minx": 110.8240229, - "miny": -6.5155532, - "maxx": 110.8640229, - "maxy": -6.4755532 - } - }, - "pk": { - "name": "Pakistan", - "bbox": { - "minx": 60.872855, - "miny": 23.4341977, - "maxx": 77.1203914, - "maxy": 37.084107 - } - }, - "af": { - "name": "Afghanistan", - "bbox": { - "minx": 60.5176034, - "miny": 29.3772926, - "maxx": 74.889862, - "maxy": 38.4910682 - } - }, - "tj": { - "name": "Tajikistan", - "bbox": { - "minx": 67.3332775, - "miny": 36.6711153, - "maxx": 75.1539563, - "maxy": 41.0450935 - } - }, - "kg": { - "name": "Kyrgyzstan", - "bbox": { - "minx": 74.714533, - "miny": 42.4857724, - "maxx": 74.714633, - "maxy": 42.4858724 - } - }, - "tm": { - "name": "Turkmenistan", - "bbox": { - "minx": 52.2643526, - "miny": 35.129093, - "maxx": 66.6903725, - "maxy": 42.7996686 - } - }, - "ir": { - "name": "Iran", - "bbox": { - "minx": 115.4442856, - "miny": 2.9368196, - "maxx": 115.4443856, - "maxy": 2.9369196 - } - }, - "sy": { - "name": "Syria", - "bbox": { - "minx": 38.5331373, - "miny": 35.8144462, - "maxx": 38.5596712, - "maxy": 35.8426126 - } - }, - "am": { - "name": "Armenia", - "bbox": { - "minx": -75.7893957, - "miny": 4.3957427, - "maxx": -75.6324993, - "maxy": 4.5881794 - } - }, - "se": { - "name": "Sweden", - "bbox": { - "minx": -70.8987665, - "miny": 44.0770978, - "maxx": -70.7348752, - "maxy": 44.1808705 - } - }, - "by": { - "name": "Belarus", - "bbox": { - "minx": 23.1783313, - "miny": 51.2626864, - "maxx": 32.7627809, - "maxy": 56.1722484 - } - }, - "ua": { - "name": "Ukraine", - "bbox": { - "minx": 22.137059, - "miny": 44.184598, - "maxx": 40.2278093, - "maxy": 52.3797464 - } - }, - "pl": { - "name": "Poland", - "bbox": { - "minx": 14.0696389, - "miny": 49.0020468, - "maxx": 24.145783, - "maxy": 55.03605 - } - }, - "at": { - "name": "Austria", - "bbox": { - "minx": 9.5307487, - "miny": 46.3722987, - "maxx": 17.1607728, - "maxy": 49.0205249 - } - }, - "hu": { - "name": "Hungary", - "bbox": { - "minx": -72.7989842, - "miny": 41.9567629, - "maxx": -72.7589842, - "maxy": 41.9967629 - } - }, - "md": { - "name": "Moldova", - "bbox": { - "minx": 26.6164054, - "miny": 45.4672099, - "maxx": 30.1637143, - "maxy": 48.4920527 - } - }, - "ro": { - "name": "Romania", - "bbox": { - "minx": 20.2619955, - "miny": 43.618682, - "maxx": 30.0454257, - "maxy": 48.2654738 - } - }, - "lt": { - "name": "Lithuania", - "bbox": { - "minx": 20.6557167, - "miny": 53.8967893, - "maxx": 26.8355198, - "maxy": 56.4504213 - } - }, - "lv": { - "name": "Latvia", - "bbox": { - "minx": 20.6009852, - "miny": 55.6746505, - "maxx": 28.2414937, - "maxy": 58.0855688 - } - }, - "ee": { - "name": "Estonia", - "bbox": { - "minx": 21.3826069, - "miny": 57.5093328, - "maxx": 28.2100175, - "maxy": 59.9383333 - } - }, - "de": { - "name": "Germany", - "bbox": { - "minx": -79.1366983, - "miny": 40.4003479, - "maxx": -79.0966983, - "maxy": 40.4403479 - } - }, - "bg": { - "name": "Bulgaria", - "bbox": { - "minx": 23.5970346, - "miny": 46.7826456, - "maxx": 23.6398011, - "maxy": 46.7927583 - } - }, - "gr": { - "name": "Greece", - "bbox": { - "minx": -77.753536, - "miny": 43.17927, - "maxx": -77.615591, - "maxy": 43.3347444 - } - }, - "tr": { - "name": "Turkey", - "bbox": { - "minx": 25.5656305, - "miny": 35.8058974, - "maxx": 44.8176638, - "maxy": 42.297 - } - }, - "al": { - "name": "Albania", - "bbox": { - "minx": -76.0033466, - "miny": 1.0977792, - "maxx": -75.7569629, - "maxy": 1.3654691 - } - }, - "hr": { - "name": "Croatia", - "bbox": { - "minx": 13.2104814, - "miny": 42.1765993, - "maxx": 19.4472713, - "maxy": 46.555029 - } - }, - "ch": { - "name": "Switzerland", - "bbox": { - "minx": 5.9559113, - "miny": 45.8179716, - "maxx": 10.4922941, - "maxy": 47.8084597 - } - }, - "lu": { - "name": "Luxembourg", - "bbox": { - "minx": 6.06917, - "miny": 49.5609875, - "maxx": 6.2036491, - "maxy": 49.6548682 - } - }, - "be": { - "name": "Belgium", - "bbox": { - "minx": 2.3889137, - "miny": 49.4969821, - "maxx": 6.408097, - "maxy": 51.550781 - } - }, - "nl": { - "name": "Netherlands", - "bbox": { - "minx": -68.6255319, - "miny": 11.825, - "maxx": 7.2274985, - "maxy": 53.744395 - } - }, - "pt": { - "name": "Portugal", - "bbox": { - "minx": -31.5575303, - "miny": 29.828247, - "maxx": -6.1891593, - "maxy": 42.1543112 - } - }, - "es": { - "name": "Spain", - "bbox": { - "minx": -18.3936845, - "miny": 27.4335426, - "maxx": 4.5918885, - "maxy": 43.9933088 - } - }, - "ie": { - "name": "Ireland", - "bbox": { - "minx": -11.0133788, - "miny": 51.222, - "maxx": -5.6582363, - "maxy": 55.636 - } - }, - "nc": { - "name": "New Caledonia", - "bbox": { - "minx": 156.2557034, - "miny": -26.4416318, - "maxx": 174.2757095, - "maxy": -14.8341667 - } - }, - "nz": { - "name": "New Zealand", - "bbox": { - "minx": -179.059153, - "miny": -52.8213687, - "maxx": 179.3643594, - "maxy": -29.0303303 - } - }, - "au": { - "name": "Australia", - "bbox": { - "minx": 72.2461932, - "miny": -55.3228175, - "maxx": 168.2261259, - "maxy": -9.0880125 - } - }, - "lk": { - "name": "Sri Lanka", - "bbox": { - "minx": 79.421989, - "miny": 5.719, - "maxx": 82.0810141, - "maxy": 10.035 - } - }, - "cn": { - "name": "China", - "bbox": { - "minx": -94.347538, - "miny": 30.030471, - "maxx": -94.308516, - "maxy": 30.075717 - } - }, - "tw": { - "name": "Taiwan", - "bbox": { - "minx": 120.0347671, - "miny": 21.8968599, - "maxx": 122.0064049, - "maxy": 25.2997353 - } - }, - "it": { - "name": "Italy", - "bbox": { - "minx": 6.6272658, - "miny": 35.2889616, - "maxx": 18.7844746, - "maxy": 47.0921462 - } - }, - "dk": { - "name": "Denmark", - "bbox": { - "minx": 7.7153255, - "miny": 54.4516667, - "maxx": 15.5530641, - "maxy": 57.9524297 - } - }, - "gb": { - "name": "United Kingdom", - "bbox": { - "minx": -14.015517, - "miny": 49.674, - "maxx": 2.0919117, - "maxy": 61.061 - } - }, - "is": { - "name": "Iceland", - "bbox": { - "minx": -25.0135069, - "miny": 63.0859177, - "maxx": -12.8046162, - "maxy": 67.353 - } - }, - "az": { - "name": "Azerbaijan", - "bbox": { - "minx": 44.7633701, - "miny": 38.3929551, - "maxx": 51.1765554, - "maxy": 41.9646768 - } - }, - "ge": { - "name": "Georgia", - "bbox": { - "minx": -85.605165, - "miny": 30.3556558, - "maxx": -80.751429, - "maxy": 35.0006769 - } - }, - "ph": { - "name": "Philippines", - "bbox": { - "minx": 114.1036921, - "miny": 4.3833333, - "maxx": 126.803083, - "maxy": 21.321928 - } - }, - "my": { - "name": "Malaysia", - "bbox": { - "minx": 98.7365109, - "miny": 0.8538205, - "maxx": 119.4699634, - "maxy": 8.3801468 - } - }, - "bn": { - "name": "Brunei", - "bbox": { - "minx": 113.017925, - "miny": 4.002508, - "maxx": 115.3635623, - "maxy": 6.546584 - } - }, - "si": { - "name": "Slovenia", - "bbox": { - "minx": 13.3754696, - "miny": 45.4214242, - "maxx": 16.5968135, - "maxy": 46.8766816 - } - }, - "fi": { - "name": "Finland", - "bbox": { - "minx": 19.0832, - "miny": 59.4541578, - "maxx": 31.5867071, - "maxy": 70.0922939 - } - }, - "sk": { - "name": "Slovakia", - "bbox": { - "minx": 16.8331891, - "miny": 47.7311798, - "maxx": 22.5657103, - "maxy": 49.6138162 - } - }, - "cz": { - "name": "Czechia", - "bbox": { - "minx": 12.0905752, - "miny": 48.5518081, - "maxx": 18.8592538, - "maxy": 51.0556945 - } - }, - "er": { - "name": "Eritrea", - "bbox": { - "minx": 36.4333653, - "miny": 12.3548219, - "maxx": 43.3001714, - "maxy": 18.0709917 - } - }, - "jp": { - "name": "Japan", - "bbox": { - "minx": 122.7141754, - "miny": 20.2145811, - "maxx": 154.205541, - "maxy": 45.7112046 - } - }, - "py": { - "name": "Paraguay", - "bbox": { - "minx": -62.6406685, - "miny": -27.6063935, - "maxx": -54.258, - "maxy": -19.2876472 - } - }, - "ye": { - "name": "Yemen", - "bbox": { - "minx": 41.60825, - "miny": 11.9084802, - "maxx": 54.7389375, - "maxy": 19.0 - } - }, - "sa": { - "name": "Saudi Arabia", - "bbox": { - "minx": 34.4571718, - "miny": 16.29, - "maxx": 55.6666851, - "maxy": 32.1542832 - } - }, - "aq": { - "name": "Antarctica", - "bbox": { - "minx": -180.0, - "miny": -85.0511287, - "maxx": 180.0, - "maxy": -60.0 - } - }, - "cy": { - "name": "Cyprus", - "bbox": { - "minx": 32.0227581, - "miny": 34.4383706, - "maxx": 34.3338842, - "maxy": 35.3991245 - } - }, - "ma": { - "name": "Morocco", - "bbox": { - "minx": -17.3190239, - "miny": 20.667252, - "maxx": -0.998429, - "maxy": 36.0021392 - } - }, - "eg": { - "name": "Egypt", - "bbox": { - "minx": -90.9598614, - "miny": 35.860483, - "maxx": -90.9347707, - "maxy": 35.8751547 - } - }, - "ly": { - "name": "Libya", - "bbox": { - "minx": 9.391081, - "miny": 19.4999907, - "maxx": 25.3770629, - "maxy": 33.3545898 - } - }, - "et": { - "name": "Ethiopia", - "bbox": { - "minx": 32.9975838, - "miny": 3.397448, - "maxx": 47.9823797, - "maxy": 14.8943383 - } - }, - "dj": { - "name": "Djibouti", - "bbox": { - "minx": 41.7713139, - "miny": 10.9149547, - "maxx": 43.6579046, - "maxy": 12.7923081 - } - }, - "ug": { - "name": "Uganda", - "bbox": { - "minx": 29.573433, - "miny": -1.4823179, - "maxx": 35.000308, - "maxy": 4.2340766 - } - }, - "rw": { - "name": "Rwanda", - "bbox": { - "minx": 28.861696, - "miny": -2.8397581, - "maxx": 30.8990738, - "maxy": -1.0474083 - } - }, - "mk": { - "name": "North Macedonia", - "bbox": { - "minx": 20.4529023, - "miny": 40.8536596, - "maxx": 23.034051, - "maxy": 42.3739044 - } - }, - "rs": { - "name": "Kosovo", - "bbox": { - "minx": 20.0142844, - "miny": 41.8576408, - "maxx": 21.7899366, - "maxy": 43.2733306 - } - }, - "me": { - "name": "Montenegro", - "bbox": { - "minx": -38.3760027, - "miny": -12.8806886, - "maxx": -38.0304294, - "maxy": -12.452 - } - }, - "tt": { - "name": "Trinidad and Tobago", - "bbox": { - "minx": -62.083056, - "miny": 9.8732106, - "maxx": -60.2895848, - "maxy": 11.5628372 - } - } - }, - "_comment": "Generated on 2023-11-20 from geopandas, OpenStreetMap and Nominatim" -} \ No newline at end of file diff --git a/config-templates/data-mappings.yml.tmpl b/config-templates/data-mappings.yml.tmpl deleted file mode 100644 index 9018b0a2f..000000000 --- a/config-templates/data-mappings.yml.tmpl +++ /dev/null @@ -1,59 +0,0 @@ -data: - $CENTRE_ID.data.core.weather.surface-based-observations.synop: - plugins: - txt: - - plugin: wis2box.data.synop2bufr.ObservationDataSYNOP2BUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*-(\d{4})(\d{2}).*\.txt$$' - csv: - - plugin: wis2box.data.csv2bufr.ObservationDataCSV2BUFR - template: aws-template - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.csv$$' - b: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.b$$' - bin: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.bin$$' - bufr: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.bufr$$' - bufr4: - - plugin: wis2box.data.bufr2geojson.ObservationDataBUFR2GeoJSON - buckets: - - $${WIS2BOX_STORAGE_PUBLIC} - file-pattern: '^WIGOS_(\d-\d+-\d+-\w+)_.*\.bufr4$$' - $CENTRE_ID.data.core.weather.surface-based-observations.temp: - plugins: - b: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.b$$' - bin: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.bin$$' - bufr: - - plugin: wis2box.data.bufr4.ObservationDataBUFR - notify: true - buckets: - - $${WIS2BOX_STORAGE_INCOMING} - file-pattern: '^.*\.bufr$$' diff --git a/config-templates/metadata-synop.yml.tmpl b/config-templates/metadata-synop.yml.tmpl deleted file mode 100644 index 856ffa3db..000000000 --- a/config-templates/metadata-synop.yml.tmpl +++ /dev/null @@ -1,61 +0,0 @@ -wis2box: - retention: P30D - topic_hierarchy: $CENTRE_ID.data.core.weather.surface-based-observations.synop - centre_id: $CENTRE_ID - -mcf: - version: 1.0 - -metadata: - identifier: urn:wmo:md:$CENTRE_ID:surface-based-observations.synop - language: en - charset: utf8 - hierarchylevel: dataset - -identification: - language: en - charset: utf8 - title: Hourly synoptic observations from fixed-land stations (SYNOP) ($CENTRE_ID) - abstract: Hourly synoptic observations from fixed-land stations (SYNOP) ($CENTRE_ID) - dates: - creation: $CREATION_DATE - publication: $PUBLICATION_DATE - keywords: - default: - keywords: - - surface - - land - - observations - wmo: - keywords: - - weather - keywords_type: theme - vocabulary: - name: WMO WIS2 Topic Hierarchy - url: https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline - extents: - spatial: - - bbox: [$BOUNDING_BOX] - crs: 4326 - temporal: - - begin: BEGIN_DATE - end: null - resolution: P1H - wmo_data_policy: core - -contact: - host: - organization: $CENTRE_NAME - url: null - individualname: null - positionname: null - phone: null - fax: null - address: null - city: null - administrativearea: null - postalcode: null - country: $COUNTRY_NAME - email: $WIS2BOX_EMAIL - hoursofservice: null - contactinstructions: email diff --git a/config-templates/metadata-temp.yml.tmpl b/config-templates/metadata-temp.yml.tmpl deleted file mode 100644 index 81045c717..000000000 --- a/config-templates/metadata-temp.yml.tmpl +++ /dev/null @@ -1,62 +0,0 @@ -wis2box: - retention: P30D - topic_hierarchy: $CENTRE_ID.data.core.weather.surface-based-observations.temp - centre_id: $CENTRE_ID - -mcf: - version: 1.0 - -metadata: - identifier: urn:wmo:md:$CENTRE_ID:surface-based-observations.temp - language: en - charset: utf8 - hierarchylevel: dataset - -identification: - language: en - charset: utf8 - title: Upper-level temperature/humidity/wind reports from fixed-land stations (TEMP) ($CENTRE_ID) - abstract: Upper-level temperature/humidity/wind reports from fixed-land stations (TEMP) ($CENTRE_ID) - dates: - creation: $CREATION_DATE - publication: $PUBLICATION_DATE - keywords: - default: - keywords: - - upper air - - humidity - - wind - - observations - wmo: - keywords: - - weather - keywords_type: theme - vocabulary: - name: WMO WIS2 Topic Hierarchy - url: https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline - extents: - spatial: - - bbox: [$BOUNDING_BOX] - crs: 4326 - temporal: - - begin: BEGIN_DATE - end: null - resolution: P12H - wmo_data_policy: core - -contact: - host: - organization: $CENTRE_NAME - url: null - individualname: null - positionname: null - phone: null - fax: null - address: null - city: null - administrativearea: null - postalcode: null - country: $COUNTRY_NAME - email: $WIS2BOX_EMAIL - hoursofservice: null - contactinstructions: email diff --git a/config-templates/station_list_example.csv b/config-templates/station_list_example.csv deleted file mode 100644 index c73ea3274..000000000 --- a/config-templates/station_list_example.csv +++ /dev/null @@ -1 +0,0 @@ -station_name,wigos_station_identifier,traditional_station_identifier,facility_type,latitude,longitude,elevation,barometer_height,territory_name,wmo_region diff --git a/docs/source/_static/wis2box-webapp-dataset_editor.png b/docs/source/_static/wis2box-webapp-dataset_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..5719f6a57490a611b3615a0238c89e9aba40a6ba GIT binary patch literal 108295 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%0D>QKH^f8ck8MTPb!Ye&yunCPzgAkI55Zit1Sc_ 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ℨ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

HcuLZ`6$M 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 literal 0 HcmV?d00001 diff --git a/docs/source/user/public-services-setup.rst b/docs/source/user/public-services-setup.rst index 89e115428..68f14c755 100644 --- a/docs/source/user/public-services-setup.rst +++ b/docs/source/user/public-services-setup.rst @@ -137,19 +137,10 @@ After restarting wis2box, repeat the commands for adding your dataset and publis Built-in SSL support -------------------- -If you do not have a reverse proxy available, you can enable SSL support in the ``wis2box-ctl.py`` script. +You can also enable HTTPS and MQTTS directly in the nginx and mosquitto containers running in wis2box. +In this case, the certificate and private key must be available on the host running wis2box -To enable HTTPS and MQTTS on your wis2box you can run wis2box with the option ``–ssl``: - -.. code-block:: bash - - python3 wis2box-ctl.py --ssl start - -.. note:: - - The `--ssl` option will use the configuration in ``nginx/nginx-ssl.conf`` and `mosquitto-ssl.conf` to configure the SSL certificates and keys for the nginx and mosquitto containers. - -When running wis2box with SSL, you have to set additional environment variables in ``dev.env`` defining the location of your SSL certificate and private key: +The location of your SSL certificate and private key are defined by the environment variables ``WIS2BOX_SSL_CERT`` and ``WIS2BOX_SSL_KEY`` respectively. .. code-block:: bash @@ -158,12 +149,15 @@ When running wis2box with SSL, you have to set additional environment variables Please remember to update the ``WIS2BOX_URL`` and ``WIS2BOX_API_URL`` environment variable after enabling SSL, ensuring your URL starts with ``https://``. -Please note that after changing the WIS2BOX_URL and WIS2BOX_API_URL environment variables, you will need to restart your wis2box: +You will need to restart your wis2box instance after enabling SSL: .. code-block:: bash python3 wis2box-ctl.py stop - python3 wis2box-ctl.py --ssl start + python3 wis2box-ctl.py start + +Your wis2box instance will now apply TLS encryption to the HTTP and MQTT services, exposing them on HTTPS (port 443) and MQTTS (port 8883). +When setting up the network routing of your wis2box instance, only ports 443 and 8883 need to be exposed to the public internet. After restarting wis2box, repeat the commands for adding your dataset and publishing your metadata, to ensure that URLs are updated accordingly: diff --git a/docs/source/user/setup.rst b/docs/source/user/setup.rst index 14d805370..0d2a4957f 100644 --- a/docs/source/user/setup.rst +++ b/docs/source/user/setup.rst @@ -42,35 +42,6 @@ Run the following command to create the initial configuration files for your wis The script will propose to automatically create passwords for ``WIS2BOX_STORAGE_PASSWORD`` and ``WIS2BOX_BROKER_PASSWORD``. These passwords are for internal use only within the wis2box, and it is recommended to accept the randomly generated passwords. - The script will ask for your TLD, the `top level domain of your country`_, for example ``fr`` for France. International organizations should use ``int``. - - It will also ask for a centre-id. Please provide a lowercase string delimited by `-`, that is comprised of the TLD and a lowercase string that identifies your organization and does not use spaces or special characters, for example ``fr-meteofrance``. - -.. note:: - - In alignment with WMO WIS2 Guidance, the centre-id is automatically converted to lowercase when saved in wis2box. - - The remaining questions will be used in the creation the discovery metadata files for the ``synop`` and ``temp`` datasets. - -Discovery metadata ------------------- - -Discovery metadata provides the data description needed for users to discover your data when searching the WIS2 Global Discovery Catalogue. - -The discovery metadata is provided in the form of a YAML file. - -If you used the ``python3 wis2box-create-config.py`` command to initialize your wis2box, you will find two initial the discovery metadata files in -the directory you specified for your configuration files, under the ``metadata/discovery/`` directory: - -* ``metadata-synop-centreid.yml``: contains the discovery metadata for the ``synop`` dataset -* ``metadata-temp-centreid.yml``: contains the discovery metadata for the ``temp`` dataset - -Where ``centreid`` is the centre-id you specified during the configuration step. - -Please review the content of these files and edit them as needed. - -You can also add additional discovery metadata files for any other datasets you wish to publish. - Starting wis2box ---------------- @@ -177,9 +148,9 @@ Repeat this step for any other discovery metadata you wish to publish, such as t :width: 800 :alt: wis2box API collections list with added collection -Finally it is recommended to prepare authentication tokens for updating your stations and ingesting data using the wis2box-webapp. +Finally it is recommended to prepare authentication tokens for updating your stations and running processes in the wis2box-webapp. -To create a token for ingesting data: +To create a token for running wis2box processes: .. code-block:: bash @@ -201,12 +172,34 @@ You can now logout of wis2box-management container: exit + +Adding datasets +--------------- + +In order to publish data using the wis2box you need to create a dataset with discovery metadata and data mappings plugins. + +Discovery metadata provides the data description needed for users to discover your data when searching the WIS2 Global Discovery Catalogue. + +Data mappings plugins are used to transform the data from the input source format before the data is published. + +You can use the wis2box-webapp to create datasets interactively: the dataset editor can be accessed by visiting the URL you specified during the configuration step, +and adding ``/wis2box-webapp/dataset_editor`` to the URL. + +.. image:: ../_static/wis2box-webapp-dataset_editor.png + :width: 800 + :alt: wis2box webapp stations page + +Please note that you need to add one dataset for each topic you want to publish data for. Please define your datasets before adding station metadata. + +Alternatively, you can define MCF files for your datasets in the ``metadata/discovery`` directory in your wis2box host directory and publish them from the CLI. +For more information on publishing datasets using MCF files, see the reference documentation. + Adding station metadata ----------------------- The next step is to add station metadata to your wis2box. This can be done interactively in the wis2box-webapp UI on the 'stations' page. -The wis2box-webapp can be accessed by visiting the URL you specified during the configuration step, and adding ``/wis2box-webapp`` to the URL. +The station editor can be accessed by visiting the URL you specified during the configuration step, and adding ``/wis2box-webapp/station`` to the URL. .. image:: ../_static/wis2box-webapp-stations.png :width: 800 diff --git a/grafana/dashboards/home.json b/grafana/dashboards/home.json index bd600aaf9..51afba30f 100644 --- a/grafana/dashboards/home.json +++ b/grafana/dashboards/home.json @@ -595,7 +595,7 @@ "uid": "P55348B596EBB51C3" }, "editorMode": "builder", - "expr": "{compose_service=\"wis2box-management\"} |= `WARNING`", + "expr": "{compose_service=\"wis2box-management\"} |= `wis2box` |= `WARNING`", "hide": false, "queryType": "range", "refId": "A" @@ -606,7 +606,7 @@ "uid": "P55348B596EBB51C3" }, "editorMode": "builder", - "expr": "{compose_service=\"wis2box-api\"} |= `ERROR`", + "expr": "{compose_service=\"wis2box-api\"} |= `wis2box-api` |= `ERROR`", "hide": false, "queryType": "range", "refId": "C" @@ -617,7 +617,7 @@ "uid": "P55348B596EBB51C3" }, "editorMode": "builder", - "expr": "{compose_service=\"wis2box-api\"} |= `WARNING` != `int() argument must be a string`", + "expr": "{compose_service=\"wis2box-api\"} |= `wis2box-api` |= `WARNING` ", "hide": false, "queryType": "range", "refId": "D" diff --git a/nginx/nginx-ssl.conf b/nginx/nginx-ssl.conf index aa8b637d5..bc3bab684 100644 --- a/nginx/nginx-ssl.conf +++ b/nginx/nginx-ssl.conf @@ -71,9 +71,6 @@ location / { proxy_pass http://wis2box-ui:80; } -# location /admin/ { -# proxy_pass http://wis2box-ui-admin:80/; -# } location /auth { internal; proxy_pass http://wis2box-auth:80/authorize; diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 525a25139..72de0f1f3 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -65,9 +65,6 @@ location /wis2box-webapp/ { proxy_pass http://wis2box-webapp:4173/wis2box-webapp/; } -# location /admin/ { -# proxy_pass http://wis2box-ui-admin:80/; -# } location /auth { internal; proxy_pass http://wis2box-auth:80/authorize; diff --git a/tests/data/metadata/discovery/int-wmo-test-buoy.yml b/tests/data/metadata/discovery/int-wmo-test-buoy.yml index 330dfe7ab..102cb95fb 100644 --- a/tests/data/metadata/discovery/int-wmo-test-buoy.yml +++ b/tests/data/metadata/discovery/int-wmo-test-buoy.yml @@ -11,11 +11,6 @@ wis2box: buckets: - ${WIS2BOX_STORAGE_INCOMING} file-pattern: '^A_[A-Z0-9]{6}.*_._[A-Z]{4}_(\d{4}\d{2}\d{2}).*\.bin$' - bufr4: - - plugin: wis2box.data.bufr2geojson.ObservationDataBUFR2GeoJSON - buckets: - - ${WIS2BOX_STORAGE_PUBLIC} - file-pattern: '^WIGOS_(\d-\d+-\d+-\w+)_.*\.bufr4$' mcf: version: 1.0 diff --git a/tests/data/metadata/discovery/int-wmo-test-ship.yml b/tests/data/metadata/discovery/int-wmo-test-ship.yml index 253f0a229..52417815f 100644 --- a/tests/data/metadata/discovery/int-wmo-test-ship.yml +++ b/tests/data/metadata/discovery/int-wmo-test-ship.yml @@ -11,11 +11,6 @@ wis2box: buckets: - ${WIS2BOX_STORAGE_INCOMING} file-pattern: '^A_[A-Z0-9]{6}.*_._[A-Z]{4}_(\d{4}\d{2}\d{2}).*\.bin$' - bufr4: - - plugin: wis2box.data.bufr2geojson.ObservationDataBUFR2GeoJSON - buckets: - - ${WIS2BOX_STORAGE_PUBLIC} - file-pattern: '^WIGOS_(\d-\d+-\d+-\w+)_.*\.bufr4$' mcf: version: 1.0 diff --git a/tests/data/metadata/discovery/int-wmo-test-wind_profiler.yml b/tests/data/metadata/discovery/int-wmo-test-wind_profiler.yml index 3003ad999..eec608cb6 100644 --- a/tests/data/metadata/discovery/int-wmo-test-wind_profiler.yml +++ b/tests/data/metadata/discovery/int-wmo-test-wind_profiler.yml @@ -11,11 +11,6 @@ wis2box: buckets: - ${WIS2BOX_STORAGE_INCOMING} file-pattern: '^WIGOS_\d-\d+-\d+-\w+_(\d{4}\d{2}\d{2}).*\.bin$' - bufr4: - - plugin: wis2box.data.bufr2geojson.ObservationDataBUFR2GeoJSON - buckets: - - ${WIS2BOX_STORAGE_PUBLIC} - file-pattern: '^WIGOS_(\d-\d+-\d+-\w+)_.*\.bufr4$' mcf: version: 1.0 diff --git a/tests/data/metadata/discovery/ro-synoptic-weather-observations.yml b/tests/data/metadata/discovery/ro-synoptic-weather-observations.yml index f553390d7..58fd2676e 100644 --- a/tests/data/metadata/discovery/ro-synoptic-weather-observations.yml +++ b/tests/data/metadata/discovery/ro-synoptic-weather-observations.yml @@ -14,9 +14,6 @@ wis2box: template: aws-template notify: true file-pattern: '^.*\.csv$' - bufr4: - - plugin: wis2box.data.bufr2geojson.ObservationDataBUFR2GeoJSON - file-pattern: '^A_SMR.*EDZW_(\d{4})(\d{2}).*.bufr4$' mcf: version: 1.0 diff --git a/tests/data/metadata/station/station_list.csv b/tests/data/metadata/station/station_list.csv index c5df75e13..a688291ac 100644 --- a/tests/data/metadata/station/station_list.csv +++ b/tests/data/metadata/station/station_list.csv @@ -95,55 +95,10 @@ KELLE,0-20000-0-64462,64462,landFixed,0.0829,14.5329,408.00,409.30,COD,africa EWO,0-20000-0-64463,64463,landFixed,-0.8541,14.8034,479.00,480.20,COD,africa N'KAYI,0-20000-0-64406,64406,landFixed,-4.2218,13.2856,165.00,166.40,COD,africa OLLOMBO,0-20000-0-64461,64461,landFixed,-1.2257,15.9166,327.05,328.45,COD,africa -VRAR6,0-22000-0-ABXFLRZ,VRAR6,seaMobile,0,0,0,0,HKG,asia -CMXXXKP,0-22000-0-CMXXXKP,CMXXXKP,seaMobile,0,0,0,0,DEU,europe -ZDLK2QL,0-22000-0-ZDLK2QL,ZDLK2QL,seaMobile,0,0,0,0,NLD,europe -BBR433T,0-22000-0-BBR433T,BBR433T,seaMobile,0,0,0,0,DEU,europe -3801710,0-22000-0-3801710,3801710,seaMobile,0,0,0,0,USA,northCentralAmericaCaribbean 1400011,0-22000-0-1400011,1400011,seaMobile,0,0,0,0,USA,northCentralAmericaCaribbean Singapore Upper Air Observatory,0-702-0-48698,48698,landFixed,1.3404,103.8879,30,0,SGP,asia -4801763,0-22000-0-4801763,4801763,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -4802506,0-22000-100-4802506,4802506,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -4802504,0-22000-100-4802504,4802504,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -7801552,0-22000-0-7801552,7801552,seaMobile,0,0,0,0,CAN,northCentralAmericaCaribbean -4803979,0-22000-0-4803979,4803979,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -4802664,0-22000-0-4802664,4802664,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -4802661,0-22000-100-4802661,4802661,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean -4802662,0-22000-100-4802662,4802662,seaOnIce,0,0,0,0,CAN,northCentralAmericaCaribbean 3WL23XC,0-22000-0-3WL23XC,3WL23XC,seaMobile,0,0,0,0,DEU,europe 4EEWQMW,0-22000-0-4EEWQMW,4EEWQMW,seaMobile,0,0,0,0,DEU,europe 4SGNZUJ,0-22000-0-4SGNZUJ,4SGNZUJ,seaMobile,0,0,0,0,IRL,europe EUCDE09,0-22000-0-EUCDE09,EUCDE09,seaMobile,0,0,0,0,DEU,europe -EUCDE34,0-22000-0-EUCDE34,EUCDE34,seaMobile,0,0,0,0,DEU,europe -EUCDE53,0-22000-0-EUCDE53,EUCDE53,seaMobile,0,0,0,0,DEU,europe -EUCFR02,0-22000-0-EUCFR02,EUCFR02,seaMobile,0,0,0,0,FRA,europe -EUCFR03,0-22000-0-EUCFR03,EUCFR03,seaMobile,0,0,0,0,FRA,europe -EUCFR10,0-22000-0-EUCFR10,EUCFR10,seaMobile,0,0,0,0,FRA,europe -EUCFR14,0-22000-0-EUCFR14,EUCFR14,seaMobile,0,0,0,0,FRA,europe -EUMDE01,0-22000-0-EUMDE01,EUMDE01,seaMobile,0,0,0,0,DEU,europe -EUMDE03,0-22000-0-EUMDE03,EUMDE03,seaMobile,0,0,0,0,DEU,europe -EUMDE08,0-22000-0-EUMDE08,EUMDE08,seaMobile,0,0,0,0,DEU,europe -EUMDE10,0-22000-0-EUMDE10,EUMDE10,seaMobile,0,0,0,0,DEU,europe -EUMDE12,0-22000-0-EUMDE12,EUMDE12,seaMobile,0,0,0,0,DEU,europe -EUMDE15,0-22000-0-EUMDE15,EUMDE15,seaMobile,0,0,0,0,DEU,europe -EUMDE24,0-22000-0-EUMDE24,EUMDE24,seaMobile,0,0,0,0,DEU,europe -EUMDE28,0-22000-0-EUMDE28,EUMDE28,seaMobile,0,0,0,0,DEU,europe -EUMDE37,0-22000-0-EUMDE37,EUMDE37,seaMobile,0,0,0,0,DEU,europe -EUMDE39,0-22000-0-EUMDE39,EUMDE39,seaMobile,0,0,0,0,DEU,europe -HLV8QXK,0-22000-0-HLV8QXK,HLV8QXK,seaMobile,0,0,0,0,DEU,europe -KTSNQYR,0-22000-0-KTSNQYR,KTSNQYR,seaMobile,0,0,0,0,DEU,europe -SPWMAUB,0-22000-0-SPWMAUB,SPWMAUB,seaMobile,0,0,0,0,DEU,europe -UYARJLY,0-22000-0-UYARJLY,UYARJLY,seaMobile,0,0,0,0,DEU,europe -XKRZECH,0-22000-0-XKRZECH,XKRZECH,seaMobile,0,0,0,0,DEU,europe -XUE7S8F,0-22000-0-XUE7S8F,XUE7S8F,seaMobile,0,0,0,0,DEU,europe -YKY5BSG,0-22000-0-YKY5BSG,YKY5BSG,seaMobile,0,0,0,0,FRA,europe -YWG5SAU,0-22000-0-YWG5SAU,YWG5SAU,seaMobile,0,0,0,0,DEU,europe -YXK7JYM,0-22000-0-YXK7JYM,YXK7JYM,seaMobile,0,0,0,0,DEU,europe -ZNSLBZM,0-22000-0-ZNSLBZM,ZNSLBZM,seaMobile,0,0,0,0,FRA,europe -6MD6NDF,0-22000-0-6MD6NDF,6MD6NDF,seaMobile,0,0,0,0,DEU,europe -5SRJ82U,0-22000-0-5SRJ82U,5SRJ82U,seaMobile,0,0,0,0,DEU,europe -BSXWJEQ,0-22000-0-BSXWJEQ,BSXWJEQ,seaMobile,0,0,0,0,DEU,europe -8DZ7Y3T,0-22000-0-8DZ7Y3T,8DZ7Y3T,seaMobile,0,0,0,0,DEU,europe -ELDMMXN,0-22000-0-ELDMMXN,ELDMMXN,seaMobile,0,0,0,0,FRA,europe -7UFD9CZ,0-22000-0-7UFD9CZ,7UFD9CZ,seaMobile,0,0,0,0,DEU,europe -7JXTK9Z,0-22000-0-7JXTK9Z,7JXTK9Z,seaMobile,0,0,0,0,DEU,europe \ No newline at end of file +EUCDE34,0-22000-0-EUCDE34,EUCDE34,seaMobile,0,0,0,0,DEU,europe \ No newline at end of file diff --git a/tests/data/observations/romania/A_SMRO01YRBK171200CCA_C_EDZW_20230117174401_51649529.txt b/tests/data/observations/romania/A_SMRO01YRBK171200CCA_C_EDZW_20230117174401_51649529.txt deleted file mode 100644 index 2063a0c0b..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK171200CCA_C_EDZW_20230117174401_51649529.txt +++ /dev/null @@ -1,5 +0,0 @@ -SMRO01 YRBK 171200 CCA -AAXX 17121 -15108 01/92 92514 11028 21028 37901 48315 50001 69941 77174 333 48014 -55300 0//// 20270 3//// 69947 91020 911// 92727 92913 96047= - diff --git a/tests/data/observations/romania/A_SMRO01YRBK171200CCB_C_EDZW_20230118094300_52396633.txt b/tests/data/observations/romania/A_SMRO01YRBK171200CCB_C_EDZW_20230118094300_52396633.txt deleted file mode 100644 index 74aa40dd4..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK171200CCB_C_EDZW_20230118094300_52396633.txt +++ /dev/null @@ -1,5 +0,0 @@ -SMRO01 YRBK 171200 CCB -AAXX 17121 -15280 01/90 92518 11047 21054 37334 47872 51004 60001 74143 333 49070 -55300 0//// 20000 3//// 60007 91026 911// 92956= - diff --git a/tests/data/observations/romania/A_SMRO01YRBK171800CCB_C_EDZW_20230118055302_52230688.txt b/tests/data/observations/romania/A_SMRO01YRBK171800CCB_C_EDZW_20230118055302_52230688.txt deleted file mode 100644 index 1457998d9..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK171800CCB_C_EDZW_20230118055302_52230688.txt +++ /dev/null @@ -1,6 +0,0 @@ -SMRO01 YRBK 171800 CCB -AAXX 17181 -15280 01/90 92012 11041 21047 37349 47889 50004 60002 74143 333 11041 -21059 -3//// 49075 55300 0//// 20000 3//// 60007 91020 911// 92818 92946= - diff --git a/tests/data/observations/romania/A_SMRO01YRBK180000_C_EDZW_20230118000502_51936144.txt b/tests/data/observations/romania/A_SMRO01YRBK180000_C_EDZW_20230118000502_51936144.txt deleted file mode 100644 index dce0558c9..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK180000_C_EDZW_20230118000502_51936144.txt +++ /dev/null @@ -1,76 +0,0 @@ -SMRO01 YRBK 180000 -AAXX 18001 -15015 01597 83201 10072 20053 39345 42589 56019 60051 76186 885// 333 -4/000 -55300 0//// 20000 3//// 55008 0//// 20214 3//// 60057 91004 91107= -15020 02597 80804 10060 20035 39775 49971 58032 60001 8457/ 333 4/000 -55300 -0//// 20000 3//// 55032 0//// 20465 3//// 60007 91006 91110= -15090 02997 80804 10060 20044 39910 40002 57022 60001 80007 333 4/000 -55300 -10144 20000 30000 55011 10119 20331 30296 60007 91007 91107= -15108 01298 52018 10011 21018 37894 48299 58027 60001 73632 83540 333 -48013 -55300 0//// 20000 3//// 55022 0//// 20331 3//// 60007 91024 911// -92727 92912= -15120 01597 81103 10063 20042 39459 49947 56016 69981 78082 8437/ 333 -55300 -10138 20000 30000 55044 00077 20610 30225 69987 91005 91106= -15150 02997 41606 10060 20047 39786 49998 58018 60001 84070 333 4/000 -55300 -0//// 20000 3//// 55024 0//// 20470 3//// 60007 91010 91110= -15170 05598 81801 10021 20018 39215 42631 58021 60001 7000/ 8287/ 333 -55300 -0//// 20000 3//// 55020 0//// 20356 3//// 60007 91005 91106= -15200 01598 81613 10111 20073 39769 49909 53006 60011 70282 8437/ 333 -55300 -0//// 20000 3//// 55034 0//// 20552 3//// 69947 91018 91118= -15230 01597 82003 10091 20075 39662 49949 55008 60041 76162 885// 333 -4/000 -55300 10179 20000 30000 55022 10042 20387 30240 60047 91005 91107= -15260 05598 71404 10118 20032 39419 49941 58009 60001 7000/ 84570 333 -55300 -0//// 20000 3//// 55035 0//// 20456 3//// 60007 91007 91115= -15280 01/90 92032 11034 21040 37301 47838 53008 60001 74143 333 49080 -55300 -0//// 20000 3//// 55000 0//// 20003 3//// 60007 91040 911// 92956= -15292 01597 81506 10095 20062 39656 49943 50005 60011 72582 883// 333 -55300 -0//// 20000 3//// 55024 0//// 20432 3//// 69977 91011 91111= -15310 02997 01604 10105 20082 39933 40018 57014 60001 333 4/000 55300 -10138 -20000 30000 55036 10029 20545 30351 60007 91008 91109= -15335 02597 31807 10107 20087 30027 40034 58013 60001 82530 333 4/000 -55300 -0//// 20000 3//// 55027 0//// 20540 3//// 60007 91009 91109= -15346 01597 83603 10063 20055 39712 40000 57007 60011 76162 8657/ 333 -4/000 -55300 0//// 20000 3//// 55006 0//// 20225 3//// 60017 91004 91106= -15350 02997 03302 10044 20037 39897 40017 57017 60001 333 4/000 55300 -0//// -20000 3//// 55032 0//// 20419 3//// 60007 91003 91103= -15360 02997 01909 10097 20086 30031 40048 57012 60001 222// 0//// -2//// 333 -55300 ///// 20000 3//// 55030 ///// 20520 3//// 60007 91011 91111 -92437= -15410 02597 70202 10088 20055 39883 49978 56010 60001 83570 333 4/000 -55300 -0//// 20000 3//// 55020 0//// 20398 3//// 60007 91005 91105= -15420 02997 50101 10053 20053 39888 49999 57019 60001 85050 333 55300 -0//// -20000 3//// 55018 0//// 20402 3//// 60007 91001 91102= -15450 02997 11105 10080 20055 39747 49979 56010 60001 81030 333 55300 -10148 -20000 30000 55030 00055 20549 30334 60007 91007 91108= -15460 02997 61803 10132 20087 39987 40012 58016 60001 86050 333 4/000 -55300 -0//// 20000 3//// 55039 0//// 20592 3//// 60007 91006 91106= -15470 02997 71704 10116 20061 39866 49989 56012 60001 87070 333 55300 -0//// -20000 3//// 55025 0//// 20506 3//// 60007 91007 91107= -15480 05597 51703 10113 20096 30021 40038 58012 60001 7000/ 85500 -222// 0//// -2//// 333 4/000 55300 10136 20000 30000 55035 10042 20593 30480 60007 -91007 -91108 92427= - diff --git a/tests/data/observations/romania/A_SMRO01YRBK180600_C_EDZW_20230118060404_52242453.txt b/tests/data/observations/romania/A_SMRO01YRBK180600_C_EDZW_20230118060404_52242453.txt deleted file mode 100644 index f3846275b..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK180600_C_EDZW_20230118060404_52242453.txt +++ /dev/null @@ -1,77 +0,0 @@ -SMRO01 YRBK 180600 -AAXX 18061 -15015 01598 80903 10090 20050 39352 42595 52007 60132 78086 8537/ 333 -10090 -20041 32001 4/000 55300 0//// 20000 3//// 60027 70144 91005 91105= -15020 02597 71103 10077 20055 39764 49959 53003 60002 83570 333 10094 -20054 -31101 4/000 55300 0//// 20000 3//// 60007 70000 91004 91106= -15090 02497 51103 10088 20069 39889 49981 55000 60002 84630 333 10101 -20050 -31101 4/000 55300 10131 20000 30000 60007 70000 91005 91106= -15108 02298 52016 10016 21013 37892 48296 52005 60002 83540 333 10016 -21024 -3//// 44013 55300 0//// 20000 3//// 60007 70007 91024 911// 92727 -92823 92910= -15120 01997 71504 10094 20051 39458 49941 55001 69992 70282 87070 333 -10094 -20054 31004 4/000 55300 10121 20000 30000 60007 70009 91006 91106= -15150 01596 51606 10064 20057 39769 49980 55002 60002 71011 82670 333 -10068 -20032 31002 4/000 55300 0//// 20000 3//// 60007 70000 91009 91110= -15170 05598 71404 10075 20035 39210 42627 53004 60002 7000/ 83370 333 -10079 -21019 31000 55300 0//// 20000 3//// 60007 70008 91007 91107= -15200 01597 81610 10113 20078 39773 49913 55001 60012 78082 8637/ 333 -10121 -20089 31008 55300 0//// 20000 3//// 69917 70015 91015 91116= -15230 01597 82203 10091 20068 39665 49952 53006 60052 7616/ 885// 333 -10122 -20078 31007 55300 10147 20000 30000 69967 70057 91005 91106= -15260 05598 61203 10105 20041 39427 49952 50004 60002 7000/ 84870 333 -10124 -20087 31005 55300 0//// 20000 3//// 60007 70000 91007 91110= -15280 01/90 92020 11030 21035 37323 47863 53005 60002 74143 333 11030 -21041 -3//// 49080 55300 0//// 20000 3//// 60007 70000 91030 911// 92828 -92956= -15292 01597 81606 10100 20068 39658 49945 50002 60012 72582 883// 333 -10114 -20079 31008 55300 0//// 20000 3//// 69937 70034 91009 91109= -15310 02997 21704 10108 20086 39925 40010 55001 60002 82030 333 10109 -20072 -31002 4/000 55300 10201 20000 30000 60007 70000 91008 91109= -15335 02597 41809 10108 20089 30017 40024 55003 60002 83530 333 10111 -20075 -31007 55300 0//// 20000 3//// 60007 70000 91012 91113= -15346 01597 83503 10072 20060 39712 49998 52005 60012 70262 8557/ 333 -10077 -20059 32002 4/000 55300 0//// 20000 3//// 60007 70030 91004 91105= -15350 02997 33102 10043 20035 39894 40015 55000 60002 83040 333 10061 -20034 -31002 4/000 55300 0//// 20000 3//// 60007 70000 91003 91104= -15360 02997 61913 10106 20085 30020 40037 55001 60002 86070 222// -06062 20503 -333 10107 20091 3//// 55300 ///// 20001 3//// 60007 70000 91017 91117 -92447= -15410 02997 70202 10096 20066 39884 49978 53003 60002 87070 333 10096 -20072 -31007 4/000 55300 0//// 20000 3//// 60007 70034 91004 91107= -15420 02997 70601 10039 20039 39895 40006 53006 60002 87070 333 10066 -20008 -31000 55300 0//// 20000 3//// 60007 70001 91001 91102= -15450 02597 71105 10079 20053 39749 49981 53004 60002 84370 333 10096 -20073 -31007 55300 10073 20000 30000 60007 70001 91006 91107= -15460 02997 72005 10156 20078 39986 40010 53004 60002 87070 333 10158 -20118 -31006 4/000 55300 0//// 20000 3//// 60007 70001 91009 91113= -15470 02997 51505 10118 20067 39870 49993 53006 60002 85070 333 10123 -20073 -31005 55300 0//// 20000 3//// 60007 70000 91007 91110= -15480 05497 71801 10118 20095 30013 40030 53002 60002 7000/ 87600 -222// 06070 -2//// 333 10119 20110 31007 4/000 55300 10091 20000 30000 60007 70000 -91006 -91107 92437= - diff --git a/tests/data/observations/romania/A_SMRO01YRBK181200_C_EDZW_20230118120404_52514693.txt b/tests/data/observations/romania/A_SMRO01YRBK181200_C_EDZW_20230118120404_52514693.txt deleted file mode 100644 index 2d93e7921..000000000 --- a/tests/data/observations/romania/A_SMRO01YRBK181200_C_EDZW_20230118120404_52514693.txt +++ /dev/null @@ -1,72 +0,0 @@ -SMRO01 YRBK 181200 -AAXX 18121 -15015 01597 82208 10074 20047 39376 42616 53024 60031 78082 883// 333 -4/000 -55300 0//// 20337 3//// 60037 91017 91117= -15020 02597 71403 10133 20073 39764 49955 58003 60001 83270 333 4/000 -55305 -0//// 20786 3//// 60007 91005 91106= -15090 02597 82103 10163 20068 39887 49976 58006 60001 83577 333 4/000 -55301 -00287 20566 30459 60007 91005 91108= -15108 02298 62520 10021 21005 37912 48315 53007 60001 83571 333 44012 -55302 -0//// 20562 3//// 60007 91028 911// 95100= -15120 01598 72904 10106 20052 39493 49975 53021 69951 72582 84370 333 -55304 -00318 20775 30462 69907 91008 91112= -15150 02597 41605 10134 20088 39774 49981 53002 60001 84800 333 4/000 -55308 -0//// 21282 3//// 60007 91009 91111= -15170 01597 72107 10114 20055 39228 42643 53016 69901 70382 87300 333 -55305 -0//// 20879 3//// 60007 91011 91113= -15200 01598 32107 10107 20071 39848 49989 51037 60041 70181 83200 333 -55305 -0//// 20902 3//// 60027 91010 91111= -15230 01597 72807 10089 20053 39720 40009 53044 60051 78086 85370 333 -55300 -10286 20169 30169 60027 91014 91116= -15260 06598 50904 10141 20053 39454 49974 50015 60001 7000/ 83370 333 -55308 -0//// 21330 3//// 60007 91007 91110 95010= -15280 01/90 92028 11024 21028 37343 47885 51025 60001 74143 333 49085 -55300 -0//// 20000 3//// 60007 91034 911// 92956= -15292 01597 70102 10099 20076 39726 40015 51052 60011 72998 87900 333 -55303 -0//// 20633 3//// 69987 91004 91111 95090= -15310 02997 11905 10181 20098 39928 40010 57002 60001 81040 333 4/000 -55309 -00670 21275 30228 60007 91012 91113= -15335 02597 21909 10186 20111 30018 40025 57004 60001 81530 333 55310 -0//// -21429 3//// 60007 91014 91114= -15346 01597 83502 10099 20084 39734 40019 51009 69951 76162 8657/ 333 -4/000 -55300 0//// 20242 3//// 69957 91003 91105 95090= -15350 02997 02204 10157 20086 39901 40017 54000 60001 333 4/000 55310 -0//// -20941 3//// 60007 91005 91106= -15360 02997 31915 10116 20087 30018 40035 56013 60001 83030 222// -06070 20502 -333 55309 ///// 21270 3//// 60007 91018 91118 92457= -15410 02597 40101 10154 20079 39908 40001 50009 60001 83330 333 4/000 -55307 -0//// 21254 3//// 60007 91003 91105= -15420 02597 41904 10197 20093 39901 40007 52004 60001 81240 333 55310 -0//// -21253 3//// 60007 91009 91112= -15450 02598 62105 10194 20065 39774 49998 51011 60001 83330 333 4/000 -55308 -00784 21510 30681 60007 91008 91110= -15460 02597 32108 10205 20076 39999 40023 53002 60001 81509 333 4/000 -55310 -0//// 21494 3//// 60007 91013 91114= -15470 02997 11604 10196 20097 39887 40007 52007 60001 81040 333 55310 -0//// -21499 3//// 60007 91007 91111= -15480 05997 22003 10178 20105 30020 40037 57002 60001 7000/ 80001 -222// 06088 -2//// 333 4/000 55310 00753 21508 30886 60007 91009 91113 92437= - diff --git a/tests/data/observations/romania_update/A_SMRO01YRBK171200_C_EDZW_20230117125200_51396856.txt b/tests/data/observations/romania_update/A_SMRO01YRBK171200_C_EDZW_20230117125200_51396856.txt deleted file mode 100644 index 3731cd128..000000000 --- a/tests/data/observations/romania_update/A_SMRO01YRBK171200_C_EDZW_20230117125200_51396856.txt +++ /dev/null @@ -1,72 +0,0 @@ -SMRO01 YRBK 171200 -AAXX 17121 -15015 01597 71702 10057 20036 39390 42628 50004 60021 78082 87300 333 -4/000 -55304 0//// 20643 3//// 69977 91003 91108= -15020 02597 61303 10104 20040 39783 49976 58007 60001 83570 333 4/000 -55308 -0//// 21085 3//// 60007 91005 91106= -15090 02597 72003 10109 20036 39904 49994 56010 60001 83571 333 4/000 -55301 -00256 20589 30542 60007 91005 91107= -15108 01/92 92514 11028 21028 37901 48315 50001 69941 77174 333 48013 -55300 -0//// 20270 3//// 69947 91020 911// 92727 92913 96047= -15120 02598 61904 10106 20016 39496 49978 50003 60001 83360 333 55309 -00649 -21413 30243 60007 91007 91107= -15150 02597 61604 10091 20036 39782 49991 57013 60001 86800 333 4/000 -55308 -0//// 20986 3//// 60007 91006 91109= -15170 01597 81701 10050 20031 39238 42652 53007 69961 76162 885// 333 -55300 -0//// 20206 3//// 69937 91003 91104= -15200 02598 51707 10111 20062 39840 49980 58006 60001 81140 333 4/000 -55310 -0//// 21428 3//// 60007 91010 91111= -15230 01598 70101 10089 20054 39714 40002 58007 60011 70262 87500 333 -55303 -00347 20712 30640 60007 91002 91102= -15260 05599 21502 10088 20029 39467 49998 50004 60001 7000/ 82800 333 -55309 -0//// 21214 3//// 60007 91003 91104 95000= -15280 01/90 92518 11047 21054 37334 47872 51004 60001 74143 333 49070 -55300 -0//// 20000 3//// 60007 91026 911// 92956= -15292 01597 61505 10074 20048 39713 40004 58010 60021 70182 86300 333 -55302 -0//// 20637 3//// 60007 91008 91108 95090= -15310 02998 71903 10111 20066 39942 40026 55008 60001 82038 333 4/000 -55304 -00448 20862 30571 60007 91007 91110= -15335 02597 51705 10124 20085 30028 40035 57013 60001 83530 333 55309 -0//// -21651 3//// 60007 91008 91110= -15346 01596 73401 10059 20047 39746 40035 58003 60021 76162 85570 333 -4/000 -55300 0//// 20415 3//// 69947 91002 91103= -15350 02997 32204 10103 20057 39906 40025 57020 60001 80002 333 4/000 -55308 -0//// 20913 3//// 60007 91006 91106= -15360 02997 61910 10098 20077 30030 40046 57009 60001 86070 222// -06070 20503 -333 55303 20758 3//// 60007 91011 91112 92447= -15410 01597 80302 10066 20045 39934 40030 57013 60031 70262 8457/ 333 -4/000 -55301 0//// 20600 3//// 60017 91004 91106= -15420 01597 62101 10127 20087 39912 40021 58011 69911 70262 83570 333 -55305 -0//// 20850 3//// 60007 91003 91103= -15450 01596 70203 10078 20054 39792 40025 50001 69911 71086 84530 333 -55306 -00537 21202 30592 69907 91004 91105= -15460 01997 72106 10175 20069 30002 40026 57014 69911 70282 87070 333 -4/000 -55310 0//// 21460 3//// 60007 91011 91111= -15470 02997 51902 10141 20082 39897 40019 58008 60001 85050 333 55310 -0//// -21469 3//// 60007 91003 91103= -15480 05997 42104 10146 20092 30023 40040 57015 60001 7000/ 82031 -222// 06078 -2//// 333 55309 00621 21275 30948 60007 91010 91112 92427= - diff --git a/tests/data/observations/romania_update/A_SMRO01YRBK171800CCA_C_EDZW_20230117184900_51697747.txt b/tests/data/observations/romania_update/A_SMRO01YRBK171800CCA_C_EDZW_20230117184900_51697747.txt deleted file mode 100644 index e7f71b096..000000000 --- a/tests/data/observations/romania_update/A_SMRO01YRBK171800CCA_C_EDZW_20230117184900_51697747.txt +++ /dev/null @@ -1,5 +0,0 @@ -SMRO01 YRBK 171800 CCA -AAXX 17181 -15090 02997 12101 10084 20048 39928 40020 51019 60002 81030 333 10122 -20059 31010 4/000 55300 10143 20000 30000 60007 91004 91104= - diff --git a/tests/data/observations/romania_update/A_SMRO01YRBK171800_C_EDZW_20230117181403_51669400.txt b/tests/data/observations/romania_update/A_SMRO01YRBK171800_C_EDZW_20230117181403_51669400.txt deleted file mode 100644 index 21e07a082..000000000 --- a/tests/data/observations/romania_update/A_SMRO01YRBK171800_C_EDZW_20230117181403_51669400.txt +++ /dev/null @@ -1,53 +0,0 @@ -SMRO01 YRBK 171800 -AAXX 17181 -15015 01598 82700 10039 20026 39397 42633 58006 60022 70282 8657/ 333 -10066 20029 31010 4/000 55300 0//// 20000 3//// 69917 91002 91104= -15020 02997 01503 10071 20018 39811 40008 51023 60002 333 10113 20024 -31012 4/000 55300 0//// 20000 3//// 60007 91006 91106= -15090 02997 12101 10084 20048 39928 40020 51019 60002 81030 333 10122 -20059 3/010 4/000 55300 10143 20000 30000 60007 91004 91104= -15108 01299 22710 11024 21040 37925 48339 52022 69972 73674 81540 333 -11021 21036 3//// 48014 55300 0//// 20000 3//// 60007 91016 911// -92727 92813 92911= -15120 02997 71303 10079 20027 39497 49984 58006 60002 87070 333 10117 -20049 31016 55300 10080 20000 30000 60007 91006 91106= -15150 02997 01902 10058 20046 39811 40024 51021 60002 333 10117 20028 -31013 4/000 55300 0//// 20000 3//// 60007 91004 91104= -15170 01998 23100 10001 21001 39242 42656 51002 69982 70361 82040 333 -10074 21016 31005 55300 0//// 20000 3//// 60007 91001 91102= -15200 01598 81007 10092 20066 39784 49924 58038 69912 78081 883// 333 -10117 20060 31014 55300 0//// 20000 3//// 69917 91011 91111= -15230 02997 71702 10086 20038 39690 49978 58010 60012 87070 333 10122 -20027 31014 4/000 55300 10110 20000 30000 60007 91004 91105= -15260 05598 30802 10088 20031 39460 49990 58005 60002 7000/ 83500 333 -10109 20016 31009 55300 0//// 20000 3//// 60007 91004 91105= -15280 01/90 92012 11041 21047 37349 47889 50004 60022 74143 333 11041 -21059 3//// 49075 55300 0//// 20000 3//// 60007 91020 911// 92818 -92946= -15292 02597 81505 10078 20052 39664 49953 58027 60022 883// 333 10112 -20045 31014 55300 0//// 20000 3//// 60007 91010 91112= -15310 02997 11801 10090 20072 39953 40039 52011 60002 80001 333 10132 -20065 31015 4/000 55300 10160 20000 30000 60007 91002 91105= -15335 02997 21804 10096 20079 30040 40047 52008 60002 82030 333 10134 -20072 31016 4/000 55300 0//// 20000 3//// 60007 91006 91107= -15346 01597 83603 10064 20048 39737 40024 58005 60022 70262 8557/ 333 -10079 20034 32010 4/000 55300 0//// 20000 3//// 60007 91005 91105= -15350 02997 01001 10058 20044 39916 40037 50006 60002 333 10121 20009 -31008 4/000 55300 0//// 20000 3//// 60007 91003 91103= -15360 02998 01908 10092 20084 30042 40059 53009 60002 222// 06068 -20402 333 10100 20087 3//// 55300 20000 3//// 60007 91009 91112 92438 -= -15410 02597 80302 10072 20047 39909 40004 58013 60032 8457/ 333 10080 -20035 31009 4/000 55300 0//// 20000 3//// 60007 91004 91106= -15420 02997 60701 10057 20056 39919 40030 52002 69912 80006 333 10157 -20028 31013 55300 0//// 20000 3//// 60007 91002 91102= -15450 02597 70706 10075 20060 39768 40001 58013 69912 83570 333 10093 -20038 31012 55300 10119 20000 30000 60007 91009 91109= -15460 02997 41702 10126 20072 30011 40035 50003 69912 80008 333 10180 -20074 31017 4/000 55300 0//// 20000 3//// 60007 91006 91106= -15470 02597 60503 10078 20064 39890 40015 58006 60002 82370 333 10143 -20037 31016 55300 0//// 20000 3//// 60007 91003 91104= -15480 05997 41901 10114 20089 30035 40052 51009 60002 7000/ 80005 -222// 06074 2//// 333 10150 20084 31015 4/000 55300 10188 20000 30000 -60007 91004 91106 92427= - diff --git a/tests/data/observations/romania_update/A_SMRO01YRBK180000CCA_C_EDZW_20230118004301_51967254.txt b/tests/data/observations/romania_update/A_SMRO01YRBK180000CCA_C_EDZW_20230118004301_51967254.txt deleted file mode 100644 index 0b0935f8f..000000000 --- a/tests/data/observations/romania_update/A_SMRO01YRBK180000CCA_C_EDZW_20230118004301_51967254.txt +++ /dev/null @@ -1,12 +0,0 @@ -SMRO01 YRBK 180000 CCA - -AAXX 18001 - -15280 01/90 92034 11034 21040 37301 47838 53008 60001 74143 333 49080 - -55300 0//// 20000 3//// 55000 0//// 20003 3//// 60007 91040 911// - -92956= - - - diff --git a/tests/data/observations/romania_update/A_SMRO01YRBK180000_C_EDZW_20230118001801_51945941.txt b/tests/data/observations/romania_update/A_SMRO01YRBK180000_C_EDZW_20230118001801_51945941.txt deleted file mode 100644 index 568c959b5..000000000 --- a/tests/data/observations/romania_update/A_SMRO01YRBK180000_C_EDZW_20230118001801_51945941.txt +++ /dev/null @@ -1,56 +0,0 @@ -SMRO01 YRBK 180000 -AAXX 18001 -15015 01597 83201 10072 20053 39345 42589 56019 60051 76186 885// 333 -4/000 55300 0//// 20000 3//// 55008 0//// 20214 3//// 60057 91004 -91107= -15020 02597 80804 10060 20035 39775 49971 58032 60001 8457/ 333 4/000 -55300 0//// 20000 3//// 55032 0//// 20465 3//// 60007 91006 91110= -15090 02997 80804 10060 20044 39910 40002 57022 60001 80007 333 4/000 -55300 10144 20000 30000 55011 10119 20331 30296 60007 91007 91107= -15108 01298 52018 10011 21018 37894 48299 58027 60001 73632 83540 333 -48013 55300 0//// 20000 3//// 55022 0//// 20331 3//// 60007 91024 -911// 92727 92912= -15120 01597 81103 10063 20042 39459 49947 56016 69981 78082 8437/ 333 -55300 10138 20000 30000 55044 00077 20610 30225 69987 91005 91106= -15150 02997 41606 10060 20047 39786 49998 58018 60001 84070 333 4/000 -55300 0//// 20000 3//// 55024 0//// 20470 3//// 60007 91010 91110= -15170 05598 81801 10021 20018 39215 42631 58021 60001 7000/ 8287/ 333 -55300 0//// 20000 3//// 55020 0//// 20356 3//// 60007 91005 91106= -15200 01598 81613 10111 20073 39769 49909 53006 60011 70282 8437/ 333 -55300 0//// 20000 3//// 55034 0//// 20552 3//// 69947 91018 91118= -15230 01597 82003 10091 20075 39662 49949 55008 60041 76162 885// 333 -4/000 55300 10179 20000 30000 55022 10042 20387 30240 60047 91005 -91107= -15260 05598 71404 10118 20032 39419 49941 58009 60001 7000/ 84570 333 -55300 0//// 20000 3//// 55035 0//// 20456 3//// 60007 91007 91115= -15280 01/90 92032 11034 21040 37301 47838 53008 60001 74143 333 49080 -55300 0//// 20000 3//// 55000 0//// 20003 3//// 60007 91040 911// -92956= -15292 01597 81506 10095 20062 39656 49943 50005 60011 72582 883// 333 -55300 0//// 20000 3//// 55024 0//// 20432 3//// 69977 91011 91111= -15310 02997 01604 10105 20082 39933 40018 57014 60001 333 4/000 55300 -10138 20000 30000 55036 10029 20545 30351 60007 91008 91109= -15335 02597 31807 10107 20087 30027 40034 58013 60001 82530 333 4/000 -55300 0//// 20000 3//// 55027 0//// 20540 3//// 60007 91009 91109= -15346 01597 83603 10063 20055 39712 40000 57007 60011 76162 8657/ 333 -4/000 55300 0//// 20000 3//// 55006 0//// 20225 3//// 60017 91004 -91106= -15350 02997 03302 10044 20037 39897 40017 57017 60001 333 4/000 55300 -0//// 20000 3//// 55032 0//// 20419 3//// 60007 91003 91103= -15360 02997 01909 10097 20086 30031 40048 57012 60001 222// 0//// -2//// 333 55300 20000 3//// 55030 20520 3//// 60007 91011 91111 92437 -= -15410 02597 70202 10088 20055 39883 49978 56010 60001 83570 333 4/000 -55300 0//// 20000 3//// 55020 0//// 20398 3//// 60007 91005 91105= -15420 02997 50101 10053 20053 39888 49999 57019 60001 85050 333 55300 -0//// 20000 3//// 55018 0//// 20402 3//// 60007 91001 91102= -15450 02997 11105 10080 20055 39747 49979 56010 60001 81030 333 55300 -10148 20000 30000 55030 00055 20549 30334 60007 91007 91108= -15460 02997 61803 10132 20087 39987 40012 58016 60001 86050 333 4/000 -55300 0//// 20000 3//// 55039 0//// 20592 3//// 60007 91006 91106= -15470 02997 71704 10116 20061 39866 49989 56012 60001 87070 333 55300 -0//// 20000 3//// 55025 0//// 20506 3//// 60007 91007 91107= -15480 05597 51703 10113 20096 30021 40038 58012 60001 7000/ 85500 -222// 0//// 2//// 333 4/000 55300 10136 20000 30000 55035 10042 20593 -30480 60007 91007 91108 92427= - diff --git a/tests/data/observations/wmo/buoy/A_IOBX18CWAO151752_C_EDZW_20240215180405_26717559.bin b/tests/data/observations/wmo/buoy/A_IOBX18CWAO151752_C_EDZW_20240215180405_26717559.bin deleted file mode 100644 index cf65514b0a8a61cdca66b877041bfb76ad313c2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7085 zcmbu^dr*^C769--C=Ug#v?9n;K=tT6R~0S(FBeCNa&_WR?`o!L9gaPB|n z~$#xY8>+DJsD$FeET6B*?*G&gyq!*NI%qtVALwktjrD8Wt`hKatIt z<$MPV)67mJiqHMGoyav!w99H(o}8R6PfyOs-kO}8B~N}$IOf&X6{ky%-2JI<%vaUk zu2!kruihMNZy!;$=WZf1o}?g;JTzGCiz13C0>daKM5D)eUT*afmA!*~pS?rT8*A<7 z*{f`Y!(w1^!T1lQwfV9iSGZI-FLtSNc6F<&aCUR4cz!M+1>}B~)VQgxI903Lk4Z^8 zNAyw-CZp;XKB~;itFNrjYw0IM0UkPA-@zFsh5O!t{JX(&HeWiLlHJ4hUEgydoepJR~4sWpIF(aM&cM z~09`Ve1{Aqy-(Our{Y7$1D?jep6ltq;f#SaNuC~f{qc7D2&LLrIM6<|`J5QQ--h0NBF4ZUGrGz$0rW>jl2D^S2aVx@4# zI`;EQ`B)~6LN51gUx30KEXGLT@lf}mNHW|(qwt+pAI)J_oF)zU}(33V=+bwS2mZeeNk5CCw#Tc>Iy~Qcf>+DzuJFrpQvlY?;CW zKmnUSD~0bi`tEmD1bI^^xM!1zm#_d(_>6-IBZZvvDJAQjQVqf`JG(2KRvH?WSOL0% z7GhQkO{%i(ma0#(XcR)p*^^iRy253Q87T~&D0_3athk9rp@HbyILyi)kTysmW~I>3 zy5?kW&~h4uE6a>mv{(Qr{0T8Dg->RiUEvZIW2A7yu54k6>}whAfaJVMm(5{To%Qsfszqrd{t6*L$#Qh0c?`*DZlY7FhJaLivHCdI5gh3Z;}87X+Y zr|}?-)fQ0{3J<%U?F)8=ZBT%fg6e|LPs57MZ4?R~sbu^bt^g=xV$4Wk&!;JS>YOrO zqTLl#{)TETX5}gL{RuH6h2l_EQP&rJhlVK>_MH2Fy29-frPpj_EpuoTRuJ7s94>+H z3M-Wmvr@=vjrzGaWalFag&PN-bxQ(W!NkEdSSb|x#m7gCKw&$^tP}!MIM4EJXKquj%{|42Dsus#z`=x-g74ZAv3Fl~35|j` zQEbEta6meZF=JP_8(4ZlEmM@!D6FZ~?Zl+e6~ZxQr0|McYgotO&`kvt3S)PS{W~x# zPzZyVl|s|G*pV&r0qU2jAw=`+G4pqY2`t7)Ve(w}HAl&i7wxXlsMJSrn3bn+LJBb} zg_APZb&b`jGzyPhNtzN100k|?tP}erm%fW<^!rpIFGE$wk z5BpIlTuC$3VNy6CsijbWl|tf?!pe1h=T}fD>^)8tk02@D6`o+sNa4=rQuRM&E#)qh zFH=`}>ULvNpsGr?vC?$BJVmw z>k4|Jxq}6uE136-k-{T&_w8iK)p6Qgp{7_L$zcVaLX8w+RtotVxA#-4mpq_!g~>%E z&3q093Y}1ZmBQsjpWF;Z>H->te<;Vta0NhN7Y7qo3X4;+esIb-YE7Y_{?%|o2}yxM QzZT-@Fw-#oQ_l4BU(1|WHUIzs diff --git a/tests/data/observations/wmo/buoy/A_IOBX18CWAO160610_C_EDZW_20240216062204_27366803.bin b/tests/data/observations/wmo/buoy/A_IOBX18CWAO160610_C_EDZW_20240216062204_27366803.bin deleted file mode 100644 index 8e6a0884ed98e7348abb44cef30394a2b25b2c1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3319 zcma)#C=SakX$4kaMzl~~jS5mFpdfR)C|{%wQivL;z@l?6ZDrz! zOhKJcjDoLhi5sHE!Lp=^zLk_-i7c|xZDJKu0>mJjK4aZBZv^RZ|_CA=JLW^muh+UjJ!?Oen%LA?H zFT+fglD7k@By*)zl@e({rSA-YA{f-gIWua?!ZPd;%%xz_!&sG`e0aQOet^knEH|Ap z);BfQn;OfF99B_3e!e%}lHgY$KH65K;_-r2YF=hhL8hu8n9I?_;8t2p>2IJf!E0D-T?CLA}f zlxyO8dtD+1fT1A(XF-35+1YwEIVlGn495p!qN9}2(MmbHglT9LqNKH)eWnrg%(L(_ z*X8ce)nfJJ3--tUt=0L+! zMuhND!1NyS!8er$Z%%))#vLCakL*%Cb1U}aU>xbYJNI zC))=(He&X%RmJt$ZOhnsgN*QjCdP=f&~=1d3V@7wmdy0~<5OD^QN@U;UlivBLV=de z1)oA-a4)u%sY6E>TUmL7!GMt%0>)d1#*UepTM-zJ8yTb@?DJ}1TW4TZVBAW1`h%IH zz10ibS@WdKFcJqs9`vFw(E<6-8G&&jK;tgbJ44QfBkKZiw9S=Ds{*7`5h1$jtKAqn z@vx&}Vk5$17$RoI+w=?(J>`r5U?P$*5u+X8yw;kWq@8pyEIeWgBZQ})XufIpogiX| zF8{-<9EONs7ZEl3+Z8SmWJLd30EeQ`1dh`e4wqTk2j8?Yls>DiP3sTq&{*5>#<*|m zX=&>u8$*)FV>X5X&Y=VQ!oJJaYWDj^2_*y@@k6%T{P!>;fc<#&T2DUw zT%-7dg%W#XP-tTqzRp^k=fCG}BI|wQjRE6QvN8B|j^;;d>HVJ>}{)oP~yLMu0?~J*c>_oxSX!6#sIYz36UAAp-gMvIE?wRZDU=-{DjF^Wgd^VJ|Zjt}T*Pl#{Lh$nnhb4}=6{BF2k=|1j zL>Ee~MIr2x zV(WqBi`=2WIpnm}F#-gIO{Dh}1!2^xmeK6mi|$Z}g|0wGfS@2|dQVZH+bYh7{*#j8 ZW=o6m#?Ue#g;8i?+QDd4=;sak`xoML2ipJu diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 44a27d580..4201a3636 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -78,8 +78,8 @@ def test_metadata_station_publish(): stations = r.json() - assert stations['numberReturned'] == 148 - assert stations['numberMatched'] == 148 + assert stations['numberReturned'] == 103 + assert stations['numberMatched'] == 103 def test_metadata_discovery_publish(): @@ -247,13 +247,13 @@ def test_message_api(): # test messages per test dataset counts = { - 'cn-cma': 11, 'mw_met_centre': 25, 'roma_met_centre': 33, 'alger_met_centre': 29, - 'rnimh': 111, - 'brazza_met_centre': 14, - 'wmo-test': 151 + 'rnimh': 50, + 'brazza_met_centre': 15, + 'wmo-test': 11, + 'cn-cma': 11 } for key, value in counts.items(): url = f'{API_URL}/collections/messages/items?sortby=-datetime&q={key}&limit=1' # noqa diff --git a/tests/test.env b/tests/test.env index 9749fc724..7681569d0 100644 --- a/tests/test.env +++ b/tests/test.env @@ -19,7 +19,7 @@ WIS2BOX_API_BACKEND_TYPE=Elasticsearch WIS2BOX_API_BACKEND_URL=http://elasticsearch:9200 # logging -WIS2BOX_LOGGING_LOGLEVEL=ERROR +WIS2BOX_LOGGING_LOGLEVEL=WARNING WIS2BOX_LOGGING_LOGFILE=stdout # map settings for wis2box-ui, wis2box-api and wis2box-webapp diff --git a/wis2box-create-config.py b/wis2box-create-config.py index 3eaa69e7a..4713d94c0 100644 --- a/wis2box-create-config.py +++ b/wis2box-create-config.py @@ -335,8 +335,8 @@ def create_wis2box_env(config_dir: str) -> None: # get password for WIS2BOX_BROKER_PASSWORD and write it to wis2box.env fh.write(get_password('WIS2BOX_BROKER_PASSWORD')) fh.write('\n') - # update WIS2BOX_PUBLIC_BROKER settings after updating broker defaults - fh.write('# update WIS2BOX_PUBLIC_BROKER settings after updating broker defaults\n') # noqa + # update WIS2BOX_BROKER_PUBLIC settings after updating broker defaults + fh.write('# update WIS2BOX_BROKER_PUBLIC settings after updating broker defaults\n') # noqa fh.write('WIS2BOX_BROKER_PUBLIC=mqtt://${WIS2BOX_BROKER_USERNAME}:${WIS2BOX_BROKER_PASSWORD}@mosquitto:1883\n') # noqa # update minio settings after updating storage and broker defaults fh.write('\n') @@ -418,42 +418,6 @@ def create_config_dir() -> str: return config_dir -def update_datamappings_file(config_dir: str, centre_id: str) -> None: - """ - creates the data mappings file in the directory config_dir - - :param config_dir: `str` of path to directory where configuration files - are to be stored - :param country_code: `str` of the country code of the wis2box - :param centre_id: `str` of the centre id of the wis2box - - :returns: None - """ - - template_file = Path("config-templates/data-mappings.yml.tmpl") - new_config_file = Path(config_dir) / "data-mappings.yml" - - template_vars = { - 'CENTRE_ID': centre_id - } - with template_file.open() as fh: - config_file = Template(fh.read()) - result = config_file.substitute(template_vars) - if new_config_file.exists(): - print(f"Adding datasets to {new_config_file}") - with new_config_file.open("a") as fh2: - fh2.write(result.replace("data:\n", "")) - else: - print(f"Creating {new_config_file}") - with new_config_file.open("w") as fh2: - fh2.write(result) - - print("*" * 80) - print("data_mappings.yml have been created/updated") # noqa - print("Please review the file and update as needed.") - print("*" * 80) - - def create_metadata_file(config_dir: str, country_name: str, centre_id: str, centre_name: str, wis2box_email: str, bounding_box: str, template: str) -> str: @@ -585,24 +549,14 @@ def create_station_list(config_dir: str) -> None: """ station_metadata_dir = Path(config_dir) / 'metadata' / 'station' - station_list_template_file = Path('config-templates') / 'station_list_example.csv' # noqa - # create directory for station metadata if it does not exist if not station_metadata_dir.exists(): station_metadata_dir.mkdir() - # create station list file - new_config_file = station_metadata_dir / "station_list.csv" - - with station_list_template_file.open() as fh: - config_file = fh.read() - with new_config_file.open("w") as fh2: - fh2.write(config_file) - - # print("*" * 80) - # print(f"Created the file {new_config_file}.") - # print("Please add your stations to this file.") - # print("*" * 80) + station_list = station_metadata_dir / 'station_list.csv' + headers = 'station_name,wigos_station_identifier,traditional_station_identifier,facility_type,latitude,longitude,elevation,barometer_height,territory_name,wmo_region' # noqa + with station_list.open("w") as fh2: + fh2.write(headers) def get_config_dir() -> str: @@ -661,28 +615,13 @@ def main(): if not config_dir: config_dir = create_config_dir() + create_station_list(config_dir) + # if wis2box.env does not exist # create it and write config_dir as the value for WIS2BOX_HOST_DATADIR to wis2box.env # noqa if not dev_env.is_file(): create_wis2box_env(config_dir) - q_metadata = 'Would you like to add datasets for another centre-id ? (y/n)' # noqa - answer = 'y' - while answer == 'y': - tld, centre_id = get_tld_and_centre_id() - create_metadata_files(config_dir, tld, centre_id) - update_datamappings_file(config_dir, centre_id) - print(" ") - print(f"Finished configuration for centre-id={centre_id}") - print(" ") - print(q_metadata) - answer = input() - if answer not in ['y', 'n']: - print(q_metadata) - answer = input() - - create_station_list(config_dir) - print("The configuration is complete.") exit() diff --git a/wis2box-ctl.py b/wis2box-ctl.py index bc239ca31..231f37190 100755 --- a/wis2box-ctl.py +++ b/wis2box-ctl.py @@ -147,9 +147,24 @@ def make(args) -> None: :returns: None. """ + if not os.path.exists('wis2box.env'): + print("ERROR: wis2box.env file does not exist. Please create one manually or by running `python3 wis2box-create-config.py`") + exit(1) + # check if WIS2BOX_SSL_KEY and WIS2BOX_SSL_CERT are set + ssl_key = None + ssl_cert = None + with open('wis2box.env', 'r') as f: + for line in f: + if 'WIS2BOX_SSL_KEY' in line: + ssl_key = line.split('=')[1].strip() + if 'WIS2BOX_SSL_CERT' in line: + ssl_cert = line.split('=')[1].strip() docker_compose_args = DOCKER_COMPOSE_ARGS - if args.ssl: + if args.ssl or (ssl_key and ssl_cert): docker_compose_args +=" --file docker-compose.ssl.yml" + if args.ssl and not (ssl_key and ssl_cert): + print("ERROR: SSL is enabled but WIS2BOX_SSL_KEY and WIS2BOX_SSL_CERT are not set in wis2box.env") + exit(1) # if you selected a bunch of them, default to all containers = "" if not args.args else ' '.join(args.args) @@ -162,9 +177,6 @@ def make(args) -> None: run(args, split( f'{DOCKER_COMPOSE_COMMAND} {docker_compose_args} build {containers}')) elif args.command in ["up", "start", "start-dev"]: - if not os.path.exists('wis2box.env'): - print("ERROR: wis2box.env file does not exist. Please create one manually or by running `python3 wis2box-create-config.py`") - exit(1) run(args, split( 'docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions')) run(args, split( diff --git a/wis2box-management/wis2box/__init__.py b/wis2box-management/wis2box/__init__.py index 0ebebee89..705827a11 100644 --- a/wis2box-management/wis2box/__init__.py +++ b/wis2box-management/wis2box/__init__.py @@ -19,7 +19,7 @@ # ############################################################################### -__version__ = '1.0b6' +__version__ = '1.0b7-dev' import click diff --git a/wis2box-management/wis2box/api/__init__.py b/wis2box-management/wis2box/api/__init__.py index 3312667fa..349b8d839 100644 --- a/wis2box-management/wis2box/api/__init__.py +++ b/wis2box-management/wis2box/api/__init__.py @@ -28,24 +28,11 @@ from wis2box import cli_helpers from wis2box.api.backend import load_backend from wis2box.api.config import load_config -from wis2box.env import (BROKER_HOST, BROKER_USERNAME, BROKER_PASSWORD, - BROKER_PORT, DOCKER_API_URL, API_URL) -from wis2box.plugin import load_plugin, PLUGINS +from wis2box.env import (DOCKER_API_URL, API_URL) LOGGER = logging.getLogger(__name__) -def refresh_data_mappings(): - # load plugin for local broker - defs_local = { - 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}', # noqa - 'client_type': 'dataset-manager' - } - local_broker = load_plugin('pubsub', defs_local) - local_broker.pub('wis2box/data_mappings/refresh', '{}', qos=0) - - def execute_api_process(process_name: str, payload: dict) -> dict: """ Executes a process on the API @@ -90,7 +77,7 @@ def execute_api_process(process_name: str, payload: dict) -> dict: response_json = response.json() if 'status' in response_json: status = response_json['status'] - sleep(0.1) + sleep(0.05) # get result from location/results?f=json response = requests.get(f'{location}/results?f=json', headers=headers) # noqa return response.json() @@ -133,9 +120,6 @@ def setup_collection(meta: dict = {}) -> bool: LOGGER.error(msg) return False - LOGGER.debug('Refreshing data mappings') - refresh_data_mappings() - return True @@ -152,11 +136,12 @@ def remove_collection(collection_id: str, backend: bool = True, api_backend = None api_config = None - collection_data = load_config().get_collection_data(collection_id) + collection_data = 'None' if config: api_config = load_config() if api_config.has_collection(collection_id): + collection_data = api_config.get_collection_data(collection_id) api_config.delete_collection(collection_id) if backend: @@ -175,14 +160,12 @@ def remove_collection(collection_id: str, backend: bool = True, if collection_id not in ['discovery-metadata', 'stations', 'messages']: try: - delete_collection_item('discovery-metadata', collection_id) + if api_backend is not None and api_backend.has_collection(collection_id): # noqa + delete_collection_item('discovery-metadata', collection_id) except Exception: msg = f'discovery metadata {collection_id} not found' LOGGER.warning(msg) - LOGGER.debug('Refreshing data mappings') - refresh_data_mappings() - return True @@ -198,6 +181,9 @@ def upsert_collection_item(collection_id: str, item: dict) -> str: backend = load_backend() backend.upsert_collection_items(collection_id, [item]) + if collection_id in ['discovery-metadata', 'stations']: + backend.flush(collection_id) + return True @@ -228,8 +214,12 @@ def delete_collection_item(collection_id: str, item_id: str) -> str: :returns: `str` identifier of added item """ + + LOGGER.info(f'Deleting item {item_id} from collection {collection_id}') backend = load_backend() backend.delete_collection_item(collection_id, item_id) + if collection_id in ['discovery-metadata', 'stations']: + backend.flush(collection_id) def delete_collections_by_retention(days: int) -> None: diff --git a/wis2box-management/wis2box/api/backend/base.py b/wis2box-management/wis2box/api/backend/base.py index 9e89a8992..d085ef72c 100644 --- a/wis2box-management/wis2box/api/backend/base.py +++ b/wis2box-management/wis2box/api/backend/base.py @@ -120,5 +120,16 @@ def delete_collections_by_retention(self, days: int) -> None: raise NotImplementedError() + def flush(self, collection: str): + """ + Flush a given index to ensure persistence + + :param collection: name of collection + + :returns: `None` + """ + + raise NotImplementedError() + def __repr__(self): return f' (url={self.url})' diff --git a/wis2box-management/wis2box/api/backend/elastic.py b/wis2box-management/wis2box/api/backend/elastic.py index 96692b3cd..65d176cf8 100644 --- a/wis2box-management/wis2box/api/backend/elastic.py +++ b/wis2box-management/wis2box/api/backend/elastic.py @@ -368,5 +368,16 @@ def delete_collections_by_retention(self, days: int) -> bool: return + def flush(self, collection: str): + """ + Flush a given index to ensure persistence + + :param collection: name of collection + + :returns: `None` + """ + + self.conn.indices.flush(index=self.es_id(collection)) + def __repr__(self): return f' (url={self.url})' diff --git a/wis2box-management/wis2box/api/config/pygeoapi.py b/wis2box-management/wis2box/api/config/pygeoapi.py index e3c6af780..d097cbdbd 100644 --- a/wis2box-management/wis2box/api/config/pygeoapi.py +++ b/wis2box-management/wis2box/api/config/pygeoapi.py @@ -123,7 +123,10 @@ def has_collection(self, name: str) -> bool: :returns: `bool` of collection result """ - r = self.http.get(f'{self.url}/{name}') + try: + r = self.http.get(f'{self.url}/{name}') + except Exception: + return False return r.ok def prepare_collection(self, meta: dict) -> bool: diff --git a/wis2box-management/wis2box/data/__init__.py b/wis2box-management/wis2box/data/__init__.py index 1277d2fef..e2d70ce5b 100644 --- a/wis2box-management/wis2box/data/__init__.py +++ b/wis2box-management/wis2box/data/__init__.py @@ -105,9 +105,12 @@ def gcm(mcf: Union[dict, str]) -> dict: LOGGER.debug('Parsing discovery metadata') - dm = DiscoveryMetadata() - record = dm.parse_record(mcf) - generated = dm.generate(record) + if 'conformsTo' in mcf: + generated = mcf + else: + dm = DiscoveryMetadata() + record = dm.parse_record(mcf) + generated = dm.generate(record) LOGGER.debug('Creating collection configuration') @@ -118,17 +121,13 @@ def gcm(mcf: Union[dict, str]) -> dict: generated['geometry']['coordinates'][0][2][1] ] - kw = record['identification']['keywords'] - - keywords = set([k for k in kw.values() for k in kw]) - return { 'id': generated['id'], 'type': 'feature', - 'topic_hierarchy': record['wis2box']['topic_hierarchy'], + 'topic_hierarchy': generated['properties']['wmo:topicHierarchy'].replace('origin/a/wis2/', '').replace('/', '.'), # noqa: E501 'title': generated['properties']['title'], 'description': generated['properties']['description'], - 'keywords': list(keywords), + 'keywords': generated['properties']['keywords'], 'bbox': bbox, 'links': generated['links'], 'id_field': 'id', diff --git a/wis2box-management/wis2box/data/base.py b/wis2box-management/wis2box/data/base.py index 05e0eab32..fb3961ff1 100644 --- a/wis2box-management/wis2box/data/base.py +++ b/wis2box-management/wis2box/data/base.py @@ -27,8 +27,7 @@ from wis2box.env import (STORAGE_INCOMING, STORAGE_PUBLIC, STORAGE_SOURCE, BROKER_PUBLIC, - BROKER_HOST, BROKER_USERNAME, BROKER_PASSWORD, - BROKER_PORT) + DOCKER_BROKER) from wis2box.storage import exists, get_data, put_data from wis2box.topic_hierarchy import TopicHierarchy from wis2box.plugin import load_plugin, PLUGINS @@ -73,12 +72,14 @@ def publish_failure_message(self, description, wsi=None): # load plugin for local broker defs = { 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}', # noqa + 'url': DOCKER_BROKER, # noqa 'client_type': 'failure-publisher' } local_broker = load_plugin('pubsub', defs) # publish with qos=0 - local_broker.pub('wis2box/failure', json.dumps(message), qos=0) + success = local_broker.pub('wis2box/failure', json.dumps(message), qos=0) # noqa + if not success: + LOGGER.error('Failed to publish failure message on internal broker') # noqa def setup_discovery_metadata(self, discovery_metadata: dict) -> None: """ @@ -163,19 +164,23 @@ def notify(self, identifier: str, storage_path: str, broker = load_plugin('pubsub', defs) # publish using filename as identifier - broker.pub(topic, wis_message.dumps()) - LOGGER.info(f'WISNotificationMessage published for {identifier}') + success = broker.pub(topic, wis_message.dumps()) + if not success: + LOGGER.error('Failed to publish data notification message on public broker') # noqa + return False + else: + LOGGER.info(f'WISNotificationMessage published for {identifier}') # load plugin for local broker defs_local = { 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}', # noqa + 'url': DOCKER_BROKER, # noqa 'client_type': 'notify-publisher' } local_broker = load_plugin('pubsub', defs_local) - local_broker.pub('wis2box/notifications', - wis_message.dumps(), - qos=0) + success = local_broker.pub('wis2box/notifications', wis_message.dumps(), qos=0) # noqa + if not success: + LOGGER.error('Failed to publish notification message on internal broker') # noqa return True def publish(self) -> bool: diff --git a/wis2box-management/wis2box/data_mappings.py b/wis2box-management/wis2box/data_mappings.py index 2960480b1..6f4deab54 100644 --- a/wis2box-management/wis2box/data_mappings.py +++ b/wis2box-management/wis2box/data_mappings.py @@ -23,11 +23,25 @@ from owslib.ogcapi.records import Records -from wis2box.env import DOCKER_API_URL +from wis2box.env import (DOCKER_BROKER, DOCKER_API_URL) +from wis2box.plugin import load_plugin, PLUGINS LOGGER = logging.getLogger(__name__) +def refresh_data_mappings(): + # load plugin for local broker and publish refresh request + defs_local = { + 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], + 'url': DOCKER_BROKER, + 'client_type': 'dataset-manager' + } + local_broker = load_plugin('pubsub', defs_local) + success = local_broker.pub('wis2box/data_mappings/refresh', '{}', qos=0) + if not success: + LOGGER.error('Failed to refresh data mappings') + + def get_data_mappings() -> dict: """ Get data mappings @@ -42,6 +56,13 @@ def get_data_mappings() -> dict: try: records = oar.collection_items('discovery-metadata') for record in records['features']: + # skip records without data mappings + if 'wis2box' not in record: + continue + if 'topic_hierarchy' not in record['wis2box']: + continue + if 'data_mappings' not in record['wis2box']: + continue key = record['wis2box']['topic_hierarchy'] value = record['wis2box']['data_mappings'] value['metadata_id'] = record['id'] diff --git a/wis2box-management/wis2box/dataset.py b/wis2box-management/wis2box/dataset.py index beb6dd34f..fbf824605 100644 --- a/wis2box-management/wis2box/dataset.py +++ b/wis2box-management/wis2box/dataset.py @@ -24,7 +24,7 @@ import click from wis2box import cli_helpers -from wis2box.data import add_collection, delete_collection +from wis2box.data import add_collection from wis2box.metadata.discovery import publish, unpublish LOGGER = logging.getLogger(__name__) @@ -51,13 +51,14 @@ def publish_(ctx, filepath, verbosity): @click.command('unpublish') @click.pass_context @click.argument('identifier') +@click.option('--force', '-f', default=False, is_flag=True, + help='Force delete associated data from API') @cli_helpers.OPTION_VERBOSITY -def unpublish_(ctx, identifier, verbosity): +def unpublish_(ctx, identifier, verbosity, force=False): """Unpublishes a dataset""" - # TODO - ctx.invoke(delete_collection, collection=identifier, verbosity=verbosity) - ctx.invoke(unpublish, identifier=identifier, verbosity=verbosity) + ctx.invoke(unpublish, identifier=identifier, verbosity=verbosity, + force=force) dataset.add_command(publish_) diff --git a/wis2box-management/wis2box/env.py b/wis2box-management/wis2box/env.py index 38c0d28a1..c8778c6d2 100644 --- a/wis2box-management/wis2box/env.py +++ b/wis2box-management/wis2box/env.py @@ -51,9 +51,8 @@ BROKER_PASSWORD = os.environ.get('WIS2BOX_BROKER_PASSWORD', 'wis2box') BROKER_HOST = os.environ.get('WIS2BOX_BROKER_HOST', 'mosquitto') BROKER_PORT = os.environ.get('WIS2BOX_BROKER_PORT', 1883) -BROKER_PUBLIC = os.environ.get('WIS2BOX_BROKER_PUBLIC', - f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}') # noqa DOCKER_BROKER = f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}' # noqa +BROKER_PUBLIC = os.environ.get('WIS2BOX_BROKER_PUBLIC', DOCKER_BROKER) STORAGE_TYPE = os.environ.get('WIS2BOX_STORAGE_TYPE', 'S3') STORAGE_SOURCE = os.environ.get('WIS2BOX_STORAGE_SOURCE', 'http://minio:9000') diff --git a/wis2box-management/wis2box/handler.py b/wis2box-management/wis2box/handler.py index 152e19261..e39dc358d 100644 --- a/wis2box-management/wis2box/handler.py +++ b/wis2box-management/wis2box/handler.py @@ -30,8 +30,7 @@ from wis2box.plugin import load_plugin from wis2box.plugin import PLUGINS -from wis2box.env import (BROKER_HOST, BROKER_USERNAME, - BROKER_PASSWORD, BROKER_PORT) +from wis2box.env import (DOCKER_BROKER, STORAGE_PUBLIC) LOGGER = logging.getLogger(__name__) @@ -73,7 +72,11 @@ def __init__(self, filepath: str, th, data_mappings, self.filetype, fuzzy=fuzzy) except Exception as err: msg = f'Topic Hierarchy validation error: {err}' - raise ValueError(msg) + # errors in public storage are not handled + if STORAGE_PUBLIC in self.filepath: + raise NotHandledError(msg) + else: + raise ValueError(msg) def publish_failure_message(self, description, plugin=None): message = { @@ -86,11 +89,14 @@ def publish_failure_message(self, description, plugin=None): # handler uses local broker to publish success/failure messages defs = { 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}', # noqa + 'url': DOCKER_BROKER, # noqa 'client_type': 'handler-publisher' } local_broker = load_plugin('pubsub', defs) - local_broker.pub('wis2box/failure', json.dumps(message), qos=0) + success = local_broker.pub('wis2box/handler', json.dumps(message), qos=0) # noqa + if not success: + msg = f'Failed to publish message: {message}' + LOGGER.error(msg) def handle(self) -> bool: for plugin in self.plugins: diff --git a/wis2box-management/wis2box/metadata/discovery.py b/wis2box-management/wis2box/metadata/discovery.py index a11807830..5ea58b7d6 100644 --- a/wis2box-management/wis2box/metadata/discovery.py +++ b/wis2box-management/wis2box/metadata/discovery.py @@ -24,19 +24,24 @@ from datetime import date, datetime import json import logging +import time +from urllib.parse import urlparse + +from typing import Union from pygeometa.schemas.wmo_wcmp2 import WMOWCMP2OutputSchema from wis2box import cli_helpers -from wis2box.api import (setup_collection, upsert_collection_item, - delete_collection_item) -from wis2box.env import (API_URL, BROKER_PUBLIC, DOCKER_BROKER, - STORAGE_PUBLIC, STORAGE_SOURCE) +from wis2box.api import (delete_collection_item, remove_collection, + setup_collection, upsert_collection_item) +from wis2box.data_mappings import refresh_data_mappings +from wis2box.env import (API_URL, BROKER_PUBLIC, + STORAGE_PUBLIC, STORAGE_SOURCE, URL) from wis2box.metadata.base import BaseMetadata from wis2box.plugin import load_plugin, PLUGINS from wis2box.pubsub.message import WISNotificationMessage from wis2box.storage import put_data -from wis2box.util import json_serial, remove_auth_from_url +from wis2box.util import json_serial LOGGER = logging.getLogger(__name__) @@ -72,30 +77,8 @@ def generate(self, mcf: dict) -> str: md['identification']['extents']['temporal'][0]['begin'] = today LOGGER.debug('Adding distribution links') - oafeat_link = { - 'url': f"{API_URL}/collections/{identifier}?f=json", - 'type': 'application/json', - 'name': identifier, - 'description': identifier, - 'rel': 'collection' - } - - mqp_link = { - 'url': remove_auth_from_url(BROKER_PUBLIC, 'everyone:everyone'), - 'type': 'application/json', - 'name': mcf['wis2box']['topic_hierarchy'], - 'description': mcf['identification']['title'], - 'rel': 'items', - 'channel': mqtt_topic - } - - canonical_link = { - 'url': f"{API_URL}/collections/discovery-metadata/items/{identifier}", # noqa - 'type': 'application/geo+json', - 'name': identifier, - 'description': identifier, - 'rel': 'canonical' - } + oafeat_link, mqp_link, canonical_link = self.get_distribution_links( + identifier, mqtt_topic, format_='mcf') md['distribution'] = { 'oafeat': oafeat_link, @@ -134,6 +117,50 @@ def generate(self, mcf: dict) -> str: return record + def get_distribution_links(self, identifier: str, topic: str, + format_='mcf') -> list: + """ + Generates distribution links + + :param identifier: `str` of metadata identifier + :param topic: `str` of associated topic + :param format_: `str` of format (`mcf` or `wcmp2`) + + :returns: `list` of distribution links + """ + + LOGGER.debug('Adding distribution links') + oafeat_link = { + 'href': f"{API_URL}/collections/{identifier}?f=json", + 'type': 'application/json', + 'name': identifier, + 'description': identifier, + 'rel': 'collection' + } + + mqp_link = { + 'href': get_broker_public_endpoint(), + 'type': 'application/json', + 'name': topic, + 'description': 'Notifications', + 'rel': 'items', + 'channel': topic + } + + canonical_link = { + 'href': f"{API_URL}/collections/discovery-metadata/items/{identifier}", # noqa + 'type': 'application/geo+json', + 'name': identifier, + 'description': identifier, + 'rel': 'canonical' + } + + if format_ == 'mcf': + for link in [oafeat_link, mqp_link, canonical_link]: + link['url'] = link.pop('href') + + return oafeat_link, mqp_link, canonical_link + def publish_broker_message(record: dict, storage_path: str, centre_id: str) -> str: @@ -153,16 +180,17 @@ def publish_broker_message(record: dict, storage_path: str, wis_message = WISNotificationMessage(record['id'], topic, storage_path, datetime_, record['geometry']).dumps() - # load plugin for broker + # load plugin for plugin-broker defs = { 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': DOCKER_BROKER, + 'url': BROKER_PUBLIC, 'client_type': 'publisher' } broker = load_plugin('pubsub', defs) - broker.pub(topic, wis_message) - LOGGER.info(f'Discovery metadata published to {topic}') + success = broker.pub(topic, wis_message) + if not success: + raise RuntimeError(f'Failed to publish message to {topic}') return wis_message @@ -188,11 +216,11 @@ def gcm() -> dict: } -def publish_discovery_metadata(metadata: str): +def publish_discovery_metadata(metadata: Union[dict, str]): """ Inserts or updates discovery metadata to catalogue - :param metadata: `str` of MCF + :param metadata: `str` of MCF or `dict` of WCMP2 :returns: `bool` of publishing result """ @@ -202,10 +230,28 @@ def publish_discovery_metadata(metadata: str): LOGGER.debug('Publishing discovery metadata') try: new_links = [] - dm = DiscoveryMetadata() - record_mcf = dm.parse_record(metadata) - record = dm.generate(record_mcf) + if isinstance(metadata, dict): + LOGGER.info('Adding WCMP2 record from dictionary') + record = metadata + dm = DiscoveryMetadata() + distribution_links = dm.get_distribution_links( + record['id'], record['properties']['wmo:topicHierarchy'], + format_='wcmp2') + if 'links' in record: + record['links'].extend(distribution_links) + else: + record['links'] = distribution_links + for link in record['links']: + if 'description' in link: + link['title'] = link.pop('description') + if 'url' in link: + link['href'] = link.pop('url') + else: + LOGGER.debug('Transforming MCF into WCMP2 record') + dm = DiscoveryMetadata() + record_mcf = dm.parse_record(metadata) + record = dm.generate(record_mcf) LOGGER.debug('Publishing to API') upsert_collection_item('discovery-metadata', record) @@ -230,9 +276,20 @@ def publish_discovery_metadata(metadata: str): put_data(data_bytes, storage_path, 'application/geo+json') LOGGER.debug('Publishing message') - message = publish_broker_message(record, storage_path, - record_mcf['wis2box']['centre_id']) - upsert_collection_item('messages', json.loads(message)) + centre_id = record['properties']['wmo:topicHierarchy'].split('/')[3] + try: + message = publish_broker_message(record, storage_path, + centre_id) + except Exception as err: + msg = 'Failed to publish discovery metadata to public broker' + LOGGER.error(msg) + raise RuntimeError(msg) from err + try: + upsert_collection_item('messages', json.loads(message)) + except Exception as err: + msg = f'Failed to publish message to API: {err}' + LOGGER.error(msg) + raise RuntimeError(msg) from err except Exception as err: LOGGER.warning(err) @@ -241,6 +298,24 @@ def publish_discovery_metadata(metadata: str): return +def get_broker_public_endpoint() -> str: + """ + Helper function to use WIS2BOX_URL to create a publically accessible + broker endpoint + """ + + url_parsed = urlparse(URL) + + if url_parsed.scheme == 'https': + scheme = 'mqtts' + port = 8883 + else: + scheme = 'mqtt' + port = 1883 + + return f'{scheme}://everyone:everyone@{url_parsed.hostname}:{port}' + + @click.group('discovery') def discovery_metadata(): """Discovery metadata management""" @@ -269,13 +344,19 @@ def publish(ctx, filepath, verbosity): publish_discovery_metadata(filepath.read()) except Exception as err: raise click.ClickException(f'Failed to publish: {err}') + refresh_data_mappings() + time.sleep(1) + refresh_data_mappings() + click.echo('Discovery metadata published') @click.command() @click.pass_context @click.argument('identifier') +@click.option('--force', '-f', default=False, is_flag=True, + help='Force delete associated data from API') @cli_helpers.OPTION_VERBOSITY -def unpublish(ctx, identifier, verbosity): +def unpublish(ctx, identifier, verbosity, force=False): """Deletes a discovery metadata record from the catalogue""" click.echo(f'Unpublishing discovery metadata {identifier}') @@ -283,6 +364,14 @@ def unpublish(ctx, identifier, verbosity): delete_collection_item('discovery-metadata', identifier) except Exception: raise click.ClickException('Invalid metadata identifier') + refresh_data_mappings() + time.sleep(1) + refresh_data_mappings() + click.echo('Discovery metadata unpublished') + + if force: + click.echo('Deleting associated data from the API') + remove_collection(identifier) discovery_metadata.add_command(publish) diff --git a/wis2box-management/wis2box/pubsub/message.py b/wis2box-management/wis2box/pubsub/message.py index be9890b63..9e51aff7b 100644 --- a/wis2box-management/wis2box/pubsub/message.py +++ b/wis2box-management/wis2box/pubsub/message.py @@ -28,6 +28,7 @@ from pathlib import Path import uuid +from wis2box import __version__ from wis2box.util import json_serial from wis2box.env import STORAGE_PUBLIC, URL, STORAGE_SOURCE from wis2box.storage import get_data @@ -181,7 +182,8 @@ def __init__(self, identifier: str, topic: str, filepath: str, 'value': self.checksum_value } }, - 'links': links + 'links': links, + 'generated-by': f'wis2box {__version__}' } if self.length < 4096: diff --git a/wis2box-management/wis2box/pubsub/mqtt.py b/wis2box-management/wis2box/pubsub/mqtt.py index 76b9c1b2d..3abb7a0d8 100644 --- a/wis2box-management/wis2box/pubsub/mqtt.py +++ b/wis2box-management/wis2box/pubsub/mqtt.py @@ -21,6 +21,7 @@ import logging import random + from typing import Any, Callable from paho.mqtt import client as mqtt_client @@ -66,7 +67,7 @@ def __init__(self, broker: str) -> None: self.conn.tls_set(tls_version=2) self.conn.connect(self.broker_url.hostname, self._port) - LOGGER.debug('Connected to broker') + LOGGER.debug('Connection initiated') def pub(self, topic: str, message: str, qos: int = 1) -> bool: """ @@ -106,10 +107,14 @@ def sub(self, topic: str) -> None: """ def on_connect(client, userdata, flags, rc): - LOGGER.debug(f'Connected to broker {self.broker}') - LOGGER.debug(f'Subscribing to topic {topic} ') - client.subscribe(topic, qos=1) - LOGGER.debug(f'Subscribed to topic {topic}') + if rc == 0: + LOGGER.debug(f'Connected to broker {self.broker}') + LOGGER.debug(f'Subscribing to topic {topic} ') + client.subscribe(topic, qos=1) + LOGGER.debug(f'Subscribed to topic {topic}') + else: + msg = 'Failed to connect to MQTT-broker:' + LOGGER.error(f'{msg} {mqtt_client.connack_string(rc)}') def on_disconnect(client, userdata, rc): LOGGER.debug(f'Disconnected from {self.broker}') diff --git a/wis2box-management/wis2box/pubsub/subscribe.py b/wis2box-management/wis2box/pubsub/subscribe.py index 5646e014a..74e4b656f 100644 --- a/wis2box-management/wis2box/pubsub/subscribe.py +++ b/wis2box-management/wis2box/pubsub/subscribe.py @@ -30,12 +30,13 @@ from wis2box import cli_helpers import wis2box.data as data_ -from wis2box.api import (remove_collection, setup_collection, - upsert_collection_item) + +from wis2box.api import (setup_collection, upsert_collection_item, + delete_collection_item, remove_collection) + from wis2box.data_mappings import get_data_mappings from wis2box.data.message import MessageData -from wis2box.env import (BROKER_HOST, BROKER_PORT, BROKER_USERNAME, - BROKER_PASSWORD, STORAGE_SOURCE, STORAGE_ARCHIVE) +from wis2box.env import (DOCKER_BROKER, STORAGE_SOURCE, STORAGE_ARCHIVE) from wis2box.handler import Handler, NotHandledError import wis2box.metadata.discovery as discovery_metadata from wis2box.plugin import load_plugin, PLUGINS @@ -119,7 +120,7 @@ def on_message_handler(self, client, userdata, msg): LOGGER.info(f'Do not process archived-data: {key}') # start a new process to handle the received data while len(mp.active_children()) == mp.cpu_count(): - sleep(0.1) + sleep(0.05) mp.Process(target=self.handle, args=(filepath,)).start() elif topic == 'wis2box/data/publication': LOGGER.debug('Publishing data') @@ -133,10 +134,15 @@ def on_message_handler(self, client, userdata, msg): metadata = message discovery_metadata.publish_discovery_metadata(metadata) data_.add_collection_data(metadata) + self.data_mappings = get_data_mappings() elif topic.startswith('wis2box/dataset/unpublication'): LOGGER.debug('Unpublishing dataset') identifier = topic.split('/')[-1] - remove_collection(identifier) + delete_collection_item('discovery-metadata', identifier) + if message.get('force', False): + LOGGER.info('Deleting data') + remove_collection(identifier) + self.data_mappings = get_data_mappings() else: LOGGER.debug('Ignoring message') @@ -151,7 +157,7 @@ def subscribe(ctx, verbosity): defs = { 'codepath': PLUGINS['pubsub']['mqtt']['plugin'], - 'url': f'mqtt://{BROKER_USERNAME}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}', # noqa + 'url': DOCKER_BROKER, # noqa 'client_type': 'subscriber' } diff --git a/wis2box-management/wis2box/storage/minio.py b/wis2box-management/wis2box/storage/minio.py index b6889b3ee..58084a3a8 100644 --- a/wis2box-management/wis2box/storage/minio.py +++ b/wis2box-management/wis2box/storage/minio.py @@ -83,6 +83,12 @@ def readwrite_policy(name): class MinIOStorage(StorageBase): """MinIO storage manager""" def __init__(self, defs: dict) -> None: + """ + initializer + + :param defs: `dict` of storage parameters + (storage_type, source, name, auth, policy) + """ super().__init__(defs) @@ -93,50 +99,94 @@ def __init__(self, defs: dict) -> None: if self.source.startswith('https://'): is_secure = True - self.client = Minio(endpoint=urlparsed.netloc, - access_key=self.auth['username'], - secret_key=self.auth['password'], - secure=is_secure) + try: + self.client = Minio(endpoint=urlparsed.netloc, + access_key=self.auth['username'], + secret_key=self.auth['password'], + secure=is_secure) + except Exception as err: + msg = f'Error creating MinIO client: {err}' + LOGGER.error(msg) def setup(self): + """ + Run setup harness specific to storage + + :returns: `bool` of setup result + """ + LOGGER.debug(f'Creating buckets at MinIO endpoint {self.source}') - self.create_bucket(bucket_policy=self.policy) + try: + self.create_bucket(bucket_policy=self.policy) + return True + except Exception as err: + msg = f'Error creating bucket: {err}' + LOGGER.error(msg) + return False def set_policy(self, bucket_policy: PolicyTypes = 'private'): + """ + Set bucket policy + + :param bucket_policy: `PolicyTypes` of bucket policy + """ + LOGGER.debug(f'Set bucket_policy={bucket_policy} on {self.name}') - if bucket_policy == 'readonly': - self.client.set_bucket_policy( - self.name, json.dumps(readonly_policy(self.name))) - elif bucket_policy == 'readwrite': - self.client.set_bucket_policy( - self.name, json.dumps(readwrite_policy(self.name))) - elif bucket_policy == 'private': - self.client.delete_bucket_policy(self.name) - else: - LOGGER.warning(f'bucket_policy={bucket_policy} unknown') + try: + if bucket_policy == 'readonly': + self.client.set_bucket_policy( + self.name, json.dumps(readonly_policy(self.name))) + elif bucket_policy == 'readwrite': + self.client.set_bucket_policy( + self.name, json.dumps(readwrite_policy(self.name))) + elif bucket_policy == 'private': + self.client.delete_bucket_policy(self.name) + else: + LOGGER.warning(f'bucket_policy={bucket_policy} unknown') + except Exception as err: + msg = f'Error setting bucket policy: {err}' + LOGGER.error(msg) def create_bucket(self, bucket_policy: PolicyTypes = 'private'): + """ + Create bucket if not exist + + :param bucket_policy: `PolicyTypes` of bucket policy + """ + # create bucket if not exist - found = self.client.bucket_exists(self.name) - if not found: - self.client.make_bucket(self.name) - else: - LOGGER.info(f'Bucket {self.name} already exists') - self.set_policy(bucket_policy) - # add notifications to be sent to local-broker - config = NotificationConfig( - queue_config_list=[ - QueueConfig( - 'arn:minio:sqs::WIS2BOX:mqtt', - ['s3:ObjectCreated:*'], - config_id='1' - ) - ] - ) - LOGGER.debug(f'Adding notification config {config}') - self.client.set_bucket_notification(self.name, config) + try: + found = self.client.bucket_exists(self.name) + if not found: + self.client.make_bucket(self.name) + else: + LOGGER.info(f'Bucket {self.name} already exists') + self.set_policy(bucket_policy) + # add notifications to be sent to local-broker + config = NotificationConfig( + queue_config_list=[ + QueueConfig( + 'arn:minio:sqs::WIS2BOX:mqtt', + ['s3:ObjectCreated:*'], + config_id='1' + ) + ] + ) + LOGGER.debug(f'Adding notification config {config}') + self.client.set_bucket_notification(self.name, config) + except Exception as err: + msg = f'Error creating bucket: {err}' + LOGGER.error(msg) def exists(self, identifier: str) -> bool: + """ + Check if object exists in storage + + :param identifier: `str` of object identifier + + :returns: `bool` of object existence + """ + LOGGER.debug(f'Checking if object {identifier} exists') try: # Attempt to get object info to check if it exists @@ -147,13 +197,19 @@ def exists(self, identifier: str) -> bool: LOGGER.debug(err) return False else: - LOGGER.error(err) raise err except Exception as err: - LOGGER.error(err) - raise err + msg = f'Error checking object existence: {err}' + LOGGER.error(msg) def get(self, identifier: str) -> Any: + """ + Access data source from storage + + :param identifier: `str` of data source identifier + + :returns: object result + """ LOGGER.debug(f'Getting object {identifier} from bucket={self.name}') # Get data of an object. @@ -164,40 +220,76 @@ def get(self, identifier: str) -> Any: response.close() response.release_conn() except Exception as err: - LOGGER.error(err) - raise err - + msg = f'Error getting object: {err}' + LOGGER.error(msg) return data def put(self, data: bytes, identifier: str, content_type: str = 'application/octet-stream') -> bool: + """ + Access data source from storage - LOGGER.debug(f'Putting data as object={identifier}') - self.client.put_object(bucket_name=self.name, object_name=identifier, - content_type=content_type, - data=BytesIO(data), length=-1, - part_size=10*1024*1024) + :param data: bytes of file to upload + :param identifier: `str` of data dest identifier + :param content_type: media type (default is `application/octet-stream`) + :returns: `bool` of put result + """ + + LOGGER.debug(f'Putting data as object={identifier}') + try: + self.client.put_object(bucket_name=self.name, + object_name=identifier, + content_type=content_type, + data=BytesIO(data), length=-1, + part_size=10*1024*1024) + except Exception as err: + msg = f'Error putting object: {err}' + LOGGER.error(msg) + return False return True def delete(self, identifier: str) -> bool: + """ + Delete data source from storage - LOGGER.debug(f'Deleting object {identifier}') - self.client.remove_object(self.name, identifier) + :param identifier: `str` of data source identifier + :returns: `bool` of put result + """ + + LOGGER.debug(f'Deleting object {identifier}') + try: + self.client.remove_object(self.name, identifier) + except Exception as err: + msg = f'Error deleting object: {err}' + LOGGER.error(msg) + return False return True def list_objects(self, prefix: str) -> list: + """ + List objects in storage starting with prefix + + :param 'str' prefix + + :returns: list of 'str'-objects + """ + LOGGER.debug(f'list identifiers starting with {prefix}') objects = [] - for object in self.client.list_objects(self.name, prefix, True): # noqa - objects.append({ - 'filename': object.object_name.split('/')[-1], - 'fullpath': f'{self.source}/{self.name}/{object.object_name}', - 'last_modified': object.last_modified, - 'size': object.size, - 'basedir': object.object_name.split('/')[0], - }) + try: + for object in self.client.list_objects(self.name, prefix, True): # noqa + objects.append({ + 'filename': object.object_name.split('/')[-1], + 'fullpath': f'{self.source}/{self.name}/{object.object_name}', # noqa + 'last_modified': object.last_modified, + 'size': object.size, + 'basedir': object.object_name.split('/')[0], + }) + except Exception as err: + msg = f'Error listing objects: {err}' + LOGGER.error(msg) return objects def __repr__(self):