From ebc02d59684122bb2202a9f2a39da55d881ebae7 Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Wed, 19 Jul 2023 04:07:42 -0700 Subject: [PATCH 1/8] portal-backend testing version. --- .versions_env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.versions_env b/.versions_env index 2184a3b3..0eef7c9f 100644 --- a/.versions_env +++ b/.versions_env @@ -1,6 +1,6 @@ EXAREME2=0.18.1 EXAREME=24.5.1 -PORTALBACKEND=7.9.0 +PORTALBACKEND=testing GATEWAY=1.5.0 FRONTEND=9.3.1 GALAXY=1.3.4 From 9389465a39d5bdd75a8e716ab726794a4855d042 Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Thu, 20 Jul 2023 06:31:20 -0700 Subject: [PATCH 2/8] Dropped Galaxy deployment. --- .versions_env | 1 - Federation/doc/MonitoringMIPFederation.md | 1 - Federation/docker-compose.yml | 22 +-------- README.md | 1 - config/keycloak/keycloak_roles_sync.py | 1 - documentation/UserAuthorizations.md | 1 - kubernetes/README.md | 1 - kubernetes/templates/frontend.yaml | 4 -- kubernetes/templates/galaxy.yaml | 57 ----------------------- kubernetes/templates/mip-secret.yaml | 4 -- kubernetes/templates/portalbackend.yaml | 17 ------- 11 files changed, 1 insertion(+), 109 deletions(-) delete mode 100644 kubernetes/templates/galaxy.yaml diff --git a/.versions_env b/.versions_env index 0eef7c9f..d96ef042 100644 --- a/.versions_env +++ b/.versions_env @@ -3,5 +3,4 @@ EXAREME=24.5.1 PORTALBACKEND=testing GATEWAY=1.5.0 FRONTEND=9.3.1 -GALAXY=1.3.4 MIP=7.1.0 diff --git a/Federation/doc/MonitoringMIPFederation.md b/Federation/doc/MonitoringMIPFederation.md index 140eb535..d82bcce8 100644 --- a/Federation/doc/MonitoringMIPFederation.md +++ b/Federation/doc/MonitoringMIPFederation.md @@ -43,7 +43,6 @@ In order for you to have a better understanding of the different available compo ||*gateway*| ||*portalbackend*| ||*portalbackend_db*| -||*galaxy*| ||*create_dbs*| You can see here that *exareme-master* and *exareme-keystore* contains a dash ( **-** ) character, and not an underscore ( **_** ), as it was indicated in the [components](../../README.md#Components) guide. diff --git a/Federation/docker-compose.yml b/Federation/docker-compose.yml index 771bd18b..a0376ed8 100644 --- a/Federation/docker-compose.yml +++ b/Federation/docker-compose.yml @@ -1,19 +1,6 @@ version: '3.7' services: - galaxy: - image: hbpmip/galaxy:${GALAXY} - environment: - - EXAREME_IP=${EXAREME_IP} - - EXAREME_PORT=9090 - - PASSWORD=password - command: bash -c "htpasswd -bc /etc/apache2/htpasswd admin $$PASSWORD && ./createExaremeVariables.sh && /etc/init.d/apache2 restart && ./run.sh" - expose: - - '80' - ports: - - '8090:80' - restart: unless-stopped - portalbackend_db: image: postgres:11.3-alpine volumes: @@ -59,11 +46,6 @@ services: EXAREME2_URL: ${EXAREME2_URL} ### Exareme ### EXAREME_URL: http://${EXAREME_IP}:9090 - ### Galaxy ### - GALAXY_URL: http://galaxy - GALAXY_API_KEY: d14a4cc5eebf805eb2ff261374ed08a2 - GALAXY_USERNAME: admin - GALAXY_PASSWORD: password ### Keycloak ### KEYCLOAK_AUTH_URL: ${KEYCLOAK_PROTOCOL}://${KEYCLOAK_URL}/auth/ KEYCLOAK_REALM: ${KEYCLOAK_REALM} @@ -104,10 +86,8 @@ services: PORTAL_BACKEND_CONTEXT: services GATEWAY_SERVER: gateway:8081 INSTANCE_NAME: 'MIP ${MIP}' - VERSION: 'Frontend: ${FRONTEND}, Gateway: ${GATEWAY}, Backend: ${PORTALBACKEND}, Exareme: ${EXAREME}, Galaxy: ${GALAXY}' + VERSION: 'Frontend: ${FRONTEND}, Gateway: ${GATEWAY}, Backend: ${PORTALBACKEND}, Exareme: ${EXAREME}' TRACKER_ID: UA-80660232-5 - GALAXY_HOST: http://galaxy - GALAXY_PATH: /nativeGalaxy DATACATALOGUE_SERVER: ${DATACATALOGUE_PROTOCOL}://${DATACATALOGUE_HOST} PUBLIC_MIP_HOST: ${PUBLIC_MIP_HOST} PUBLIC_MIP_PROTOCOL: ${PUBLIC_MIP_PROTOCOL} diff --git a/README.md b/README.md index aca73d7d..657d70d8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ The "short names" listed here represent the different MIP components, as well as * [gateway](https://github.com/HBPMedical/gateway): "Middleware" layer between the MIP Frontend and a federated analytic engine * [portalbackend](https://github.com/HBPMedical/portal-backend): The "Backend API" supports the Web App * [portalbackend_db](https://github.com/docker-library/postgres): The portal backend's database -* [galaxy](https://github.com/madgik/galaxy): The "Workflow Engine" provides the ability to unite separate algorithms into one larger one * [keycloak](https://github.com/keycloak/keycloak-containers): The "AuthN/AuthZ" system, based on KeyCloak (this component usually doesn't run in a *federated* MIP, as an "external" KeyCloak service does the job). In case this *local* "embedded" component is used, you may need to know some details, which you can find [here](documentation/UsersConfiguration.md) * [keycloak_db](https://github.com/docker-library/postgres): The KeyCloak's database, required only if the *keycloak* component needs to be used * [create_dbs](https://github.com/HBPMedical/docker-create-databases): The *one shot* container which creates and populates the DBs when required diff --git a/config/keycloak/keycloak_roles_sync.py b/config/keycloak/keycloak_roles_sync.py index 34fa04ae..571c3131 100755 --- a/config/keycloak/keycloak_roles_sync.py +++ b/config/keycloak/keycloak_roles_sync.py @@ -234,7 +234,6 @@ def synchronize_client(self, client_id, pathologies): {'name': 'RESEARCH_DATASET_ALL', 'composite': False, 'description': 'All datasets, without distinction'}, {'name': 'RESEARCH_EXPERIMENT_ALL', 'composite': False}, {'name': 'RESEARCH_PATHOLOGY_ALL', 'composite': False, 'description': 'All pathologies, without distinction'}, - {'name': 'WORKFLOW_ADMIN', 'composite': False, 'description': 'Galaxy administrator'} ] full_role_list = [] diff --git a/documentation/UserAuthorizations.md b/documentation/UserAuthorizations.md index 6592bd89..b7945e5b 100644 --- a/documentation/UserAuthorizations.md +++ b/documentation/UserAuthorizations.md @@ -32,7 +32,6 @@ The following ROLES should exist: - One role (RESEARCH_DATASET_ALL) with which the user gets access to all datasets. - One role (RESEARCH_PATHOLOGY_ALL) with which the user gets access to all pathologies. - One role (RESEARCH_EXPERIMENT_ALL) with which the user gets access to all experiments. - - One role (WORKFLOW_ADMIN) with which the user is given the authorization to edit existing and create new workflows through the galaxy workflow engine editor. ### Adding a new dataset diff --git a/kubernetes/README.md b/kubernetes/README.md index 448240fd..62116b4b 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -47,7 +47,6 @@ Now, with the Kubernetes (K8s) deployment, we have 3 main, big components, which * [gateway_db](https://github.com/docker-library/postgres): The gateway's database * [portalbackend](https://github.com/HBPMedical/portal-backend): The "Backend API" which supports the Web App * [portalbackend_db](https://github.com/docker-library/postgres): The portal backend's database -* [galaxy](https://github.com/madgik/galaxy): The "Workflow Engine" provides the ability to unite separate algorithms into one larger one * [keycloak](https://github.com/keycloak/keycloak-containers): The "AuthN/AuthZ" system, based on KeyCloak (this component usually doesn't run in a *federated* MIP, as an "external" KeyCloak service does the job). In case this *local* "embedded" component is used, you may need to know some details, which you can find [here](documentation/UsersConfiguration.md) * [keycloak_db](https://github.com/docker-library/postgres): The KeyCloak's database, required only if the *keycloak* component needs to be used * [create_dbs](https://github.com/HBPMedical/docker-create-databases): The *one shot* container which creates and populates the DBs when required diff --git a/kubernetes/templates/frontend.yaml b/kubernetes/templates/frontend.yaml index 4c410a12..962a2770 100644 --- a/kubernetes/templates/frontend.yaml +++ b/kubernetes/templates/frontend.yaml @@ -179,10 +179,6 @@ spec: configMapKeyRef: name: mip-config key: mip.LINK - - name: GALAXY_HOST - value: http://galaxy-service - - name: GALAXY_PATH - value: /nativeGalaxy - name: DATACATALOGUE_SERVER # Only used in federated installation valueFrom: configMapKeyRef: diff --git a/kubernetes/templates/galaxy.yaml b/kubernetes/templates/galaxy.yaml deleted file mode 100644 index 31f43e3d..00000000 --- a/kubernetes/templates/galaxy.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: galaxy-deployment - namespace: {{ .Values.namespace }} - labels: - app: galaxy -spec: - replicas: 1 - selector: - matchLabels: - app: galaxy - template: - metadata: - labels: - app: galaxy - spec: - nodeSelector: - master: "true" - containers: - - name: galaxy - image: {{ .Values.galaxy.image.name }}:{{ .Values.galaxy.image.version }} - ports: - - containerPort: 80 - args: - - bash - - -c - - "htpasswd -bc /etc/apache2/htpasswd admin $$PASSWORD && ./createExaremeVariables.sh && /etc/init.d/apache2 restart && ./run.sh" - env: - - name: EXAREME_IP - valueFrom: - configMapKeyRef: - name: mip-config - key: engines.exareme.IP - - name: EXAREME_PORT - value: "9090" - - name: PASSWORD - valueFrom: - secretKeyRef: - name: mip-secret - key: galaxy.PASSWORD - ---- -apiVersion: v1 -kind: Service -metadata: - name: galaxy-service - namespace: {{ .Values.namespace }} - labels: - app: galaxy -spec: - selector: - app: galaxy - ports: - - name: "80" - port: 80 - targetPort: 80 diff --git a/kubernetes/templates/mip-secret.yaml b/kubernetes/templates/mip-secret.yaml index 1129f2d7..89137917 100644 --- a/kubernetes/templates/mip-secret.yaml +++ b/kubernetes/templates/mip-secret.yaml @@ -5,10 +5,6 @@ metadata: namespace: {{ .Values.namespace }} type: Opaque data: - galaxy.API_KEY: ZDE0YTRjYzVlZWJmODA1ZWIyZmYyNjEzNzRlZDA4YTI= - galaxy.USER: YWRtaW4= - galaxy.PASSWORD: cGFzc3dvcmQ= - gateway-db.DB_ADMIN_USER: cG9zdGdyZXM= gateway-db.DB_ADMIN_PASSWORD: cGFzczEyMw== diff --git a/kubernetes/templates/portalbackend.yaml b/kubernetes/templates/portalbackend.yaml index e1da1950..4ea01a9e 100644 --- a/kubernetes/templates/portalbackend.yaml +++ b/kubernetes/templates/portalbackend.yaml @@ -187,23 +187,6 @@ spec: configMapKeyRef: name: mip-config key: engines.exareme2.URL - - name: GALAXY_URL - value: http://galaxy-service - - name: GALAXY_API_KEY - valueFrom: - secretKeyRef: - name: mip-secret - key: galaxy.API_KEY - - name: GALAXY_USERNAME - valueFrom: - secretKeyRef: - name: mip-secret - key: galaxy.USER - - name: GALAXY_PASSWORD - valueFrom: - secretKeyRef: - name: mip-secret - key: galaxy.PASSWORD - name: KEYCLOAK_AUTH_URL valueFrom: configMapKeyRef: From ce10dd70b980b626c486996b350c4ab8a9304cd4 Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Thu, 20 Jul 2023 06:32:37 -0700 Subject: [PATCH 3/8] MIP no longer deployed through docker-compose in production. --- Federation/README.md | 4 - Federation/config/.gitignore | 1 - Federation/config/disabledAlgorithms.json | 1 - .../doc/MIP_Federated_Configuration.png | Bin 39327 -> 0 bytes .../doc/MIP_Federated_Configuration.vsdx | Bin 37075 -> 0 bytes Federation/doc/MIP_Federated_Deployment.png | Bin 31557 -> 0 bytes Federation/doc/MIP_Federated_Deployment.vsdx | Bin 36439 -> 0 bytes .../doc/MIP_Federated_Deployment_II.png | Bin 31601 -> 0 bytes .../doc/MIP_Federated_Deployment_II.vsdx | Bin 36848 -> 0 bytes Federation/doc/MonitoringMIPFederation.md | 50 ----- Federation/doc/OperatingMIPFederation.md | 186 ------------------ .../doc/PreparingKeycloakRealmClient.md | 84 -------- Federation/doc/PreparingMaster.md | 50 ----- Federation/doc/PreparingPusher.md | 61 ------ Federation/doc/PreparingUI.md | 83 -------- Federation/doc/PreparingWorkers.md | 62 ------ Federation/doc/Readme.md | 90 --------- Federation/doc/SynchronizingKeycloakRoles.md | 55 ------ Federation/doc/UpgradingMIPFederation.md | 159 --------------- Federation/docker-compose.yml | 94 --------- 20 files changed, 980 deletions(-) delete mode 100644 Federation/README.md delete mode 100644 Federation/config/.gitignore delete mode 100644 Federation/config/disabledAlgorithms.json delete mode 100644 Federation/doc/MIP_Federated_Configuration.png delete mode 100644 Federation/doc/MIP_Federated_Configuration.vsdx delete mode 100644 Federation/doc/MIP_Federated_Deployment.png delete mode 100644 Federation/doc/MIP_Federated_Deployment.vsdx delete mode 100644 Federation/doc/MIP_Federated_Deployment_II.png delete mode 100644 Federation/doc/MIP_Federated_Deployment_II.vsdx delete mode 100644 Federation/doc/MonitoringMIPFederation.md delete mode 100644 Federation/doc/OperatingMIPFederation.md delete mode 100644 Federation/doc/PreparingKeycloakRealmClient.md delete mode 100644 Federation/doc/PreparingMaster.md delete mode 100644 Federation/doc/PreparingPusher.md delete mode 100644 Federation/doc/PreparingUI.md delete mode 100644 Federation/doc/PreparingWorkers.md delete mode 100644 Federation/doc/Readme.md delete mode 100644 Federation/doc/SynchronizingKeycloakRoles.md delete mode 100644 Federation/doc/UpgradingMIPFederation.md delete mode 100644 Federation/docker-compose.yml diff --git a/Federation/README.md b/Federation/README.md deleted file mode 100644 index 53ce300f..00000000 --- a/Federation/README.md +++ /dev/null @@ -1,4 +0,0 @@ -[MIP Deployment](../README.md#FederatedDeployment) -> `Federated MIP Deployment` - -# Federated MIP Deployment -[Here](doc/Readme.md), you can find details about deploying and operating the *federated* MIP. diff --git a/Federation/config/.gitignore b/Federation/config/.gitignore deleted file mode 100644 index 289ccc68..00000000 --- a/Federation/config/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pathologies.json diff --git a/Federation/config/disabledAlgorithms.json b/Federation/config/disabledAlgorithms.json deleted file mode 100644 index 28ce8657..00000000 --- a/Federation/config/disabledAlgorithms.json +++ /dev/null @@ -1 +0,0 @@ -["THREE_C"] diff --git a/Federation/doc/MIP_Federated_Configuration.png b/Federation/doc/MIP_Federated_Configuration.png deleted file mode 100644 index 7b16cc31ad29171d4a20fa9839395087e0b95953..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39327 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 Y-Oj$X8$Y@*AG(>ap!mz|=W4G12PuG96#xJL diff --git a/Federation/doc/MIP_Federated_Configuration.vsdx b/Federation/doc/MIP_Federated_Configuration.vsdx deleted file mode 100644 index ca950e5a9d2b8e34e8274123ca7577db3868bc06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37075 zcmeFYQ{Q*|u%lwr$(4nq}LzZQC~P{MNpAwSC)O`xl%u9&$WnzGSqF zp1ntpl#>JoK>+{*fB*mhAOP@QAms@J1OP|?0{}n0Q$TB|9<}mBhZ*UCNn^fBK#uuDR@XDv6;dT!F&~<52>95UwfIlO!&*F zV`1kZD@34T-XPx!iR8KKWPREbGiIfHI%T($sh#+%K$Ky26QazN#67qB@P+un-@MPF zFrOIv)`+XU6GMg-%`3#>N|FJ-k4NtO4F}~RxGvAWh(V^#FV;qc<^azM%xU&wOWwu_ z0QVk&d(`X|1XXj9z*Fbo!Wu~qb?jHm17SI_g76ndEYV}>F#i?2(B!&?1Dc()Q`r!I zeW$c?UVZw($^1K2)HgIi%;leVNHb^K)@&K5QPRm^)e?G6Z3!(j)DSup+D^7BXht}_aVy& zZz49|`JHQVJcXoe){%ZhlVp^RtJle zu7&b2tvmFL^3vAUtkzz= zJjLCIu3r~cCz@$ZB$>-<5!Uo+o9x7~F_<3+6hxri4;l|i9Y5PdpE(Gv>~MT6RVgMk zhFae7H*8}kHG*@XdrRI2WKwPij5Onj;^Zv`O<-hoz7z*hGY3glv(Di`RV34k-t2tM zm+D2Gj|HkL<1%tvN68#gZub=FKDtBL;Uw=Gj)x4vmJSl6YmiDl2Q26|Uijdts#5M> zp#CSTFs_2@!chPKN&)}?kp8Z4F?Te#r8BZMbhiF`FaC2OUTR+{9SSCB-w6&2tUyU=gnraPbfOeVr+jn7Z~90rZ%bvdtkj7KeUfL` z$~wiDi7A>%%(q4^vu*?X<@poU-Lmj|kmbyqzvJ`{*nHp4!v{av?SuP2-Fh9&Fh2s6 zbHTF+sJ=YE8tuG#F~D{?%Wz-f;y%hgN1G!3_K^?{6O8Y?`b7@lPD~JZOMo!O!*s{~ zm^UzE=*t7w^}#o}(=7X({)dd!J}DyI!t%b^x!#s%^i0y02BzI3?@U!EA)o*O>^)p0 z%iA5|1SO6eppIc^tYg;*0~C>7@h5hG$;U>^i^tjpE+cy5p<&?|{tNP(CBGO+0-qZ; zeH0JQp_E`irN-HAl>Z&ef!-o^0~ijdy~c>02S+NTl6nM-A-!=obZ;IsT2V`*%Exk4 zU>z#}cE8Pu>ClP+Cw4eSK}^xUoB$7=Tk{?AlC`s+*Gj+GaLH@(vSGBh5@ zY5^;F=*X8&#nLtFcBAH(P0t29+c|R#_Uz9e_-0zUEK8Rn10WA*(%Yg%2c7f)W<`{MW;Zx~ zu6pC7@?2^)fu>`+=H|nA72m|4p~mHAo>eXvdERQDk7Z0@DFdU7?RN^bgtah9n)cY% z0$MJY1_yRLVF#xdi$9^Ce;wT}{&Dt9MIBQyr1|kC*oYW=+E> zo7^?YwIZtf`23>KwTbnmzhlIb&=!s_X7mhxuj4c+!gw{{M;=>NiWM~ruWzJm) z_0;(>c8C_AAm6}@Rd&mqNfICY z+h0u4^<}jyoHkD$SI_btTjx&joWn|jIz+r+x&lPh7)!InB4lw)V$1kcx*K93gvKW1DTyq8bYRV4WSb&oC;O}*9aE|AX*|&< z-GV}wvaF~kT(4tX8xL&jY!*RBj?ZVfx>iu$`{#68eRfZ~k}~^dBve%i=Ws5N7Up`F zq|Y57QRvVJ1yH}E`VsJVdG7ngSQ5!AjT{iChmdu@RVsmRdENF!>7I|i2S{~AZ*G2` zrlC)E^Bfm%NuLEbW9$a?v6N7f6F;a8K)!2p0=L`FS{n-q*PQ!R^a%$)w==!#!dpG} zV4un8ULBAlgTwR-N1c$QaynLh-4NE@SV;d!-v-MuviOUdvPhM*%blw0;HcT!i&ARx zx$6=W%`TfahO-mY6iWWc8)cH1)be5XO??V$g}?fnA2@?35@^&4LLy5AIm))=yqc%( zmgz(l6%l$p=t~SGPmIg%zgeQpBy%N2NW0_hgQ?JJCj{4-`H#nTE=~;3Z5{X#jqeUe zx$9BYEgML~1c2cMx{E_hpgsSMu;)rK%H=86+vb?P_zfCW!> zs!Fsy<_LR{sJx_CE3BVvNaOD#+Keb3A-hI5-JZ}mDSvY-O|}7*H%7i=vOYChG!2b! z-sMu2)nB3j5mq{k*UOYsUm!`KIU~+=ElHT?=BMbDEnxJ6Sv)=;ib&jOOdqBN5ssDQ ziU+?T2y&5QyD)^i)xdC2-6NZnNT?cJ{jzN0F0!bnf3z#O#2M8NCD|){w|oo1YUzRv`J3^X4VGJN>!UxSOo3>XC&!Hi6yKp zYa#S!hD$}wEGd99v#`q0Vbz~7K@K&?fk}w8>(VzF-u-=lqPC|_-AOmuPlY=78$-FA za3XpzV?(QzSuWPP`q!|~l2Rz!Dh%via$%5OBk;tI@Cv;r!`8YbE*`Fv$JN0=p-zS8 zCO20}NU@)3XZfBfAlnkg@a&{xvv-*AuP1o&8=OjYRe$hZJigY<+H0H_jxU`$&!S4` z#$)YZmFPj=rd8&5kiCpI6*di!rmq!dlsf9y2`E!kH*H~NANn8k%M?3VBUyM9rMVlz zOvL*Sg2MlQBnVZNqpTzI2tJ-`7mZCDKQF&uOwHt+Z<=LcZ2A~2ZZ zj{Esl-}QdD!HU3%<{#(0)}flf1J)Sf8D*=pI_Hx*))7E3Y}*TPP!#=AJQS62!U0w^>Y zF;L`L2SEKlc?C_WW^MX2s=x|n$pn!&-Vm2Q+X6rC&|kTlr=3By>G5+K5EHBGAU0O# z{EgzFm~Hr%vI3R%hM*%ZQ3vi+Xc!wo$Ls+MpniCv0WtlnglJAap&qm?{B29c9!ZHf z1q9jX7Eo)Y1A&!b57MB53Ghx|fE>I#3S5TSWe;2c$D8J zA29C&XH$#>Rzs~PApYDDj3JOs@81SoE->z_lZ-Ba4Qg@_YIIjy&yyUz6Fl+fu1~~0 zR#OEJzX^7?#qLHhu?N{V=ZO4r)&Nd;nZqu;XhqKT#&8rP8%F`U4~3d)1srk<>w22c z$JBcu^O<$XMWO(}IhudX{iPJZrJJ3Q06a#5oE_Kwn|xB0Md~aXqyNE%8=gTy235`VHN}=Cu>o==-xWCo@(C83LOp5vOC+v>m%#Dcp90CG%qUP? zNGB-2PD(%p);%UgOg+OxSBbvF?_(`msoMI5vOos3K$Yl1bA21)L7d1=E>@W7`w4N6 zR-2STs``uiwGllgGFAZ=h6kffBvj|4lmf*y;~_0a#-{m?O}RxdyPOBD9t)%RLgnZR zR#WRYa36Uj0WcQ1{3HIP?nZynJGspyxfpxnk{!(DNk96koB<(3I z;rm?ySn4r3qZ0KuCO-|t3k*Lb$va;O1;D2F^oS5>|KYkcP_Qzc62b+sLr8ngBZD?r z2_5HrukkCobUG$35BJ4Ay%#ZRz-8O)jv60PZFaD2HS!r0BhRt z*wW2=J=^joC<9Ju?f}kNm_lp*CIlfwQ-r00QvgyU>uIPVt`jKAkDGligkgo+;bW=t ztB3S_XPb60tbxUFYy_#eE0nPa%lDrJpx1__x!^Vi$MiWY;F)V+3})nSOR$Tk-DW7K z=eDOS6(^!A&Adq`rcucDIfqE5nKIm)HPbn5nv`!LKYi2NzIg`*lYr;2^14F+Up?() z9x}>@>9PCcR+2)JW9XWf9P9r zzlxt!Ej%N6gmsNmH6-}o5_olQr)1BHK$GWYDNhO|v*24pfZLK6d-ASFm~|E?<|e+u zvl91rP5sGUgQ}Ij&tg$RC{5$~w0}Q@vfnkaBzihaB)gxLQf3aA2}OotC^^e zX8G;!GX(0>(KEKr3!8xH>zbMH6-Q3N1a$3A?sg9eo0e#(8AL+Qz+|_*PUfvb&%(gn zw{_kNn}L~Vhndh9N6x|YOQh=4JCxDWNidRs_cH+}T}q%+zzVYhQVJ*mlG7?+;a?LL zOJ>|hi=bCKRZW2cgD-XJodId6-eBM#PAntPbu6g0yNxvn-eCDVbX;t!Bc>cyYcop% zl;0Tu(^=?AqBCy67_-JaKxe?nF%+RQQ~iGICj~a5wLwB*hr(W-B6a#G7M2^8>Q|vF z36QFZC=d=IK$NXv6fPkU92Ap@C!WY5KLVGj>S}gZjTUDyDkmqwWeDSHk|V=oz;Hz( z1a3}5#EiSVr4gMnaWEKys7;SxzHE7HFSzdtN$y6EDA z${n#)ht6o|eyCc9;-cTK%V3#ge=FjOz9-_Mo-^&aKFvi1hdmQJCYZt}FgJ);L(v=)E)4;o_)r5j+ zqa%ru0o*}U5w7#Y8B4A2xy8os#lYA(&eBP^snxr>55Ab{Nb>fQuakp~({f#ZK*uuIlm4+D8r5!^`E@O7ffM^LM0fdWB649|E!D&ZwZlH3}SfQP%LC zgH7CG_x+$ygwKl9QMNm&_h?*t)N5&lm;3F>jOneiApGxc zS+NkD@6VMVYitVoosajVXMA66A13U0pQ%Kf?qhLuz*O{1GHslJ&dLW`5C1|l;E>=m zbM#n)>2^20HwA`R!V|4ab~PV>B;#?!xDVn+r~EY>oO)#wyO_&9r1@kPwSY6~9$sd3Z(0o-B!1%LZwoS%#zFQ+L{-W=D8-F_i|3SE4+WzkJC!t_(GsSh%#*+a%|GJf79a>MnjgnQMRF8)8E3s3(BgGof^# zN08T!MtTmO{?P&gZv|)-PGA%XEQiwx10#+$OL^o2~_; z(;d07Ky#7$!uwHHNT?>K+8~6xudR&foVg7V?oRI;otc~oXH7ywkUcL;q8S6ozdQ5` zum)zI5vmpATsgNpaI>JOLQ1ONXNRi>XZH_cMwF9xI;UPP$aNM@3Zameb;&fisfTs6 z_3wE-hLCTP-Z>=03X|EN2;dmk#nGhII-E$^5#=Jq$62dN@x@#S_WBs1mySdhcI*PC z01iL}x(hKoM3&eb(}-+vMQ@WX$x`i1IULE#-^MZ%UHp_;WR#qrmlo#TJ<4$mH&x>= ztHO9zQgp??)5&ABndjR+c{baW<8CZA#qeNc&bkicZp=1W7V@5+$)LZt*o62;4d-)? zu=NLI_P$_(;mvu)@fPqKT`V2ZV0pCYQ6q1IAa+bN6ENhl!u8k6cncL)EG`vK^~5+?>gFSEmC&pbK6UW*q08s6q{?0-e&Df7o!NR$mH~9?K#9ULpBzZ*bQ@ZE2*w zvOL(l{c#gtpS7Byxs0JZQ375}eZkG02X6f9c)2KySqGi2;u<<=!)3qFy24qn=wFX<#1}T&=qo_kdKkwApNYL^y6aEuaHz1<<%(2gN3sSjUta^}6o$ zcL5DP14UI_8G?~<{v*geki`NqT^=axeF1s7u?s5E35{%G>Kz|5s^pDxHS@%oQ%B5t zUG>v%GEP0t<5tsjvbfZo?XQZn35w3*d`-o;+}PU2)@5jYcQ}3dFo#Wj$lkUPu?K9| z$iVTXqfj>@DIVmt>a*Y3m~7E?70QLb>n_=--4fO>nA7LAk4-MJes0$#e5T7Z`?@qb z&sZP}viw^H(V_+sBi)psL(Pu58w>JjqI3tGV`dW+25Mcrd<*RHkXbDn1x4`ut5LIG zkda$PT&D?J_8Bfs@*B1YvqC+r%O0Nf-|fQkH8K&+j3wO#H@2Am{4pM$!buuRyL~U6 z#vZS&vlOjw8l$ilx909$>+u!zF$eY?p!^^T+HAk)rW{rL;1+#G(e*f?P97%8sV6ez})&t%KF-32h z^L*<#IyBcIBi5OdEQ0hc!LLeBq03~*oH|T*5j@L*lz@YL z-I2=Fj4>H6(H|cb)mZ*j-pDGT(ZoaK%QKB=TqGYao~Qs;L~U7$M7@MTunjj7mN`D- z3m&FfLuliIQR9t}OB7QCyhalcB7N7hC#nD*2>8d~o$*s_ylLBUm*?!UDt>*gOKE1^Z(RFQeO#7`8{39(qK_8G+IiLHe9Fnmr^Gwhl^p&Myl{4#Soa9q zdEsJUNxY+K%=Py*G9>^u9dJ-_JavKJYHC2O8d~AIMs;SE*eVUDL}VrpMPe#h)Ul7m zS^bSflxiXx`xuRRJQn5A1;6+Qxq2v4b;@VisxDv)C(j2_oaFK2y!c0?GttXj45o9{ z)LENg7sy4o+=EYX=s-uB>9(HUZ&-Z0(bx#uP!`uRAgckSZjoH9py z$+Mj}3x=zbBD05wF5}}b?eZ?Igt_GP2@=ent54nz;ttn^ULx9hgZZo>$rKVzbJWlc zc5)d7S_lP!mqsLAoFes384mMZ#-HHytk%857L}sfcE(n$HFD>u+Zd z&vY8t`3=|6?rxsY-#*T(BuNqa5o*+Zr+Dw~Kht4A+vS00pJ`&W+WL9}cfQ*V zbCE(iO5RgTU==Ehu?^az;32dO5EF^L8N?Xwnk*9Ne#nEo4cP#T0yP3@CowIpiyI}^?&_Ym!JRusQ=FV?DS2I9qIlx{*(1( zDfKz5(<5}bmi+8y%G4QS8|oP_KuT$vh@I*skcy5})|E6LjspqwbW^Q?FiJU)lO1}0 zJYQ`jSKDrn_pkd`rtc!!TT4*ymZ>pzhmWcls{$KrMfLc!eWzRHH6u)2gdIvK6OEKD z;EWeFOQuN3M@p@gk>kc2Q-hk0#4X&FUa%YYvza@H-V7T-T&-~%ql_g?T@BY;9h_ zSW{4RqAX&<3F3~ZezfzogLrw_LQ+h>wD0?7js6N=$`2^n7419;OE$`c6}!4avb#JJ zLJ`<+O^MsWtyDJTHdh6=c3F=~++)mXP%SaYQA$m2f_9Am))7$Nm1zdpdfPJiePh8z z_-CVB&%A(VfM+`A7OAT~plrZcn!EIYHrCQe5Xfg4Nz@+YMTDw_Uc zIe9I{4AObUx&``o;!Glx!DZ1}|3j;hmiepzL#OodeTG1eNjSk$i@K~)4o>f+z-1Mt z4tsh$*j?knB%)Thi0qn~ z8l>w1=XbpS(U(DBaA~^tcjg}>BJRHr`G0wy{<}NlU;d|}_&$pPdYF)#z^=eKZ~6H? zj6xaPKpn{!fMFNpg;jav-M@aX{}}+_ zh|18{|6;Q87aQV#WAp!&e*cBhe=7k01ySSt)ISjQ-hJrZ=sD>o6T$=gs|^b9$x_q0C>V|b6YF$M@U1T1Q6Fx= zoEl0vp0MuAcKkSY5=VX4x7TpkbtH@nM=BD*#Tv^_f&VO`Bxh8>DQUwPEj_iPqq+2l#$6Aio&3_#pqA3N_ut;DDmbl86i~&Dbg&b$SB69 zF<_V}3=gNcll3TLhsLZf2iTH6nz={sE&X)T4YE*@?9chMcG44nE_bcPldhZUC`SLB z07bc6VI6UWQm5_LQohP)577B9c4;Zk;{C^6i;t1ytv~|+n2G-HV!-czi2+*Hj_YDb z-#*GOcu7`EfN&69Jw^vs`n@d9U>^uBm$Ir1QT&z!_6c_T8|yzl+>8kr{&Kw`9Uyqg z5>USvwiXs@3Qh0NT5J_lJq7qrU$^bW4}+0HJM(!RI`8bwe{nBHA-!yz^n4#FuD}tY zFnQAn=%6^zPeI2@CX#1(SFQ}~fk)*OJm>wLoK4NDAKWA@_ zmWKc;Tl)Bx$XsVi9`$p?QFV}@u@5%Gh~bR(`k)@(VaH`8JVVPBFS%KE&>m?E{756w zvO5eXnjP$+f72&x?V>6okvR7e=g(ySN)5vh1*)ls&R;mwi1?$} z_BXNbtcxtIrCw89Ntd^Vnus;jxjf*3%Jwa+<{i&xdPVCp@pj@kQb%@^XIYJfY_`{` zUk@D4+u@-x1c&fqJ?tx|v9#mdy`ikk;)~jnv_BIJbLQ;2&-3Pdoi~S_o(P2OKUOJI zIFjsam!fajO2~Qh^9*oHTX*kpbm0*jRYvAd2{(@BmrLbI(&`L5$_B|$Hym9;jT&bK zdG&7srFJ`M^qR-q)Ons5#3_Hh>5x#aTpk=p#GPza+R61~W2nMHws*B;c(;)LtT{A| z-%6hN*r~gz`39J9k2>gsZDr;h5un8qc|%))q?yTqz0`{`U^Oyr&c#=0y51_efWb3V zzcZk#S?6xQ>2fg9oQ~(jeq}Tj1KVNS?d2kpu5=5g$8l;G{3l!GJ{|E!vc;h=Hi=`h{ zu z3K|vPf=fJWec(_f$0@yX5$L6rt8PZo;!YT4pLw|S%{IQ`_+$=V!S9?&+*FYH><%Z1 zw_fJem#?$yt5`X1yD4XD+iyMiLigI(q=fwLY|@RN3{J*PlUn&NQCX@uL(zx5Z3ZW) zZ{zTz6QxqzOW91m51O)sbra~|f!_2zEXNl_#@})&W7u{dzaqNd`wVAt_6K5cQ;&Yz z$H?u-q~9&(QUthEV-+GY3GH2@uC?tWt%KKySo+-LL&!klxz>nEWD*U+>%>RTE#7h1 zO=9sSH-g`gK4#PRp8&~*Y1(nPP0b%~__hW(264M_lFYop;~3lBzFWO7oNYQ5m{LLA zmTgWW%a8j-vg3409DI(cFmkR~By6jbg4e9t6s8yLXH%h=^<;DHbhd8?76Q**N!>y# z_2>)50;Z?)dUR|HEAsY^TyWB^8Ma4}vamIOiaWbO){yHRbk#k<)D*qgL8UV68o=2K zRGqlcTY${m?HV|h$-N5JO3ITu*1neIF46!$$ zybbB#ZHoiu?XWHMSJ$H=72V_s2$PqCgm7&%=HNYra( z7QdV456uL&3jit9mQ{Q+KfdG?r8G{SiJyW&`up3$Q%dc(M!5=@mh4Ntv$wrn2)W|>L`#*XdIC`=_vyir?NjKDjS|>XI9pC2*nmOPc}B;8Zy<9Oc?14iSgv5+d|IMw)dQWpTa*9E?w%94_scNEg6NCu>M zGh{=6h4$9#6u^;H>m4TA@f+F8y#tKe_U$k!0MC9;Itc;P$*LutsLv4`I~S1ZNcm19 zyW;YSBUsSyYG=OLe2;Y=?_H~eI$U%fU-lCuO zm|2hrt8O#U1MUJuJ6RA%YzF2D1dk#PJFv;+Rzh_2NiYucjmra{VF$Az_Xe*Mgm44;i?&MPkFWl)W@Wgg20Jqk$wd zPe#@~6j(H21p84~zModB>G1ON&T*a-zJq-?Y09gNs88?Kol>Vc&PppE-SLqUuM5%^r5ih}LgZq|%E>}5uY|JXM zG+|Q2DON_Nt;<7~3qH?V2-M)A7|M%7PqVNni|PlRd#^x`+=mGb7d``Tp3)fpYt(`L zo!B(c3oiP-B7NEyo!vC>oh!a~J^U`U?y|=t1966xddpsxMCX*EWh!}I*Pm{=X_eT^ zpKc=XDb7$s&T!aZWy8Sin*Ep(CWmumP=alO8P?t!Jg~&tx_Mzk-wzo|I~stWQC_g8 zrI=={w53Em#FYQaq7?n4S#lAFBFI22%<5lCYH`La+N>+qL|v9pWDtRcQ!qv?Trj0} zRi8%@VZ!&2pYgg*h&`T|mSU!hCX5M@kx(ts|E?pnxaWhXmCxv3MLA(dUY{|hKq0Ih zm}M$p8;FggGHx}ExTo`nm@1)Eyrc{=T`F@lbqr%|TZo=X5wK2bYSHgtpMyftg+_aP z({o&30dxzdq%Ta<*(+s2^TF@&^p>YNc6c^n_8Yt9JRwgdQClL=S9z1td)M96pW6W- zb?4ctbximHXHzi*C3WRSudgMuP8q3o*TvD3Nn{VKO11s8%fikZ+dUvB4jLUgJuuf< zgGf;Jll=YPxftAzDidb2d7tbONm6vJfV1?D$p2mSOLKp(57Cf zJQfTQKjXq0Agk4YFpZ7EiDa)_s8l_XiaX8Ki+bOcwXhPXbm(k#|2%9$MMO&DdW}tK z@0EHLODT5!`?Su{FKyGRRGKXow)8Qp{aCXuJ1MB>QUK|C;{Lq?AZ~~A$-B8p87Ws! z9=ayY(qSiYRH;$}J7Xz}XzsPE=1Lu_7UA`cUb8jerU7~l@?2wbWM;v*{PDuNdb31z zSmKf0Opre7x6VnkJ>JNCJmnQ9F;e0Q6q=KvIu}cs8v3=TcFi;^2LQ33;2&d?C;sQ2 ziMP-HTn)TJj@F+4Z5rA`{qNNP!#~wPl!~_FItPOH&Eyw+_f=pX$v2Qd0_0h%9uOm+ z!sSNq0`3Sz<#01sy`Z4)*HUP-v0dvEa8&VDgJ_4TYWLSWTJShr0?J-1Jd83pRFrq8 z$XzqMPQ%m?s$BQ>*VCTMaWN(_Fpeqxv_f$EkU1WRddjAm#yL3sy#liUPanm$QF$y8 ztS!rE=sKLd-P9tiVk8#|@SdljOs74NVMIqC@qNA0kWr=}Ox+LGTrG-F&msg&SJb>T za8eP@6{$2A-ZQ^|9@H3IK!+yTEmGR?&$!VlxON^FLZfgUdcCTLoNKNenGIwTMMQq$ zm0Ey>Q;*ezCr=I!6Q`Ce_$A)>!Q;yFN!29kup5<>V zrfz;L$G4%Fm5CA)7Jn5$wJ(C+2*Ao1z?@kQXa|skaeAi)Z}SX~Kc+6kP}GmXdGe6F zA!gV{n_vBMGw(G)Kjr(pL*E3T?dx639omIL@epc0GsAiWVS1c#62 zzu_TQLRCm6$%Vqn6Rnu*OJckY$VRhTG<#9LLKr(~1tdXH#eM$ZkGK$~mHm=fo~z-^ zwp7Qjbild#sf5BzK0_yrGibL}SEtFL8*!m=)sMxhtbl}5OoseI{1eusO2XdHUZdQl zGF@1vd?wea(9!t3bds=;sXh^6Nw@W9Y!;Vw6M(^GO6BDN|2FJ=mJmlV38bpRS=XNI0SEB+54koeG9i98H4t;hozJik3Z|U;M2;&!6Z7&TDr(UcpvU zxz}7=Kb1Ra2Ndi45$AIoVcYOlJQHzV+nnNI^^=~{C!#}o=T`*jeW#d6*Ha4ze7Ve^ z*dOjKvyvwMzIC8&mZui>++ovI#4lT52^O72iw~_y@?QL@y~KDtP!kk|rE@q7J6vCnJaiX!@b(~#_2A=1W7iK14~~=9w!g|1)JdaT zL;7(z0l%FkqN_)A6k5K&P~YeT_pK+SNx@p+P29k>so5}*{tKD?*Jhyn$J%87d&y9MsHtUhsKzj0?V#s)m1vPn*p!&?^f%EKy#>D;>4=2n-B45 zJ|N5)Qg;pB$)t|r!iVe&*F~R)g&Ns|@e?l=Jk-QMO6vp*l3bX`j=96Vy!X!5otg6F z-0kltI$K(>2Vovd^h9AE^+~E#mUKKNJoLyj_^M^E*EC+(8e4OjB!_2zq2=_r@HB=IwWQXyk9^w6lm zjwgM;y+45=>(=DJg%!Kf^YoyLUa_;?ZGLiKe{`Ss?!(kproyAfiCLVs7a%Ew|^>qJW&^n*R z6s(^#_C%oLbM%^bzPL&xjR{22lB(unOZT+C>C5JhaZ+5Lzj|bWdZhkph=2{gq|@TN+H?ME#R_OL;;u8i^!n+Y_3PN0 zdl9)`KzBYGkL+^)vVQqy`_Y%@;!)P}Yh*J-vr8T;m-(oq_ixrH$}1*YYFeD_G9x4N zp`$fGRZl?;2S&*1BVjLzkeL%S$%c{8++Xg5zblLZt#a3Jus)~$bG)TI6)ZUL?Wt)y zJIqsclN=eC)J~a0*34iHo+$aUeXvE>HL{QXk`bcg{&J_w^3tLUF#X4v`_-=z?&a~` zo=2;Q=-O&K6Q<;=?~*{bM8CD+1GB08!G7~sY)EcOyGHHJwkw@vkM1&IOV;s$c~4qQ zh|OK-+-L4hjg3v_z{nrlZbcz6WC>gJ1-b_1?;4+q#gL~6n)+mD-5{zf0~Q*SYBd(B zWI>eQE%rL<%VQlSsGGo2Ns6kwCZ&B1L+B{875nHPgP6_Sza~%IjxTmY`(E3aNH0$Y zI|%15bb}+hb5Ug%=DGF`9{NVSF8#pjfywYX?r+e7Hav!`?p{78mLHOJ3n?8~>aaT@-zT##?UW&8KBu#5E^2of#Z#FUKIv)BC_bLx^P7=Q^bV}7c>8HtD1J^uMp_?ON zPm`q9YPe~0?0;p9+%V)Me)yVoEHjW6?skgU>VlJgLd$BbC%{NyHPuGTZMw`(AbvkN zMA+Xjl}+@-#)R*78a{8}PQCnn0D5h@8TTb6?z5O2LZAPAP_TQ5rH{tul)7Kr>;CPx z>gb-tmEJ{3WqxLy7#Veyp=~!>@BDM)WZ+u3Rg+Jm)p?9vUyl>Jzv8&#!HPOkUhA_Vxw7t_xfp zC(Evdffwx!RBp#I*6y!n9otc`@Jss(XMMV}`Od8}SRKLK;--8#h(_+Ttk)DY)NlNs z>U;Cwcx^J~Zy8%U%kilSW(v!ZHa{{yvJ%yw^ z4Qnj79cjcN>|f}ksgs_tfQm5s?#S+MW{4-}iccVYhnmc95zKl{J_sxN9NgO{t{8e~ zynC6lzc9@29TL+;*XPh~tzyTd>4=qy4Ngk5Ng^shreMkt|2fSDNueaOVvVNht3;j4 zQjB)b!WvLMEpyv^3v8g9z(5rcFDWWH-O87-)b2mfZXrhuj{WIeTLJwmGsZ@rC$8<< z1ceuIV)`MFtzIen*oxxHl{ss5baJ%ube=Enb9~;zG-CbL&Qj$p?2(Uy7w_+*k;ivOo;WaN^|W=^h-i3}X6zA0+hfiR3?O+jCTi$KF}2nx5JMN^yrB+a6p|pKKw2mTm0hji%Y(GAsv{(% zlao#GM=&X_Uk*tYLJsa1~;gIu@=SO)aHuD zd?`>!$Wh95I5F{7##O*nmnb0$GUT@{nX)>>^U(&AVxrmwB=KDry&`Unr1Y^1L1e2_ z&ACzDk0v8x@WTvZ>K_m`#=Nj^lWRpnR;Me}Dzpw0i9U)??_EYQu5y%LkvP1VvC5?b zjN@YjZ(&6$5*E}x@yW-<>q0Qc><-?`@xv?8r-|Daqh9*^L9A5z2?&K`O~C*>jC%ee zQ^0g5t%acIRWF(zA6T7+%*S{jJ{UY2wb`Wv#E~EjP#WGHrjCR;x2GZnHKm7+A2c%G zoYdhoC0+KDs!@m$ZrJ3;j>&u#oH>o?VeiE*8oW9UDalw)rBEk1 z=U%-LDCTy6lcH%KtPf*c9ne(s{L;lOrB^zvmbT@r@Gcw>mVj%V=p@9PlKHPHCT4vo z3!jZokTl!^X++C~C3=SSkuse*3+WKkT7(2dKx|f@B*@d}u7q&}Rl4NC>XFDN+tYuc zL`a1=a*JW1vZW@x2d2o6iD{`*kUv4 zG6cme=pw_O4u6h;5X=+85cA(>yyxv4j8M0t5`tY&YPqH}k|fXYpANtzVJx;`>hWGZtW4CRMu3Ns*e zZyhJa>MR|ZPcl9=*;#66d*ZZn3aBJ(AaG+!u8ELPZ@-FeHgc3v3inKP^K3jx7bkuC z6&-P#&p{MoHxRebHr;y88o_oTAXh#@y|%wITdv%%MjL;O+EoN=Qw#pwtW zq<$4eXMLd%!y3AMdX9Tz&45+(m<}+yBr?kJg3rY2q)72^Ykzz>M=`Qbc@C$gkti*l9WBx%Ccn z9foLDkPA*SJKP&gm*)?cOA-ur*J}aa)>K4P!5iw(FhrzOk{_ZI1G52@VwbTgXnq}} zoJ>yc_2m9H3A~aAvS9-hNAmJ(7?0SBC*-^C?9%%`ZY;wM$bc{Y2nQIj|8rfy_)lFh zp`sBz&kpY;!v5XcPot^{$4RU1omv%IKi*+fchtp=%5|I+#MIGDT#O8u++Ono;YQ7V56uwb|U- zqyeVyHi`^Civ&tm1tprnl@&h(w6v&4SAt+konSH7ZH)Fv;i9fXbaVhn%Ik>H1OkRv;Nw-3C_O z9M=yJ_LSvL<~#f@g}3y#hluW76h z{jhY$py`%PJvYkKQwl+RacLn3RO9R)@ca))q2m3PD|PMSz{cC-HUua%ot{1yXMM^4m3f017j!bF2;D2$l9?I zV;a1>3>+eVAmSLO!NPO*zB=!p<=nqXsXFtH+R?>z&k2v5lpLMa z*;61-RVxlWt!$z3PV!vT3Xr?bAe2HeIf=8fDNtxpzN0|^0AzkF zX8%)v0n@)M1*+@T>%#D!v@_l-1#suc)YmK+MZijNv_6F;2t*t*paRvzgv81Ca6Sc%5HG1k>miK54jwY7AdCwm4BWa=@^kQjBbolP1kly#4wHvj4nstcH^)6eh?E};l<8_ z6@$-$U70<)%dROV4T{*VeR;m_9VZkN9 zkf2@N|A=y%Tz)Y+Z&RWoLIdgA5~oQER8uoE8yFc*=amSO`3-M8)pc0I7G7?(9i0S7 z?gwhutKL8R`}&LU#vrI{0hea^Ab2}|;X6Iz)blV_z@q^$3Wf*Nzvr1-1-ehAGd+rA z>SU_5Gb^KGH?ud{^QxHsR-Ee9c}l(Y6{rHd)Ua7{&)*+Q-lgJr71;JP$-1j}oGmoApnxEPMa zyMYVpU$$;SzKfZa=P-}y&xh(&Qk5I!-Ey6VjC~Jra7Hi5v$4|bnb2pj!@Gc?M2}oR zDg88L1|s~0;5%9v1x1R9&K?dLWFu5?FYsu))Yi?Va$?cAwKo)aNgQ|rEDn|YThpy> zHrGxjkI!^svb6;>+vjPfn2wne&Bd^4mAe$<9%J}n%_`msq9+i)mn<(nX#*Io?NwDj zmBfxjrI$;nUQ-(A#uCQF@copDe&&bxvYUHyq%a!Zw`Je;G{w=LFEJ*N96EQd$7PI8 z;+pS?`{^wno3t4^(dQDrlBc7L&y=EWlF?%1(JgBUGR2B8#4YGj0IWIx+)M z(XLRBF7EvNEv~>UmJr4x6$TWbTI?r#$D}mje*7na>yh^`Q3Vy1$cBr1-koL5;5d4j z2FF0j0YStPxT!IBT(1E~EJ#MvW1+JFN5}qEx2s`Zr`XXOE!f-JACjhjtI$cV?5;QN zi0O;4_GD52ifpgvnrO*8oiOKBtq_Js4!I-uRPr6UThGXJP=swN-)*l?y~9-5*mQl7 z!N7s3@6DKUf+@M(K__xYU&OFct+nWfqqx3T=daAl-?dhhP(V9J`jlzC%knoj;bAq2 z_&qO9J+js`p-`fNdzKvR;aQ^%9#x*a{%0E@c4}fF8zBI|xGdm*^veF*6s4tMzb;(& zFO_qR9nL{|75*w&F@ahr&q~QqLnIFAbyP^AX5g2QIp3JYbv2L9^v)bGOi#ICIxZJ6 z2^~5Nrhk{$=I1yM&vuj(cj2l7E5`LB_W0Fh!T$K@qOC{ga(lL?2kNua*X#NA_tW_3 z4m(#$gvqH3^JcHm{;Br0F?0L7tFyD4Yy1{$(bDA&L%Jj>p2^bTfs^yy?fwnJ<|pI8 zLVML^OMA2bZVPGkC!2*PSF6Nu%JV*PJI+MW5^?)fF>8kA*7jag$^fi$y#r&4UhRDP z?xy0z)BS_BZR*wIRh>hcarWF+b$7-l%+;LP*LP;V0B7p?lhai9hF%8i>N*>4)O`JF zGC~wad?Oq4K3^|Kpk|E-YCadfA)u8=nTU{+>}^og7G+`5u* zCsIBkB@{L(~6CVm^U4no6>7^$5^ybZ&WN0-BtJK?L z=k}mZGs|Ew)=K2BCAc)A|C38sXI9Jp_nPudhh)`q6E>~#*MOrPwx{&^=)D}+7Nxaz(2;&<1pIh2G`%ikZ zzfMohg2tAw_0f{9dOwTwpZmqEY__8Ze;>u~%$FH+wDdY8=eUns>htO2NDt_&U~F{~ zhaHxs+tGD#vm+OC8&AV(&k5l?d&f#!&#OHZF1ti6Be{*=;eJe2lRkJk3{P8=e9z+I z!Cg5FgAT&+O`Z`WJa|+)r$uQ?aTgvVwNeGgs@(h5Lqm%g&iZYXtX^ukz)82S;G-R^MHSw%87Q~vkY9kKw zA+pPz1c#36kS8NK84l~AgZZB8uBE^k*!9(F()t+s1=tyI$Q_1KkeHff9k@qn$Ax1F{xi6th1F8v(-s-@V4 zBgq$QTA2|)B9lDTrCY4|jE7_t%cZAdib`^HVjD@s4&-C#J=}3O^d19;Jv_X8p_mj_ zSGxyOZ~mHCrNa73oU*QaKPrPIeV+L}h7kX5Q7T9w>FH*J!2r9EMy|q5H`0ZUy*Ka0J!5bmE$;AZ;Up@%mT@?yQVe!D^Fr zo`{1YmRVEb+yw&8qHvlOC6Sw;N~50O`d~=IN^g85A;K@j8XpP?!_=36HaZW@>J@on z{2TA<=xFV4ZG2Ej<2scO{E|&A=2uS1Y5=H-+JJy0(4H3WH@EK>E-f#+x1*2A-t>Ka zfL^{aa)?%O-WM|_z7PRY8*dJ7_8Dzy{M9~%Ci}e*C+{2j#V}jU&=VRsJ{WNEgY}Q?7;u5LxCj`IOrG7SNtv9t&i_hP7m&@t zWZ;Vtt~UaTND=A)Tt*GCJUoD|o60jwdK|zDB4VjyY3V5dk3$3KJ^Ihwc9H=yC(3#t zI&I_WXmP$%cM9JH_}QDpC>i1s>tW-6_acQtzwX(sbh$CGaVi(>hA1yX>nWQA50uBQ9+K3ND?E| z2PP$hK;o`Dog9K7iobtNf9`MVV0T~g^!D6ax_D+9%g{UDPV<20cT3kj7^D%XkSdk< zLa$xP*ce8pMuY3jT`0M|1G0XDsn)4y5XZTEUejh}XXAFd%<^~=#<9_45zXg!t$m~p z7``~6)l=~x3|hC_w|@Pj!$0v?KRf-bHaq6RMK zD#lJ#)koAzWU_g@iSPa0(4VG9_mE`5RrFZcRVAWH0&K2Tf~H1p@FI@=b-mJr@n%?u zr?}{83mK9fPy0Qj%f{fuDmhoh=c85sxrcyt|8+|~7>PypC-5g_cve{@A>*>Y|IV7n zi3C99jsohlk>1CP%-z;En+Pt$s;xg@B7h4(nP_|&Yvqnufty|*M&4nF@|jO>TB=J) z5~y90%;3V!BNR5w%a2)Z3Re-^(=BvjIgr=ALEmR1|6xvRCpvNEzyU^Bz!~KK?RF6Q zC_%bOdxUsXu>uRc{Hvd==93KG%}8%cIjzm=wH6CZI^w`1rN41OQiw-~@f`Q{wQIkX z59pnpkVpfwcQ5Pl;x$d*dhTky?!kM;LtN;$waQXCTt+Ial#{ZhcRZ|4(UJYogd>cZ zG}M)vtQ3R(sBG_2As+?O$1BywDT#@k_MTt;&5QaumHu8EuSLyEv&d!###8sfWGhe7 zt??b7BBlRHa+oTeci#(%onkg0P8)rs+eTZ zRLZRjHBsB=m>pOVEDwXGy?M1zCJt1F4FSUz|81gPZs&SWlZ@8NmSDo&EsqzpPQ_gd zcn#(KU(xs}%p{4A1(P_=au0CSvEw+i@^(dSnq60IFP7VsbEQgJv^Lcq)0-S287hm1 zua}AEN+KC;wFTj_?-RlYw5ln`;3%(U(D_@zIp?W}POgSBV9g!wf_*m-n&`oavsPU$;8;6F3liO zexs>ih!a$-?07jT;Nnj!&xTAt!EOrh-WC4#1@pc+M=Xwv6dO033^%b4*?x#qqk zzW(!7a5XJ&BB7Yk(o~Dta7-bMtKkZl;gEb1SKS?six6mL#Yn?EIhR)w<W>$4JUGEp^glMk_ zfQ0|b4BP~LLJ*Ap^$P-h!PGOdZPf9v|2iq@6B28*-WH0BK%gJ6>IC@*Y2*45TT7PU zkm7*6|87j0rPmW=0yS5nF&i^b+|~9wc{LNC1EIB|SXS_&oQ`d3$<12Mu%iP26n(5V zfr%S?L;W<^k`7y@@}a8-RCee4+MAM={qh>Gn_^0?q&>qAQxMQmmH>*3L`&P*)?>lj z^Xu!RrX|S-;II+-QN*Gu`gE}yd=VrsR>d=@{;Sul`|ME&g`~~kvxHgAq8;TpxU5OT zL6H2u&9_Oz7sW;Hpd~l*t)l5N91%8+KOZ?xB}|Ddvf<3Cd9TKz!3WSbM?kGh8JlV? zG!)OBOKvo;JWALvuZisvRyf|hAa=gGQ~OImg8u5zFww?f+unopW)EU>4AJw~F9(09 z(zTZ392rz|tB@)=%&^?d1Zl%nfilXbe$0a0at2lr}UVB1P2rik@rSUR<(`|-gS z<$xdt*3a)GY^ZY4oRNT zLo5MhIEwT)F?L3FiJ`B)%-?}y%4EzP^~7&oIY>kenA6X)qQ7R8H<#o!B+A}-W|;Gd zH?0npBbsnllh>3Zo)m`vX0wNuGaDeU*DpoYx~Rj~I|YUEI52DG$>j&BO5UMvoBMNG zO0Jg3{YgBEH_pVws;}F9w8p#uOuyr*_8A$+vt!KuBJVl4K^6xC$o3IvE#TTTtc!rU zzm_|tNUQ1>n6Hw_F_#Ks&LbmCF%4Ay z9i*$6Q%qlhlt3@|t*D9L)$#j4cJVSsPnLTcHv$arJ zv`^Y$&m#dz=YoK1Uh6R=%#H!e5mf<6{sm>MKHU_&PjOhFI#R}H$ z-54kv;_!LB?^eQR!sua$jVMSaS^w-W<4CF_sI!+<)3Ye!KUM|T=X#X8PewpQFycI{ z5auRc4}mmMwvr`iVS}~pcl|2vlEzMMcH|+4+A#y|FF#~40+B=-=zm+Y@aG*Q;b9;X3dJamv{NN9^0nLV^tt>LYx zg_xhf!OU8?FBHwm?1|3iQ<7^9l>5PJ12-TBMMrh|SGgqqSWk-!BUJdX`})s>$eLxg zb6ZS0dvhtAEp91Jbr>MWd2IXY{P^f^%bKz}GTM(S61|67O>+dobgqU?t?tafFFL%} zP%8b_s}ii(!zp>nn_Fz<^)5lL0*jZ@?|e9Y z%HcN!7fEBYOD|oOTeO?qqtg!!^wI&EPFH7#XMBw+YL0A z9PEsK$e~HA(@;Posfy8uJ0m>sl-r6o}hE5-r~dc;m6e^Do}`)d_tpesLBZmeNOV1 zqmzUB76Gy}nm~~(&celM2^v_BlMPYow~rljhr#l){+E-ek$}_FV2RwPLz0+nP3O=Q zavY3)$|qBCeJqGrg0~1U%1+#|DceQqxudJ-C}#y@h1VJBy4R+xm-gmxb2)ufd0ERnDf#85Z zLLTLNSrV=)hmJ$-<{jnv2(!9Yp4~BbBv?BlV_+`s3sxUJ`-G|Y;g)vZ%j)#mgX#3C z=Pr8i?f~^Y;Uy=+l(ojgK+Fsgn;;*!&srU^_S9F8N^JHT3i^RdHryZK+GRKD5WjQ` zl-_P2tR$(<#oV3Zi~i)y#e16$>a-H=T$hYm5d0}0f0VEBtQK{zZqweXI^615BQ$O6*ABBq3O1p&z2X<(@1Ad9dYjAuFV&IR`(yyUqkzoBmym}LGS~0!y z40y%N0Fz(T_SY!v0p$-^l1z$Rsiq@hd% z{l*Jl+qOK+ITWnn>jRx`TpKM3qK4@-Mr;W#5{z2mX(R4>G(;meFs)A7mxIa}mB5Tl z-!3DOBE`P$JGY_%<)|nPQBZoq5H$*fKRVhqK8Q&r*-!_&!}gKCX~E@)&24$w$>Cy5 z%1;Zs@$D!Z2WQO)2FHmcy`wf5<}T>(M6fVK?9kO_enz-O9-S)Z2v zrABTAP!}Txc?nUTC(E=qBd_R|=@NP=A30{gZ%&lYmZ%WSh*9;%yGneTwI_h~lAV)A zUIjY6BXV7&cIA~|qtO)~d*vl1nDDQIuFV$A$}}Yp4q-lHAm;wPxoXWVF48K^8@%0)jVu6rsl5bPxa5YVbC3~-nrj1;Bh10NuIs-b#LlA)XfEy)Dkn#+vLp~vK+38)ezm<7+c z=(Bb4H|w3aGIw-I*B?^ats*S54fL#Zwa$QYPw4>5UH`C-4K>kekpv3LhRIB3#*)|DtSGfipLFdXrRQnfhf_8 zzKM}mMNjE2<*2=GmO!ftUhc$GDzU#Nzch4MmEFklH6;40-4mi(_|{15|o%#uvjkk2ZA;6=w$ey^@AqfSzx?LBco-L7#?$oSiz7cCU+09yuCWJ7fS5I%!@Uv(iwI#!d~VBNy4hV z1z-o0Jmm>hfKtYyI5m4~#Dbp$V&kIRA{2zc3y$MQM}oQ_GTdT;Y}i1qcYrM|^D0lX zQBPMaihuN(fpMi_yyJWXAyg?YsAipM=ih*qCDZc&?_z zY4o35EBJYL8wi2)A7$=Rzqyp3!`Sejc12_(2hotbRKLq?w3H2X-7{X)~R}9=>#rjyT$; ziYyKPj{GDTQU2wd0}$mhgnqO6O6b$yUJV zp(`|4#hPYdudrs|nYCum-W}n#GcfoRf{UmuhR}0lpKv)hr^2uE^r|4GZ2-*j(%Z3B z^rQER=>om2i5^SIG7M`9y-*n|dW*zzwpM9i*37%wusYc=HwIcDwdB-`$!Z5UnrHjm z?U?a2cVsWSU%%NM(m~Jv21@OBvMOR+I|!OHxNf=Sj@s5Gr%6IuMc_s{o<-jVu%GQ}g(d5n*9!6J{L3um3CWX9i0#NK65RRsb1) z@dYwDkRk+>8f&Y28>HzyrzK~(BBklM!?dVJK{Pa)1znaI0WT1zhyYf^R6Y7pqopI5 zgnxZO6l?>-x+4p}7VFN5d6QY@33EM=?+FO>^4tS~m(?-t_m zj9+D7KxP-ZRm60y?$!3yIj0CP0~%4>tJzGH%UBFAFVw`42&D{z1X4L`3$O*XR&FCP z$BRNxNk>s}%TwVG?2V;pT3=L!&nG_jVExNvGijM?`HEvoK~%Lf0#WMZA$n%2Af_py z>Df8+S0T)SZZ!mstPnPV1iOOFZLaRrDH9K$w6oEghbi=1)}pn za|?8*^Zlevas2uW8Ob)xb6I_+fmW5TixvJuHC$1B+PGz>Gk*4hlocPL!1Dj z_0D9SxSzk_rydgW;;GorTuteMk)ekL&^QC;)fLB4&U2gD0nUudjXuU!zB9gr8yU+E z@d}{pZRQff6Qi>HM^!3VKSIh1*Eq&XlD0d|)lx_~gL_g)b?w7&tjCnoizF&oqek~i zjaZbx%!$Imm9XB6tDmFMm}IYJM08!uHes(TdKZDT3EQ>q&e@p{wl%n^-k&I)n ze2-MfN(SaKW@yo79T)A< zNkJ;_osh!yqS^Mw?%Iex0`6CKZGfwG zF?c`U2~hO5E{qGEUHQ{=Gk+f?SJ!;9Eo2L1ZHjkYXTek{d}Q#UWqrMU|3?T<2j%|P z?9UnGA1o98&+*@{68pc=L;rG|{2R#>`!6Ju&%dcv%5i=FP^$v}MXi!UntuWRhsabe z04{~m{zy!mdmTUHW)E4qKqfY!W+zN;Z`w{F>{Zq7vz zN=ZLc_-iOm2W9Adt$8v~Rwp+8Op_YF+){~RfZn+1qR-MZ;`YAyc#x0xcsts7`}e_R z_~B(WHd^D3sY)-co-KD=RcCxKnRl5cZaO(noh0C zumYTWjtM<6OjSor?NLhMfKt=Ak4$VTRTtXqpn`E#pU=+H8e+m~4Yh8PfyiOGdHVZ& zl0s5%19abrB(V3zj%6U`@Ej2p)L%7g^lz+~E|GEp;-jgjoDo~pW0-;OE7oQzF3rY6~^@Wfe`wY63l*MOQ3K`MB(OZ=!A`MjiH#kq%2{Zri|s zXrTU%q#9ZW2Y4)hr!Z)S$>f}ZZ;bi9hfnpi=%Xn)Cx^wTUc>l$G>B^5>Yh8raDVoc zu|9fX!mN>1V3H*N^zz`yA&pJMdSkvj}$Fz`VY^_0Ph^@Ci3pp|LGAcA=W{ zcXqDvd~b8S`4LX3nsa+*uJNhqo2__uY4Q|(kS?7Ob>0 zD9E)~lY3d{0quW`5+^FLM(Ceb+zDv_0MwrmKWsZ$I~!YjS_e}{D}8G{GfNsHE8~lF zZ%gE1lwt5hVnHPhb){dGKI~+m%fvp4nyDx}5eb3`aj4{SmTHY{C5!xrW%x1lmDI5f zXL<$7R7wD$Sr!W|{A{DDP_u{$@lnEJVkr9J-wsZ=$n_FsjeLNNA1_0sVp4Lf)KaRUGt~y@YB*JFP|@e32?n7LyC^IU9>}f?+rBv zynq}^ym$p&!@Z6@Kyg}01irwblp7VsI(0s$tSodYc2IQ@Xdk>=on@f}4|30vdf|H! zKgrx)?LM2WWlJ|v(V$Gy6cceR{ayN`U4pK7u>B z+H4Qx*DH`k$HpLsf#u)sa8-EpBR-zazQ(}5P}`pWGdExF1s4Gw6&+`#udlyaXLKp7 zUaLR2vXW9(x$EfSl9CkZwI=mgV`yq_o@WuC8x)r!wb|wQUfFDo3?0VxdL89g0Bbl5D!ehB|7_p0xDSU^}Y-?&rJYCzU-Dn zAbfv6sgws87}(gZ{n13~4;z%+BWw^tsqFH&Fxi^>QL+BXiv+(?=XC*`N9VbCdF}Cd zGMn@BaFLQpMngm6-HFuu!G0Mw)Gm9wwJxljaIE&!ZZPTkv!aPM3oWVy8SnQ{u0XKU z^;YH0?rj#UoPkFaZlrWJ?ogfd%!31e?9R5bVbKby~nE^-2!BPMf_LEql|<^_&#gW_w0Fg zrAD)Sq2v~0j~cikcl9Q;%;oV?6GjP~lnuyNoDFuCc3<$!)8d?6NS{kbX>j@!yAGWP|6HACHb%ZX zcIGB}2k(g_ux()Mdy1sdABPvRZ>gTMIMWwQzeL_u0?_&Jk`h@S*g!+Syr_)AS@`=+ zScr<-qXJMl6*T)bl$Ijq51+U31A5(zECi*7FTfcpWf%THN=8OSOC#o1$*o=iMHsD? zysHWTP;}jO>m{@ZE%ZouFBKM)k-DqLbIb#eoYn6Wa2AteMBekW{8z!>PUGl#q;t z2jYVWJI<-;40^`Y0;Rtt{1ApstsCy+2@`i=ny#6gmnbToGse8isZ^b@ZK5DLt{T-X+_647s*>aWEFNWfDnVtb~o${?- z`X6h>=%4!W5TNVvcP2@4*)fGkR4EKQ%NnX*dk`SyLbRbwm^5Y{M>;sZA}&c9%o zdqpg8rCnTfRRXC)50LE%@Vw>w3^F z!?{byMpvd+v>L1k0npWUxYO$o@AmR5Dw-X<@0>2E!te+OwU^2Sos}(LBp0GnCT|HY zKR@5>sTKvua%^_+a6CC0n~i~zyJMR!dS3NmU?4%0wVAE6NM@(GZDutI)fE;5r7j1* z{j)s#V}CUA4Ibj>)@ig`?I$ymqE~4&SGBfcY#O|@X@>X40&L}_Vft}duQypKd$8X2 z?Wf)TQ4L|XdHPe8j0HFJ=j+9AAq`TKV2D%zEpKtOg)ko44kP;9aOzu$X2cN4bop`E z-V4^R;#~H`_(O7Nd{%KPyOtJ8L?k~pmOe?iXrct%z{KJEZJgtu%NV}yvVOx`VP|J& zJsRNapGu%4Emb zv5(GVf$^T3O7vbJ$Ik3Rf-;(9f*CC!3niPw`O5Y||M2s4u25ohZg03kbcS;w_Y;n_ zV_wB&mqZ*6r-`%d_``)4;(8|0%kAn+@tz+FhF7)Gr8zZ4 z?daCk70uTEKIh@jOXf?C&v45JL^4+eQXLso?$j4v2`DtaiN}?HbAPDyq+w5Ia{OG4 zS!EWqc)7R)VhYwTywkbWvUmx=)+oV1u|lFc@tu-wwiu|T;3OC2Ch^Z&^>mA+5XFPa zqWk7D2P=LwOHcUB)VGN@j6(4iqouOIK@;U$)QnvM?$Ky`47_m+_0@wUG?B{C=!fP% zcwzBTDp`p{7h!oXzv~}HyPm!4n{{sc8E)yZQtc97CQkin7S!Jam}&l8Ygjgf%9MT? zdPb|1c*QV11{;}ja|qrH9#NG2&_P-bZ_}{ro!@NtfJhCBsKpUASmajksiL z!z9gR%&ED215>bsV)B=yC@R6-#+Yno$pP z6iS;5M^}^F6Bdo{)`tXh7UwdtazlS<=TyDuhEyc(4e%b?bi^clMPxWQ-}Nw3O2V@5 z4Uz4t+znc4ru3>1o3D0qu8)Uk0b4xirs{>k8AP09zhJ@jCD z`oN}&R_Cqi1(;s#nz=$9LfL12OLf_LW%H`-qc1FA3}ew19LvdPfF?Z}Z;4&>l4wI1 z4?izZ5q|V{X@3wG0obVPoXkvl1AbusUVd~ZKa|0^|J-yLs5bO%=(Eolnc@coRllTR zwP?PH;mR-#aKzCJ9~>-FH7bhtJmCO$=Xwk!>DbmD!HjhEepX#1Zg~CM?^QKcn1211 z5rdfzlyBF2gLD<@N)ER|UnYzE-|81RYWK~gopWEyHZ!b6$39T(e*Adp<>}Y!du0kV zEYhp_+taq_9*m)G(}d>CF%AuYr?gQA^&q7ji8svZ!MbVc3wg-pbzdFj6y(ZwjUFQ- z3JBCOpP0u$3NgTT4;Ez`Iv$@*?Z)3bQe_>0o{nHo4qDMq-UDsOO5{oH=HwOxfjN+* zdmyHF2owQJ!~LdVSwswhoR-fHM!#C_tkW+u6kNCOfvvxObdmqgu6~dxx?THOsVqNj zm+=4RV%D>@{r_wEf5&fzqE_@g9lVFg9*>}x&1POcf8st;0Xs@462j!;CTsdqUoG`I zn%3*1Wt;_f4!<9FrE}B%>Z;?)r$l8un1}3Afnld+EJ#E{AwzKjCkk9R95JL^ioB@> z{%H!Mq( z_4A;9&jV}W7^L1twS1)apJv-HbT+6^wfFer#q{nzPuHNr)%0}B{ zTine#vT_11>W6*9SW%WmQ4V`6Oo(9zf|Z7ZmGqhKEaePV z+2iRWEFbZ7q>=GBtm_z$hzy5^*lCX68r{)-JJ?)n41%eZnRIAf)A7^7G#H&ED`-Yu z0i-G`TJ0It<4iJj{c0{`w~Ly$^u13lD%wP&q?&b3i1+ulFsm5v{&(WHq&>E~(Vxyf z)hy#EUu`0`ll1IeE?++i$bXLq1^g8wyPtTl{&~0~{h#q*;Arn)WA*<-;(v!kMZAoS zA07J8wZs#S=(T7#K0iRdU+Adu#1Q9&K71H+F11;By4usGiX(dcMT6`4JL&YbYh3g0 zN#@cJF0>eHIKUa7uCDS;T#Xfoc3mnPNRT2M<$c;8Y+v9cUF7ZUK#5)~D&*d|8AM58 z-C{)~)g&sXl%u-hVM!SZ8q+EV@+Q=33i+J?H0nV~HRb@|a?`@S1xp&coeH#zMV;Rg z+Sr?;N=~K?o2(zGJm@G&1ra?Wd(!a(fmLS3KOA;=Dar0@cM@!VQ<;m?3B2O$9@D6C zW;#`A$A?FAHkqaUU@A}bhQuf%CUbzVFa$=NB=FGrQUUwM)EW z!HvbOr;X=W7&gV-FJ_x(ZWDQ7?uA`~_kOa@pZ0|PuIs-I{d;C#zT0Ovy+498XpVBe z#rkB`s2Zkut7fyxm7M4asF}M($uw-2`0WeZd^>zzq@3WKAiv|@>ysx^AK&UJwK@9P zy>mY>&>=~ckx7?9gn^BL11O@%PBM*C_F}bz`Rp1H-Z=1_mJ{Z9pDy zYd>Tb0{O}ebp7Zz2_lR*Ee+ETyIcb4S_yPx(66OI7;_YO%pme)2>b>HbR*C&{XiJ8 zN&{vDBp{IegEdgl@5n$Hbxadx6wFf)Bays@)kO50B@iY)(t??YKG_4F=fG+j`i%_; z({5v)Fhq8PcmQw;jrjlo diff --git a/Federation/doc/MIP_Federated_Deployment.png b/Federation/doc/MIP_Federated_Deployment.png deleted file mode 100644 index 527284cbe329b1aaabbe3ddb02e1eb963e17dacd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31557 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
  • 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%0AZ>xzKXh`QWbnZvl1k+wG1>Tw_4C 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 diff --git a/Federation/doc/MIP_Federated_Deployment_II.png b/Federation/doc/MIP_Federated_Deployment_II.png deleted file mode 100644 index 6f41c2a82a95330a746be1880b1583a5fd59a5a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31601 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 diff --git a/Federation/doc/MonitoringMIPFederation.md b/Federation/doc/MonitoringMIPFederation.md deleted file mode 100644 index d82bcce8..00000000 --- a/Federation/doc/MonitoringMIPFederation.md +++ /dev/null @@ -1,50 +0,0 @@ -[Federated MIP Deployment](Readme.md#MonitoringMIPFederation) -> `Monitoring the MIP Federation` - -# Monitoring the MIP Federation -## Real-time federation-wide logs -In the *tmux* session (opened as **mipadmin** user), in the **logs** window (#1), you have a multi-pane view of each node of the federation. -In each of them, you have the equivalent of the command *mip -f logs*, which shows the most appropriate logs (according to the node type), in "follow" mode. -Don't try to CTRL-C the command, because it will "kill" the pane, and then, as it's configured to stay, it will be in "dead pane" mode. If it's the case, you can "respawn" it. -For the different commands to use in the *tmux* session for this purpose, please see this [guide](OperatingMIPFederation.md#ShortTmuxUsageNotice). - -## Extended MIP Federation Backend status -There's a *mip* script's feature to get extended MIP federation status, at the backend side (without anything related to the Web interface). -In the *tmux* session (opened as **mipadmin** user), in the **ms** (master) window (#3) -``` -mip status -``` -Note that here again, we don't necessarily need to use the *--node-type ms* parameter, as we already did the **master** node configuration, and this node type has been stored for this machine. - -With this *mip status* command, you can use parameters like the *--verbose-level * one, to specify the amount of details you want to see. -i.e. with a VERBOSE_LEVEL of *4*, you will see a lot of details, with all the different IPs (machines and Docker Swarm Networking IPs, Docker version, containers image name and version, deployment datetime, etc...) - -## Specific MIP Federation Component logs -In the MIP [*tmux* session description](OperatingMIPFederation.md#CreatingTmuxSession), you saw that there's a dedicated window for each node. -According to the node type, there are different [components](../../README.md#Components) which run on the machine, and from which you can extract specific logs. -In each machine **but** the pusher, you can run -``` -mip --component logs -``` -Alternatively, if you want to see the logs in "real-time", you can use the *-f* flag. -You can also "limit" the lines that you wanna see with *--limit \*. - -As usual, don't hesitate to use -``` -mip --help -``` - -In order for you to have a better understanding of the different available components for each type of node, here's a little table -|Node Type|Components| -| -- | -- | -|**worker**|*exareme*| -|**master**|*exareme-master*| -||*exareme-keystore*| -|**ui**|*frontend*| -||*gateway*| -||*portalbackend*| -||*portalbackend_db*| -||*create_dbs*| - -You can see here that *exareme-master* and *exareme-keystore* contains a dash ( **-** ) character, and not an underscore ( **_** ), as it was indicated in the [components](../../README.md#Components) guide. -This is because in this guide, the components are mainly the ones available in the *local* MIP, and in this specific case, the name differs a little bit. -You can also notice that in the Federation, there's no *keycloak* or *keacloak_db* component. diff --git a/Federation/doc/OperatingMIPFederation.md b/Federation/doc/OperatingMIPFederation.md deleted file mode 100644 index 3467b6ec..00000000 --- a/Federation/doc/OperatingMIPFederation.md +++ /dev/null @@ -1,186 +0,0 @@ -[Federated MIP Deployment](Readme.md#OperatingMIPFederation) -> `Operating the MIP Federation` - -# Operating the MIP Federation -## Using the *tmux* session -*tmux* is a virtual console, which will continue to live on the **pusher**, even if you disconnect your current session. Anytime you may need to operate the federation, you will have to open an SSH session on the **pusher**, and **re**-attach to the *tmux* session. Then, when you're done and you want to disconnect, first detach the *tmux* session, then close your SSH connection. - -### Short *tmux* usage notice -We won't detail the *tmux* usage here, but as a short notice: - -* When you're outside (not attached to) a *tmux* session - * List all the available *tmux* sessions (before you attach a session) - ``` - tmux ls - ``` - You will see the different sessions name or id. - * Attach to a *tmux* session - ``` - tmux a -t - ``` -* When you're inside (attached to) a *tmux* session - * Any operation will have to begin with *CTRL + j* (usually *CTRL + b*, but to avoid issues when having *tmux* inside *tmux* inside *tmux*, all the generated MIP-related *tmux* sessions have been redefined with *CTRL + j*) - * Detach the *tmux* session - ``` - "CTRL + j" d - ``` - * Navigate to the *next* window inside the session - ``` - "CTRL + j" n - ``` - * Navigate to the *previous* window inside the session - ``` - "CTRL + j" p - ``` - * Directly jump to a certain window number (0-9 only) - ``` - "CTRL + j" - ``` - * Navigate to the *next* pane inside a multi-panes window (like the logs window (1)) - ``` - "CTRL + j" o - ``` - On the MIP-related generated *tmux* session, on any OS **but** Mac (currently), you can also use the *ALT + ARROW_KEYS* shortcut - * Quickly show the pane numbers - ``` - "CTRL + j" q - ``` - * Directly jump to a certain pane number (0-9 only) - ``` - "CTRL + j" q (the number must be entered really quickly after the q) - ``` - * Zoom (in or out) the currently selected pane - ``` - "CTRL + j" z - ``` - * Enter copy mode (to stop the live logs and go back in the pane's history) - ``` - "CTRL + j" [ - ``` - * Quit copy mode - ``` - q or ESC - ``` - * Respawn a "dead" pane or window - ``` - "CTRL + j" r - ``` -If you want to know more about *tmux*, go on the [tmux Cheat Sheet](https://tmuxcheatsheet.com). - -### Creating the *tmux* session -Now that you know a bit more about *tmux*, we will generate the MIP special *tmux* session (or connect to it, if it already exists). - -On the **pusher** node, with the **mipadmin** user, you have to run -``` -mip --pusher --federation tmux -``` -This will generate the session if it does not exist, and then, in any case, attach to it. -If you want to force **re**-generate the *tmux* session, you can use the *--force* flag. - -Now that you're inside the session, you will notice that several windows are present (with **:** tab names). You can also notice that, left to the tab names, there's the *tmux* session's name (in blue), and it should be the . - -|Window Number|Window Name|Description| -| -- | -- | -- | -|0|bash|Console on the **pusher** node| -|1|logs|Multi-panes window to display real-time logs of all the nodes (top-to-bottom, left-to-right: **master**, **ui**, then all the workers)| -|2|deploy|A quick help about the **pusher** commands to operate the main MIP Federation tasks| -|3|-ms|Console on the **master** node| -|4|-ui|Console on the **ui** node| -|5-n|-wk-|Console on the **worker** nodes| - -## Consolidating data -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0): -``` -mip --pusher --federation data consolidate -``` -For each pathology accross the federation **workers**, this will list the different available datasets in the pathology's Common Data Elements (CDE) file, and then, from all the CDEs, it will generate the *pathologies.json* file (and push it to the **ui** node), used by the MIP Web interface to display the different variables. -In order for you to better understand this process, here's a step-by-step action list - -1. For each **worker** node - - 1. Connect to the node - 1. Ask it to prepare a list of the available datasets (and for each of those, give a prototype (headers list) of the data) - 1. Download this archived list on the **pusher** - 1. For each pathology in this list, download (on the **pusher**) the corresponding CDE from the **master** node (or directly from the data catalogue, if asked via optional parameters) -1. On the **pusher** node, parse the prepared pathology list - 1. For each pathology, edit the CDE and there, list **only** the available datasets in the federation - 1. Redistribute this modified CDE on the **master** and on each participating **worker** node - 1. With all the available CDE files, "compile" the *pathologies.json* file, and push it on the **ui** node - -Alternatively, you can ask to **re**-label the pathologies and/or the datasets by using the flag *--review-dataset-labels*. -As said earlier, you can ask the script to download the CDEs from the data catalogue using the flag *--online-cdes*. -If you want to use an non-default data catalogue, you can use the following parameters -* *--datacatalogue-protocol* (*http* | *https*) -* *--datacatalogue-host * - -Don't hesitate to use: -``` -mip --help -``` - -## Compiling data -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0) -``` -mip --pusher --federation data compile -``` -At any time, you can **re**-compile by using the *--force* flag. -You can also specify the pathology(ies) to compile, with the *--pathology \* parameter (comma-separated pathologies list). -If you want to limitate the compilation to a certain node, you can use the *--node \* parameter, but in this case, you'll also have to pass the *--pathology* argument. - -As usual, to get more details, use *mip --help* - -## Deploying services -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0) -``` -mip --pusher --federation service deploy -``` -This will deploy the Docker Swarm network, the master-related containers on the **master** node, and the worker container on each **worker** node. -At any time, you can **re**-deploy by using the same command. - -### Service-related additionnal features -Alternatively, there are different things that you can run in the same way (still on the **pusher** console of the *tmux* session) - -* Showing the services status - ``` - mip --pusher --federation service status - ``` -* Starting the services - ``` - mip --pusher --federation service start - ``` -* Stopping the services - ``` - mip --pusher --federation service stop - ``` -* Restarting the services - ``` - mip --pusher --federation service restart - ``` - Note that a *restart* is actually different from a "*stop* *start*" cycle. See the Docker documentation. - -For all these commands (including the *deploy* one), you can use the *--node * parameter to limitate the scope to a certain node. - -As usual, to get more details, use *mip --help* - -## [Synchronizing the KeyCloak roles](SynchronizingKeycloakRoles.md). - -## Run the MIP Web Interface -In the *tmux* session (opened as **mipadmin** user), in the **ui** window (#4) -``` -mip start -``` -Note that here, we don't necessarily need to use the *--node-type ui* parameter, as we already did the **ui** node configuration, and this node type has been stored for this machine. - -After launching, you should be able to browse the MIP on the URL which will be displayed. -Note that once the command ends, it may still take up to one minute until the MIP's Web interface is really operational. -Of course, you can also do other actions here: -* Stopping the MIP Web interface - ``` - mip stop - ``` -* Restarting the MIP Web interface - ``` - mip restart - ``` - Note that a *restart* is actually different from a "*stop* *start*" cycle. See the Docker documentation. - -At anytime, you can learn more about the *mip* commands with *mip --help* diff --git a/Federation/doc/PreparingKeycloakRealmClient.md b/Federation/doc/PreparingKeycloakRealmClient.md deleted file mode 100644 index 0aa50263..00000000 --- a/Federation/doc/PreparingKeycloakRealmClient.md +++ /dev/null @@ -1,84 +0,0 @@ -[Operating MIP Federation](OperatingMIPFederation.md#PreparingKeycloak) -> `Preparing KeyCloak Realm's Client` - -# Preparing KeyCloak Realm's Client -1. Preparing KeyCloak realm's client - - In the KeyCloak's interface, you will have to create a client (**realm-management** in our case) which has - * *openid-connect* Client Protocol - * *confidential* Access Type - * *Direct Access Grants Enabled* - * *Service Accounts Enabled* - * *Valid Redirect URIs* set to "*" - - Then, in its roles, prepare the following *Roles* - * *create-client* - * *impersonation* - * *manage-authorization* - * *manage-clients* - * *manage-events* - * *manage-identity-providers* - * *manage-realm* - * *manage-users* - * *query-clients* - * *query-groups* - * *query-realms* - * *query-users* - * *view-authorization* - * *view-events* - * *view-identity-providers* - * *view-realm* - - Also, you'll have to prepare "composite" *Roles* - * *view-clients*, containing the following *realm-management* client's roles - * *query-clients* - * *view-users*, containing the following *realm-management* client's roles - * *query-groups* - * *query-users* - * *realm-admin*, containing the following *realm-management* client's roles - * *create-client* - * *impersonation* - * *manage-authorization* - * *manage-clients* - * *manage-events* - * *manage-identity-providers* - * *manage-realm* - * *manage-users* - * *query-clients* - * *query-groups* - * *query-realms* - * *query-users* - * *view-authorization* - * *view-clients* (previously created composite role) - * *view-events* - * *view-identity-providers* - * *view-realm* - * *view-users* (previously created composite role) - - Then, you'll have to prepare *Mappers* - * *Client ID* - |Parameter|Value| - | -- | -- | - |*Mapper Type*|*User Session Note*| - |*User Session Note*|*clientId*| - |*Token Claim Name*|*clientId*| - * *Client IP Address* - |Parameter|Value| - | -- | -- | - |*Mapper Type*|*User Session Note*| - |*User Session Note*|*clientAddress*| - |*Token Claim Name*|*clientAddress*| - * *Client Host* - |Parameter|Value| - | -- | -- | - |*Mapper Type*|*User Session Note*| - |*User Session Note*|*clientHost*| - |*Token Claim Name*|*clientHost*| - - Finally, you'll have to prepare the following *Service Account Roles*, *Client Roles* - * *account* - * *manage-account* (composite role which contains "*manage-account-links*" *account* client's role) - * *realm-management* - * All the roles -1. Preparing KeyCloak realm's admin user - - Here, you just have to create a simple user, without any role, **realmadmin**, in our case diff --git a/Federation/doc/PreparingMaster.md b/Federation/doc/PreparingMaster.md deleted file mode 100644 index a894de65..00000000 --- a/Federation/doc/PreparingMaster.md +++ /dev/null @@ -1,50 +0,0 @@ -[Federated MIP Deployment](Readme.md#PreparingMaster) -> `Preparing the master node` - -# Preparing the **master** node -1. Install the **master** - - As a "sudoer" user: - 1. Set the hostname, with a meaningful name, i.e. - ``` - sudo hostnamectl set-hostname -ms - ``` - 1. Configure the networking, including the DNS client - 1. Install the MIP - ``` - git clone https://github.com/HBPMedical/mip-deployment - ``` - ``` - sudo mip-deployment/mip --node-type ms --yes install - ``` - Here, the *--node-type* parameter is very important, because it tells the script that this node will be a **master** (ms). - Following the same process than for the workers, you can also put the specific parameters (*--version*, *--branch* or *--commit*, used with the flag *--force-install-unstable*) if you want to install a specific version. - -1. Configure the **master** - - Still as a "sudoer" user: - ``` - sudo mip --node-type ms --yes configure all - ``` - - Like for the workers, by default, this will create a user *mipadmin*, and you can also change its password: - ``` - sudo passwd mipadmin - ``` - - Again, later on, you will have to give the *pusher* informations about this *master* node: - * Its IP address - * The user (*mipadmin*) - * The user's password - -1. Prepare the CDE metadata files (**only if you don't want to automatically download their latest version on the data catalogue**) - - If you want to manage your CDEs by yourself, you'll have to place them on the **master** node, as follows. - * For every pathology over the whole federation, as **mipadmin** user: - ``` - sudo mkdir -p /data// - ``` - * Then, still as **mipadmin**, place the corresponding *CDEsMetadata.json* file in the right pathology folder. - * Once it's done, you can set the data owner to *mipadmin* - ``` - sudo chown -R mipadmin.mipadmin /data - ``` diff --git a/Federation/doc/PreparingPusher.md b/Federation/doc/PreparingPusher.md deleted file mode 100644 index 69f6dffb..00000000 --- a/Federation/doc/PreparingPusher.md +++ /dev/null @@ -1,61 +0,0 @@ -[Federated MIP Deployment](Readme.md#PreparingPusher) -> `Preparing the pusher node` - -# Preparing the **pusher** node -As the **pusher** can virtually be any type of node (as it doesn't conflict with any MIP component), the pusher is not a type of node, but a flag in the *mip* script. -That said, preparing a dedicated **pusher** node is strongly encouraged, in order to avoid any confusion. -Also, as the **pusher** will operate the federation, remotely controlling the **worker** and the **master** nodes, the federation name will be required for each pusher operation. It also means that a pusher can manage many different federations from the same machine (which can also be a participant node in a federation, at the same time, but again, this kind of setup can become a source of confusions). - -1. Install the **pusher** - - As a "sudoer" user: - 1. Set the hostname, with a meaningful name, i.e. - ``` - sudo hostnamectl set-hostname -pusher - ``` - 1. Configure the networking, including the DNS client - 1. Install the MIP - ``` - git clone https://github.com/HBPMedical/mip-deployment - ``` - ``` - sudo mip-deployment/mip --yes --pusher --federation install - ``` - As said earlier, for the **pusher**, the *--node-type* parameter is not used. Instead, the *--pusher* flag and the *--federation \* parameter are mandatory! - - In the case of the **pusher**, it's a bit special: as this installation will clone the *exareme* repository, it shouldn't install any version of *exareme*, but instead, it should install the same version which is listed in the *mip-deployment/.versions_env* file, a version which has been tested and validated to work well with the MIP's Web interface. - The *exareme* folder will be cloned by default in */opt/\/exareme*, and *mip-deployment* won't be cloned by this installation. - That said, some binaries will be cloned from the *mip-deployment* repository, and installed in */usr/local/bin*. - - Following the same process than for the different federation nodes, you can also put the specific parameters (*--version*, *--branch* or *--commit*, used with the flag *--force-install-unstable*) if you want to install a specific version. - That said, the specification of the **pusher** makes it a bit trickier, because the along the process, the "signification" of the *--version*, *--branch* or *--commit* parameters can be used to target versions of *exareme*, instead of *mip-deployment*. That's why they should come by pair: - - * *--version \* with *--mip-version \* - * *--branch \* with *--mip-branch \* - * *--commit \* with *--mip-commit \* - - Again, by default, without specifying anything, the *exareme* version installed will match the one which is written in the *mip-deployment/.versions_env* file of the *mip-deployment* latest stable release, so if you don't understand these complexities, no worries. - - As usual, to get more details, use: - ``` - mip --help - ``` - -1. Configure the **pusher** - - As this operation is something that requires to be interactive at a moment, we won't use *--quiet* nor *--yes* parameters. - - Still as a "sudoer" user: - ``` - sudo mip --pusher --federation configure all - ``` - - This will first ask you to enter a vault password. This vault will securely store every sensitive details (like credentials) about the remote nodes. - For each future tasks which will imply an access on the remote nodes, the *mip* script will ask you this vault password again. - Now that this vault question is answered, the *mip configure* process will ask you to provide informations about the **master**, **ui** and **workers** nodes, to prepare SSH identity exchange with the federation participants. - For each node (**master**, **ui**, then the **workers**), you'll have to enter: - - * The internal IP address or address which is in the same federation LAN (physical or virtual, if you prepared a VPN). - * The node's administration user (usually *mipadmin*) - * This user's password - - The *mip* script will then establish an SSH connection to the node, install its SSH identity there for future passwordless connections, and get the real node's hostname. If you configured the nodes with a meaningful hostname, you should recognize it, and it should actually allow you to verify that you configured the correct machine. diff --git a/Federation/doc/PreparingUI.md b/Federation/doc/PreparingUI.md deleted file mode 100644 index a2fa9627..00000000 --- a/Federation/doc/PreparingUI.md +++ /dev/null @@ -1,83 +0,0 @@ -[Federated MIP Deployment](Readme.md#PreparingUI) -> `Preparing the ui node` - -# Preparing the **ui** node -1. Install the **ui** - - As a "sudoer" user: - 1. Set the hostname, with a meaningful name, i.e. - ``` - sudo hostnamectl set-hostname -ui - ``` - 1. Configure the networking, including the DNS client - 1. Install the MIP - ``` - git clone https://github.com/HBPMedical/mip-deployment - ``` - ``` - sudo mip-deployment/mip --node-type ui --yes --no-run install - ``` - Here, the *--node-type* parameter is very important, because it tells the script that this node will be a **ui**. - Following the same process than for the workers and the master, you can also put the specific parameters (*--version*, *--branch* or *--commit*, used with the flag *--force-install-unstable*) if you want to install a specific version. - As a reminder, the MIP will be installed by default in */opt/mip-deployment*. - Still by default, the *mip-deployment* folder you just created when cloning the repository will be deleted after the installation. If you want to keep it, just use the *--keep-installer* flag. - -1. Configure the **ui** - - Still as the "sudoer" user: - ``` - sudo mip --node-type ui configure all - ``` - With this command, the *mip* script will interactively ask you for the different parameters. Of course, you can also set these parameters within the *mip configure* command. - If you want to configure a particular part, you can run *sudo mip configure \* (like *sudo mip --node-type ui configure exareme-ip*). - At last, if you want to **re**-configure something, you will have to use the *--force* flag. - - The configurations which come as variables will be written in the *.mipenv* file, at the root level of the *mip-deployment* folder. This file is automatically set by the *mip configure* command, but can also be edited by hand (**IF YOU KNOW WHAT YOU'RE DOING**) later on (running *mip configure* is still a mandatory step prior to doing anything like that). - - For each of these variables, there is a matching *mip configure* command parameter: - - |Variable|Command parameter|Mandatory| - | -- | -- | -- | - |MIP_LINK|--link \|yes| - |EXTERNAL_MIP_PROTOCOL|--ext-protocol \|yes| - |PUBLIC_MIP_PROTOCOL|--protocol \|yes| - |PUBLIC_MIP_HOST|--host \|yes| - |EXAREME_IP|--exareme-ip \|yes| - |KEYCLOAK_AUTHENTICATION (0/1)|--without-keycloak-authentication \| --with-keycloak-authentication|yes| - |KEYCLOAK_PROTOCOL|--keycloak-protocol \|| - |KEYCLOAK_URL|--keycloak-url \|| - |KEYCLOAK_REALM|--keycloak-realm \|| - |KEYCLOAK_CLIENT_ID|--keycloak-client-id \|| - |KEYCLOAK_CLIENT_SECRET|--keycloak-client-secret \|| - |DATACATALOGUE_PROTOCOL|--datacatalogue-protocol \|| - |DATACATALOGUE_HOST|--datacatalogue-host \|| - - Note that if you don't provide anything for KeyCloak, it will use the default configuration to connect to the central *EBRAINS*' KeyCloak, with the *mipfedqa* client. - For the data catalogue, the default *EBRAINS* DC hostname will be used as well, if no replacement value is provided. - - In order to better understand the different configuration parameters, check the following picture: - ![MIP Federated Configuration Scheme](MIP_Federated_Configuration.png) - - You can see that there are two different setups: - * **direct** - - The **ui** node's Web interface is directly reachable from the browser. - - * **proxied** - - The **ui** node's Web interface is reachable through a reverse-proxy server. Currently, it's been tested and validated with Apache server, with this specific VirtualHost configuration: - - ``` - ServerName public.mip.address - - - ProxyPass http://internal.mip.address/ - ProxyPassReverse http://internal.mip.address/ - Allow from all - Required all granted - - ``` - - Again, don't hesitate to use: - ``` - mip --help - ``` diff --git a/Federation/doc/PreparingWorkers.md b/Federation/doc/PreparingWorkers.md deleted file mode 100644 index 1573223e..00000000 --- a/Federation/doc/PreparingWorkers.md +++ /dev/null @@ -1,62 +0,0 @@ -[Federated MIP Deployment](Readme.md#PreparingWorkers) -> `Preparing the worker nodes` - -# Preparing the **worker** nodes -1. Install the **workers** - - On each worker node, as a "sudoer" user: - 1. Set the hostname, with a meaningful name, i.e. - ``` - sudo hostnamectl set-hostname -wk - ``` - 1. Configure the networking, including the DNS client - 1. Install the MIP - ``` - git clone https://github.com/HBPMedical/mip-deployment - ``` - ``` - sudo mip-deployment/mip --node-type wk --yes install - ``` - Here, the *--node-type* parameter is very important, because it tells the script that this node will be a **worker** (wk). - If you want to install a specific version of the MIP, you can precise the tag (*--version \*), the branch (*--branch \*) or even the commit ID (*--commit \*), each of these parameters having precedence over the next one(s). If you specify a non-default version, you also have to force this installation with the flag *--force-install-unstable*. - - Don't hesitate to use: - ``` - mip --help - ``` - -1. Configure the **workers** - - On each worker node, still as a "sudoer" user: - ``` - sudo mip --node-type wk --yes configure all - ``` - - By default, this will create a user *mipadmin* (which will be in *docker* and *sudo* groups). This user will be used by the *pusher* to operate this node. If you don't know its password or want to change it, do it right now with: - ``` - sudo passwd mipadmin - ``` - - Later on, you (or the central system administrator) will need to provide the *pusher* node informations about this *worker* node: - - * The node IP address, with which the pusher will connect (via ssh), using the *mipadmin* user: - ``` - ip a - ``` - This command will give you the machine's networking configuration details. You'll have to search for the IP with which you will reach this node from the other federation's nodes. If you prepared a VPN network, you'll have to use the VPN IP of this node. - * The user (*mipadmin*) - * The user's password - -1. Prepare the datasets on the **workers** - - * On each worker, as **mipadmin** (*sudo su - mipadmin* can help you becoming this user if you don't know its password) user, prepare the federation data folder. Go with this pattern: - ``` - sudo mkdir -p /data/ - ``` - - If your federation is named *mipfed1*, the data folder will have to be */data/mipfed1*. - * Place your datasets in /data/// - * If you have CDE metadata files, it will have to be, or placed in the **master** node, or downloaded (latest version for now) from the central data catalogue. - * Set the data folder to be owned by *mipadmin* - ``` - sudo chown -R mipadmin.mipadmin /data - ``` diff --git a/Federation/doc/Readme.md b/Federation/doc/Readme.md deleted file mode 100644 index 6dbade6d..00000000 --- a/Federation/doc/Readme.md +++ /dev/null @@ -1,90 +0,0 @@ -[MIP Deployment](../../README.md#FederatedDeployment) -> `Federated MIP Deployment` - -# Federated MIP Deployment -## Structure -![MIP Federated Deployment Scheme](MIP_Federated_Deployment_II.png) - -The federated MIP is meant to run on different VM/Physical servers (nodes): - -* A **Pusher** (*4*) -* A **Master** (*5*) -* A **UI** (*6*) -* Some **Workers** (*7*) - -As the *pusher* service can run on any node, the bare minimum number of required servers is 3. That said, it's still strongly encouraged to deploy a pusher on a dedicated server. -As the opposite as a *local* MIP setup, all the required components are not in this *mip-deployment* repository (*1*). -As an additional part, for all the backend requirements, we will need the *exareme* repository (*2*) content, and another component (provided as an external service) which is the global *HBP* KeyCloak's instance (*5*). -Additionally, another external service can be used in a more punctual way: the *data catalogue*. -All these statements mean that the federated MIP is not designed (at least not at the moment) to run as an independant MIP setup. -That said, you can deploy your own KeyCloak server and your own data catalogue, but these processes won't be documented here. - -### Pusher (*4*) -The **Pusher** will contain all the *exareme* repository (*2*) structure. Its role will be to "push" containers and configurations to the **Master** and the **Worker** nodes, and to initiate a Docker Swarm network from the **Master**. -This pusher will need to have an *ssh* access to the master and the workers, and this will also include *root* access there. -As a Docker Swarm network is quite complicated in terms of layer 3 network protocols and layer 4 TCP and UDP port requirements, it is highly recommended to run all the different nodes in the same network subnet. That's why, if you need to have remote **Worker** nodes in hospitals, it's better to build a VPN connectivity (preferably in layer 2) between all the nodes. Such a VPN setup won't be documented here either. -As this **Pusher** is a central actor in the federation, it's also the best candidate to initiate federated actions, like the synchronization (consolidation) of metadata and pathologies details over the federation, the automated data compilation on the workers and master nodes, and the remote KeyCloak roles synchronization, amongst other useful features. - -### Master (*5*) -The **Master** will "schedule" and "rule" the **Workers**. This will be the main Docker Swarm network component, the Swarm Master. -The Docker containers running on this machine will be *exareme-master* and *exareme-keystore*. - -### UI (*6*) -The **UI** will contain most of the MIP Docker containers, but it won't have any *exareme* nor *keycloak* related container. -This node will require a TCP connection with the **Master** node only (not mentioning the external KeyCloak's instance, nor the reference to the external data catalogue). -This *mip-deployment* repository (*1*) includes a "Federation" subfolder with another *docker-compose.yml* file, which contains the references to the different Docker images (hosted on Docker Hub (*2*)) and their version tag, required to run the **UI** node only! -As this node will actually run the user Web interface, it will be the only one which is required to be reachable via HTTP/HTTPS. - -### Workers (*7*) -The **Worker** (usually located in hospital premises) nodes (at least one) will have to host the actual node-related datasets, and will run the *exareme* container only. - -## Requirements -For the different nodes, the requirements are the same as for a local MIP. - -### Hardware -* 40 GB HDD -* 8 GB RAM -* 2 CPU Cores - -### Software -* Ubuntu Server 20.04 (minimal installation, without GUI). - -## Setup -### Preparing the machines -Prepare a VM/Physical machine with **Ubuntu Server 20.04** (basic OS, without GUI), for each node of the federation. As we want to be able to run federated data analysis, we'll typically go with two workers, with the following plan: - -* **pusher** -* **ms** (master) -* **ui** (frontend) -* **wk1** (worker 1) -* **wk2** (worker 2) - -On every node **but** the pusher, we'll use the *mip-deployment* repository as an installer. Then, once installed, the *mip* script will (by default) delete the installer folder. -In the **ui** node, the installer will **re**-clone the *mip-deployment* repository, but in /opt. Therefore, after the installation has been done, it can also remove the *mip-deployment* folder that was used as the installer. -If you want to keep the installer folder, you'll have to explicitely use the flag *--keep-installer*. - -1. [Preparing the **worker** nodes](PreparingWorkers.md">) -1. [Preparing the **master** node](PreparingMaster.md) -1. [Preparing the **ui** node](PreparingUI.md) -1. [Preparing the **pusher** node](PreparingPusher.md) - -## Operating the MIP Federation -The first time, after the setup, just remember to follow these steps in the right order: - -1. [Generate the *tmux* session](OperatingMIPFederation.md#GeneratingTmuxSession) and connect to it -1. [Consolidate the data](OperatingMIPFederation.md#ConsolidatingData) -1. [Compile the data](OperatingMIPFederation.md#CompilingData) -1. [Deploy the backend services](OperatingMIPFederation.md#DeployingServices) (Docker Swarm) -1. [Synchronize the KeyCloak roles](OperatingMIPFederation.md#SynchronizingKeycloakRoles) -1. [Run the MIP Web Interface](OperatingMIPFederation.md#RunningWebInterface) - -## Monitoring the MIP Federation -1. [Real-time federation-wide logs](MonitoringMIPFederation.md#RealTimeLogs) -1. [Extended MIP Federation Backend status](MonitoringMIPFederation.md#ExtendedMIPFederationBackendStatus) -1. [Specific MIP Federation Component logs](MonitoringMIPFederation.md#SpecificMIPFederationComponentLogs) - -## Upgrading the MIP Federation -1. [Upgrading the **pusher** node](UpgradingMIPFederation.md#UpgradingPusher) -1. [Upgrading the **master** node](UpgradingMIPFederation.md#UpgradingMaster) -1. [Upgrading the **ui** node](UpgradingMIPFederation.md#UpgradingUI) -1. [Upgrading the **worker** nodes](UpgradingMIPFederation.md#UpgradingWorkers) -1. [Redeploying](UpgradingMIPFederation.md#Redeploying) diff --git a/Federation/doc/SynchronizingKeycloakRoles.md b/Federation/doc/SynchronizingKeycloakRoles.md deleted file mode 100644 index 459c771c..00000000 --- a/Federation/doc/SynchronizingKeycloakRoles.md +++ /dev/null @@ -1,55 +0,0 @@ -[Operating the MIP Federation](OperatingMIPFederation.md#SynchronizingKeycloakRoles) -> `Synchronizing the KeyCloak Roles` - -# Synchronizing the KeyCloak Roles -The Authentication (AuthN) and the authorization (AuthZ) processes of the MIP are managed by *KeyCloak*, which is usually, as the opposite of the *Local* MIP, an **external** service, which is by default in the EBRAINS infrastructure. -The roles are taken into account by the MIP Web interface, each role name being parsed and then checked to show (or not) certain items (features, pathologies, datasets...) -Then, within a federation, which may have many different pathologies and datasets, amongst many different nodes, the number of roles can rapidly increase, and also, each time you change anything related to these things, you will have to change roles accordingly into the KeyCloak interface (to have an idea of the MIP-related roles naming convention, read this [guide](../../documentation/UserAuthorizations.md). - -The KeyCloak's interface is excruciatingly slow and clearly not ergonomic at all. Additionally, it's way not MIP-comprehensive. All this will make you lose a considerable amount of time, and with the complexity and sensitivity of this process, you will probably do mistakes as well! - -In an attempt to drastically reduce the required time to do this, and also eliminate the risk of human errors, a roles synchronization script has been written, and is introduced into the *mip-deployment* starting with the MIP 6.5 release. -This script should be automatically installed in the **pusher** node, as it **has** to be executed from there! - -### Preparing the *realm-management* client and the *realmadmin* realm user in KeyCloak -Prior to run anything here, you need to make sure that the KeyCloak server's *realm* is ready to be used by the script. In other words, the *keycloak_roles_sync.py* script needs a realm client (with certain configurations and roles) and a realm user (using this realm client). -For that purpose, you will need to use a KeyCloak Administrator who has all the required privileges to fully manage the realm. - -In order to do this, follow this [guide](PreparingKeycloakRealmClient.md). - -### Exporting the MIP Federation data structure -As the synchronization script will use a JSON file representing the federation structure (nodes, pathologies, datasets), we have to generate this file with the *mip* script, prior to using the *keycloak_roles_sync.py* script. - -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0) -``` -mip --pusher --federation --export-data-structure data consolidate > mip_data_structure.json -``` -The *mip_data_structure.json* can be any other file you want. -Obviously, you'll have to **re**-run this anytime you will change **anything** about the -* nodes (adding or removing a node, or changing its hostname) -* pathologies (adding, removing or renaming or moving from/to any node) -* datasets (adding, removing, or renaming, or moving from/to any node) - -### Using the *keycloak_roles_sync.py* script -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0) -``` -keycloak_roles_sync.py --admin-client-secret --admin-password '' --sync-client-id '' --data-structure-json-file mip_data_structure.json -``` - -For this script, the different parameters are -|Parameter|Description|Mandatory| -| -- | -- | -- | -|*--server-url* \*|KeyCloak server URL (if different from the default EBRAINS one)|| -|*--realm-name \*|KeyCloak Realm name (if different from the default EBRAINS one)|| -|*--admin-client-id \*|KeyCloak Realm Administrator client-id (if different from the default EBRAINS one)|| -|*--admin-client-secret \*|KeyCloak Realm Administrator client-secret|**yes**| -|*--admin-username \*|KeyCloak Realm Administrator username (if different from the default EBRAINS one)|| -|*--admin-password '\'*||**yes**| -|*--sync-client-id \*|KeyCloak Realm client-id to create/synchronize. You are strongly encourage to use the MIP Federation name here!|**yes**| -|*--copy-users-from-client-id \*|If you want to copy the user/group relationships from another client/group_prefix|| -|*--data-structure-json-file \*|JSON data structure file to use as a pattern for roles/groups creation/synchronization|**yes**| - -Alternatively, you can generate the JSON structure with the *mip* script, and then, "pipe" it to the *keycloak_roles_sync.py* script. In this sense, the last parameter here is not "really" mandatory. -This should look like -``` -mip --pusher --federation --export-data-structure data consolidate | keycloak_roles_sync.py --admin-client-secret --admin-password '' --sync-client-id '' -``` diff --git a/Federation/doc/UpgradingMIPFederation.md b/Federation/doc/UpgradingMIPFederation.md deleted file mode 100644 index 43c556e8..00000000 --- a/Federation/doc/UpgradingMIPFederation.md +++ /dev/null @@ -1,159 +0,0 @@ -[Federated MIP Deployment](Readme.md#UpgradingMIPFederation) -> `Upgrading the MIP Federation` - -# Upgrading the MIP Federation -## Upgrading the **pusher** node -### Check-list -* Connect to the **pusher** node as *mipadmin* -* If you're in the *tmux* session, detach it, prior to upgrading the node -* Make sure you're not in the *exareme* deployment folder - ``` - cd - ``` - -### Installing the new version of the *mip* script -``` -git clone https://github.com/HBPMedical/mip-deployment -``` -``` -sudo mip-deployment/mip --pusher --federation --self --force install -``` -As it was already explained in the [**pusher** preparation](PreparingPusher.md), there are other parameters you can use here to install another specific version of the *MIP* (and they can be used in the case of the *mip script* install as well). - -#### Cleanup -``` -rm -rf mip-deployment -``` - -### Installing the new *MIP* version -``` -sudo mip --pusher --federation install -``` -Again, as explained in the [**pusher** preparation](PreparingPusher.md), if you have to install a specific version, use the documented parameters, and don't hesitate to call *mip --help*. - -When upgrading a **pusher** or **ui** node, a backup of the current installation is made. Then, the current installation folder is deleted, the new one is cloned from github, and finally, the backup is automatically restored. - -### Fixing the pre-6.5 folders after the automatic restore -When upgrading to *MIP 6.5* from an older version, there's a breaking change in the *exareme* deployment folder, which means that the restore will result in some folders being put in the wrong place. Let's fix it: - -``` -sudo cp -r /opt//exareme/Federated-Deployment/Compose-Files /opt//exareme/Federated-Deployment/docker-swarm/ -``` -``` -sudo rm -r /opt//exareme/Federated-Deployment/Compose-Files -``` -``` -sudo cp -r /opt//exareme/Federated-Deployment/Docker-Ansible /opt//exareme/Federated-Deployment/docker-swarm/ -``` -``` -sudo rm -r /opt//exareme/Federated-Deployment/Docker-Ansible -``` - -### Fixing the configuration/permissions -``` -sudo mip --pusher --federation configure all -``` - -You can now reconnect the tmux session to work on the other nodes -``` -mip --pusher --federation tmux -``` - -## Upgrading the **master** node -### Check-list -* In the *tmux* session (opened as **mipadmin** user), go in the **ms** (master) window (#3) - -### Installing the new version of the *mip* script -``` -git clone https://github.com/HBPMedical/mip-deployment -``` -``` -sudo mip-deployment/mip --self --force install -``` -As it was already explained in the [**master** preparation](PreparingMaster.md), there are other parameters you can use here to install another specific version of the *MIP* (and they can be used in the case of the *mip script* install as well). - -#### Cleanup - -``` -rm -rf mip-deployment -``` - -### Installing the new *MIP* version -``` -sudo mip --node-type ms install -``` -Again, as explained in the [**master** preparation](PreparingMaster.md), if you have to install a specific version, use the documented parameters, and don't hesitate to call *mip --help*. - -## Upgrading the **ui** node -### Check-list -* In the *tmux* session (opened as **mipadmin** user), in the **ui** window (#4) -* Make sure you're not in the *mip-deployment* folder - ``` - cd - ``` - -### Installing the new version of the *mip* script -``` -git clone https://github.com/HBPMedical/mip-deployment -``` -``` -sudo mip-deployment/mip --self --force install -``` -As it was already explained in the [**ui** preparation](PreparingUI.md), there are other parameters you can use here to install another specific version of the *MIP* (and they can be used in the case of the *mip script* install as well). - -#### Cleanup - -``` -rm -rf mip-deployment -``` - -### Installing the new *MIP* version -``` -sudo mip --node-type ui install -``` -Again, as explained in the [**ui** preparation](PreparingUI.md), if you have to install a specific version, use the documented parameters, and don't hesitate to call *mip --help*. - -## Upgrading the **wk** nodes -In the *tmux* session (opened as **mipadmin** user), do this in each worker windows (#5-#n) - -### Installing the new version of the *mip* script -``` -git clone https://github.com/HBPMedical/mip-deployment -``` -``` -sudo mip-deployment/mip --pusher --federation --self --force install -``` -As it was already explained in the [**workers** preparation](PreparingWorkers.md), there are other parameters you can use here to install another specific version of the *MIP* (and they can be used in the case of the *mip script* install as well). - -#### Cleanup -``` -rm -rf mip-deployment -``` - -### Installing the new *MIP* version -``` -sudo mip --node-type wk install -``` -Again, as explained in the [**workers** preparation](PreparingWorkers.md), if you have to install a specific version, use the documented parameters, and don't hesitate to call *mip --help*. - -## Redeploying -### Detach the *tmux* session -``` -CTRL+j d -``` - -### Regenerate the *tmux* session -``` -mip --pusher --federation --force tmux -``` - -### Stop the services -In the *tmux* session (opened as **mipadmin** user), in the **pusher** window (#0) -``` -mip --pusher --federation service stop -``` - -### Deploy the services -Still in the window #0 -``` -mip --pusher --federation service deploy -``` diff --git a/Federation/docker-compose.yml b/Federation/docker-compose.yml deleted file mode 100644 index a0376ed8..00000000 --- a/Federation/docker-compose.yml +++ /dev/null @@ -1,94 +0,0 @@ -version: '3.7' - -services: - portalbackend_db: - image: postgres:11.3-alpine - volumes: - - ./.stored_data/portalbackenddb:/var/lib/postgresql/data - hostname: portalbackend_db - environment: - POSTGRES_PASSWORD: test - command: -p 5433 - expose: - - 5433 - restart: unless-stopped - - create_dbs: - image: hbpmip/create-databases:1.1.0 - depends_on: - - portalbackend_db - environment: - DB_HOST: portalbackend_db - DB_PORT: 5433 - DB_ADMIN_USER: postgres - DB_ADMIN_PASSWORD: test - DB4: portal - USER4: portal - PASSWORD4: portalpwd - restart: on-failure - - portalbackend: - image: hbpmip/portal-backend:${PORTALBACKEND} - ports: - - '8080:8080' - - '8089:8089' - environment: - ### API ### - LOG_LEVEL: INFO - LOG_LEVEL_FRAMEWORK: INFO - AUTHENTICATION: ${KEYCLOAK_AUTHENTICATION} - ### Database ### - PORTAL_DB_URL: jdbc:postgresql://portalbackend_db:5433/portal - PORTAL_DB_SERVER: portalbackend_db:5433 - PORTAL_DB_USER: portal - PORTAL_DB_PASSWORD: portalpwd - ### Exareme2 ### - EXAREME2_URL: ${EXAREME2_URL} - ### Exareme ### - EXAREME_URL: http://${EXAREME_IP}:9090 - ### Keycloak ### - KEYCLOAK_AUTH_URL: ${KEYCLOAK_PROTOCOL}://${KEYCLOAK_URL}/auth/ - KEYCLOAK_REALM: ${KEYCLOAK_REALM} - KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} - KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET} - KEYCLOAK_SSL_REQUIRED: ${KEYCLOAK_SSL_REQUIRED} - depends_on: - - create_dbs - volumes: - - ./config:/opt/portal/api - - ./logs:/opt/portal/logs - restart: unless-stopped - - gateway: - image: hbpmip/gateway:${GATEWAY} - environment: - ENGINE_TYPE: exareme - ENGINE_BASE_URL: http://portalbackend:8080/services/ - GATEWAY_PORT: 8081 - ports: - - '8081:8081' - depends_on: - - portalbackend - restart: unless-stopped - - frontend: - image: hbpmip/portal-frontend:${FRONTEND} - depends_on: - - gateway - ports: - - '80:80' - - '443:443' - volumes: - - ./.stored_data/caddy/caddy_data:/data - environment: - ERROR_LOG_LEVEL: info - PORTAL_BACKEND_SERVER: portalbackend:8080 - PORTAL_BACKEND_CONTEXT: services - GATEWAY_SERVER: gateway:8081 - INSTANCE_NAME: 'MIP ${MIP}' - VERSION: 'Frontend: ${FRONTEND}, Gateway: ${GATEWAY}, Backend: ${PORTALBACKEND}, Exareme: ${EXAREME}' - TRACKER_ID: UA-80660232-5 - DATACATALOGUE_SERVER: ${DATACATALOGUE_PROTOCOL}://${DATACATALOGUE_HOST} - PUBLIC_MIP_HOST: ${PUBLIC_MIP_HOST} - PUBLIC_MIP_PROTOCOL: ${PUBLIC_MIP_PROTOCOL} - restart: unless-stopped From 9406da4fb67cc624e26c806ca88a0efa4a116d1c Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Thu, 20 Jul 2023 07:27:47 -0700 Subject: [PATCH 4/8] Removed Galaxy remnants. --- kubernetes/values.yaml | 7 +------ mip | 14 +++++++------- tests/backend_components/docker-compose.yml | 5 ----- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml index 5c3a9d16..67520ab8 100644 --- a/kubernetes/values.yaml +++ b/kubernetes/values.yaml @@ -18,7 +18,7 @@ mip: PUBLIC_PROTOCOL: "" PUBLIC_HOST: "" NAME: "MIP {{ .Values.mip.version }}" - VERSION_STRING: "Frontend: {{ .Values.frontend.image.version }}, Gateway: {{ .Values.gateway.image.version }}, Backend: {{ .Values.portalbackend.image.version }}, Engine1 (Exareme): {{ .Values.engines.exareme.image.version }}, Engine2 (Exareme2): {{ .Values.engines.exareme2.image.version }}, Galaxy: {{ .Values.galaxy.image.version }}" + VERSION_STRING: "Frontend: {{ .Values.frontend.image.version }}, Gateway: {{ .Values.gateway.image.version }}, Backend: {{ .Values.portalbackend.image.version }}, Engine1 (Exareme): {{ .Values.engines.exareme.image.version }}, Engine2 (Exareme2): {{ .Values.engines.exareme2.image.version }}" datacatalogue: PROTOCOL: https @@ -117,8 +117,3 @@ keycloak: storage1: data_path: /opt/mip-deployment/config/keycloak/HBPTheme data_size: 100Mi - -galaxy: - image: - name: hbpmip/galaxy - version: 1.3.4 diff --git a/mip b/mip index fc6364e5..48c1b419 100755 --- a/mip +++ b/mip @@ -35,13 +35,13 @@ DOCKERHUB_APP_TOKEN="p5BLk2iEmpkJoJINBl2lIL2DmpnKmChGBlLkk22CAHLNKJ2Kpl2lK3ApmHn DOCKER_PROJECT_NAME="mip" DOCKER_FEDERATION_PROJECT_NAME="mipfederation" -MIP_COMPONENTS="frontend gateway portalbackend portalbackend_db galaxy keycloak keycloak_db create_dbs exareme_master exareme_keystore" -MIP_COMPONENT_LABELS="Portal Frontend,Gateway,Portal Backend,Portal Backend PostgreSQL DB,Galaxy,KeyCloak,KeyCloak DB,Create DBs,Exareme Master,Exareme Keystore" -MIP_COMPONENT_IMAGES=("portainer/portainer" "bitnami/consul" "hbpmip/exareme" "hbpmip/galaxy" "postgres" "hbpmip/create-databases" "hbpmip/portal-backend" "hbpmip/gateway" "hbpmip/portal-frontend" "jboss/keycloak") +MIP_COMPONENTS="frontend gateway portalbackend portalbackend_db keycloak keycloak_db create_dbs exareme_master exareme_keystore" +MIP_COMPONENT_LABELS="Portal Frontend,Gateway,Portal Backend,Portal Backend PostgreSQL DB,KeyCloak,KeyCloak DB,Create DBs,Exareme Master,Exareme Keystore" +MIP_COMPONENT_IMAGES=("portainer/portainer" "bitnami/consul" "hbpmip/exareme" "postgres" "hbpmip/create-databases" "hbpmip/portal-backend" "hbpmip/gateway" "hbpmip/portal-frontend" "jboss/keycloak") MIP_FEDERATION_MS_COMPONENTS="exareme-master exareme-keystore" MIP_FEDERATION_MS_COMPONENT_LABELS="Exareme Master,Exareme Keystore" -MIP_FEDERATION_UI_COMPONENTS="frontend gateway portalbackend portalbackend_db galaxy create_dbs" -MIP_FEDERATION_UI_COMPONENT_LABELS="Portal Frontend,Gateway,Portal Backend,Portal Backend PostgreSQL DB,Galaxy,Create DBs" +MIP_FEDERATION_UI_COMPONENTS="frontend gateway portalbackend portalbackend_db create_dbs" +MIP_FEDERATION_UI_COMPONENT_LABELS="Portal Frontend,Gateway,Portal Backend,Portal Backend PostgreSQL DB,Create DBs" MIP_FEDERATION_WK_COMPONENTS="exareme" MIP_FEDERATION_WK_COMPONENT_LABELS="Exareme" CONFIGURE_PARTS="docker-mtu user ssh pusher pathologies link protocol host exareme-ip logs data keycloak dataset-compilation all" @@ -1081,9 +1081,9 @@ Operate the Human Brain Project MIP, whether it's a local setup or a federation --host [PUBLIC_MIP_HOST] Set the hostname/IP on which the MIP will listen. For "local" installation and "ui" federation node. --component [MIP_COMPONENT] Specific MIP component which can be targetted (in logs, install, version, pull, start, stop, restart action). Typical MIP_COMPONENT for "local" MIP: - [frontend|gateway|portalbackend|portalbackend_db|galaxy|keycloak|keycloak_db|create_dbs|exareme_master|exareme_keystore] + [frontend|gateway|portalbackend|portalbackend_db|keycloak|keycloak_db|create_dbs|exareme_master|exareme_keystore] Typical MIP_COMPONENT for "ui" federation node: - [frontend|gateway|portalbackend|portalbackend_db|galaxy|create_dbs] + [frontend|gateway|portalbackend|portalbackend_db|create_dbs] --pathology [DATASET_PATHOLOGY(IES)] In case of data compile, specify the pathology or pathologies (comma-separated) to compile. --with-keycloak-authentication Use Keycloak authentication. Complementary keycloak parameters may be set, or they may be asked at diff --git a/tests/backend_components/docker-compose.yml b/tests/backend_components/docker-compose.yml index 52b6b09e..2be28819 100644 --- a/tests/backend_components/docker-compose.yml +++ b/tests/backend_components/docker-compose.yml @@ -227,11 +227,6 @@ services: EXAREME2_URL: http://172.17.0.1:5000 ### Exareme ### EXAREME_URL: http://exareme_master:9090 - ### Galaxy ### - #GALAXY_URL: http://galaxy - #GALAXY_API_KEY: d14a4cc5eebf805eb2ff261374ed08a2 - #GALAXY_USERNAME: admin - #GALAXY_PASSWORD: password ### Keycloak ### AUTHENTICATION: 0 # Should be enabled for keycloak KEYCLOAK_AUTH_URL: http://172.17.0.1/auth/ From 10662a7bf7a907ed9e01e353d588dafced2bbf57 Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Tue, 1 Aug 2023 16:12:37 +0300 Subject: [PATCH 5/8] Integration with new portalbackend authentication adapter. --- config/caddy/Caddyfile | 32 +++++++++++++++----- federation_info.py | 2 +- tests/backend_components/docker-compose.yml | 5 +-- tests/frontend_components/docker-compose.yml | 4 ++- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/config/caddy/Caddyfile b/config/caddy/Caddyfile index 3d4b7e99..0348097d 100644 --- a/config/caddy/Caddyfile +++ b/config/caddy/Caddyfile @@ -44,23 +44,41 @@ import rp-{$MIP_LINK:direct} {$KEYCLOAK_HOST} } + + ### Portal-Backend Keycloak Authentication ---------------> ### + handle /services/sso/* { uri replace /services/ /{$PORTAL_BACKEND_CONTEXT:services}/ import rp-{$MIP_LINK:direct} {$PORTAL_BACKEND_SERVER} } - handle_path /services/* { - import rp-{$MIP_LINK:direct} {$GATEWAY_SERVER} + handle /services/oauth2/* { + uri replace /services/ /{$PORTAL_BACKEND_CONTEXT:services}/ + import rp-{$MIP_LINK:direct} {$PORTAL_BACKEND_SERVER} + } + + handle /services/login/oauth2/* { + uri replace /services/ /{$PORTAL_BACKEND_CONTEXT:services}/ + import rp-{$MIP_LINK:direct} {$PORTAL_BACKEND_SERVER} } - # keycloak redirect on /services/ after login. In that case, redirect to / + handle /services/logout { + uri replace /services/ /{$PORTAL_BACKEND_CONTEXT:services}/ + import rp-{$MIP_LINK:direct} {$PORTAL_BACKEND_SERVER} + } + + # Keycloak redirects on /services/ after login and logout. In that case, redirect to / redir /services/ / permanent - # hotfix for whitelabel error after login - redir /services/error / temporary + ### <--------------- Portal-Backend Keycloak Authentication ### + + + handle_path /services/* { + import rp-{$MIP_LINK:direct} {$GATEWAY_SERVER} + } handle_errors { - @4xx expression `{http.error.status_code} == 401 || {http.error.status_code} == 404` + @4xx expression `{http.error.status_code} == 404` redir * / temporary } -} +} \ No newline at end of file diff --git a/federation_info.py b/federation_info.py index 9ef8232a..c59b3d3c 100644 --- a/federation_info.py +++ b/federation_info.py @@ -21,7 +21,7 @@ def cli(): LOG_FILE_CHUNK_SIZE = 1024 # Will read the logfile in chunks TIMESTAMP_REGEX = ( - r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}" # 2022-04-13 18:25:22.875 + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z" # 2022-04-13T18:25:22.875Z ) EXPERIMENT_FINISHED_PATTERN = rf"({TIMESTAMP_REGEX}) INFO .*? User -> (.*?) ,Endpoint.*?Finished the experiment: .*?uuid=(.*?), name.*?, status=(.*?), result.*?, finished=(.*?), algorithm=(.*?), algorithmId.*? created=(.*?), updated.*?" EXPERIMENT_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f" diff --git a/tests/backend_components/docker-compose.yml b/tests/backend_components/docker-compose.yml index 2be28819..3a313222 100644 --- a/tests/backend_components/docker-compose.yml +++ b/tests/backend_components/docker-compose.yml @@ -183,7 +183,7 @@ services: restart: unless-stopped portalbackend_db: - image: postgres:11.3-alpine + image: postgres:11.20-alpine volumes: - ./.stored_data/portalbackenddb:/var/lib/postgresql/data hostname: portalbackend_db @@ -228,7 +228,8 @@ services: ### Exareme ### EXAREME_URL: http://exareme_master:9090 ### Keycloak ### - AUTHENTICATION: 0 # Should be enabled for keycloak + AUTHENTICATION: 0 +# AUTHENTICATION: 1 # Should be enabled for keycloak KEYCLOAK_AUTH_URL: http://172.17.0.1/auth/ KEYCLOAK_REALM: MIP KEYCLOAK_CLIENT_ID: MIP diff --git a/tests/frontend_components/docker-compose.yml b/tests/frontend_components/docker-compose.yml index e4c3b192..f7f29c9d 100644 --- a/tests/frontend_components/docker-compose.yml +++ b/tests/frontend_components/docker-compose.yml @@ -18,10 +18,12 @@ services: - ENGINE_TYPE=exareme - ENGINE_BASE_URL=http://172.17.0.1:8080/services/ - AUTH_SKIP=true + - AUTH_ENABLE_SSO=false # - AUTH_ENABLE_SSO=true # Should be enabled for Keycloak - BASE_URL_CONTEXT=services - GATEWAY_PORT=8081 - CACHE_ENABLED=false + - NODE_ENV=development links: - gateway-db depends_on: @@ -52,10 +54,10 @@ services: EXTERNAL_MIP_PROTOCOL: http KEYCLOAK_HOST: http://keycloak:8095 KEYCLOAK_AUTH_PATH: /auth + PORTALBACKEND_AUTH_URL: /oauth2/authorization/keycloak MIP_LINK: direct restart: unless-stopped - # keycloak_db: # image: postgres:12.2 # volumes: From e6a537eb892e4b52d9a623b30fb63d526d314bcf Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Fri, 4 Aug 2023 17:23:26 +0300 Subject: [PATCH 6/8] Complete removal of Exareme1. Refactored and simplified the test deployment. Removed unecessary tests and combined the backend and frontend docker-compose files. Removed the dependency on poetry and used a simple requirements.txt --- .../portalbackend-integration-tests.yml | 39 +- .versions_env | 1 - README.md | 3 +- federation_info.py | 5 +- kubernetes/.direct | 2 - kubernetes/.direct.internal_auth | 2 - kubernetes/.direct.internal_auth.http | 2 - kubernetes/.direct.no_auth | 2 - kubernetes/.proxied | 2 - kubernetes/.proxied.internal_auth | 2 - kubernetes/.proxied.internal_auth.http | 2 - kubernetes/.proxied.no_auth | 2 - kubernetes/README.md | 4 - kubernetes/doc/Readme.md | 22 - kubernetes/templates/mip-config.yaml | 2 - kubernetes/templates/portalbackend.yaml | 5 - kubernetes/values.yaml | 6 - .../config/localnodes_config.json | 1 - tests/backend_components/poetry.lock | 735 ------------------ tests/backend_components/pyproject.toml | 17 - tests/backend_components/tasks.py | 150 ---- .../test_exareme2_post_experiment_fail.py | 137 ---- .../test_exareme_post_experiment_fail.py | 162 ---- .../test_exareme_post_experiment.py | 65 -- .../test_get_algorithms_request.py | 27 - tests/config/localnodes_config.json | 1 + .../docker-compose.yml | 232 +++--- tests/frontend_components/docker-compose.yml | 95 --- tests/requirements.txt | 3 + tests/start.sh | 23 +- tests/stop.sh | 3 +- tests/test.sh | 10 +- .../test_exareme2_post_experiment.py | 148 +--- .../test_exareme2_post_experiment_fail.py | 98 +++ .../test_federation_info.py | 4 +- tests/tests/test_get_algorithms_request.py | 13 + .../test_get_pathologies_request.py | 0 37 files changed, 275 insertions(+), 1752 deletions(-) delete mode 100644 tests/backend_components/config/localnodes_config.json delete mode 100644 tests/backend_components/poetry.lock delete mode 100644 tests/backend_components/pyproject.toml delete mode 100644 tests/backend_components/tasks.py delete mode 100644 tests/backend_components/test_integration/test_fail_cases/test_exareme2_post_experiment_fail.py delete mode 100644 tests/backend_components/test_integration/test_fail_cases/test_exareme_post_experiment_fail.py delete mode 100644 tests/backend_components/test_integration/test_success_cases/test_exareme_post_experiment.py delete mode 100644 tests/backend_components/test_integration/test_success_cases/test_get_algorithms_request.py create mode 100644 tests/config/localnodes_config.json rename tests/{backend_components => }/docker-compose.yml (51%) delete mode 100644 tests/frontend_components/docker-compose.yml create mode 100644 tests/requirements.txt rename tests/{backend_components/test_integration/test_success_cases => tests}/test_exareme2_post_experiment.py (80%) create mode 100644 tests/tests/test_exareme2_post_experiment_fail.py rename tests/{backend_components/test_integration => tests}/test_federation_info.py (71%) create mode 100644 tests/tests/test_get_algorithms_request.py rename tests/{backend_components/test_integration/test_success_cases => tests}/test_get_pathologies_request.py (100%) diff --git a/.github/workflows/portalbackend-integration-tests.yml b/.github/workflows/portalbackend-integration-tests.yml index 6ca2febc..93b94a5c 100644 --- a/.github/workflows/portalbackend-integration-tests.yml +++ b/.github/workflows/portalbackend-integration-tests.yml @@ -1,16 +1,12 @@ name: CI - on: push: branches: [ master ] pull_request: branches: [ master ] - - jobs: - portal-backend-integration-tests: runs-on: ubuntu-latest steps: @@ -22,40 +18,13 @@ jobs: with: python-version: 3.8 - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: 1.3.2 - virtualenvs-create: true - virtualenvs-in-project: true - - - name: Load cached venv - id: cached-poetry-dependencies - uses: actions/cache@v3 - with: - path: ./tests/backend_components/.venv - key: venv-${{ runner.os }}-${{ hashFiles('./tests/backend_components/pyproject.toml') }} - - - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - working-directory: ./tests/backend_components/ - run: poetry install --no-interaction - - - name: Deploy portalbackend, exareme and exareme2 + - name: Deploy portalbackend and exareme2 working-directory: ./tests/ run: bash start.sh - name: Get deployment status run: docker ps - - name: Run the success tests - working-directory: ./tests/backend_components/test_integration/ - run: poetry run pytest test_success_cases - - - name: Run the failure tests - working-directory: ./tests/backend_components/test_integration/ - run: poetry run pytest test_fail_cases - - - name: Run the federation info tests - working-directory: ./tests/backend_components/test_integration/ - run: poetry run pytest test_federation_info.py + - name: Run the tests + working-directory: ./tests/ + run: bash test.sh diff --git a/.versions_env b/.versions_env index d96ef042..45ab1f41 100644 --- a/.versions_env +++ b/.versions_env @@ -1,5 +1,4 @@ EXAREME2=0.18.1 -EXAREME=24.5.1 PORTALBACKEND=testing GATEWAY=1.5.0 FRONTEND=9.3.1 diff --git a/README.md b/README.md index 657d70d8..e28f2625 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,7 @@ The "short names" listed here represent the different MIP components, as well as * [keycloak](https://github.com/keycloak/keycloak-containers): The "AuthN/AuthZ" system, based on KeyCloak (this component usually doesn't run in a *federated* MIP, as an "external" KeyCloak service does the job). In case this *local* "embedded" component is used, you may need to know some details, which you can find [here](documentation/UsersConfiguration.md) * [keycloak_db](https://github.com/docker-library/postgres): The KeyCloak's database, required only if the *keycloak* component needs to be used * [create_dbs](https://github.com/HBPMedical/docker-create-databases): The *one shot* container which creates and populates the DBs when required -* [exareme_master](https://github.com/madgik/exareme): The "Analysis Engine" offers the federated (also used by the *local* MIP) analysis capabilities -* [exareme_keystore](https://github.com/bitnami/bitnami-docker-consul): A "Key-Value" storage service used by the different nodes (the workers and the master in a *federated* MIP, or the same machine in a *local* MIP) to store/exchange variables +* [exareme2](https://github.com/madgik/exareme2): The "Analysis Engine" offers the federated (also used by the *local* MIP) analysis capabilities ## Deployment ### Local diff --git a/federation_info.py b/federation_info.py index c59b3d3c..be74e86a 100644 --- a/federation_info.py +++ b/federation_info.py @@ -19,11 +19,12 @@ def cli(): # Experiment log is of format: # 2022-10-26 06:32:59.145 INFO 12 --- [ Thread-7] eu.hbp.mip.utils.Logger : User -> anonymous ,Endpoint -> (POST) /experiments ,Info -> Finished the experiment: ExperimentDAO(uuid=760d9ded-f66e-49b6-ad34-dbae1dcad67b, name=One Way Anova, createdBy=UserDAO(username=anonymous, subjectId=anonymousId, fullname=anonymous, email=anonymous@anonymous.com, agreeNDA=true), workflowHistoryId=null, status=success, result=[{"anova_table":{"n_obs":825.0,"y_label":"leftententorhinalarea","x_label":"neurodegenerativescategories","df_residual":822.0,"df_explained":2.0,"ss_residual":37.01440328649596,"ss_explained":15.04582027432878,"ms_residual":0.045029687696467105,"ms_explained":7.52291013716439,"p_value":1.1102230246251565E-16,"f_stat":167.06556323184572},"tuckey_test":[{"groupA":"PD","groupB":"AD","meanA":1.7211585185185185,"meanB":1.4287119708029197,"diff":0.29244654771559886,"se":0.01659898611255717,"t_stat":17.61833799561784,"p_tuckey":0.001},{"groupA":"PD","groupB":"MCI","meanA":1.7211585185185185,"meanB":1.5088550684931508,"diff":0.21230345002536777,"se":0.020484309422929142,"t_stat":10.364198550317033,"p_tuckey":0.001},{"groupA":"AD","groupB":"MCI","meanA":1.4287119708029197,"meanB":1.5088550684931508,"diff":-0.08014309769023109,"se":0.02174314706679941,"t_stat":-3.6859014678976805,"p_tuckey":0.001}],"min_max_per_group":{"categories":["PD","AD","MCI"],"min":[1.0429,0.39954,0.39954],"max":[2.3952,1.9971,2.1113]},"ci_info":{"sample_stds":{"PD":0.1745754772473241,"AD":0.24280162815714373,"MCI":0.23944727425923165},"means":{"PD":1.7211585185185185,"AD":1.4287119708029197,"MCI":1.5088550684931508},"m-s":{"PD":1.5465830412711945,"AD":1.185910342645776,"MCI":1.269407794233919},"m+s":{"PD":1.8957339957658426,"AD":1.6715135989600634,"MCI":1.7483023427523825}}}], finished=2022-10-26 06:32:59.130, algorithm={"name":"anova_oneway","desc":null,"label":"One Way Anova","type":"exareme2","parameters":[{"name":"y","desc":null,"label":"y","type":null,"columnValuesSQLType":null,"columnValuesIsCategorical":null,"value":"leftententorhinalarea","defaultValue":null,"valueType":null,"valueNotBlank":null,"valueMultiple":null,"valueMin":null,"valueMax":null,"valueEnumerations":null},{"name":"x","desc":null,"label":"x","type":null,"columnValuesSQLType":null,"columnValuesIsCategorical":null,"value":"neurodegenerativescategories","defaultValue":null,"valueType":null,"valueNotBlank":null,"valueMultiple":null,"valueMin":null,"valueMax":null,"valueEnumerations":null},{"name":"pathology","desc":null,"label":"pathology","type":null,"columnValuesSQLType":null,"columnValuesIsCategorical":null,"value":"dementia:0.1","defaultValue":null,"valueType":null,"valueNotBlank":null,"valueMultiple":null,"valueMin":null,"valueMax":null,"valueEnumerations":null},{"name":"dataset","desc":null,"label":"dataset","type":null,"columnValuesSQLType":null,"columnValuesIsCategorical":null,"value":"desd-synthdata,ppmi","defaultValue":null,"valueType":null,"valueNotBlank":null,"valueMultiple":null,"valueMin":null,"valueMax":null,"valueEnumerations":null},{"name":"filter","desc":null,"label":"filter","type":null,"columnValuesSQLType":null,"columnValuesIsCategorical":null,"value":"{\"condition\": \"AND\", \"rules\": [{\"id\": \"dataset\", \"type\": \"string\", \"value\": [\"desd-synthdata\", \"ppmi\"], \"operator\": \"in\"}, {\"condition\": \"AND\", \"rules\": [{\"id\": \"neurodegenerativescategories\", \"type\": \"string\", \"operator\": \"is_not_null\", \"value\": null}, {\"id\": \"leftententorhinalarea\", \"type\": \"string\", \"operator\": \"is_not_null\", \"value\": null}]}], \"valid\": true}","defaultValue":null,"valueType":null,"valueNotBlank":null,"valueMultiple":null,"valueMin":null,"valueMax":null,"valueEnumerations":null}]}, algorithmId=anova_oneway, created=2022-10-26 06:32:52.589, updated=null, shared=false, viewed=false) + LOG_FILE_CHUNK_SIZE = 1024 # Will read the logfile in chunks TIMESTAMP_REGEX = ( r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z" # 2022-04-13T18:25:22.875Z ) -EXPERIMENT_FINISHED_PATTERN = rf"({TIMESTAMP_REGEX}) INFO .*? User -> (.*?) ,Endpoint.*?Finished the experiment: .*?uuid=(.*?), name.*?, status=(.*?), result.*?, finished=(.*?), algorithm=(.*?), algorithmId.*? created=(.*?), updated.*?" +EXPERIMENT_FINISHED_PATTERN = rf"({TIMESTAMP_REGEX}) INFO .*? User -> (.*?) , Endpoint.*?Experiment finished: .*?uuid=(.*?), name.*?, status=(.*?), result.*?, finished=(.*?), algorithm=(.*?), algorithmId.*? created=(.*?), updated.*?" EXPERIMENT_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f" @@ -51,7 +52,7 @@ def print_audit_entry(log_line): par["name"]: par["value"] for par in algorithm_properties["parameters"] } print( - f"{log_timestamp} - {user} - EXPERIMENT_FINISHED - {uuid} - {algorithm_properties['name']} - {parameters['pathology']} - {parameters['dataset']} - {status} - {exp_time_to_finish} - {parameters}" + f"{log_timestamp} - {user} - EXPERIMENT_FINISHED - {uuid} - {algorithm_properties['name']} - {parameters['pathology']} - {parameters['dataset']} - {status} - {exp_time_to_finish} - {parameters} - {algorithm_properties['preprocessing']}" ) diff --git a/kubernetes/.direct b/kubernetes/.direct index 06f3e051..533321a9 100644 --- a/kubernetes/.direct +++ b/kubernetes/.direct @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.direct.internal_auth b/kubernetes/.direct.internal_auth index c11fc8bc..b9549a2b 100644 --- a/kubernetes/.direct.internal_auth +++ b/kubernetes/.direct.internal_auth @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.direct.internal_auth.http b/kubernetes/.direct.internal_auth.http index 6d402f75..0bf03a1e 100644 --- a/kubernetes/.direct.internal_auth.http +++ b/kubernetes/.direct.internal_auth.http @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.direct.no_auth b/kubernetes/.direct.no_auth index 6aabc740..33d7fcce 100644 --- a/kubernetes/.direct.no_auth +++ b/kubernetes/.direct.no_auth @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.proxied b/kubernetes/.proxied index 5669150f..b79efcfd 100644 --- a/kubernetes/.proxied +++ b/kubernetes/.proxied @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.proxied.internal_auth b/kubernetes/.proxied.internal_auth index 63a30f9e..9f8c6b38 100644 --- a/kubernetes/.proxied.internal_auth +++ b/kubernetes/.proxied.internal_auth @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.proxied.internal_auth.http b/kubernetes/.proxied.internal_auth.http index 36875f7d..5beada05 100644 --- a/kubernetes/.proxied.internal_auth.http +++ b/kubernetes/.proxied.internal_auth.http @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/.proxied.no_auth b/kubernetes/.proxied.no_auth index c6d5eb13..1c1bb0c2 100644 --- a/kubernetes/.proxied.no_auth +++ b/kubernetes/.proxied.no_auth @@ -1,6 +1,4 @@ engines: - exareme: - IP: exareme2: URL: http://:30000 diff --git a/kubernetes/README.md b/kubernetes/README.md index 62116b4b..4daa8ede 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -22,10 +22,6 @@ From now on, most of our deployments will be done with Ubuntu Server 22.04, but ## MIP Components Now, with the Kubernetes (K8s) deployment, we have 3 main, big components, which come as Helm charts: -### [Exareme](https://github.com/madgik/exareme/tree/master/Federated-Deployment/kubernetes) -* [exareme](https://github.com/madgik/exareme/tree/master/Exareme-Docker): The "Analysis Engine" offers the federated (also used by the *local* MIP) analysis capabilities -* [exareme_keystore](https://github.com/bitnami/bitnami-docker-consul): A "Key-Value" storage service used by the different nodes (the workers and the master in a *federated* MIP, or the same machine in a *local* MIP) to store/exchange variables - ### [Exareme2](https://github.com/madgik/Exareme2/tree/master/kubernetes) (Exareme 2) * [controller](https://github.com/madgik/Exareme2/tree/master/exareme2/controller) diff --git a/kubernetes/doc/Readme.md b/kubernetes/doc/Readme.md index a185a82a..60627fb8 100644 --- a/kubernetes/doc/Readme.md +++ b/kubernetes/doc/Readme.md @@ -78,28 +78,6 @@ sudo chown -R mipadmin.mipadmin /data For a "federated" deployment, you may want to add nodes to your cluster. "microk8s add-node" will give you a **one-time usage** token, which you can use on a worker node to actually "join" the cluster. This process must be repeated on all the worker nodes. -## Exareme -* Install the repository content - ``` - sudo git clone https://github.com/madgik/exareme /opt/exareme - ``` - ``` - sudo chown -R mipadmin.mipadmin /opt/exareme - ``` -* Set the variables in /opt/exareme/Federated-Deployment/kubernetes/values.yaml - * data_path: /data/ - * exareme.convert_csvs: FALSE - * workers: 0 for a "local" deployment, or more for a "federated" deployment -* Label the nodes - For all the worker nodes (even on a "local" deployment where the master and the worker are the **same** machine), add a *worker* label: - ``` - microk8s kubectl label node worker=true - ``` -* Deploy the Helm chart - ``` - microk8s helm3 install exareme /opt/exareme/Federated-Deployment/kubernetes - ``` - ## Exareme2 * Install the repository content ``` diff --git a/kubernetes/templates/mip-config.yaml b/kubernetes/templates/mip-config.yaml index 77b5eb4c..8e03d9ea 100644 --- a/kubernetes/templates/mip-config.yaml +++ b/kubernetes/templates/mip-config.yaml @@ -4,8 +4,6 @@ metadata: name: mip-config namespace: {{ .Values.namespace }} data: - engines.exareme.IP: {{ .Values.engines.exareme.IP }} - engines.exareme.URL: {{ tpl .Values.engines.exareme.URL . }} engines.exareme2.URL: {{ .Values.engines.exareme2.URL }} mip.INSTANCE_NAME: "{{ tpl .Values.mip.NAME . }}" diff --git a/kubernetes/templates/portalbackend.yaml b/kubernetes/templates/portalbackend.yaml index 4ea01a9e..012d345a 100644 --- a/kubernetes/templates/portalbackend.yaml +++ b/kubernetes/templates/portalbackend.yaml @@ -177,11 +177,6 @@ spec: secretKeyRef: name: mip-secret key: portalbackend-db.PORTAL_DB_PASSWORD - - name: EXAREME_URL - valueFrom: - configMapKeyRef: - name: mip-config - key: engines.exareme.URL - name: EXAREME2_URL valueFrom: configMapKeyRef: diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml index 67520ab8..fb0e9088 100644 --- a/kubernetes/values.yaml +++ b/kubernetes/values.yaml @@ -1,11 +1,6 @@ namespace: default engines: - exareme: - IP: "" - URL: "http://{{ .Values.engines.exareme.IP }}:31000" - image: - version: 24.5.1 exareme2: URL: "" image: @@ -77,7 +72,6 @@ frontend: MATOMO_ENABLED: false MATOMO_URL: https://stats.humanbrainproject.eu/ MATOMO_SITE_ID: 29 - EXTERNAL_IP: "{{ .Values.engines.exareme.IP }}" image: name: hbpmip/portal-frontend version: 9.3.1 diff --git a/tests/backend_components/config/localnodes_config.json b/tests/backend_components/config/localnodes_config.json deleted file mode 100644 index eff06db8..00000000 --- a/tests/backend_components/config/localnodes_config.json +++ /dev/null @@ -1 +0,0 @@ -["172.17.0.1:5670", "172.17.0.1:5671", "172.17.0.1:5672"] \ No newline at end of file diff --git a/tests/backend_components/poetry.lock b/tests/backend_components/poetry.lock deleted file mode 100644 index bc4c4c9f..00000000 --- a/tests/backend_components/poetry.lock +++ /dev/null @@ -1,735 +0,0 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - -[[package]] -name = "certifi" -version = "2022.12.7" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "flake8" -version = "3.9.2" -description = "the modular source code checker: pep8 pyflakes and co" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" - -[[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "invoke" -version = "1.7.3" -description = "Pythonic task execution" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, - {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, -] - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] - -[[package]] -name = "mipdb" -version = "2.0.4" -description = "" -category = "dev" -optional = false -python-versions = ">=3.8,<3.9" -files = [ - {file = "mipdb-2.0.4-py3-none-any.whl", hash = "sha256:c1c6401941e3c3942f3c000848f7790b0c4ebfd2b3a87acc61e7d33acec07f83"}, - {file = "mipdb-2.0.4.tar.gz", hash = "sha256:f6a37b435b859f9602530c9a7d65eefa0e80ea11255991eb51f48d07e3532c12"}, -] - -[package.dependencies] -click = ">=8.1,<8.2" -pandas = ">=1.5,<1.6" -pandera = ">=0.13,<0.14" -pymonetdb = ">=1.6,<1.7" -SQLAlchemy = ">=1.3,<1.4" -sqlalchemy_monetdb = ">=1.0,<1.1" - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "numpy" -version = "1.24.2" -description = "Fundamental package for array computing in Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, - {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, - {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, - {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, - {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, - {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, - {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, - {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, - {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, - {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, - {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, -] - -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] - -[[package]] -name = "pandas" -version = "1.5.3" -description = "Powerful data structures for data analysis, time series, and statistics" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, - {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, - {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, - {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, - {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, - {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, - {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, - {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, -] - -[package.dependencies] -numpy = {version = ">=1.20.3", markers = "python_version < \"3.10\""} -python-dateutil = ">=2.8.1" -pytz = ">=2020.1" - -[package.extras] -test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] - -[[package]] -name = "pandera" -version = "0.13.4" -description = "A light-weight and flexible data validation and testing tool for statistical data objects." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pandera-0.13.4-py3-none-any.whl", hash = "sha256:9e91687861406284270add1d467f204630377892e7a4b45809bb7546f0013153"}, - {file = "pandera-0.13.4.tar.gz", hash = "sha256:6ef2b7ee00d3439ac815d4347984421a08502da1020cec60c06dd0135e8aee2f"}, -] - -[package.dependencies] -numpy = ">=1.19.0" -packaging = ">=20.0" -pandas = ">=1.2.0" -pydantic = "*" -typing-inspect = ">=0.6.0" -wrapt = "*" - -[package.extras] -all = ["black", "dask", "fastapi", "frictionless", "geopandas", "hypothesis (>=5.41.1)", "modin", "pandas-stubs (<=1.4.3.220807)", "pyspark (>=3.2.0)", "pyyaml (>=5.1)", "ray (<=1.7.0)", "scipy", "shapely"] -dask = ["dask"] -fastapi = ["fastapi"] -geopandas = ["geopandas", "shapely"] -hypotheses = ["scipy"] -io = ["black", "frictionless", "pyyaml (>=5.1)"] -modin = ["dask", "modin", "ray (<=1.7.0)"] -modin-dask = ["dask", "modin"] -modin-ray = ["modin", "ray (<=1.7.0)"] -mypy = ["pandas-stubs (<=1.4.3.220807)"] -pyspark = ["pyspark (>=3.2.0)"] -strategies = ["hypothesis (>=5.41.1)"] - -[[package]] -name = "pluggy" -version = "0.13.1" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - -[[package]] -name = "pycodestyle" -version = "2.7.0" -description = "Python style guide checker" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] - -[[package]] -name = "pydantic" -version = "1.10.7" -description = "Data validation and settings management using python type hints" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, - {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, - {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, - {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, - {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, - {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, - {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, - {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, - {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, - {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, - {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, - {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, - {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, - {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, - {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, - {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, - {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, - {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, - {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, - {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, - {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, - {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, - {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, - {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, - {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, - {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, - {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, - {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, - {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, - {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, - {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, - {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, - {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, - {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, - {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, - {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] - -[[package]] -name = "pymonetdb" -version = "1.6.4" -description = "Native MonetDB client Python API" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "pymonetdb-1.6.4-py2.py3-none-any.whl", hash = "sha256:d5f8bb9b59551cadb82dc5d045920bd44644764b7176e06095f0b9438ca64a7a"}, - {file = "pymonetdb-1.6.4.tar.gz", hash = "sha256:7f3b47f643f2f6c5780eb7c76f25248b128f1117df60cf4222b7924024602134"}, -] - -[package.extras] -doc = ["sphinx", "sphinx-rtd-theme"] -test = ["mypy", "pycodestyle", "pytest", "types-setuptools"] - -[[package]] -name = "pytest" -version = "6.2.4" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, -] - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<1.0.0a1" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2023.2" -description = "World timezone definitions, modern and historical" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.2-py2.py3-none-any.whl", hash = "sha256:8a8baaf1e237175b02f5c751eea67168043a749c843989e2b3015aa1ad9db68b"}, - {file = "pytz-2023.2.tar.gz", hash = "sha256:a27dcf612c05d2ebde626f7d506555f10dfc815b3eddccfaadfc7d99b11c9a07"}, -] - -[[package]] -name = "requests" -version = "2.25.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sqlalchemy" -version = "1.3.24" -description = "Database Abstraction Library" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, - {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, -] - -[package.extras] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mysql = ["mysqlclient"] -oracle = ["cx-oracle"] -postgresql = ["psycopg2"] -postgresql-pg8000 = ["pg8000 (<1.16.6)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] - -[[package]] -name = "sqlalchemy-monetdb" -version = "1.0.0" -description = "SQLAlchemy dialect for MonetDB" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "sqlalchemy_monetdb-1.0.0.tar.gz", hash = "sha256:56cec7003182ee03d4100b6883f67a7e8fe27b79657c80069e8f73dea3fa92fb"}, -] - -[package.dependencies] -pymonetdb = "*" -sqlalchemy = "*" - -[package.extras] -lite = ["monetdblite", "numpy"] - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - -[[package]] -name = "typing-extensions" -version = "4.5.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, -] - -[[package]] -name = "typing-inspect" -version = "0.8.0" -description = "Runtime inspection utilities for typing module." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "typing_inspect-0.8.0-py3-none-any.whl", hash = "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188"}, - {file = "typing_inspect-0.8.0.tar.gz", hash = "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"}, -] - -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "urllib3" -version = "1.26.15" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "~3.8" -content-hash = "90787a9e34238e28524b64c44261768fb5bb94be3166960b1f4a07ef5e257436" diff --git a/tests/backend_components/pyproject.toml b/tests/backend_components/pyproject.toml deleted file mode 100644 index e369f5b5..00000000 --- a/tests/backend_components/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[tool.poetry] -name = "mip-deployment-test" -version = "0.0.1" -description = "" -authors = ["Your Name "] - -[tool.poetry.dependencies] -python = "~3.8" -pytest = "6.2.4" -requests = "2.25.1" -flake8 = "3.9.2" -pymonetdb = "~1.6" - -[tool.poetry.dev-dependencies] -mipdb = "2.0.4" -invoke = "^1.7.1" - diff --git a/tests/backend_components/tasks.py b/tests/backend_components/tasks.py deleted file mode 100644 index c57bc754..00000000 --- a/tests/backend_components/tasks.py +++ /dev/null @@ -1,150 +0,0 @@ -import json -import os -import pathlib -from enum import Enum -from itertools import cycle -from textwrap import indent -from time import sleep - -from invoke import UnexpectedExit -from invoke import task - - -wheel = cycle(r"-\|/") - -this_mod_path = os.path.dirname(os.path.abspath(__file__)) -TEST_DATA_FOLDER = pathlib.Path(this_mod_path).parent.parent / "data" - - -def spin_wheel(promise=None, time=None): - dt = 0.15 - if promise is not None: - print(" ", end="") - for frame in wheel: - print(frame + " ", sep="", end="", flush=True) - sleep(dt) - print("\b\b\b", sep="", end="", flush=True) - if promise.runner.process_is_finished: - print("\b\b", end="") - break - elif time: - print(" ", end="") - for frame in wheel: - print(frame + " ", sep="", end="", flush=True) - sleep(dt) - print("\b\b\b", sep="", end="", flush=True) - time -= dt - if time <= 0: - print("\b\b", end="") - break - - -def run(c, cmd, attach_=False, wait=True, warn=False, raise_error=False, show_ok=True): - if attach_: - c.run(cmd, pty=True) - return - - if not wait: - c.run(cmd, disown=True) - # nevertheless, it will wait (sleep) for 4 seconds here, why?? - spin_wheel(time=4) - if show_ok: - message("Ok", Level.SUCCESS) - return - - promise = c.run(cmd, asynchronous=True, warn=warn) - spin_wheel(promise=promise) - stderr = promise.runner.stderr - if stderr and raise_error: - raise UnexpectedExit(stderr) - result = promise.join() - if show_ok: - message("Ok", Level.SUCCESS) - return result - - -class Level(Enum): - HEADER = ("cyan", 1) - BODY = ("white", 2) - SUCCESS = ("green", 1) - ERROR = ("red", 1) - WARNING = ("yellow", 1) - - -def message(msg, level=Level.BODY): - if msg.endswith("..."): - end = "" - else: - end = "\n" - color, indent_level = level.value - prfx = " " * indent_level - print(indent(msg, prfx), end=end, flush=True) - - -@task -def setup_dbs(c): - """Install project dependencies using poetry.""" - message("Installing dependencies...", Level.HEADER) - cmd = "poetry install" - run(c, cmd) - - # TODO Should be refactored in order not to require change here - # for each new dataset that is added. - datasets_per_location = { - 50001: { - "longitudinal_dementia": ["longitudinal_dementia.csv"], - "dementia": ["desd-synthdata.csv", "ppmi.csv"], - "tbi": ["dummy_tbi.csv"], - }, - 50002: { - "dementia": ["edsd.csv"], - "mentalhealth": ["demo.csv"], - }, - } - - for port in datasets_per_location.keys(): - message(f"Initializing MonetDB with mipdb in port: {port}...", Level.HEADER) - cmd = f"""poetry run mipdb init --ip 127.0.0.1 --port {port} --username admin --password admin --db_name db""" - run(c, cmd) - - data_model_folders = [ - TEST_DATA_FOLDER / folder for folder in os.listdir(TEST_DATA_FOLDER) - ] - for data_model_folder in data_model_folders: - with open( - data_model_folder / "CDEsMetadata.json" - ) as data_model_metadata_file: - data_model_metadata = json.load(data_model_metadata_file) - data_model_code = data_model_metadata["code"] - data_model_version = data_model_metadata["version"] - data_model = f"{data_model_code}:{data_model_version}" - cdes_file = data_model_folder / "CDEsMetadata.json" - - if data_model_code not in datasets_per_location[port].keys(): - continue - - message( - f"Loading data model '{data_model_code}:{data_model_version}' metadata to database (127.0.0.1:{port})", - Level.HEADER, - ) - cmd = f"mipdb add-data-model {cdes_file} --ip 127.0.0.1 --port {port} --username admin --password admin --db_name db" - run(c, cmd) - - csvs = sorted( - [ - data_model_folder / file - for file in os.listdir(data_model_folder) - if file in datasets_per_location[port][data_model_code] - ] - ) - - for csv in csvs: - cmd = f"mipdb add-dataset {csv} -d {data_model_code} -v {data_model_version} --copy_from_file false --ip 127.0.0.1 --port {port} --username admin --password admin --db_name db" - run(c, cmd) - - message( - f"Loading dataset {pathlib.PurePath(csv).name} to database (127.0.0.1:{port})", - Level.HEADER, - ) - - message(f"Data loaded to database (127.0.0.1:{port})", Level.HEADER) diff --git a/tests/backend_components/test_integration/test_fail_cases/test_exareme2_post_experiment_fail.py b/tests/backend_components/test_integration/test_fail_cases/test_exareme2_post_experiment_fail.py deleted file mode 100644 index 54793aeb..00000000 --- a/tests/backend_components/test_integration/test_fail_cases/test_exareme2_post_experiment_fail.py +++ /dev/null @@ -1,137 +0,0 @@ -import pytest -import json -import time - -import requests - - -def do_get_experiment_request(uuid): - url = f"http://127.0.0.1:8080/services/experiments/{uuid}" - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.get(url, headers=headers) - return response - - -all_error_cases = [ - ( - "Invalid parameter name", - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": [ - { - "name": "xyz", - "label": "x", - "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", - }, - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "classes", "label": "classes", "value": "AD,CN"}, - ], - "type": "exareme2", - }, - "name": "Exareme2 Invalid parameter name", - }, - "text/plain+user_error", - ), - ( - "Invalid parameter value", - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": [ - {"name": "x", "label": "x", "value": "xyz"}, - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "classes", "label": "classes", "value": "AD,CN"}, - ], - "type": "exareme2", - }, - "name": "Exareme2 Invalid parameter value", - }, - "text/plain+user_error", - ), -] - - -@pytest.mark.parametrize("test_case,test_input,expected_error_type", all_error_cases) -def test_post_request_EXAREME2(test_case, test_input, expected_error_type): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps(test_input) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - logistic = json.loads(response.text) - assert not logistic["shared"] - assert logistic["status"] == "pending" - assert test_input["algorithm"]["name"] == logistic["algorithm"]["name"] - assert test_input["algorithm"]["label"] == logistic["algorithm"]["label"] - assert test_input["algorithm"]["type"] == logistic["algorithm"]["type"] - while True: - logistic_current_state_response = do_get_experiment_request(logistic["uuid"]) - logistic_current_state = json.loads(logistic_current_state_response.text) - status = logistic_current_state["status"] - - if status != "pending": - assert status == "error" - assert expected_error_type == logistic_current_state["result"][0]["type"] - break - time.sleep(2) - - -def test_post_request_EXAREME2_invalid_parameter_type(): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps( - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": "xyz", - "type": "exareme2", - }, - "name": "Error_Logistic_Regression", - } - ) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - assert "Algorithm: LOGISTIC_REGRESSIO does not exist." in response.text - - -def test_post_request_EXAREME2_invalid_parameter_type(): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps( - { - "algorithm": { - "name": "LOGISTIC_REGRESSIO", - "label": "Logistic Regression", - "parameters": [ - { - "name": "x", - "label": "x", - "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", - }, - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "classes", "label": "classes", "value": "AD,CN"}, - ], - "type": "exareme2", - }, - "name": "Exareme2 Invalid name", - }, - ) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - assert "Algorithm: LOGISTIC_REGRESSIO does not exist." in response.text diff --git a/tests/backend_components/test_integration/test_fail_cases/test_exareme_post_experiment_fail.py b/tests/backend_components/test_integration/test_fail_cases/test_exareme_post_experiment_fail.py deleted file mode 100644 index c1626d97..00000000 --- a/tests/backend_components/test_integration/test_fail_cases/test_exareme_post_experiment_fail.py +++ /dev/null @@ -1,162 +0,0 @@ -import json -import time - -import pytest -import requests - - -def do_get_experiment_request(uuid): - url = f"http://127.0.0.1:8080/services/experiments/{uuid}" - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.get(url, headers=headers) - return response - - -all_success_cases = [ - ( - "Invalid parameter name", - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": [ - { - "name": "xyz", - "label": "x", - "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", - }, - {"name": "xyz", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "formula", "label": "formula", "value": ""}, - { - "name": "positive_level", - "label": "Positive level", - "value": "AD", - }, - { - "name": "negative_level", - "label": "Negative level", - "value": "CN", - }, - ], - "type": "Exarene Invalid parameter name", - }, - "name": "dsafas", - }, - ), - ( - "Invalid parameter value", - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": [ - {"name": "x", "label": "x", "value": "xyz"}, - {"name": "y", "label": "y", "value": "xyz"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "formula", "label": "formula", "value": ""}, - { - "name": "positive_level", - "label": "Positive level", - "value": "AD", - }, - { - "name": "negative_level", - "label": "Negative level", - "value": "CN", - }, - ], - "type": "python_iterative", - }, - "name": "Exarene Invalid parameter value", - }, - ), -] - - -@pytest.mark.parametrize("test_case,test_input", all_success_cases) -def test_post_request_exareme(test_case, test_input): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps(test_input) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - - algorithm = json.loads(response.text) - assert test_input["algorithm"]["name"] == algorithm["algorithm"]["name"] - assert test_input["algorithm"]["label"] == algorithm["algorithm"]["label"] - assert test_input["algorithm"]["type"] == algorithm["algorithm"]["type"] - while True: - logistic_current_state_response = do_get_experiment_request(algorithm["uuid"]) - logistic_current_state = json.loads(logistic_current_state_response.text) - status = logistic_current_state["status"] - if status != "pending": - assert status == "error" - break - time.sleep(2) - - -def test_post_request_exareme_invalid_parameter_type(): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps( - { - "algorithm": { - "name": "LOGISTIC_REGRESSION", - "label": "Logistic Regression", - "parameters": "xyz", - "type": "python_iterative", - }, - "name": "LOGISTIC_REGRESSION", - } - ) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - assert response.text == "" - - -def test_post_request_exareme_invalid_parameter_type(): - url = "http://127.0.0.1:8080/services/experiments" - - request_json = json.dumps( - { - "algorithm": { - "name": "LOGISTIC_REGRES", - "label": "Logistic Regression", - "parameters": [ - { - "name": "x", - "label": "x", - "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", - }, - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "pathology", "label": "pathology", "value": "dementia"}, - {"name": "dataset", "label": "dataset", "value": "edsd,ppmi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "formula", "label": "formula", "value": ""}, - { - "name": "positive_level", - "label": "Positive level", - "value": "AD", - }, - { - "name": "negative_level", - "label": "Negative level", - "value": "CN", - }, - ], - "type": "python_iterative", - }, - "name": "Exarene Invalid name", - }, - ) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - assert "Algorithm: LOGISTIC_REGRES does not exist." in response.text diff --git a/tests/backend_components/test_integration/test_success_cases/test_exareme_post_experiment.py b/tests/backend_components/test_integration/test_success_cases/test_exareme_post_experiment.py deleted file mode 100644 index 460e25c5..00000000 --- a/tests/backend_components/test_integration/test_success_cases/test_exareme_post_experiment.py +++ /dev/null @@ -1,65 +0,0 @@ -import json -import time - -import pytest -import requests - - -def do_get_experiment_request(uuid): - url = f"http://127.0.0.1:8080/services/experiments/{uuid}" - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.get(url, headers=headers) - return response - - -all_success_cases = [ - { - "algorithm": { - "name": "NAIVE_BAYES", - "label": "Naive Bayes classifier", - "parameters": [ - { - "name": "x", - "label": "x", - "value": "righthippocampus,lefthippocampus", - }, - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, - {"name": "alpha", "label": "alpha", "value": "0.1"}, - {"name": "k", "label": "number of batches", "value": "10"}, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, - {"name": "dataset", "label": "dataset", "value": "edsd"}, - {"name": "filter", "label": "filter", "value": ""}, - ], - "type": "python_multiple_local_global", - }, - "name": "Naive Bayes classifier", - }, -] - - -@pytest.mark.parametrize("test_input", all_success_cases) -def test_post_request_exareme(test_input): - url = "http://127.0.0.1:8080/services/experiments" - - print(f"POST to {url}") - request_json = json.dumps(test_input) - - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.post(url, data=request_json, headers=headers) - algorithm = json.loads(response.text) - assert test_input["algorithm"]["name"] == algorithm["algorithm"]["name"] - assert test_input["algorithm"]["label"] == algorithm["algorithm"]["label"] - assert test_input["algorithm"]["type"] == algorithm["algorithm"]["type"] - while True: - logistic_current_state_response = do_get_experiment_request(algorithm["uuid"]) - logistic_current_state = json.loads(logistic_current_state_response.text) - status = logistic_current_state["status"] - if status != "pending": - assert status == "success" - assert logistic_current_state["result"] is not None - assert algorithm["algorithm"]["type"] != "exareme2" - assert algorithm["algorithm"]["type"] != "workflow" - break - time.sleep(2) - - print(f"POST Exareme result-> {algorithm}") diff --git a/tests/backend_components/test_integration/test_success_cases/test_get_algorithms_request.py b/tests/backend_components/test_integration/test_success_cases/test_get_algorithms_request.py deleted file mode 100644 index b4b4c7c7..00000000 --- a/tests/backend_components/test_integration/test_success_cases/test_get_algorithms_request.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -import json -import requests - - -def test_get_algorithms_request(): - url = "http://172.17.0.1:8080/services/algorithms" - headers = {"Content-type": "application/json", "Accept": "application/json"} - response = requests.get(url, headers=headers) - assert response.status_code == 200 - print(f"Algorithms result-> {response.text}") - algorithms = json.loads(response.text) - # assert len(algorithms) == 34 - - EXAREME2_algorithms = [ - algorithm for algorithm in algorithms if algorithm["type"] == "exareme2" - ] - - assert len(EXAREME2_algorithms) == 16 - - exareme_engine_algorithms = [ - algorithm - for algorithm in algorithms - if algorithm["type"] not in ["exareme2", "workflow"] - ] - print(f"exareme_engine_algorithms-> {exareme_engine_algorithms}") - assert len(exareme_engine_algorithms) == 16 diff --git a/tests/config/localnodes_config.json b/tests/config/localnodes_config.json new file mode 100644 index 00000000..6a9ffafd --- /dev/null +++ b/tests/config/localnodes_config.json @@ -0,0 +1 @@ +["172.17.0.1:5672"] \ No newline at end of file diff --git a/tests/backend_components/docker-compose.yml b/tests/docker-compose.yml similarity index 51% rename from tests/backend_components/docker-compose.yml rename to tests/docker-compose.yml index 3a313222..10ba73d2 100644 --- a/tests/backend_components/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,65 +1,7 @@ version: '3.7' services: - exareme_keystore: - image: bitnami/consul:1.8.3 - environment: - - CONSUL_AGENT_MODE=server - - CONSUL_BOOTSTRAP_EXPECT=1 - - CONSUL_CLIENT_LAN_ADDRESS=0.0.0.0 - - CONSUL_ENABLE_UI=true - restart: unless-stopped - - exareme_master: - image: hbpmip/exareme:${EXAREME} - ports: - - '9090:9090' - environment: - - CONSULURL=exareme_keystore:8500 - - FEDERATION_ROLE=master - - NODE_NAME=miplocal - - TEMP_FILES_CLEANUP_TIME=30 - - NODE_COMMUNICATION_TIMEOUT=30000 # (MILIS) NODE COMMUNICATION WILL DROP IF TIMEOUT IS PASSED - - ENVIRONMENT_TYPE=PROD # TEST / DEV / PROD - - LOG_LEVEL=INFO # INFO / DEBUG - - CONVERT_CSVS=TRUE # TRUE / FALSE - depends_on: - - exareme_keystore - volumes: - - ../../data:/root/exareme/data/ - restart: unless-stopped - - rabbitmq_globalnode: - image: madgik/exareme2_rabbitmq:${EXAREME2} - ports: - - "5670:5672" - environment: - - RABBITMQ_ADMIN_USER=user - - RABBITMQ_ADMIN_PASSWORD=password - - RABBITMQ_ADMIN_VHOST=user_vhost - - RABBITMQ_SLEEP_BEFORE_CONFIGURATION=30 - healthcheck: - test: rabbitmq-diagnostics -q ping - interval: 30s - timeout: 30s - retries: 3 - - rabbitmq_localnode1: - image: madgik/exareme2_rabbitmq:${EXAREME2} - ports: - - "5671:5672" - environment: - - RABBITMQ_ADMIN_USER=user - - RABBITMQ_ADMIN_PASSWORD=password - - RABBITMQ_ADMIN_VHOST=user_vhost - - RABBITMQ_SLEEP_BEFORE_CONFIGURATION=30 - healthcheck: - test: rabbitmq-diagnostics -q ping - interval: 30s - timeout: 30s - retries: 3 - - rabbitmq_localnode2: + exareme2_rabbitmq: image: madgik/exareme2_rabbitmq:${EXAREME2} ports: - "5672:5672" @@ -74,52 +16,25 @@ services: timeout: 30s retries: 3 - monetdb_globalnode: + exareme2_monetdb: image: madgik/exareme2_db:${EXAREME2} ports: - "50000:50000" environment: - SOFT_RESTART_MEMORY_LIMIT=1000 - HARD_RESTART_MEMORY_LIMIT=1000 + volumes: + - ../data:/opt/data - monetdb_localnode1: - image: madgik/exareme2_db:${EXAREME2} - ports: - - "50001:50000" - environment: - - SOFT_RESTART_MEMORY_LIMIT=1000 - - HARD_RESTART_MEMORY_LIMIT=1000 - - monetdb_localnode2: - image: madgik/exareme2_db:${EXAREME2} - ports: - - "50002:50000" - environment: - - SOFT_RESTART_MEMORY_LIMIT=1000 - - HARD_RESTART_MEMORY_LIMIT=1000 - - exareme2_globalnode: - image: madgik/exareme2_node:${EXAREME2} + exareme2_mipdb: + image: madgik/exareme2_mipdb:${EXAREME2} environment: - - NODE_IDENTIFIER=globalnode - - NODE_ROLE=GLOBALNODE - - LOG_LEVEL=DEBUG - - FRAMEWORK_LOG_LEVEL=INFO - - CELERY_TASKS_TIMEOUT=20 - - CELERY_RUN_UDF_TASK_TIMEOUT=120 - - RABBITMQ_IP=172.17.0.1 - - RABBITMQ_PORT=5670 - - MONETDB_IP=172.17.0.1 - - MONETDB_PORT=50000 - - MONETDB_PASSWORD="executor" - - SMPC_ENABLED=false - - SMPC_OPTIONAL=false - depends_on: - - rabbitmq_globalnode - - monetdb_globalnode - restart: unless-stopped + - DB_IP=172.17.0.1 + - DB_PORT=50000 + volumes: + - ../data:/opt/data - exareme2_localnode1: + exareme2_node: image: madgik/exareme2_node:${EXAREME2} environment: - NODE_IDENTIFIER=localnode1 @@ -129,36 +44,15 @@ services: - CELERY_TASKS_TIMEOUT=20 - CELERY_RUN_UDF_TASK_TIMEOUT=120 - RABBITMQ_IP=172.17.0.1 - - RABBITMQ_PORT=5671 - - MONETDB_IP=172.17.0.1 - - MONETDB_PASSWORD="executor" - - MONETDB_PORT=50001 - - SMPC_ENABLED=false - - SMPC_OPTIONAL=false - depends_on: - - rabbitmq_localnode1 - - monetdb_localnode1 - restart: unless-stopped - - exareme2_localnode2: - image: madgik/exareme2_node:${EXAREME2} - environment: - - NODE_IDENTIFIER=localnode2 - - NODE_ROLE=LOCALNODE - - LOG_LEVEL=DEBUG - - FRAMEWORK_LOG_LEVEL=INFO - - CELERY_TASKS_TIMEOUT=20 - - CELERY_RUN_UDF_TASK_TIMEOUT=120 - - RABBITMQ_IP=172.17.0.1 - RABBITMQ_PORT=5672 - MONETDB_IP=172.17.0.1 - MONETDB_PASSWORD="executor" - - MONETDB_PORT=50002 + - MONETDB_PORT=50000 - SMPC_ENABLED=false - SMPC_OPTIONAL=false depends_on: - - rabbitmq_localnode2 - - monetdb_localnode2 + - exareme2_monetdb + - exareme2_rabbitmq restart: unless-stopped exareme2_controller: @@ -169,7 +63,7 @@ services: - LOG_LEVEL=DEBUG - FRAMEWORK_LOG_LEVEL=INFO - DEPLOYMENT_TYPE=LOCAL - - NODE_LANDSCAPE_AGGREGATOR_UPDATE_INTERVAL=30 + - NODE_LANDSCAPE_AGGREGATOR_UPDATE_INTERVAL=10 - CLEANUP_FOLDER=/tmp/exareme2 - NODES_CLEANUP_INTERVAL=60 - NODES_CLEANUP_CONTEXTID_RELEASE_TIMELIMIT=3600 @@ -225,8 +119,6 @@ services: PORTAL_DB_PASSWORD: portalpwd ### Exareme2 ### EXAREME2_URL: http://172.17.0.1:5000 - ### Exareme ### - EXAREME_URL: http://exareme_master:9090 ### Keycloak ### AUTHENTICATION: 0 # AUTHENTICATION: 1 # Should be enabled for keycloak @@ -239,5 +131,97 @@ services: - create_dbs volumes: - ./config:/opt/portal/api - - ./logs:/opt/portal/logs restart: unless-stopped + + gateway-db: + image: postgres + restart: unless-stopped + environment: + POSTGRES_PASSWORD: pass123 + volumes: + - ./.stored_data/gatewaydb:/var/lib/postgres + expose: + - 5432 + + gateway: + image: hbpmip/gateway:${GATEWAY} + environment: + - DB_HOST=gateway-db + - ENGINE_TYPE=exareme + - ENGINE_BASE_URL=http://172.17.0.1:8080/services/ + - AUTH_SKIP=true + - AUTH_ENABLE_SSO=false + # - AUTH_ENABLE_SSO=true # Should be enabled for Keycloak + - BASE_URL_CONTEXT=services + - GATEWAY_PORT=8081 + - CACHE_ENABLED=false + - NODE_ENV=development + links: + - gateway-db + depends_on: + - gateway-db + expose: + - '8081' + restart: unless-stopped + + frontend: + image: hbpmip/portal-frontend:${FRONTEND} + depends_on: + - gateway + ports: + - '80:80' + - '443:443' + volumes: + - ../config/caddy/Caddyfile:/etc/caddy/Caddyfile + - ./.stored_data/caddy/caddy_data:/data + environment: + INSTANCE_NAME: 'MIP ${MIP}' + VERSION: 'Frontend: ${FRONTEND}, Gateway: ${GATEWAY}, Backend: ${PORTALBACKEND}, Exareme: ${EXAREME2}' + ERROR_LOG_LEVEL: info + PORTAL_BACKEND_SERVER: 172.17.0.1:8080 + PORTAL_BACKEND_CONTEXT: services + GATEWAY_SERVER: gateway:8081 + PUBLIC_MIP_HOST: 172.17.0.1 + PUBLIC_MIP_PROTOCOL: http + EXTERNAL_MIP_PROTOCOL: http + KEYCLOAK_HOST: http://keycloak:8095 + KEYCLOAK_AUTH_PATH: /auth + PORTALBACKEND_AUTH_URL: /oauth2/authorization/keycloak + MIP_LINK: direct + restart: unless-stopped + +# keycloak_db: +# image: postgres:12.2 +# volumes: +# - ./.stored_data/keycloak:/var/lib/postgresql/data +# environment: +# POSTGRES_DB: keycloak +# POSTGRES_USER: keycloak +# POSTGRES_PASSWORD: password +# restart: unless-stopped +# +# keycloak: +# image: jboss/keycloak:15.0.2 +# command: -Djboss.http.port=8095 +# volumes: +# - ../config/keycloak/keycloak.json:/tmp/mip.json +# - ../config/keycloak/HBPTheme:/opt/jboss/keycloak/themes/HBPTheme +# environment: +# DB_VENDOR: POSTGRES +# DB_ADDR: keycloak_db +# DB_PORT: 5432 +# DB_DATABASE: keycloak +# DB_USER: keycloak +# DB_SCHEMA: public +# DB_PASSWORD: password +# KEYCLOAK_USER: admin +# KEYCLOAK_PASSWORD: Pa55w0rd +# KEYCLOAK_IMPORT: /tmp/mip.json +# KEYCLOAK_HOSTNAME: 172.17.0.1 +# PROXY_ADDRESS_FORWARDING: 'true' #important for reverse proxy +# ports: +# - '8095:8095' +# - '8443:8443' +# depends_on: +# - keycloak_db +# restart: unless-stopped \ No newline at end of file diff --git a/tests/frontend_components/docker-compose.yml b/tests/frontend_components/docker-compose.yml deleted file mode 100644 index f7f29c9d..00000000 --- a/tests/frontend_components/docker-compose.yml +++ /dev/null @@ -1,95 +0,0 @@ -version: '3.7' - -services: - gateway-db: - image: postgres - restart: unless-stopped - environment: - POSTGRES_PASSWORD: pass123 - volumes: - - ./.stored_data/gatewaydb:/var/lib/postgres - expose: - - 5432 - - gateway: - image: hbpmip/gateway:${GATEWAY} - environment: - - DB_HOST=gateway-db - - ENGINE_TYPE=exareme - - ENGINE_BASE_URL=http://172.17.0.1:8080/services/ - - AUTH_SKIP=true - - AUTH_ENABLE_SSO=false -# - AUTH_ENABLE_SSO=true # Should be enabled for Keycloak - - BASE_URL_CONTEXT=services - - GATEWAY_PORT=8081 - - CACHE_ENABLED=false - - NODE_ENV=development - links: - - gateway-db - depends_on: - - gateway-db - expose: - - '8081' - restart: unless-stopped - - frontend: - image: hbpmip/portal-frontend:${FRONTEND} - depends_on: - - gateway - ports: - - '80:80' - - '443:443' - volumes: - - ../../config/caddy/Caddyfile:/etc/caddy/Caddyfile - - ./.stored_data/caddy/caddy_data:/data - environment: - INSTANCE_NAME: 'MIP ${MIP}' - VERSION: 'Frontend: ${FRONTEND}, Gateway: ${GATEWAY}, Backend: ${PORTALBACKEND}, Exareme1: ${EXAREME}, Exareme2: ${EXAREME2}' - ERROR_LOG_LEVEL: info - PORTAL_BACKEND_SERVER: 172.17.0.1:8080 - PORTAL_BACKEND_CONTEXT: services - GATEWAY_SERVER: gateway:8081 - PUBLIC_MIP_HOST: 172.17.0.1 - PUBLIC_MIP_PROTOCOL: http - EXTERNAL_MIP_PROTOCOL: http - KEYCLOAK_HOST: http://keycloak:8095 - KEYCLOAK_AUTH_PATH: /auth - PORTALBACKEND_AUTH_URL: /oauth2/authorization/keycloak - MIP_LINK: direct - restart: unless-stopped - -# keycloak_db: -# image: postgres:12.2 -# volumes: -# - ./.stored_data/keycloak:/var/lib/postgresql/data -# environment: -# POSTGRES_DB: keycloak -# POSTGRES_USER: keycloak -# POSTGRES_PASSWORD: password -# restart: unless-stopped -# -# keycloak: -# image: jboss/keycloak:15.0.2 -# command: -Djboss.http.port=8095 -# volumes: -# - ../../config/keycloak/keycloak.json:/tmp/mip.json -# - ../../config/keycloak/HBPTheme:/opt/jboss/keycloak/themes/HBPTheme -# environment: -# DB_VENDOR: POSTGRES -# DB_ADDR: keycloak_db -# DB_PORT: 5432 -# DB_DATABASE: keycloak -# DB_USER: keycloak -# DB_SCHEMA: public -# DB_PASSWORD: password -# KEYCLOAK_USER: admin -# KEYCLOAK_PASSWORD: Pa55w0rd -# KEYCLOAK_IMPORT: /tmp/mip.json -# KEYCLOAK_HOSTNAME: 172.17.0.1 -# PROXY_ADDRESS_FORWARDING: 'true' #important for reverse proxy -# ports: -# - '8095:8095' -# - '8443:8443' -# depends_on: -# - keycloak_db -# restart: unless-stopped diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..e98ed07b --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest~=7.4 +requests~=2.31 +click~=8.1 \ No newline at end of file diff --git a/tests/start.sh b/tests/start.sh index 86e01b88..ffa03485 100755 --- a/tests/start.sh +++ b/tests/start.sh @@ -1,18 +1,19 @@ #!/bin/env bash -cd backend_components/ -docker-compose --env-file ../../.versions_env down -docker-compose --env-file ../../.versions_env up -d -echo "Installing dependencies..." -poetry install -sleep 60 -poetry run inv setup-dbs +docker-compose --env-file ../.versions_env down +docker-compose --env-file ../.versions_env up -d -cd ../frontend_components/ -docker-compose --env-file ../../.versions_env down -docker-compose --env-file ../../.versions_env up -d +echo -n "Installing pip requirements ..." +pip install -r requirements.txt -echo -n "Waiting for the containers to be ready..." +echo -n "Waiting for containers to start ..." +sleep 10 + +echo -n "Loading data into exareme2 db ..." +docker exec tests_exareme2_mipdb_1 mipdb init +docker exec tests_exareme2_mipdb_1 mipdb load-folder /opt/data + +echo -n "Waiting for exareme2 to see the data ..." # TODO Replace never ending loop with limited attempts while true diff --git a/tests/stop.sh b/tests/stop.sh index 0b72a129..6474e04a 100755 --- a/tests/stop.sh +++ b/tests/stop.sh @@ -1,4 +1,3 @@ #!/bin/env bash -docker-compose --log-level ERROR -f backend_components/docker-compose.yml down -docker-compose --log-level ERROR -f frontend_components/docker-compose.yml down +docker-compose --env-file ../.versions_env down diff --git a/tests/test.sh b/tests/test.sh index 19479841..5dccf476 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,11 +1,5 @@ #!/bin/env bash -cd backend_components/test_integration/ -poetry run pytest test_success_cases/ - -poetry run pytest test_fail_cases/ - -poetry run pytest test_federation_info.py - -cd ../../ +pytest -k "not test_federation_info.py" tests +pytest tests/test_federation_info.py diff --git a/tests/backend_components/test_integration/test_success_cases/test_exareme2_post_experiment.py b/tests/tests/test_exareme2_post_experiment.py similarity index 80% rename from tests/backend_components/test_integration/test_success_cases/test_exareme2_post_experiment.py rename to tests/tests/test_exareme2_post_experiment.py index 6db34f1a..fea8af5e 100644 --- a/tests/backend_components/test_integration/test_success_cases/test_exareme2_post_experiment.py +++ b/tests/tests/test_exareme2_post_experiment.py @@ -19,17 +19,14 @@ def do_get_experiment_request(uuid): { "algorithm": { "parameters": [ - {"name": "dataset", "label": "dataset", "value": "dummy_tbi"}, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "pathology", "label": "pathology", "value": "tbi:0.1"}, + {"name": "dataset", "value": "dummy_tbi"}, + {"name": "filter", "value": ""}, + {"name": "pathology", "value": "tbi:0.1"}, { "name": "y", - "label": "y", "value": "pupil_reactivity_right_eye_result", }, ], - "label": "DESCRIPTIVE_STATS", - "type": "bla", "name": "DESCRIPTIVE_STATS", }, "name": "Descriptive analysis", @@ -37,118 +34,97 @@ def do_get_experiment_request(uuid): { "algorithm": { "name": "PCA", - "label": "Principal component algorithm", "parameters": [ { "name": "y", - "label": "y", "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "edsd", }, - {"name": "filter", "label": "filter", "value": None}, + {"name": "filter", "value": None}, ], - "type": "exareme2", }, "name": "Principal component algorithm", }, { "algorithm": { "name": "pearson_correlation", - "label": "Pearson Correlation", "parameters": [ { "name": "y", - "label": "y", "value": "rightsplsuperiorparietallobule,rightttgtransversetemporalgyrus,leftcaudate,leftocpoccipitalpole", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "edsd", }, - {"name": "filter", "label": "filter", "value": ""}, - {"name": "alpha", "label": "alpha", "value": "0.9529895484370635"}, + {"name": "filter", "value": ""}, + {"name": "alpha", "value": "0.9529895484370635"}, ], - "type": "exareme2", }, "name": "Pearson Correlation", }, { "algorithm": { "name": "anova_oneway", - "label": "One Way Anova", "parameters": [ - {"name": "y", "label": "y", "value": "leftententorhinalarea"}, - {"name": "x", "label": "x", "value": "neurodegenerativescategories"}, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "y", "value": "leftententorhinalarea"}, + {"name": "x", "value": "neurodegenerativescategories"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": true}', }, ], - "type": "exareme2", }, "name": "One Way Anova", }, { "algorithm": { "name": "linear_regression", - "label": "Linear Regression", "parameters": [ - {"name": "y", "label": "y", "value": "rightcuncuneus"}, + {"name": "y", "value": "rightcuncuneus"}, { "name": "x", - "label": "x", "value": "rightioginferioroccipitalgyrus,leftententorhinalarea,rightamygdala,leftmpogpostcentralgyrusmedialsegment,rightporgposteriororbitalgyrus,leftpoparietaloperculum,righttrifgtriangularpartoftheinferiorfrontalgyrus,rightmpogpostcentralgyrusmedialsegment,rightlateralventricle,rightmfcmedialfrontalcortex,rightorifgorbitalpartoftheinferiorfrontalgyrus,opticchiasm,neurodegenerativescategories,rightpcggposteriorcingulategyrus", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, ], - "type": "exareme2", }, "name": "Linear Regression", }, { "algorithm": { "name": "linear_regression_cv", - "label": "Linear Regression CV", "parameters": [ - {"name": "y", "label": "y", "value": "leftocpoccipitalpole"}, + {"name": "y", "value": "leftocpoccipitalpole"}, { "name": "x", - "label": "x", "value": "righthippocampus,rightsogsuperioroccipitalgyrus,leftppplanumpolare,leftsmgsupramarginalgyrus,leftgregyrusrectus,rightitginferiortemporalgyrus,leftcalccalcarinecortex", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { @@ -157,287 +133,233 @@ def do_get_experiment_request(uuid): "value": 4, }, ], - "type": "exareme2", }, "name": "Linear Regression CV", }, { "algorithm": { "name": "linear_regression_cv", - "label": "Linear Regression CV", "parameters": [ - {"name": "y", "label": "y", "value": "righthippocampus"}, + {"name": "y", "value": "righthippocampus"}, { "name": "x", - "label": "x", "value": "lefthippocampus", }, { "name": "pathology", - "label": "pathology", "value": "longitudinal_dementia:0.1", }, { "name": "dataset", - "label": "dataset", "value": "longitudinal_dementia", }, { "name": "filter", - "label": "filter", "value": "", }, { "name": "n_splits", - "label": "n_splits", "value": 4, }, ], "preprocessing": [ { "name": "longitudinal_transformer", - "label": "longitudinal_transformer", "parameters": [ { "name": "visit1", - "label": "visit1", "value": "BL", }, { "name": "visit2", - "label": "visit2", "value": "FL1", }, { "name": "strategies", - "label": "strategies", "value": '{"righthippocampus": "first", "lefthippocampus": "diff"}', }, ], } ], - "type": "exareme2", }, "name": "Linear Regression CV Longitudinal", }, { "algorithm": { "name": "ttest_independent", - "label": "T-Test Independent", "parameters": [ - {"name": "y", "label": "y", "value": "rightgregyrusrectus"}, + {"name": "y", "value": "rightgregyrusrectus"}, { "name": "x", - "label": "x", "value": "dataset", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { "name": "alt_hypothesis", - "label": "alt_hypothesis", "value": "less", }, { "name": "alpha", - "label": "alpha", "value": 0.5727207100545569, }, { "name": "groupA", - "label": "groupA", "value": "edsd", }, { "name": "groupB", - "label": "groupB", "value": "ppmi", }, ], - "type": "exareme2", }, "name": "T-Test Independent", }, { "algorithm": { "name": "logistic_regression", - "label": "Logistic Regression", "parameters": [ - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, + {"name": "y", "value": "alzheimerbroadcategory"}, { "name": "x", - "label": "x", "value": "rightttgtransversetemporalgyrus,leftpinsposteriorinsula,leftpoparietaloperculum,rightptplanumtemporale,leftventraldc", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { "name": "positive_class", - "label": "positive_class", "value": "Other", }, ], - "type": "exareme2", }, "name": "Logistic Regression", }, { "algorithm": { "name": "logistic_regression_cv", - "label": "Logistic Regression CV", "parameters": [ - {"name": "y", "label": "y", "value": "alzheimerbroadcategory"}, + {"name": "y", "value": "alzheimerbroadcategory"}, { "name": "x", - "label": "x", "value": "leftopifgopercularpartoftheinferiorfrontalgyrus,rightmsfgsuperiorfrontalgyrusmedialsegment,leftbasalforebrain,leftinflatvent", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { "name": "positive_class", - "label": "positive_class", "value": "AD", }, { "name": "n_splits", - "label": "n_splits", "value": 3, }, ], - "type": "exareme2", }, "name": "Logistic Regression CV", }, { "algorithm": { "name": "ttest_paired", - "label": "Paired t-test", "parameters": [ - {"name": "y", "label": "y", "value": "rightppplanumpolare"}, + {"name": "y", "value": "rightppplanumpolare"}, { "name": "x", - "label": "x", "value": "rightorifgorbitalpartoftheinferiorfrontalgyrus", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,ppmi,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { "name": "alt_hypothesis", - "label": "alt_hypothesis", "value": "less", }, { "name": "alpha", - "label": "alpha", "value": 0.06109997172168302, }, ], - "type": "exareme2", }, "name": "Paired t-test", }, { "algorithm": { "name": "ttest_onesample", - "label": "T-Test One-Sample", "parameters": [ - {"name": "y", "label": "y", "value": "leftmcggmiddlecingulategyrus"}, + {"name": "y", "value": "leftmcggmiddlecingulategyrus"}, { "name": "x", - "label": "x", "value": "rightorifgorbitalpartoftheinferiorfrontalgyrus", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,edsd", }, { "name": "filter", - "label": "filter", "value": '{"condition": "AND", "rules": [{"id": "dataset", "type": "string", "value": ["desd-synthdata", "ppmi", "edsd"], "operator": "in"}, {"condition": "AND", "rules": [{"id": "rightcuncuneus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightioginferioroccipitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftententorhinalarea", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightamygdala", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightporgposteriororbitalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "leftpoparietaloperculum", "type": "string", "operator": "is_not_null", "value": null}, {"id": "righttrifgtriangularpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmpogpostcentralgyrusmedialsegment", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightlateralventricle", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightmfcmedialfrontalcortex", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightorifgorbitalpartoftheinferiorfrontalgyrus", "type": "string", "operator": "is_not_null", "value": null}, {"id": "opticchiasm", "type": "string", "operator": "is_not_null", "value": null}, {"id": "neurodegenerativescategories", "type": "string", "operator": "is_not_null", "value": null}, {"id": "rightpcggposteriorcingulategyrus", "type": "string", "operator": "is_not_null", "value": null}]}], "valid": True}', }, { "name": "alt_hypothesis", - "label": "alt_hypothesis", "value": "greater", }, { "name": "alpha", - "label": "alpha", "value": 0.6764545707122654, }, { "name": "mu", - "label": "mu", "value": -1.7510563394418988, }, ], - "type": "exareme2", }, "name": "T-Test One-Sample", }, { "algorithm": { "name": "DESCRIPTIVE_STATS", - "label": "Descriptive stats", "parameters": [ { "name": "y", - "label": "y", "value": "leftttgtransversetemporalgyrus,rightmprgprecentralgyrusmedialsegment", }, - {"name": "pathology", "label": "pathology", "value": "dementia:0.1"}, + {"name": "pathology", "value": "dementia:0.1"}, { "name": "dataset", - "label": "dataset", "value": "desd-synthdata,edsd,ppmi", }, { "name": "filter", - "label": "filter", "value": "", }, ], - "type": "bla", }, "name": "Descriptive stats", }, @@ -460,7 +382,6 @@ def test_post_request_exareme2(test_input): assert not algorithm["shared"] assert algorithm["status"] == "pending" assert test_input["algorithm"]["name"] == algorithm["algorithm"]["name"] - assert test_input["algorithm"]["label"] == algorithm["algorithm"]["label"] while True: algorithm_current_state_response = do_get_experiment_request(algorithm["uuid"]) algorithm_current_state = json.loads(algorithm_current_state_response.text) @@ -471,18 +392,3 @@ def test_post_request_exareme2(test_input): assert algorithm_current_state["result"] is not None break time.sleep(2) - - -def count_experiments_run_on_exareme2(): - cmd = f"docker logs backend_components_portalbackend_1" - res = subprocess.run( - cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - return len( - re.findall("Starting Exareme2 algorithm execution", res.stdout.decode()) - ) - - -def test_algorithms_runs_on_proper_engine(): - assert len(all_success_cases) == count_experiments_run_on_exareme2() - diff --git a/tests/tests/test_exareme2_post_experiment_fail.py b/tests/tests/test_exareme2_post_experiment_fail.py new file mode 100644 index 00000000..1a9d97a0 --- /dev/null +++ b/tests/tests/test_exareme2_post_experiment_fail.py @@ -0,0 +1,98 @@ +import pytest +import json +import time + +import requests + + +def do_get_experiment_request(uuid): + url = f"http://127.0.0.1:8080/services/experiments/{uuid}" + headers = {"Content-type": "application/json", "Accept": "application/json"} + response = requests.get(url, headers=headers) + return response + + +all_error_cases = [ + ( + "Invalid parameter name", + { + "algorithm": { + "name": "LOGISTIC_REGRESSION", + "parameters": [ + { + "name": "xyz", + "value": "rightppplanumpolare,righthippocampus,lefthippocampus,rightamygdala,leftamygdala", + }, + {"name": "y", "value": "alzheimerbroadcategory"}, + {"name": "pathology", "value": "dementia"}, + {"name": "dataset", "value": "edsd,ppmi"}, + {"name": "filter", "value": ""}, + {"name": "classes", "value": "AD,CN"}, + ], + }, + "name": "Exareme2 Invalid parameter name", + }, + "text/plain+user_error", + ), + ( + "Invalid parameter value", + { + "algorithm": { + "name": "LOGISTIC_REGRESSION", + "parameters": [ + {"name": "x", "value": "xyz"}, + {"name": "y", "value": "alzheimerbroadcategory"}, + {"name": "pathology", "value": "dementia"}, + {"name": "dataset", "value": "edsd,ppmi"}, + {"name": "filter", "value": ""}, + {"name": "classes", "value": "AD,CN"}, + ], + }, + "name": "Exareme2 Invalid parameter value", + }, + "text/plain+user_error", + ), +] + + +@pytest.mark.parametrize("test_case,test_input,expected_error_type", all_error_cases) +def test_post_request_exareme2(test_case, test_input, expected_error_type): + url = "http://127.0.0.1:8080/services/experiments" + + request_json = json.dumps(test_input) + + headers = {"Content-type": "application/json", "Accept": "application/json"} + response = requests.post(url, data=request_json, headers=headers) + logistic = json.loads(response.text) + assert not logistic["shared"] + assert logistic["status"] == "pending" + assert test_input["algorithm"]["name"] == logistic["algorithm"]["name"] + + while True: + logistic_current_state_response = do_get_experiment_request(logistic["uuid"]) + logistic_current_state = json.loads(logistic_current_state_response.text) + status = logistic_current_state["status"] + + if status != "pending": + assert status == "error" + assert expected_error_type == logistic_current_state["result"][0]["type"] + break + time.sleep(2) + + +def test_post_request_exareme2_invalid_parameters_type(): + url = "http://127.0.0.1:8080/services/experiments" + + request_json = json.dumps( + { + "algorithm": { + "name": "LOGISTIC_REGRESSION", + "parameters": "xyz", + }, + "name": "Error_Logistic_Regression", + } + ) + + headers = {"Content-type": "application/json", "Accept": "application/json"} + response = requests.post(url, data=request_json, headers=headers) + assert response.status_code == 400 diff --git a/tests/backend_components/test_integration/test_federation_info.py b/tests/tests/test_federation_info.py similarity index 71% rename from tests/backend_components/test_integration/test_federation_info.py rename to tests/tests/test_federation_info.py index 905259a8..6f062bd7 100644 --- a/tests/backend_components/test_integration/test_federation_info.py +++ b/tests/tests/test_federation_info.py @@ -2,12 +2,12 @@ import re -EXPERIMENTS_EXECUTED = 19 +EXPERIMENTS_EXECUTED = 16 EXPERIMENT_AUDIT_ENTRY_IDENTIFIER = " - EXPERIMENT_FINISHED - " def test_federation_info(): - cmd = f"docker logs backend_components_portalbackend_1 | python3 ../../../federation_info.py show-portal-backend-audit-entries" + cmd = f"docker logs tests_portalbackend_1 | python3 ../federation_info.py show-portal-backend-audit-entries" res = subprocess.run( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) diff --git a/tests/tests/test_get_algorithms_request.py b/tests/tests/test_get_algorithms_request.py new file mode 100644 index 00000000..f9d9391c --- /dev/null +++ b/tests/tests/test_get_algorithms_request.py @@ -0,0 +1,13 @@ +import pytest +import json +import requests + + +def test_get_algorithms_request(): + url = "http://172.17.0.1:8080/services/algorithms" + headers = {"Content-type": "application/json", "Accept": "application/json"} + response = requests.get(url, headers=headers) + assert response.status_code == 200 + print(f"Algorithms result-> {response.text}") + algorithms = json.loads(response.text) + assert len(algorithms) == 16 diff --git a/tests/backend_components/test_integration/test_success_cases/test_get_pathologies_request.py b/tests/tests/test_get_pathologies_request.py similarity index 100% rename from tests/backend_components/test_integration/test_success_cases/test_get_pathologies_request.py rename to tests/tests/test_get_pathologies_request.py From e88cdafb6b474eed8c4df8fbcd307b51fc6abb7f Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Thu, 7 Sep 2023 16:25:34 +0300 Subject: [PATCH 7/8] New portalbackend and exareme2 versions. --- .versions_env | 2 +- kubernetes/values.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.versions_env b/.versions_env index 45ab1f41..99e651f7 100644 --- a/.versions_env +++ b/.versions_env @@ -1,5 +1,5 @@ EXAREME2=0.18.1 -PORTALBACKEND=testing +PORTALBACKEND=8.0.0 GATEWAY=1.5.0 FRONTEND=9.3.1 MIP=7.1.0 diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml index fb0e9088..0828f93e 100644 --- a/kubernetes/values.yaml +++ b/kubernetes/values.yaml @@ -4,16 +4,16 @@ engines: exareme2: URL: "" image: - version: 0.17.1 + version: 0.18.1 mip: - version: 7.1.0 + version: 8.0.0 LINK: "" EXTERNAL_PROTOCOL: "" PUBLIC_PROTOCOL: "" PUBLIC_HOST: "" NAME: "MIP {{ .Values.mip.version }}" - VERSION_STRING: "Frontend: {{ .Values.frontend.image.version }}, Gateway: {{ .Values.gateway.image.version }}, Backend: {{ .Values.portalbackend.image.version }}, Engine1 (Exareme): {{ .Values.engines.exareme.image.version }}, Engine2 (Exareme2): {{ .Values.engines.exareme2.image.version }}" + VERSION_STRING: "Frontend: {{ .Values.frontend.image.version }}, Gateway: {{ .Values.gateway.image.version }}, Backend: {{ .Values.portalbackend.image.version }}, Exareme: {{ .Values.engines.exareme2.image.version }}" datacatalogue: PROTOCOL: https @@ -39,7 +39,7 @@ portalbackend: ALGORITHM_UPDATE_INTERVAL: 60 # seconds image: name: hbpmip/portal-backend - version: 7.8.1 + version: 8.0.0 storage: storage0: data_path: /opt/mip-deployment/config From dd93fd280e3532b29529d5f4b05603bc07658b81 Mon Sep 17 00:00:00 2001 From: ThanKarab Date: Thu, 7 Sep 2023 16:26:52 +0300 Subject: [PATCH 8/8] SSL-REQUIRED no longer used from portal-backend. --- kubernetes/templates/portalbackend.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kubernetes/templates/portalbackend.yaml b/kubernetes/templates/portalbackend.yaml index 012d345a..6dc17737 100644 --- a/kubernetes/templates/portalbackend.yaml +++ b/kubernetes/templates/portalbackend.yaml @@ -202,11 +202,11 @@ spec: configMapKeyRef: name: mip-config key: keycloak.CLIENT_SECRET - - name: KEYCLOAK_SSL_REQUIRED - valueFrom: - configMapKeyRef: - name: mip-config - key: keycloak.SSL_REQUIRED +# - name: KEYCLOAK_SSL_REQUIRED +# valueFrom: +# configMapKeyRef: +# name: mip-config +# key: keycloak.SSL_REQUIRED - name: ALGORITHM_UPDATE_INTERVAL valueFrom: configMapKeyRef: