From 458adaae55d44b52d9940eaeb557055625c22425 Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Wed, 30 Apr 2025 14:39:06 +0200 Subject: [PATCH 1/7] [doc] remove FLP doc sync from the release procedure there is no need for it, it is synced automatically. --- docs/development.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/development.md b/docs/development.md index bfccde00..0c94e2fc 100644 --- a/docs/development.md +++ b/docs/development.md @@ -13,7 +13,6 @@ Bugs go to [JIRA](https://alice.its.cern.ch/jira/browse/OCTRL). 3. Run `hacking/release_notes.sh HEAD` to get a formatted commit message list since the last tag, copy it. 4. Paste the above into a [new GitHub release draft](https://github.com/AliceO2Group/Control/releases/new). Sort, categorize, add summary on top. 5. Pick a version number. Numbers `x.x.80`-`x.x.89` are reserved for Alpha pre-releases. Numbers `x.x.90`-`x.x.99` are reserved for Beta and RC pre-releases. If doing a pre-release, don't forget to tick `This is a pre-release`. When ready, hit `Publish release`. -6. Go to your local clone of [`alice-flp/documentation`](https://gitlab.cern.ch/alice-flp/documentation), descend into `docs/aliecs`. `git pull --rebase` to ensure the submodule points to the tag created just now. Commit and push (or merge request). -7. Go to your local clone of `alidist`, ensure that the branch is `master` and that it's up to date. Then branch out into `aliecs-bump` (`git branch aliecs-bump`). -8. Bump the version in `control.sh`, `control-core.sh`, `control-occplugin.sh` and `coconut.sh`. Commit and push to `origin/aliecs-bump` (`git push -u origin aliecs-bump`). -9. Submit pull request with the above to `alisw/alidist`. +6. Go to your local clone of `alidist`, ensure that the branch is `master` and that it's up to date. Then branch out into `aliecs-bump` (`git branch aliecs-bump`). +7. Bump the version in `control.sh`, `control-core.sh`, `control-occplugin.sh` and `coconut.sh`. Commit and push to `origin/aliecs-bump` (`git push -u origin aliecs-bump`). +8. Submit pull request with the above to `alisw/alidist`. From cbf9ad287277fa414aecbce6f2f578ec97d8e2ea Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Wed, 30 Apr 2025 15:17:35 +0200 Subject: [PATCH 2/7] [doc] add a GH workflow to look for broken links --- .github/workflows/linkspector.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/linkspector.yml diff --git a/.github/workflows/linkspector.yml b/.github/workflows/linkspector.yml new file mode 100644 index 00000000..20999edf --- /dev/null +++ b/.github/workflows/linkspector.yml @@ -0,0 +1,15 @@ +name: Linkspector +on: [pull_request] +jobs: + check-links: + name: runner / linkspector + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run linkspector + uses: umbrelladocs/action-linkspector@v1 + with: + github_token: ${{ secrets.github_token }} + reporter: github-pr-check + fail_level: any + filter_mode: nofilter From 960b75731d0edef4635b7f14c28e4745b148ac46 Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Tue, 6 May 2025 09:07:17 +0200 Subject: [PATCH 3/7] [doc] add common.proto and events.proto to aliecs API doc without these two, protoc doc generator creates broken links to messages in imported protos. --- Makefile | 2 +- apricot/README.md | 2 +- docs/apidocs_aliecs.md | 335 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8a3a5dee..483758d5 100644 --- a/Makefile +++ b/Makefile @@ -313,7 +313,7 @@ docs/coconut: docs/grpc: @echo -e "generating gRPC API documentation \033[1;33m==>\033[0m \033[1;34m./docs\033[0m" @cd apricot/protos && PATH="$(ROOT_DIR)/tools:$$PATH" protoc --doc_out="$(ROOT_DIR)/docs" --doc_opt=markdown,apidocs_apricot.md "apricot.proto" - @cd core/protos && PATH="$(ROOT_DIR)/tools:$$PATH" protoc -I=. -I=../../common --doc_out="$(ROOT_DIR)/docs" --doc_opt=markdown,apidocs_aliecs.md "o2control.proto" + @cd core/protos && PATH="$(ROOT_DIR)/tools:$$PATH" protoc -I=. -I=../../common --doc_out="$(ROOT_DIR)/docs" --experimental_allow_proto3_optional --doc_opt=markdown,apidocs_aliecs.md o2control.proto ../../common/protos/events.proto ../../common/protos/common.proto @cd occ/protos && PATH="$(ROOT_DIR)/tools:$$PATH" protoc --doc_out="$(ROOT_DIR)/docs" --doc_opt=markdown,apidocs_occ.md "occ.proto" docs/swaggo: diff --git a/apricot/README.md b/apricot/README.md index 243b580d..0ad56e0f 100644 --- a/apricot/README.md +++ b/apricot/README.md @@ -11,4 +11,4 @@ Usage of bin/o2-apricot: --verbose Verbose logging ``` -Protofile: [apricot.proto](apricot/protos/apricot.proto) +Protofile: [apricot.proto](protos/apricot.proto) diff --git a/docs/apidocs_aliecs.md b/docs/apidocs_aliecs.md index 6783e0b1..f14d9981 100644 --- a/docs/apidocs_aliecs.md +++ b/docs/apidocs_aliecs.md @@ -88,6 +88,26 @@ - [Control](#o2control-Control) +- [protos/events.proto](#protos_events-proto) + - [Ev_CallEvent](#events-Ev_CallEvent) + - [Ev_EnvironmentEvent](#events-Ev_EnvironmentEvent) + - [Ev_EnvironmentEvent.VarsEntry](#events-Ev_EnvironmentEvent-VarsEntry) + - [Ev_IntegratedServiceEvent](#events-Ev_IntegratedServiceEvent) + - [Ev_MetaEvent_CoreStart](#events-Ev_MetaEvent_CoreStart) + - [Ev_MetaEvent_FrameworkEvent](#events-Ev_MetaEvent_FrameworkEvent) + - [Ev_MetaEvent_MesosHeartbeat](#events-Ev_MetaEvent_MesosHeartbeat) + - [Ev_RoleEvent](#events-Ev_RoleEvent) + - [Ev_RunEvent](#events-Ev_RunEvent) + - [Ev_TaskEvent](#events-Ev_TaskEvent) + - [Event](#events-Event) + - [Traits](#events-Traits) + + - [OpStatus](#events-OpStatus) + +- [protos/common.proto](#protos_common-proto) + - [User](#common-User) + - [WorkflowTemplateInfo](#common-WorkflowTemplateInfo) + - [Scalar Value Types](#scalar-value-types) @@ -1488,6 +1508,321 @@ Not implemented yet + +

Top

+ +## protos/events.proto + + + + + +### Ev_CallEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| func | [string](#string) | | name of the function being called, within the workflow template context | +| callStatus | [OpStatus](#events-OpStatus) | | progress or success/failure state of the call | +| return | [string](#string) | | return value of the function | +| traits | [Traits](#events-Traits) | | | +| output | [string](#string) | | any additional output of the function | +| error | [string](#string) | | error value, if returned | +| environmentId | [string](#string) | | | +| path | [string](#string) | | path to the parent callRole of this call within the environment | + + + + + + + + +### Ev_EnvironmentEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| environmentId | [string](#string) | | | +| state | [string](#string) | | | +| runNumber | [uint32](#uint32) | | only when the environment is in the running state | +| error | [string](#string) | | | +| message | [string](#string) | | any additional message concerning the current state or transition | +| transition | [string](#string) | | | +| transitionStep | [string](#string) | | | +| transitionStatus | [OpStatus](#events-OpStatus) | | | +| vars | [Ev_EnvironmentEvent.VarsEntry](#events-Ev_EnvironmentEvent-VarsEntry) | repeated | consolidated environment variables at the root role of the environment | +| lastRequestUser | [common.User](#common-User) | | | +| workflowTemplateInfo | [common.WorkflowTemplateInfo](#common-WorkflowTemplateInfo) | | | + + + + + + + + +### Ev_EnvironmentEvent.VarsEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### Ev_IntegratedServiceEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | name of the context, usually the path of the callRole that calls a given integrated service function e.g. readout-dataflow.dd-scheduler.terminate | +| error | [string](#string) | | error message, if any | +| operationName | [string](#string) | | name of the operation, usually the name of the integrated service function being called e.g. ddsched.PartitionTerminate()" | +| operationStatus | [OpStatus](#events-OpStatus) | | progress or success/failure state of the operation | +| operationStep | [string](#string) | | if the operation has substeps, this is the name of the current substep, like an API call or polling phase | +| operationStepStatus | [OpStatus](#events-OpStatus) | | progress or success/failure state of the current substep | +| environmentId | [string](#string) | | | +| payload | [string](#string) | | any additional payload, depending on the integrated service; there is no schema, it can even be the raw return structure of a remote API call | + + + + + + + + +### Ev_MetaEvent_CoreStart + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| frameworkId | [string](#string) | | | + + + + + + + + +### Ev_MetaEvent_FrameworkEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| frameworkId | [string](#string) | | | +| message | [string](#string) | | | + + + + + + + + +### Ev_MetaEvent_MesosHeartbeat + + + + + + + + + +### Ev_RoleEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | role name | +| status | [string](#string) | | posible values: ACTIVE/INACTIVE/PARTIAL/UNDEFINED/UNDEPLOYABLE as defined in status.go. Derived from the state of child tasks, calls or other roles | +| state | [string](#string) | | state machine state for this role | +| rolePath | [string](#string) | | path to this role within the environment | +| environmentId | [string](#string) | | | + + + + + + + + +### Ev_RunEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| environmentId | [string](#string) | | | +| runNumber | [uint32](#uint32) | | | +| state | [string](#string) | | | +| error | [string](#string) | | | +| transition | [string](#string) | | | +| transitionStatus | [OpStatus](#events-OpStatus) | | | +| lastRequestUser | [common.User](#common-User) | | | + + + + + + + + +### Ev_TaskEvent + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | task name, based on the name of the task class | +| taskid | [string](#string) | | task id, unique | +| state | [string](#string) | | state machine state for this task | +| status | [string](#string) | | posible values: ACTIVE/INACTIVE/PARTIAL/UNDEFINED/UNDEPLOYABLE as defined in status.go. | +| hostname | [string](#string) | | | +| className | [string](#string) | | name of the task class from which this task was spawned | +| traits | [Traits](#events-Traits) | | | +| environmentId | [string](#string) | | | +| path | [string](#string) | | path to the parent taskRole of this task within the environment | + + + + + + + + +### Event + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| timestamp | [int64](#int64) | | | +| timestampNano | [int64](#int64) | | | +| environmentEvent | [Ev_EnvironmentEvent](#events-Ev_EnvironmentEvent) | | | +| taskEvent | [Ev_TaskEvent](#events-Ev_TaskEvent) | | | +| roleEvent | [Ev_RoleEvent](#events-Ev_RoleEvent) | | | +| callEvent | [Ev_CallEvent](#events-Ev_CallEvent) | | | +| integratedServiceEvent | [Ev_IntegratedServiceEvent](#events-Ev_IntegratedServiceEvent) | | | +| runEvent | [Ev_RunEvent](#events-Ev_RunEvent) | | | +| frameworkEvent | [Ev_MetaEvent_FrameworkEvent](#events-Ev_MetaEvent_FrameworkEvent) | | | +| mesosHeartbeatEvent | [Ev_MetaEvent_MesosHeartbeat](#events-Ev_MetaEvent_MesosHeartbeat) | | | +| coreStartEvent | [Ev_MetaEvent_CoreStart](#events-Ev_MetaEvent_CoreStart) | | | + + + + + + + + +### Traits + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| trigger | [string](#string) | | | +| await | [string](#string) | | | +| timeout | [string](#string) | | | +| critical | [bool](#bool) | | | + + + + + + + + + + +### OpStatus + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| NULL | 0 | | +| STARTED | 1 | | +| ONGOING | 2 | | +| DONE_OK | 3 | | +| DONE_ERROR | 4 | | +| DONE_TIMEOUT | 5 | | + + + + + + + + + + + +

Top

+ +## protos/common.proto + + + + + +### User + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| externalId | [int32](#int32) | optional | The unique CERN identifier of this user. | +| id | [int32](#int32) | optional | The unique identifier of this entity. | +| name | [string](#string) | | Name of the user. | + + + + + + + + +### WorkflowTemplateInfo + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | | +| description | [string](#string) | | | +| path | [string](#string) | | | +| public | [bool](#bool) | | whether the environment is public or not | + + + + + + + + + + + + + + + ## Scalar Value Types | .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | From ff528940965c6afc1d5945e6c1c3b40d6410a1fc Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Wed, 7 May 2025 10:56:13 +0200 Subject: [PATCH 4/7] [doc] Table of Contents in README.md and filling missing bits --- README.md | 210 ++++++++++++++++---- apricot/README.md | 19 +- apricot/docs/apricot.md | 6 +- apricot/docs/apricot_http_service.md | 2 +- coconut/README.md | 2 +- core/integration/README.md | 158 +++++++++++++++ core/protos/o2control.proto | 8 + docs/CONTRIBUTING.md | 49 +++++ docs/apidocs_aliecs.md | 8 +- docs/building.md | 4 +- docs/faq.md | 2 - docs/handbook/AliECS-environment.png | Bin 0 -> 85353 bytes docs/handbook/appconfiguration.md | 8 +- docs/handbook/concepts.md | 31 ++- docs/handbook/configuration.md | 17 +- docs/handbook/operation_order.md | 131 ++---------- docs/handbook/overview.md | 7 +- docs/kafka.md | 6 +- docs/running.md | 10 +- docs/using_grpcc_occ.md | 2 +- hacking/COG.md | 6 +- occ/README.md | 2 +- occ/occlib/examples/dummy-process/README.md | 2 +- occ/peanut/README.md | 2 +- 24 files changed, 476 insertions(+), 216 deletions(-) create mode 100644 core/integration/README.md create mode 100644 docs/CONTRIBUTING.md delete mode 100644 docs/faq.md create mode 100644 docs/handbook/AliECS-environment.png diff --git a/README.md b/README.md index c72abca5..17d77d23 100644 --- a/README.md +++ b/README.md @@ -2,54 +2,192 @@ [![godoc](https://img.shields.io/badge/godoc-Reference-5272B4.svg)](https://godoc.org/github.com/AliceO2Group/Control) # AliECS -The ALICE Experiment Control System +The ALICE Experiment Control System (**AliECS**) is the piece of software to drive and control data taking activities in the experiment. +It is a distributed system that combines state of the art cluster resource management and experiment control functionalities into a single comprehensive solution. -## Install instructions +Please refer to the [CHEP 2023 paper](https://doi.org/10.1051/epjconf/202429502027) for the latest design overview. -What is your use case? +## How to get started -* I want to **run AliECS** and other O²/FLP software +Regardless of your particular interests, it is recommended to get acquainted with the main [AliECS concepts](docs/handbook/concepts.md). - :arrow_right: [O²/FLP Suite deployment instructions](https://alice-flp.docs.cern.ch/system-configuration/utils/o2-flp-setup/) +After that, please find your concrete use case: - These instructions apply to both single-node and multi-node deployments. +### I want to **run AliECS** and other O²/FLP software - Contact [alice-o2-flp-support](mailto:alice-o2-flp-support@cern.ch) for assistance with provisioning and deployment. - -* I want to ensure AliECS can **run and control my process** +See [O²/FLP Suite deployment instructions](https://alice-flp.docs.cern.ch/system-configuration/utils/o2-flp-setup/) - * My software is based on FairMQ and/or O² DPL - - :palm_tree: Nothing to do, AliECS natively supports FairMQ (and DPL) devices. - - * My software does not use FairMQ and/or DPL, but should be controlled through a state machine - - :telescope: See [the OCC documentation](occ/README.md) to learn how to integrate the O² Control and Configuration library with your software. [Readout](https://github.com/AliceO2Group/Readout) is currently the only example of this setup. - - * My software is a command line utility with no state machine - - :palm_tree: Nothing to do, AliECS natively supports generic commands. Make sure the task template for your command sets the control mode to `basic` ([see example](https://github.com/AliceO2Group/ControlWorkflows/blob/basic-tasks/tasks/sleep.yaml)). - -* I want to build and run AliECS for **development** purposes +These instructions apply to both single-node and multi-node deployments. +Contact [alice-o2-flp-support](mailto:alice-o2-flp-support@cern.ch) for assistance with provisioning and deployment. - :hammer_and_wrench: [Building instructions](https://alice-flp.docs.cern.ch/aliecs/building/) - - :arrow_right: [Running instructions](https://alice-flp.docs.cern.ch/aliecs/running/) +There are two ways of interacting with AliECS: -* I want to communicate with AliECS via one of the plugins - - * [Receive updates on running environments via Kafka](docs/kafka.md) +- The AliECS GUI (a.k.a. Control GUI, COG) - not in this repository, but included in most deployments, recommended -## Using AliECS + :arrow_right: [AliECS GUI documentation](hacking/COG.md) -There are two ways of interacting with AliECS: - -* The AliECS GUI - not in this repository, but included in most deployments, recommended +- `coconut` - the command-line control and configuration utility, included with AliECS core, typically for developers and advanced users - :arrow_right: [AliECS GUI documentation](hacking/COG.md) + :arrow_right: [Using `coconut`](https://alice-flp.docs.cern.ch/aliecs/coconut/) + + :arrow_right: [`coconut` command reference](https://alice-flp.docs.cern.ch/aliecs/coconut/doc/coconut/) -* `coconut` - the command-line control and configuration utility, included with AliECS core +### I want to ensure AliECS can **run and control my process** + +* **My software is based on FairMQ and/or O² DPL (Data Processing Later)** + + AliECS natively supports FairMQ (and DPL) devices. + Head to [ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) for instructions on how to configure your software to be controlled by AliECS. + +* **My software does not use FairMQ and/or DPL, but should be controlled through a state machine** + + See [the OCC documentation](occ/README.md) to learn how to integrate the O² Control and Configuration library with your software. [Readout](https://github.com/AliceO2Group/Readout) is an example of this setup. + + Once ready, head to [ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) for instructions on how to configure it to be controlled by AliECS. - :arrow_right: [Using `coconut`](https://alice-flp.docs.cern.ch/aliecs/coconut/) +* **My software is a command line utility with no state machine** + + AliECS natively supports generic commands. + Head to [ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) for instructions to have your command ran by AliECS. + Make sure the task template for your command sets the control mode to `basic` ([see example](https://github.com/AliceO2Group/ControlWorkflows/blob/master/tasks/o2-roc-cleanup.yaml)). - :arrow_right: [`coconut` command reference](https://alice-flp.docs.cern.ch/aliecs/coconut/doc/coconut/) +### I want to develop AliECS + +:hammer_and_wrench: Welcome to the team, please head to [contributing instructions](/docs/CONTRIBUTING.md) + +### I want to receive updates about environments or services controlled by AliECS + +:pager: Learn more about the [kafka event service](/docs/kafka.md) + +### I want my application to send requests to AliECS + +:scroll: See the API docs of AliECS components: +- [core gRPC server](/docs/apidocs_aliecs.md) +- [apricot gRPC server](/docs/apidocs_apricot.md) +- [apricot HTTP server](/apricot/docs/apricot_http_service.md) + +### I want my service to be sent requests by AliECS + +:electric_plug: Learn more about the [plugin system](/core/integration/README.md) + +## Table of Contents + +* Introduction + * [Basic Concepts](/docs/handbook/concepts.md#basic-concepts) + * [Tasks](/docs/handbook/concepts.md#tasks) + * [Workflows, roles and environments](/docs/handbook/concepts.md#workflows-roles-and-environments) + * [Design Overview](/docs/handbook/overview.md#design-overview) + * [AliECS Structure](/docs/handbook/overview.md#aliecs-structure) + * [Resource Management](/docs/handbook/overview.md#resource-management) + * [FairMQ](/docs/handbook/overview.md#fairmq) + * [State machines](/docs/handbook/overview.md#state-machines) + +* Component reference + * AliECS GUI + * [AliECS GUI overview](/hacking/COG.md) + * AliECS core + * [Workflow Configuration](/docs/handbook/configuration.md#workflow-configuration) + * [The AliECS workflow template language](/docs/handbook/configuration.md#the-aliecs-workflow-template-language) + * [Workflow template structure](/docs/handbook/configuration.md#workflow-template-structure) + * [Task roles](/docs/handbook/configuration.md#task-roles) + * [Call roles](/docs/handbook/configuration.md#call-roles) + * [Aggregator roles](/docs/handbook/configuration.md#aggregator-roles) + * [Iterator roles](/docs/handbook/configuration.md#iterator-roles) + * [Include roles](/docs/handbook/configuration.md#include-roles) + * [Template expressions](/docs/handbook/configuration.md#template-expressions) + * [Task Configuration](/docs/handbook/configuration.md#task-configuration) + * [Task template structure](/docs/handbook/configuration.md#task-template-structure) + * [Variables pushed to controlled tasks](/docs/handbook/configuration.md#variables-pushed-to-controlled-tasks) + * [Resource wants and limits](/docs/handbook/configuration.md#resource-wants-and-limits) + * [Integration plugins](/core/integration/README.md#integration-plugins) + * [Plugin system overview](/core/integration/README.md#plugin-system-overview) + * [Integrated service operations](/core/integration/README.md#integrated-service-operations) + * [Bookkeeping](/core/integration/README.md#bookkeeping) + * [CCDB](/core/integration/README.md#ccdb) + * [DCS](/core/integration/README.md#dcs) + * [DCS operations](/core/integration/README.md#dcs-operations) + * [DCS PrepareForRun behaviour](/core/integration/README.md#dcs-prepareforrun-behaviour) + * [DCS StartOfRun behaviour](/core/integration/README.md#dcs-startofrun-behaviour) + * [DCS EndOfRun behaviour](/core/integration/README.md#dcs-endofrun-behaviour) + * [DD Scheduler](/core/integration/README.md#dd-scheduler) + * [Kafka (legacy)](/core/integration/README.md#kafka-legacy) + * [ODC](/core/integration/README.md#odc) + * [Test plugin](/core/integration/README.md#test-plugin) + * [Trigger](/core/integration/README.md#trigger) + * [Environment operation order](/docs/handbook/operation_order.md#environment-operation-order) + * [State machine triggers](/docs/handbook/operation_order.md#state-machine-triggers) + * [START_ACTIVITY (Start Of Run)](/docs/handbook/operation_order.md#start_activity-start-of-run) + * [STOP_ACTIVITY (End Of Run)](/docs/handbook/operation_order.md#stop_activity-end-of-run) + * [Protocol documentation](/docs/apidocs_aliecs.md) + * coconut + * [The O² control and configuration utility overview](/coconut/README.md#the-o-control-and-configuration-utility-overview) + * [Configuration file](/coconut/README.md#configuration-file) + * [Using coconut](/coconut/README.md#using-coconut) + * [Creating an environment](/coconut/README.md#creating-an-environment) + * [Controlling an environment](/coconut/README.md#controlling-an-environment) + * [Command reference](/coconut/doc/coconut.md) + * apricot + * [ALICE configuration service overview](/apricot/README.md#alice-configuration-service-overview) + * [HTTP service](/apricot/docs/apricot_http_service.md#apricot-http-service) + * [Configuration](/apricot/docs/apricot_http_service.md#configuration) + * [Usage and options](/apricot/docs/apricot_http_service.md#usage-and-options) + * [Examples](/apricot/docs/apricot_http_service.md#examples) + * [Protocol documentation](/docs/apidocs_apricot.md) + * [Command reference](/apricot/docs/apricot.md) + * occ + * [O² Control and Configuration Components](/occ/README.md#o-control-and-configuration-components) + * [Developer quick start instructions for OCClib](/occ/README.md#developer-quick-start-instructions-for-occlib) + * [Manual build instructions](/occ/README.md#manual-build-instructions) + * [Run example](/occ/README.md#run-example) + * [The OCC state machine](/occ/README.md#the-occ-state-machine) + * [Single process control with peanut](/occ/README.md#single-process-control-with-peanut) + * [OCC API debugging with grpcc](/occ/README.md#occ-api-debugging-with-grpcc) + * [Dummy process example for OCC library](/occ/occlib/examples/dummy-process/README.md#dummy-process-example-for-occ-library) + * [Protocol documentation](/docs/apidocs_occ.md) + * peanut + * [Process control and execution utility overview](/occ/peanut/README.md) + * Event service + * [Kafka producer functionality in AliECS core](/docs/kafka.md#kafka-producer-functionality-in-aliecs-core) + * [Making sure that AliECS sends messages](/docs/kafka.md#making-sure-that-aliecs-sends-messages) + * [Currently available topics](/docs/kafka.md#currently-available-topics) + * [Decoding the messages](/docs/kafka.md#decoding-the-messages) + * [Legacy events: Kafka plugin](/docs/kafka.md#legacy-events-kafka-plugin) + * [Making sure that AliECS sends messages](/docs/kafka.md#making-sure-that-aliecs-sends-messages-1) + * [Currently available topics](/docs/kafka.md#currently-available-topics-1) + * [Decoding the messages](/docs/kafka.md#decoding-the-messages-1) + * [Getting Start of Run and End of Run notifications](/docs/kafka.md#getting-start-of-run-and-end-of-run-notifications) + * [Using Kafka debug tools](/docs/kafka.md#using-kafka-debug-tools) + +* Developer documentation + * [Contributing](/docs/CONTRIBUTING.md) + * [Package pkg.go.dev documentation](https://pkg.go.dev/github.com/AliceO2Group/Control) + * [Building AliECS](/docs/building.md#building-aliecs) + * [Overview](/docs/building.md#overview) + * [Building with aliBuild](/docs/building.md#building-with-alibuild) + * [Manual build](/docs/building.md#manual-build) + * [Go environment](/docs/building.md#go-environment) + * [Clone and build (Go components only)](/docs/building.md#clone-and-build-go-components-only) + * [Makefile reference](/docs/makefile_reference.md) + * [Component Configuration](/docs/handbook/appconfiguration.md#component-configuration) + * [Apache Mesos](/docs/handbook/appconfiguration.md#apache-mesos) + * [Connectivity to controlled nodes](/docs/handbook/appconfiguration.md#connectivity-to-controlled-nodes) + * [Running AliECS as a developer](/docs/running.md#running-aliecs-as-a-developer) + * [Running the AliECS core](/docs/running.md#running-the-aliecs-core) + * [Running AliECS in production](/docs/running.md#running-aliecs-in-production) + * [Health checks](/docs/running.md#health-checks) + * [Development Information](/docs/development.md#development-information) + * [Release Procedure](/docs/development.md#release-procedure) + * [Metrics in ECS](/docs/metrics.md#metrics-in-ecs) + * [Overview and simple usage](/docs/metrics.md#overview-and-simple-usage) + * [Types and aggregation of metrics](/docs/metrics.md#types-and-aggregation-of-metrics) + * [Metric types](/docs/metrics.md#metric-types) + * [Aggregation](/docs/metrics.md#aggregation) + * [Implementation details](/docs/metrics.md#implementation-details) + * [Event loop](/docs/metrics.md#event-loop) + * [Hashing to aggregate](/docs/metrics.md#hashing-to-aggregate) + * [Sampling reservoir](/docs/metrics.md#sampling-reservoir) + * [OCC API debugging with grpcc](/docs/using_grpcc_occ.md#occ-api-debugging-with-grpcc) + +* Resources + * T. Mrnjavac et. al, [AliECS: A New Experiment Control System for the ALICE Experiment](https://doi.org/10.1051/epjconf/202429502027), CHEP23 + diff --git a/apricot/README.md b/apricot/README.md index 0ad56e0f..c32dce70 100644 --- a/apricot/README.md +++ b/apricot/README.md @@ -1,14 +1,9 @@ -# `APRICOT` +# ALICE configuration service overview -**A** **p**rocessor and **r**epos**i**tory for **co**nfiguration **t**emplates +**A** **p**rocessor and **r**epos**i**tory for **co**nfiguration **t**emplates, or apricot, implements the configuration service for the ALICE data taking activities. +It adds templating, load balancing and caching on top of the configuration store. -The `o2-apricot` binary implements a centralized configuration (micro)service for ALICE O². - -``` -Usage of bin/o2-apricot: - --backendUri string URI of the Consul server or YAML configuration file (default "consul://127.0.0.1:8500") - --listenPort int Port of apricot server (default 32101) - --verbose Verbose logging -``` - -Protofile: [apricot.proto](protos/apricot.proto) +See also: +* [apricot HTTP service](docs/apricot_http_service.md) - make essential cluster information available via a web server +* Protofile: [apricot.proto](protos/apricot.proto) +* [Command reference](docs/apricot.md) diff --git a/apricot/docs/apricot.md b/apricot/docs/apricot.md index e0448f6d..43fd6027 100644 --- a/apricot/docs/apricot.md +++ b/apricot/docs/apricot.md @@ -13,8 +13,4 @@ Usage of bin/o2-apricot: --backendUri string URI of the Consul server or YAML configuration file (default "consul://127.0.0.1:8500") --listenPort int Port of apricot server (default 32101) --verbose Verbose logging -``` - -### SEE ALSO - -* [apricot HTTP service](apricot_http_service.md) - make essential cluster information available via a web server +``` \ No newline at end of file diff --git a/apricot/docs/apricot_http_service.md b/apricot/docs/apricot_http_service.md index 15dd91a9..fd460973 100644 --- a/apricot/docs/apricot_http_service.md +++ b/apricot/docs/apricot_http_service.md @@ -46,4 +46,4 @@ Besides configuration retrieval, the API also includes calls for browsing the co Getting a template-processed configuration payload for a component (entry `tpc-full-qcmn` for component `qc`, with `list_of_detectors` and `run_type` passed as template variables): * In a browser: `http://localhost:32188/components/qc/ANY/any/tpc-full-qcmn?process=true&list_of_detectors=tpc,its&run_type=PHYSICS` -* With `curl`: `curl http://127.0.0.1:32188/components/qc/ANY/any/tpc-full-qcmn\?process\=true\&list_of_detectors\=tpc,its\&run_type\=PHYSICS` \ No newline at end of file +* With `curl`: `curl http://127.0.0.1:32188/components/qc/ANY/any/tpc-full-qcmn\?process\=true\&list_of_detectors\=tpc,its\&run_type\=PHYSICS` diff --git a/coconut/README.md b/coconut/README.md index f20c2bfa..c6cb97a6 100644 --- a/coconut/README.md +++ b/coconut/README.md @@ -1,4 +1,4 @@ -# `coconut` - the O² control and configuration utility +# The O² control and configuration utility overview The O² **co**ntrol and **con**figuration **ut**ility is a command line program for interacting with the AliECS core. diff --git a/core/integration/README.md b/core/integration/README.md new file mode 100644 index 00000000..3095650c --- /dev/null +++ b/core/integration/README.md @@ -0,0 +1,158 @@ +# Integration plugins + +The integration plugins allow AliECS to communicate with other ALICE services. +A plugin can register a set of callback which can be invoked upon defined environment events (state transitions). + +## Plugin system overview + +All plugins should implement the [`Plugin`](/core/integration/plugin.go) interface. +See the existing plugins for examples. + +In order to have the plugin loaded by the AliECS, one has to: +- add `RegisterPlugin` to the `init()` function in [AliECS core main source](https://github.com/AliceO2Group/Control/blob/master/cmd/o2-aliecs-core/main.go) +- add plugin name in the `integrationPlugins` list and set the endpoint in the AliECS configuration file (typically at `/o2/components/aliecs/ANY/any/settings` in the configuration store) + +# Integrated service operations + +In this chapter we list and describe the integrated service plugins. + +## Bookkeeping + +The legacy Bookkeeping plugin sends updates to Bookkeeping about the state of data taking runs. +As of May 2025, Bookkeeping has transitioned into consuming input from the Kafka event service and the only call in use is "FillInfo", which allows ECS to retrieve LHC fill information. + +## CCDB + +CCDB plugin calls PDP-provided executable which creates a General Run Parameters (GRP) object at each run start and stop. + +## DCS + +DCS plugin communicates with the ALICE Detector Control System (DCS). + +### DCS operations + +The DCS integration plugin exposes to the workflow template (WFT) context the +following operations. Their associated transitions in this table refer +to the [readout-dataflow](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml) workflow template. + +| **DCS operation** | **WFT call** | **Call timing** | **Critical** | **Contingent on detector state** | +|-----------------------|---------------------|---------------------------|--------------|----------------------------------| +| Prepare For Run (PFR) | `dcs.PrepareForRun` | during `CONFIGURE` | `false` | yes | +| Start Of Run (SOR) | `dcs.StartOfRun` | early in `START_ACTIVITY` | `true` | yes | +| End Of Run (EOR) | `dcs.EndOfRun` | late in `STOP_ACTIVITY` | `true` | no | + +The DCS integration plugin subscribes to the [DCS service](https://github.com/AliceO2Group/Control/blob/master/core/integration/dcs/protos/dcs.proto) and continually +receives information on operation-state compatibility for all +detectors. +When a given environment reaches a DCS call, the relevant DCS operation +will be called only if the DCS service reports that all detectors in that +environment are compatible with this operation, except EOR, which is +always called. + +### DCS PrepareForRun behaviour + +Unlike SOR and EOR, which are mandatory if `dcs_enabled` is set to `true`, +an impossibility to run PFR or a PFR failure will not prevent the +environment from transitioning forward. + +#### DCS PFR incompatibility + +When `dcs.PrepareForRun` is called, if at least one detector is in a +state that is incompatible with PFR as reported by the DCS service, +a grace period of 10 seconds is given for the detector(s) to become +compatible with PFR, with 1Hz polling frequency. As soon as all +detectors become compatible with PFR, the PFR operation is requested +to the DCS service. + +If the grace period ends and at least one detector +included in the environment is still incompatible with PFR, the PFR +operation will be performed for the PFR-compatible detectors. + +Despite some detectors not having performed PFR, the environment +can still transition forward towards the `RUNNING` state, and any DCS +activities that would have taken place in PFR will instead happen +during SOR. Only at that point, if at least one detector is not +compatible with SOR (or if it is but SOR fails), will the environment +declare a failure. + +#### DCS PFR failure + +When `dcs.PrepareForRun` is called, if all detectors are compatible +with PFR as reported by the DCS service (or become compatible during +the grace period), the PFR operation is immediately requested to the +DCS service. + +`dcs.PrepareForRun` call fails if no detectors are PFR-compatible +or PFR fails for all those which were PFR-compatible, +but since it is non-critical the environment may still reach the +`CONFIGURED` state and transition forward towards `RUNNING`. + +As in the case of an impossibility to run PFR, any DCS activities that +would have taken place in PFR will instead be done during SOR. + +### DCS StartOfRun behaviour + +The SOR operation is mandatory if `dcs_enabled` is set to `true` +(AliECS GUI "DCS" switched on). + +#### DCS SOR incompatibility + +When `dcs.StartOfRun` is called, if at least one detector is in a +state that is incompatible with SOR as reported by the DCS service, +or if after a grace period of 10 seconds at least one detector is +still incompatible with SOR, the SOR operation **will not run for any +detector**. + +The environment will then declare a **failure**, the +`START_ACTIVITY` transition will be blocked and the environment +will move to `ERROR`. + +#### DCS SOR failure + +When `dcs.StartOfRun` is called, if all detectors are compatible +with SOR as reported by the DCS service (or become compatible during +the grace period), the SOR operation is immediately requested to the +DCS service. + +If this operation fails for one or more detectors, the +`dcs.StartOfRun` call as a whole is considered to have failed. + +The environment will then declare a **failure**, the +`START_ACTIVITY` transition will be blocked and the environment +will move to `ERROR` + +### DCS EndOfRun behaviour + +The EOR operation is mandatory if `dcs_enabled` is set to `true` +(AliECS GUI "DCS" switched on). However, unlike with PFR and SOR, there +is **no check for compatibility** with the EOR operation. The EOR +request will always be sent to the DCS service during `STOP_ACTIVITY`. + +#### DCS EOR failure + +If this operation fails for one or more detectors, the +`dcs.EndOfRun` call as a whole is considered to have failed. + +The environment will then declare a **failure**, the +`STOP_ACTIVITY` transition will be blocked and the environment +will move to `ERROR`. + +## DD Scheduler + +DD scheduler plugin informs the Data Distribution software about the pool of FLPs taking part in data taking. + +## Kafka (legacy) + +See [Legacy events: Kafka plugin](/docs/kafka.md#legacy-events-kafka-plugin) + +## ODC + +ODC plugin communicates with the [Online Device Control (ODC)](https://github.com/FairRootGroup/ODC) instance of the ALICE experiment, which controls the event processing farm used in data taking and offline processing. + +## Test plugin + +Test plugin serves as an example of a plugin and is used for testing the plugin system. + +## Trigger + +Trigger plugin communicates with the ALICE trigger system. diff --git a/core/protos/o2control.proto b/core/protos/o2control.proto index 17698b13..df3069b9 100644 --- a/core/protos/o2control.proto +++ b/core/protos/o2control.proto @@ -30,11 +30,16 @@ option go_package = "github.com/AliceO2Group/Control/core/protos;pb"; import public "protos/events.proto"; +// The Control service is the main interface to AliECS service Control { rpc GetFrameworkInfo (GetFrameworkInfoRequest) returns (GetFrameworkInfoReply) {} rpc GetEnvironments (GetEnvironmentsRequest) returns (GetEnvironmentsReply) {} + // Creates a new environment which automatically follows one STANDBY->RUNNING->DONE cycle in the state machine. + // It returns only once the environment reaches the CONFIGURED state or upon any earlier failure. rpc NewAutoEnvironment (NewAutoEnvironmentRequest) returns (NewAutoEnvironmentReply) {} + // Creates a new environment. + // It returns only once the environment reaches the CONFIGURED state or upon any earlier failure. rpc NewEnvironment (NewEnvironmentRequest) returns (NewEnvironmentReply) {} rpc GetEnvironment (GetEnvironmentRequest) returns (GetEnvironmentReply) {} rpc ControlEnvironment (ControlEnvironmentRequest) returns (ControlEnvironmentReply) {} @@ -42,6 +47,9 @@ service Control { rpc GetActiveDetectors (Empty) returns (GetActiveDetectorsReply) {} rpc GetAvailableDetectors (Empty) returns (GetAvailableDetectorsReply) {} + // Creates a new environment. + // It returns once an environment ID is created and continues the creation asynchronously to the call. + // The environment will be listed in GetEnvironments() only once the workflow is loaded and deployment starts. rpc NewEnvironmentAsync (NewEnvironmentRequest) returns (NewEnvironmentReply) {} // rpc SetEnvironmentProperties (SetEnvironmentPropertiesRequest) returns (SetEnvironmentPropertiesReply) {} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000..0bd697dc --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing + +Thank you for your interest in contributing to the project. +This document provides guidelines and information to help you contribute effectively. + +If you are not in contact with the project maintainers, please reach out to them before proposing any changes. +We use JIRA for issue tracking and project management. +This software component is part of the O²/FLP project in the ALICE experiment. + +## Getting started + +Getting acquainted with the introduction chapters is absolutely essential, glossing over the whole documentation is highly advised. + +A development environment setup will be necessary for compiling binaries and running unit tests, see [Building](/docs/building.md) for details. + +## Testing + +Run unit tests in the Control project with `make test`. +To obtain test coverage reports, run `make coverage`. + +Typically, you will also want to prepare a test setup in form of an [FLP suite deployment](https://alice-flp.docs.cern.ch/system-configuration/utils/o2-flp-setup/) on a virtual machine. +Since AliECS interacts with many other project components, the last testing step might involve replacing the modified binary on the test VM and trying out the new functionality or the fix. + +The binaries are installed at `/opt/o2/bin`. + +`o2-aliecs-core` and `o2-apricot` are ran as systemd services, so you will need to restart them after replacing the binary. + +`o2-aliecs-executor` is started by `mesos-slave` if it is not running already at environment creation. +To make sure that the replaced binary is used, kill the running process (`pkill -f o2-aliecs-executor`). + +## Pull requests guidelines + +- Make sure your work has a corresponding JIRA ticket and it is assigned to yourself. +Trivial PRs are acceptable without a ticket. + +- Work on your changes in your fork on a dedicated branch with a descriptive name. + +- Make focused, logically atomic commits with clear messages and descriptions explaining the design choices. +Multiple commits per pull request are allowed. +However, please make sure that the project can be built and the tests pass at any commit. + +- Commit message or description should include the JIRA ticket number + +- Add tests for your changes whenever possible. +Gomega/Ginkgo tests are preferred, but other style of tests are also welcome. + +- Add documentation for new features. + +- Your contribution will be reviewed by the project maintainers once the PR is marked as ready for review. \ No newline at end of file diff --git a/docs/apidocs_aliecs.md b/docs/apidocs_aliecs.md index f14d9981..7004c905 100644 --- a/docs/apidocs_aliecs.md +++ b/docs/apidocs_aliecs.md @@ -1473,20 +1473,20 @@ Not implemented yet ### Control - +The Control service is the main interface to AliECS | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetFrameworkInfo | [GetFrameworkInfoRequest](#o2control-GetFrameworkInfoRequest) | [GetFrameworkInfoReply](#o2control-GetFrameworkInfoReply) | | | GetEnvironments | [GetEnvironmentsRequest](#o2control-GetEnvironmentsRequest) | [GetEnvironmentsReply](#o2control-GetEnvironmentsReply) | | -| NewAutoEnvironment | [NewAutoEnvironmentRequest](#o2control-NewAutoEnvironmentRequest) | [NewAutoEnvironmentReply](#o2control-NewAutoEnvironmentReply) | | -| NewEnvironment | [NewEnvironmentRequest](#o2control-NewEnvironmentRequest) | [NewEnvironmentReply](#o2control-NewEnvironmentReply) | | +| NewAutoEnvironment | [NewAutoEnvironmentRequest](#o2control-NewAutoEnvironmentRequest) | [NewAutoEnvironmentReply](#o2control-NewAutoEnvironmentReply) | Creates a new environment which automatically follows one STANDBY->RUNNING->DONE cycle in the state machine. It returns only once the environment reaches the CONFIGURED state or upon any earlier failure. | +| NewEnvironment | [NewEnvironmentRequest](#o2control-NewEnvironmentRequest) | [NewEnvironmentReply](#o2control-NewEnvironmentReply) | Creates a new environment. It returns only once the environment reaches the CONFIGURED state or upon any earlier failure. | | GetEnvironment | [GetEnvironmentRequest](#o2control-GetEnvironmentRequest) | [GetEnvironmentReply](#o2control-GetEnvironmentReply) | | | ControlEnvironment | [ControlEnvironmentRequest](#o2control-ControlEnvironmentRequest) | [ControlEnvironmentReply](#o2control-ControlEnvironmentReply) | | | DestroyEnvironment | [DestroyEnvironmentRequest](#o2control-DestroyEnvironmentRequest) | [DestroyEnvironmentReply](#o2control-DestroyEnvironmentReply) | | | GetActiveDetectors | [Empty](#o2control-Empty) | [GetActiveDetectorsReply](#o2control-GetActiveDetectorsReply) | | | GetAvailableDetectors | [Empty](#o2control-Empty) | [GetAvailableDetectorsReply](#o2control-GetAvailableDetectorsReply) | | -| NewEnvironmentAsync | [NewEnvironmentRequest](#o2control-NewEnvironmentRequest) | [NewEnvironmentReply](#o2control-NewEnvironmentReply) | | +| NewEnvironmentAsync | [NewEnvironmentRequest](#o2control-NewEnvironmentRequest) | [NewEnvironmentReply](#o2control-NewEnvironmentReply) | Creates a new environment. It returns once an environment ID is created and continues the creation asynchronously to the call. The environment will be listed in GetEnvironments() only once the workflow is loaded and deployment starts. | | GetTasks | [GetTasksRequest](#o2control-GetTasksRequest) | [GetTasksReply](#o2control-GetTasksReply) | | | GetTask | [GetTaskRequest](#o2control-GetTaskRequest) | [GetTaskReply](#o2control-GetTaskReply) | | | CleanupTasks | [CleanupTasksRequest](#o2control-CleanupTasksRequest) | [CleanupTasksReply](#o2control-CleanupTasksReply) | | diff --git a/docs/building.md b/docs/building.md index ffd5fbe1..62489880 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,6 +1,6 @@ # Building AliECS -> **WARNING**: The building instructions described in this page are **for development purposes only**. Users interested in deploying, running and controlling O²/FLP software or their own software with AliECS should refer to the [O²/FLP Suite instructions](../../installation/) instead. +> **WARNING**: The building instructions described in this page are **for development purposes only**. Users interested in deploying, running and controlling O²/FLP software or their own software with AliECS should refer to the [O²/FLP Suite instructions](https://alice-flp.docs.cern.ch/Operations/Experts/system-configuration/utils/o2-flp-setup/) instead. ## Overview @@ -84,6 +84,6 @@ You should find several executables including `o2control-core`, `o2control-execu For subsequent builds (after the first one), plain `make` (instead of `make all`) is sufficient. See the [Makefile reference](makefile_reference.md) for more information. -If you wish to also build the process control library and/or plugin, see [the OCC readme](./occ/README.md). +If you wish to also build the process control library and/or plugin, see [the OCC readme](/occ/README.md). This build of AliECS can be run locally and connected to an existing O²/FLP Suite cluster by passing a `--mesosUrl` parameter. If you do this, remember to `systemctl stop o2-aliecs-core` on the head node, in order to stop the core that came with the O²/FLP Suite and use your own. diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index d3993587..00000000 --- a/docs/faq.md +++ /dev/null @@ -1,2 +0,0 @@ -# Frequently Asked Questions - diff --git a/docs/handbook/AliECS-environment.png b/docs/handbook/AliECS-environment.png new file mode 100644 index 0000000000000000000000000000000000000000..e944448062ae981851c520fabca5197205be99bd GIT binary patch literal 85353 zcmce7Ra6{Z)MY`?;1US#1a}Rt!6CR4AV_ct5S-xd?(Xgm!8&+wcXxN2`u0;<$0+`v@TAMIB8atSn*f^To{yclvB?thdfV8-X>aXOb<2?9vg%2J1ZtSm)Qf{_SHhDJoLC4*yyF7GB z{@ju0(i3E0tC@1To9cUKmd8&UXXn#}ol}>(asDf0)4wgz_D;m4=m;>mgnsUe01WhR zVSeJ=eQ$D!1~&=;%lF`zbh%mwY3j@hsz}q=C(ejDD%D>Y{D#ZNfj_v>1Tf$S=m4p5 z4Dc4vK7IKA&xWldG}HTYR`c%be|aOf_^oA4_J(enyvZ;{1%+b+MDz=>8Z3rLX2Yy-pd^73t+#n;sI3Mq78<2ku+~SFm`t@rs zmWz?#*CIJgF0wGi(WjlqQFpac7t6`h2q=o8g=5sFJa^Re-2{B#^}!`+baxfFnACi{ z<9gi~uyJ|5I%85y^AnCoNpSm~k)aZu5I3G=qAXpJI+NK_mMtt~wlD{gr6ga$r=%`A zLMepYV&M^Q7AVI%Pn$Xt^1HoG&hWLk;zw?^cf^0??D^biiTRbWxn8CIb8n?*5oX*} zUG0?wc}|qJ8u#Z~#~@Qdiy184M82AfLG`qkn$g_6o&wAZ_G<%b7}&?w_!r^{%-_Yr zsunew{7z%HE0Bj;UFuOqUIrYbtctQqqBQT%*2hB)ZJyJm242abhRHg9Xhq{oj_cRP zPrQ!i5@owLVRJeaw^=ylk0uw|8LRuw2))&U0*hX6JMpaz6naNUoR4@0Oxg`LQN1E9 z8NOTh)gpSGk3Wr6BdQ?=X-HKaoL2)C?orl>IBj3>H(6_550=n4wRvsNzgmttvx$dHNy*Uis87R0al#%y} zg=$b2d`JGsd*W=R98zRQ*krjjCY|;GOM>QP7Yq)(R(zrUvA)8qD{7m^jOVE^m&S2!kUO;Q&I2i|u2B zP7w~%8S!j`@s$?)fcL{n7Wn7oCZ`E@8cu-2=Htt@2AJZRSu&rYb7|bp{;&h2n23#_Fr4q{R^Q>u2L&{qe4ee*i!(cN zT+QXY!<+)L4XT_?HWfB_PM@+g&Ub7wwA)#8=dd!9Qxp4f$$MlYFrk1H)l2?e0p7Rg zwxi{K9pJqG)0UX;ONIK=;f=d`#Zp{!yiLtKd5<6b&B^^lTUA@+1y<~&mws~~^LX;- z!g`Vf9T!Y*oz@uU@uyd@7Pgk~DPyOC% zzR^KtdNdY$?odsU#qUwmEePoq~q6ZZ7s6D zD8_Xxy{pDzg&6qA+N=X{mgkB5FCj^Wo0i+-vL`-1zqGeG*|5IkDKj5VRe4RBqp+3y zdPP%tu+%+&YTYWT+}{1+>-X&DV$1%L$NlmWx@__TkGJc&@wPk0@y7u#T|zr(aCRZQ zGmil}j6lOV>(wsPmrgL8rD6 zYiB*gQ#J!1Tbb4iZ0tx`JLh!-vXNW+Rc9Yq`nhsR9y`Q;$36yXV_hkaW;=W7C zU868QvJth(!b}2O4Fq>Z zCfn;gf-bl4!|FC~-yX-uer2{H-knh|ueDyy*K~NRBB6Y?+g=W4&EkFMKZ0`vhGf&3 zpx3u2TgJ46f9ycM**2={R$Q3IDUW6O>~s0?bKWPWQJrSf;V#ECj&i-{@=?CX^Q$Ex zkDJq$Fzzpk_iSClNBnNP&k^i24Y6u?No@cAIYOZKMTWGIV(a{w520 zPobUs0&NY@J~;A6muxX92`&c~<)0!PH`XU|zkSGwKAtF;iOZU=_QE#c`FY&)Alo#w zT5GoRT%eQjq|l0$q)XMk*N`J1nEsrqQn7hfppbH#S684Ub$c|E$$U2N*j|t7aw_l# zHxT9Uv3_8C$5Nwm5{BmT zxtQPn;iGuEvl$;>E-W;W_jr2(Eq6`ADqqCS6UNc>B{RnRTbXFl5rd^6dc=<8c$H+R~HF;`U?UW0>^g79z}vU9`>1iG!ikJl|n zye$F1ziKa4%^6{!+rIrvQsQ&oQKKFtm9uio5DN-e-`vUFPz1|o(R+a8%5BX6=WUp! zyjR5Okp0JFkZtGN7{VNDUQzPR3JG$r*ZJ7rv#!)lkImQXvbEijH+Z1&ls_>c<4kve z&BIQ^>tzGfxD1u^6-9dB+h3nCEC}lDt$KdJ9*@{3 zGmn=2kp}`m7~PlZ$}HU5(dWwb=wA&|@7l9!8GC-ANkrQ7=&N;@j61eYogs5?bp{`Y z&I3f_kI1XX&Z?i88`=Z#pT(R{1++H#Va6d@Z**}oja5$z2TUMGnhhcGKm*{TGP@iE zB6IiquL`@^y%21>bBu9YZ?d3)yQw;tGa=66tS5mKekTP73u-_$s<_pAiF~i$qCRkL zEw$zCT&v^H^{x9_reJeBN8IYm{TJlk#E~pk`(@JCB){B!kfU9&3x~Ze@__L7kO*Sg zTt3mxnWNGuv4_cX0wc}Ovy_r>iX)H`Tm^J8{V@ZJ$_|o_Goj}LbwV&Z+C-jSbbkpJ z(Zxiu?;#veM#qKa1vUie@&lXvp z5jErK-sq(6d^V{}LrH{6M@9?MuGl4uZno|D!QtN3^snumHb1w^@od{>!Q8(0J@VVe zy6nHc@akNBOKa)=-U#>%T4}X7-+@H)#iJt#d7uBs^7^BbC4bz5gw?87e&5~>sx`il zQG&c&N2+6|*La0;NwgZ``OXVsWu}rZI`ZLMjEf2gVn)wt@0JyQhTgE>3j;nTTHZoV z_U^#9sm9*ts?+cWb~tF}e)QyX;RVW9>bvTXHEhH8Zx;U%+az$732DNoX=EcJ-op1GWlx6O>{!;& z-qiu5)uFaqa1){T!YP}Q?X6WwOj4uZ1nni^sym_3u8=t0h?M*j+HC9JR+oGJV zwY@Nh%~fhQ@o`s4-CZHocQG$LLoOLX7X0v%q?q;K{(&HCB*>xd!dxYdtCSJD)l41= zXlRPXwG7SGI|@QO90`2wnCNnTgubbr<&x4d73xYy-5%iCjWWK^NY?4q3 zOpLw$N6>`~!->tPG(3N!?&?}LPV}~8bMad5zkXs&o1x#(t;(HdtLTyj^b`SK$Ue-E^IfW?fmtO)g7|Ss+j|V}3Y5h1i2h6Z(U5b1|ZA ze6~)Lf#HHK{kN8hXxl7`E4=T@DbICvli}0p7Q-;KFxZ;NV)uRe9R*7ldDS>wrE+)` zbTuJf#7#|Y#y|5UsM9=gb#2weXR|D`KHOlTN?#*y_icjtuA}dXxtpW!O8G!?mPy3j zdIk07XvXHS=n85b?DGaoQlg|x?)cjU%lAU5>N4{Sbg3tJXaE*`#__92N@?W%DMR5b zRP7AInO1=-oQjPkaS)fhC zH5*CqEH}IQ4ftp>SS@F(U%16B7&U4>)Rt8(M0C@kIXvy`SY|rwZ&0Zy6+51W)Yx3T z8W%C)+8VRqm;`X7RB*iXBwdncCJs@}f|U1{VwpHO9dR@X9x%>;qT_Azi%RCgFw7MSkF=xXQk*?xvH{1tF6xC*U4|tc&+U-}lCI z2fwZRbt6_Z#b5w8e!mT5?inT_nyQNjY)NLppQOExv~)AJDn-_)dh7`$CZx)T2@+(> zn~r&Ua6blC%z6B}fKynraYa7@v+KE4W{!$-JW7u4;pvXKN;hiDyNSz2KsB?!)ithl zM$}Jvs+L2$_rP)PLG9)VHFKxQ#eAvs4+UJ;c%amMx3v63!DGB0cQy^0VN4Vpgrxb4 z#Z39_ehKVvAW2H*B|-F8=n(a+grO@cd_R18;y_DSldiFsfannpYg!`_oX>H(Z+?8Z zS+D{#_L=s=zBY1n#cUcqgmk7Gt-ZX+oPwUaU ze{wEK_>OmO)^ojHPkWQ1^)SEgs^%gN)Q1xa>c8);2Ju?-|&kUhltv9dfowM?=SS7EE&pK9T(!AJ+J0>Xh%;Z?dZ60(D!F22 zW>1hPzq;f!8^dMHWn^qk3^Eu(0I((6DjgCBiAP?SGQUmDv*qX=NsbY|fw*wUd;hY@ zwCj|HCeDpFS@DML#dUcckVXOV^we*~Un)5J?q!lE?BmU`bA#)F zVbXp6*5?MoM!S0`Da^YkXAcqExe67eJ^Zc<71K6GBf9DRFX0d2Z;a){UuO;qX!qI$ z+dm264cjmOt;ynz!{ZtJJ3_#ETZbvcKJJ$!b1E0SpQ;{?7WajpH^Ic$9=tPhFSM{- zw6|Qxmx_9Iy6-32-25KLPgGcYR9me zEJhWfGPDt;{YC4pS`9v9J@5DzO_#CAjZ&d4K}k8q<^Xm>{N4wN?Ay0kgX;S>3*$ z%`)N#OY|Hs`%djug5@s$_=qv0)XQ-(5{0a!#7r#geV47-j*7O( z48CWV5MEOZDrHtdv+<~_k}j~LkJ1yGZ8GE2FGnNfH3OkC1uvHJ5oGm{xN^-7$Ns~N zQT(}<^J}Awgbag4!A7N6cr6B;)8+Hw>G9n+u#tneb+r)~vKi>raJ_mI;m3kL?3!QWHas}v? zpy5HqLh(Xqu+9;zp|}jRj}Dn#zlM1|Ucu_$@Of(jE9pK7H1;J@{n&-xHy`FVoYhq- z@fCH%bJtj^m`*|ce)@i+jo1hFRs0q2*IaL~^BQz#Ne^oMX)b7ZEUdAn07?or2J_AK zOF2$ysDwgJ3+8U-P~X4*`kU>Qv6{idz>z;>d}2>Q`FG8nL#Q!jiRk?5X1A)NuWYN0 zGpz#1R!&H*wdjjJ9o9HmALfZf+n>s*Po$LQv274u@2nN0!?^lFLLjr;DW7*wuVXr{ z*Jyu4HrFmiSKu+9U77V}tNUYkIpICt8%_ZVf#)<`=kG3C#CP-mO4aURX%)_X)2pSE z)W!O?d3v_r=XrHftsNTrwQ_+{(+!#|IGKY(k=w6j|EU`{D|5z&EqlM9eWJ4guU_1O zT7M!wR%FLjyRXtM2KWQwMDw>x+a->=@4n*CM!DHXmTO>+L_3mg9r7#Zm#$t1R1te| z{v8>Tl^au{$#=@5kd>3Y7B&2MtH1ejoDNM0d3k9s3xG{%{D6ffF#+P<1MKIp{@tT3 zHLC8SXL#5oZq3Awq~ye;Dtn%NkkEi~t^V&BPma-L`Q|Kx?Ee3hLA^S+ecAaqK~~-u zFKsRnbWZXzH)jkn*dEg{*_K;0(i%xp^x(^Im2!Gn4g^qLS2d5;q(DayhXq17{~H4T z--dMk#NouhP09(yYU)4mq!x@2LaO6ajI|QkpsFRT&5)l?=)skbnb|Iult>v7w?Em=d}T z7@GdTl1%bsUKB6v~EF*9+nH<%+V)evpf<~Y_~v8$x?&f zM@dc18iGJ6Dko=zNk)xj_@0&;*O(0hJUJjzW0C23EC0KjC0bOM(z!C16*L*lv4*Xu zN~+zRn1xp$CoFd|`D;@-Y8PQEyDQJWcX4(o-sb_=`5;8>6f>-FTgE~eO{II>gt38J!>0^C| z71^9MZb(J!b)VVI>T9McvWAEw0Ejd+E;=+c1PbUCmJ+N-Vu2&Y#zY>TycX+44K(?Y z`VL6kp-DC6iOl`jG)Ba+m;DEbdJE`gjh1C$rmc)MTxHXx{nNgaEE8JxJz4uVBCOO9 ziIQu7sxjVBtV9YM;Q;bb`UZ=MQXz5>uV3Ctlz@hCDGa&o?|)(FZb<(6JY>CyDL&C# z`N+6`?Uk8GcX-GkEKmw`Krf7c?#PK5%{!nvKE{|fk&C@Pr2v`1*pLQGHce}>M;UH0 z$As|nK#uXTb|90o$AIBCLbx`;mS0&sWLQIDQW7k*NEqO&Yqb)wsLZ<+FI9AAX!y~c zqXlRRop0-x5mvu{GjiDBvkwcNBBDT8Yl7p05lS>^Fk(c>$fyx>lt?LIMSam@p>v|a z69#R!eK;ioG-Sxb#W`_@kfXPjL4uuQl$T~vgdG*$y2u>9f1Y+ggcTLuq~@zSpGm_6 z7oP+xGEhzUks;Hw;2Nq*LV48<(r5sS(EO$e%3 zsa}>jHzgnP9d9CC3u_>jD2Xz@aPTvNJwSHxNutw#-ayz5QG6A85~+9izU~znL8{=Y zOA#6}*={$5ief!nWZhC<;ZwWwuMv7N^UD%vcMXGeB168>9zvf`1BYj77nr=Yv2+%N+}Ye*-FLrk6$j-p_QeFMExhxmGQ~r z1EMFIYe=MfJ7Yt#+m^kXwqPDBo(imUyuv?~|Lg?eb}wQh(0dc1eh zvJ!MHng!9t+vEwlxyn-5Y%${7uDnc+QonuT-(ye4Mkym3vn+Q825O_%o{C{6Gc0;^6GF7NAdj< z3i0w1+Ts+}q?sKy+=pwx`F#J7>-K?v{>%B0f8n>lbR;~Dy%=(}n?fuB>yf(dNgUZz z6WLW0*;mfA)6-tokI72vYUQPjx225g5@FaeQmI)afs;!&cjbXRX-u<)Q3VWIm0wtO zNpC9|Z)_Sc_XNRclYIz+p((qkd*^}Osn7PDfVZ2V@l(<= zb0A*FeB|%c2Mfl^G3o%ADit*)=J4VV4Ct62c1dz`Lpe0xk=AvjK>SdW z&bqoe*jJ7aGy6TFQowx~Hd_t+Xxa8W(NFbGGIqtRl%nyuc{W@}C& zua1)<-q2b?R}5ZB{x-sE^LPHJI{%6jI%l$N7vT)Q7gjXE%-UU>?ftx16X9GOtyd#@ zFX_C28a+QLzvhd~fK|I&HA7mArIggTq8xqo+ul0r^+4MI0>PK)9Xre%=wMFkQReWc zFInw=CRP9`X+mf>Q)#py=GkK=7s`c|9nPqe=OdHxnBl6JFP3uE-`|JD+z266Mb_^8 zgDoS!EhFc{bE}$?MDdAEHRyW`$Rr{o-y@DzOCP}6)4Cq;YOjiH=5@DD<2IB_BMeRP zw0VSaLM*Vjw$~p041USWlonG+mXzLB`RBk^Q8F0M6j)DDR^?c`0zO!5UCFeoF=9g} z-~9#$th>H-B`d}6(9A2&&tXAVpl3_dD$>$ki~G#vE3zL{eK0wE6M7Q-xH|6#OUKjx z5%#R=c*I`Bs()l&4Taeg*JUV>d89ZE9O~EJ8r1Y?xTe>y>IDTw5p=%q=3QoL&Ps`Z z@5*0~9t|7Fq4}+`H~Woh=M{COWn`FPd`LN86Gu^fYzTyrtj-Yu2&rMf5r8d&GC8MUXcnx4Rb%6sCF zl+nWjfxBaA_H?2ZM+Bjk77DXNZ zU#`K}7&(kdARO=x#5fry$`kDU2>Z6id1yo)0aag2GTR^_Ut2ykO*X{N+4~IVQuzrZ z23FGfK%~siwPc}NHZMJxGs)cq6{3uJmRvfD(;4ia8|)D2qpEJ*sky0_WK77LUJq?^wi6W-26{GvMBqHy+zTl2%dBp>_)#5K? zT$llm5W}7Bf~a$tk4(trZyy4YtQIvb|Hz|hJA95b(mcOPC?)t+V@)1gKs#2^-vQI302Yi3^xW>4Sw@?Z=>6JHW&$bG11+uguv-ui>==6 zCHR0*R%gI2d6M56q33Fz@eazzSm1zaTkW&r6`_s|xjy1gN&4PeKseqnff$^Wv=$4% zQa=&z-ZRxm${)hIN7F^iLW^>ljrTlO^FvEJEiXP|4L!k6)+Mxd;ctItlFYac0Ptwl zZ@Ao!E}*KEez6w$g&ai7xwq@$vG5Rcjv$Kp4uBs$7Q%{3Cre12-%jUgdLa>zX+gIn z)+IF|Y<)6=XmMnw>x%w@DLdVLakOQRC&v9H*hQm$ZNR{&({yt#A6B6&ZbPO|J$GhG z+!74{J7(Njmqzfw-)HUj1!{Oh(Ow&R)pJs+cc(?3SL<&IjtYB#A(FxqFVSrAP!#E5|50zsz$W$TO(8sddMrzjr);kzGhCn1w^0n&{L7d*4 z_=H{Q@v$Y+QuI>86EkJRAF)1A#>F%9VT>a}99+)!3?hbU{-r<(GhsB?V&ZAdK&G4p z#2+v%fo{7T+fnJE^-SKTJb#Y2KW1sKb_nsLyUF$PGm zV=1o{svVBeG%b9?X0u_izo~r6Mll%hHCk~r)(xwk}jQUxS>dwL= zal+~yfDFMGhixjx)kdxR6VF$(i!szAn)YThqsuI(|6@qNnzr4Fwp}jR^MhJzap~;` z`r(N>#7#u|WM$-qpzDLSbR9OCc4LpyF4{ZWr?q(f2ddgq#fEQZ{?pH4E^I#~hfn&B zmqHdS63W$^4NiiAl8-3$^dAW!CfkQwDh^naNIz1c0I0}0r%ee!Jb=ZqoIv(hWhhtakdYhhc6c4vsO(SuQXYzGbN^07!}jmz?D2W)ss6=l zdoE91uV*7=e9AjpdNlJ-H#Rv`}Pv8vlRT56WBq|yL()GZ_`|~I@sJ_%M!lmuKfv!O)b!o8?T0wFxpR5Dh;;CfQ zT1C=Yp?U)_t3E4d4Pa(TVpcsi11E-$wT5_UYVK3HEk?BMMpLUBJbQ~ma^<11@bxNi z&L4()1%4Q%Z05x_+Gtjo_*GvM>M6S!$|Me0)$Cz^bbKFfX1k1wG)U@gBIQFjzc!4E z*z}1-he*ryam2$@GE(2Tn&$`+iukxH6yc%-ZzN_ zL^c#w@OMG^9jKG-S8IftF8EQ8ob``5IQv96`=e0XmU6TysqrH{9ub4P+4mb15j??6O7cLmYp^hoQFy{&XjfxvO1UD7OLrB=S5&C*OVq|eqdA<58a*t zdkdbg3ZKx2cp0q0lsGP=Y$KZ}jB3K$)OyT0d&h(>+kPnPfiLZe559?S;%Ucy54T>G z_*AwtdK932qN<)rM)4n~q@PZ)M*Ykw8<$$Jp)*vF3p6ax>FjP%U451A*v2d;=l4qz z8&MCEw4K-=ebAQ89MCkcwnHn*NDK`ido9fdOoF?Hbg0VqU0R^S8nBRYWo9Ds4<*4B z3-R*qkkXDHoP7&FYvQTm3K&7=IVQ{sgvY~$Zex(U`YRDDfSq71y6E5Y^Uk2eXLqifMaY8&V2wKvrikM-bBh@b#W3=ph>q9z;oibwdxz@9fiQm1h+FGe8ZW@Zm=H1}6QWIZ1I{5{IYG2MHbkOFV3%4m+Pwoh;$Sd(R&aFg&Ih4a?Y(nJ*8ZgtaJ`o|E0(V?;YwyLXcwR-}t!+xzg=gc~iB zVv7VGuT+q)s9TgN;-%iKOj)y``&zXsdY0wr7L(%4avh4&@PqIpXP z`NE`0A=}D1a&)nwQYFdp!O|hY*kMv7polZ*&>Y|UA#umbdB~!u!a1m4QtKpsR6*wi zHE+SDkw;D)l81YJ)n#Z=pn5`e@paHcRXLy>GXS%F)93^)FoxX3DW>)_jPp}fWS;fI z*aoG^V1D^RJZF$SwgW?N4F~A3a^8YdL%$7Zqya1?Y0m)0|*u4_T-76i#HO`T)`+mbpa6n{^9Kax(!V_uo0}H|is#-tT z|5LU4luZM{iV*Uv+QE!2 zztAC;@VE&D02F<%{_`Z$5yYjtnu}khv>d?Lw;s;3at-gOHutcYV?iYvp1(JJZcgx( zi_(1a2Q?1ieJb=4#Mj@#+hUr@zqFGkJFdZBj&HsYnEWW@8u<)kF?HN}St$Xmo|;@*Ey zuF;iIFAr`_g2Oyg?zR)%z6;Kw8lL|c6lrh%ai~ujE|9A(KEz}MbOsI9aI)r5ADig|Dt|>p#~QUP!}(u$ugDVZ;sBZzQMg|r5w_SwxStVE zq@xltzO}3b8zqhN8-(-S!wl>7QW9l|jjtTvYfyVoY>2 zf^!K1mpVLKH8IC}B!U zana3%LZx3qftDx|Q{}QB)6UAx=~~&K}A#Zl*p}igkfB8u#?U zGZrBou8e&29ePE?GHR|-TB7`jQswltF9RO&v57ZbgLyHUU9ro}4u;p!l|c2`G$Z9~ z72IExP;iI92Y{oN=|jb;9jI~(=Uv^YahDV9q(1i>H7pRJLNO`P5ihd*v#47-b|&mq zlWD1oHPrMQ+%BI!MA5MA-}<;(Zb+1(+A|0EG|3;(CpXq+DO4hGe4nUMr^5KU{bbZXGxPC1#1zJttWBss0C zJ(t*&y^GEE-i}m$?O*(c=jSX?wlVw|HwXaaF`5jC{AX%p(V9uY>bCdaZ+m9a?iPt8 zE=pJFcKB>jQyw;SFl!J1!6(BDUy~yXN&i)Wj+vW2X7AUlBC4`dH58ea>M5<1 z37ZR{m&V$+=sBo${pXDSE7L3iC)KJcp0vU3P{gxW+e!GWR!L75ju%J|?;C^O{$j1j z)-Kj++G@df2%C?K{h1%i~Y%TRNsF;>6?;9Q!#Hd%oWxH9*UGrK+ zpuFy4&mgO^Dd5`Hj3thf>B56Lr!wy3$gB>w*R6$TVo@ls!*0M}4i2|!h4`}}Tvats zBxJf03V_AegOJ4;ol((mPjZdo-#?WqW_Gzz-0CM8o)PMHHCtZ=+MLB^Ch$32n(5pU zTCdf9%_#nR)O42F;c<0_!Uz8Uad*sv1W=W6-Fln=?Dzx+PwimtOT?hx0oh{11LMwm z!IzRV)xoVd1Sunh)A{az5=CjF6eXNb@xGhKyA<6{?>n3vCDkrNl{Y$(_+I;$A=nOu ztKoDlqnhu>`BCY5qtCx46K~!CVbv42h9fQphQDnDE=P|ENM~IF&)o-nYQ5w#b%ryw z5}>IRqPKHR2%?pfK1h|XJX}Am*$hx2NIMMIws^KS?$9&qw>jRZKeN+wj=Qt(1;W5F zWZg;B1v(*9*DX!Fjp~zP#?s@CoJ=;h{P~_ZoDZBoq*K-0#fm|{jGw%IqWnh!(1a3m z-=uNs+(tf}m$el&wwmllD9Xvpk1w`3c&$~Q+@~61nOQA5-H*@cbY`&zkit}?4d||R z;REE_*Rs;X_yYB4%$va)fp4A{?|^h6&+x2oy3JN%-y_WD;Qo*q9yzB6SI%`G+_B(a z8wWadIlWwFh3$6oZoDIeK&^YpY|f(4Lw$SRjuJ}Y46+?$8Ob~$__^5gh301Gs>u7< z?K9wUaZ<&&_|t<7SBq8zD;zB*A+5_}1rU|83yp1NKc8oOTVSi$HV8e+&Roqr%^Me_5gs?p6Bp->`t~9ulHHV z`C@bWV~Nnev)9(=B%{2m>NDBdPrIIGl|mibFJ{{)*3$2DnX;9A`yR=hiFpQV8Gvy7 z<)fO2ti;ufrE{iW%n}{?TJO8tO3tUqjE$FRKQ||b3bb<-?otgTKq0+Nxt)Z2*t1>D zBj?wp0Zfmy-He6DpYIGZHyVt&=G=!f!`^kt@>H2Sf&Esg9Bw{^EL~1dOj({US4b>% z%6`kFX!aDh#K5H_@vBYbCb9V--(t*Aj^;Vp3hKv$qkLwTQ!@(IdI5Qz48yI9Ek zcj_ICIXKeneYpQS-~U(I=zAalRsv0b+51V>-#_r~P{`^mG6LS&w!>~$ylM4a+a2+A zsr4tQueIiO-JZ7-OD@K?0be9vT7T;O7)2jPZp}f9(a44c8j_SL8J-wRdc^!P56!)D zZ~$-tPL*_H4{vu9t>x zGu+=Jh^i!aoflOF-CCHi;VdlPm8pMV*zC981J~jP-2&b4Sst7h9S4dIp>mb`Lfbd5V*1Psf79+( z_R{8Fh+LiqN=?G3y{}tzhV@?piG}LTo(ee~&Bo3DHB0ynj1Ip-)3KU=e?ez;vbYSm z@0*5jnQGk?NqK$KHyxO+LXketIuDdV0K=y*&qcCy)Zo zZU6G)Yl0lux0ShnZ!1#+HHgjr!txo$UlLLxI-M&1(n393+zp#XbCTBw`0 zhw0Pl=BHvUp`2yXDpdVdL2EwecRo~I9&-$zvkmj_ibXPP)|xT3DBQaLaRcALxXMVI z`N#+EKEU~!)h^ViS9F@udIRM%UZl|M^uh)Z&UdJ(BnzEArBockHd$&`7*8FJ=Dtg0 zPJstTbsD^{eUob91UKE&ULJxjTx>f1k19a6>inf3o@D8HuW9PjRn1T8Iti&*uw*aQ zx&3Cx)}~G%T&&Lb9G3hTxFV}CJjVFTSgH!iU7af=#70-B)uuvf4z-`$5G=J3M_=f7 zE;nt1paw{G^yqD9wkLZDl14HfyhekSBDW^l9FI{;4OfZ{Iy2q2{(fdI=vkQ5IO)Ih z6KZeEP-faXS}CLnx;?o(;+}$j=W&7>q;6ZT)`8#k5AI^<{AyEe&D+$=ImJHOb=D)$>w!(U{q%=xU^$4 z>VZLrsi%zYb28bU=)jC?zLZ)=34~CtMZ&{&IEM3*8d8i~*av z2Ur;O9!_;{YhK$16%{ubOIRf0wMW=*?0W^ePw-0nt9f7PzDJE+G9*bqgv&Dj#=X|B z)T+a%BIb#FdW6NUg0PhLjZ9OSHmJLn46m~FsrW9FBEUd@g%UQ3_#f&|Is1P#e3_1q zvzkNbqgPxgtSR3I`=9s9leOeJw@H%quQycE1hBg-Q~Yo zq;eH!I2dYpukJOqi^P-3dOslo+D&JdbE$eT($GLJ zOOvf`3BlC-RG|QoyNk`|X0tRY%2)f1_f2AF3QxHjZ#l{-?K0n_LiW_TKp-Uoi7+4( zKfQag{lP-Ld<_w(E9^Y^p`*{KnJ6#aiUbg~UT=b%mSLbpBnvs^ORqAujr)jL83QVn59bo`T)O})nU048zLC36)Z?VCAzSiE;tZvJ! zO1Ig$O(M_jM2Aaed`R#4ZlG`aI(d<^>)yP*ZZu-vJ2Rf_6akaowGs$^Is>gZ4*?L&{I#DAbqM2FGD)kK4;p72HbBa)Ox(&#>_0 z_O90zoUG*1XleMn)8#r9`p!|X8)`h}RnDFW2!d0@W3Shq$;PxjxZ%5-wH1nv&7`6n z1b>Gl%Bfoann1!;qQDm{l__^83q9smH*M3gwIi1S>ilS!i4yrKZn!)0!QAo5S7S5V zK5EeW=ge;_t!LN$N5H>ESFt^7Z%&r9@Fni5kS<4+BF1a=r$+OY9NF<#4`1(3Txm|@ zwV}oH@X9-C%Dc-2`siWL)n2 zA-~zd)1k0cacjL=>Is6eUX%EG%dgYz8~VK?ZmU@kuR9d#oL+|bnkpx-KTH)bRDa!C zIaTH@+O!soPKld4xq$?Gta``QZnShBd=nAN>Nk#e-C*deGe5!st(qnxaCi5g-G^=_`F5yGV zOy|4uWKP(8NfBzX;aO;ruj_bYLXV{cpwAa7zNg-QAtwL4&&! z+#$G2LVyH!m*DR11b26LcjsVtKkrxf*ZqGfiaMM+GJDU=>eZ{er>*NnSzljwhgrLy zuqxbgvTX#vA1(YViq&_U9`f(pfc-kpZM78R=ka<|6-m{muNk$!ByqaU?KUZN$|q3U z^l)Z-T+q&5?Ait=#H`P0dZM^C{BL{rw8nFIvvBDa*qFN=4)CgVFMTN}Uv)d;2W+(7 z#!tqDYLn#3ZL^R2X6zSa>EgxbZ6}(%3<8xoUtS!Rxyp;zD-ZX4_H}i}Kv%hsa~OQ# z36FQLmdEx{Z&sW3BLtS9Esn>`qM;;l49Z`Xsw3sEw9Iefi9AH{rgKR=%#U~DeJ`_Q zRsb+uUH4OFVM2@{L04h!`iTN`P;LIFQr+*29&uW?a4p}QU)h`cn$&6Q(wB(O>0aHN zH6&cOKRu6&3!xEkHJ|^Do~G)^n#JTn2OXQTWe?rcZ4F5HJKzbR0ah0uj}Ry7wz}@; zRP5cKPyWEZ|M1e1-u(S^>9mE`QtMe=U*45~2MX|Mso#!=OzwQLkm$J2TQoYID6Xnl zD)9==W}b~|5L)!w35%i-^7b-`j3@((#l3DuK<{?`oJVT%X0Kxp>-?yO7;8O5A?=~> z@8(PMhhH1>9dmYzPB)QiI&dJ=<(0Jvzl9+*J|FH#cpaQs(6--{fzm|NB^YkIJO@J+-g-|?MPVd%q98Ldy2>I zis5Xld4=B{FE6E$E=yg>#0o35_Y8n4BFd}#-aSM2$lE3|a4_ff?IlPqy?}Zxe6`&& zTqjc!?F4d?)4&^n&+6IL_=niD$j8WD@6B~j5+uQ{$DH4XW|GI9h-;U+@zp69aKF%ngLLAvG@DL*oT<@BCfUo6(7H>mw`PRvUHv7?NUBoX^8W z^jwLWwy@tHxDLgKnu9W(a*cZWqRkbEn?>qoj5PJ=FO`n)j(Hv|XamIZG(n zG?0xy*V_^|F#cISE#550<|$^bgnW;$I^=28RC#7SYhU5~H(y!=|JXi7rq}Qjc+E4M z%)S0wFdxpy@rq1?^54Dgz~OAfHdq7=;{o30cERXc_5MKw3^l~f{(NqBwDH&azBixc z&9_%m&>fIeFqk_+Z9z|`W172XuWqTgZa2S$Ui)x)&ukFHan{Rnay07o_IO*u9SnLf zd*Mgm7`z3|2N#FAX+m6^89pjcJ-`Zfcngd^y0-B(|I` zYoY0nmpyuttMi^XH+({Gopt;XmkKW~$J|qxJ8z&~kFUXerQ-xLa(VZAjFfEmD!b*Z zc*-5+BZb0WjR+~rO-sSZr5qGu_SU6kX@j#m? zi+jqKU-Cgee!ZK8x}wv=vtUkcv%{el<26r|>2$JcHg>_RUX!|=bwRBN>RT^_5Kt_E zye;ydSJFgqM|60-MC9C4kr|0lB9IwDd0~g+d2w5DOlES{YhvW|eaWf1w_{l7{E0wE zqMR$OqA2XSzu~$3@=~?)8U2fD^R>s-4z8KG+nUaKJO9%d|9Nc&pKFrm%gLXd0j1~7 znl&|MPS4*3GJH_}qee>&RdD{#T`|SsTC#nMK00a;Ydb3wn?iWNIQX7AJ|*{Se?9P5 zk%ryrN-ciBd^LXf6O{y<6O~tyfzxdyI}{1eXDa#z;hdI;yLvxc?R@?1gKy}-qxkH? z9GSHI@2~D3PywN`2XUVcdX(-F#?nDYBX3z{Zw7!tvpPl9>LZt=_3Kt9)P|b!=R>}U zZ}YFyN3V-_Xz}{5TiIR3;X)x_$f&rnsqgVLChOB-9u*NA{hrvFN+PnN2gmW6sw!MPI0K+6mFb}6A`zLXeY>SLIXzv$Ax-xfbM_PZ1rXNI$aLN+ z*-M&k-Db2TaVevv6@T(+z}r5OG=dd8_&O>bv^d zVF3#bZVwlWb=gbQSRL8VTgly#L=Wv?!g+)ASE(Ag5sq^@ASp>rp2yTYf8H=sE_jb( zF0U1-TG6%=>W?hbTNwSZg*EEw{Po^Tf5S6TtkLN)aj7EBt#d8s3LfX*UPEh?%;8t| z1!02*D=#%l0~#BKDynsgt{e>Tx7hZtAc|yERK?~JpQq{)yr1B%X&%ryJD1h=HUteg z^9AJeenNV{+r+)?xkpa4UvE9CzYkpVfe**y#|7L?DXkNIBexqP$}{F0X${C6iO_+Yo030MN~mjrsE)??8Q=xU zO6=ahCgwR2@-JyDODOEK!%$O45P4s}Za+n4NawO)#fm_L`%sQ`I9qe?3O}RM&8inP z#z|tyqTt&~Q-lsi4S#y^bkX4vb23Wk>8&z`Nu9YN1qG9zNO{WAdFsSty+%%eFwF-2 zjw5PY!eDN$tRII(seIB)H(QfvyW9TWr_lWKg;tsMP8RahI$5gOxyeh z;ph@46KEYN2nmvCT1>WU``qH{l*x1YR7`4TAg?TXavVniXto-4Cq8Kw8yVWh;t;E7 zMj6~Hg6gSOWo>oQwQnl)sO8TpCog{Gd1x^*p`{0v%}T8tIM8N@_o42W>&n2+LK`t482 z{7aXDZu8T02&!6qibRfx3x@w=(_-CuISF$x!@@$nT7|~Ui9*S3Voz@!HE=N`Ga&hs z??aXX;9pWUT_$P#ntv1tE-4PYcoem}OY}RhXZ?j(Stjav4nM@$J`^bbW%Qrr>!|Pi z9mpZ<#XJN;@rxL30Li6ViiGASXls&rA8~?4Gw)8ZT`wu4z9RL0JS;Br;#B3lc|^YD z49N^hcF5DH+LcrCydgF+u|Wc!-k#Mu{qCbw=;DjxgU@^*I+}|>=VsgTEcO!O!zant zM;Ktm({pL5&K|I?VP}s+3REsa@z^pLqi1AhE^!Uy1Cn&KVTASN6J)8>)G~PMnQ1Ch zelMVBIf~?VNGqv*d{^~WVK7)NOQ&Y9ur?U8m8~r}BN?75%5<<^W1It_)v}*1*Z+(N z7*mhUXkM-xS;*VH2Nr6**ErYxySAoSYc!sDynafeo1+9I7U4>c8H^7M`8b~<;G*Aq zpd2oXDM+?dHr}k%wE&9L89$n0C5b!SuRV09PV#h&FjRtWPDvB#y@`Ve1iivPLnz_= zczsrye%%kz>-jGOtP6(=1{<;2ZH9$!#xlX2_rDA4OFnB1(r9UJ)?< ziWGO~NJ3ZreDTB`kD>+abG9$mk6o4(DlvU%4&+X5Cg!Fq?XILk%>DaHVS%Rfbn(rX z_if1LzNuGi4%LbMC$PgKjAb=z`RSi)`M2eeQb!kSw=;vP=CW!qaNk$&JpCz_!ArMW z?t5MS!)`XFlN};Rg)OEYcgfM*tWf2hK3Zn=5ldT9$-G`37$2S#Cn#wZdGShqR)GKZ-&y%w0hHmjpND8 zqN-+ZYsmL^$ZAl*XgP&=zwDWjiPrSFbtrQ2G&N#}u77WAz@Azy?%LHxKR!C4*?73c z?+d$A37A-cJgg2(_+1R7dnwrIxA4#ky3Q)FtJ#LhjiP5<%}E(5`B^yBDZF^u&)ZvX zXOj=`U;&B)yG0c-uM5q4Rd1`YiRaN`lc0sTC;rTK@#Fk<*u3085C$}gFSSMi;kL_5 zp`79JZ^mzju+x&*pLQ5i^qML|Wpc`vA=t^j6rJPyhq1u{lB(^BclaD#i?afJSBr-^ zUJAsk$be16T+(@dQ3co3x7V#%qpF7iA^rg9^O8u&GQTsg<5@zzJZH29V}Kde9LSUd z^tNK@HSC_?gFcTl)0wd*r}Hm(pxzzaje4#7QUy5dM7jVIHAC9_tEq`!bCtvIC`$ZN zcK23r0h`hh&Z&p!lf`OXy4WVZwnSFrlZ#i^va&|)G%Hjtt{J}*pPknnb2k`Kw(4t5 z*`W0B*_%u9;?;i}4YQ$i573TvS$tq1E1kphd+I&*0|=1IJyIQ3twZ&kLRYY&;lJbv zm+GnPfA=BbVZ}OJZq)ZWQG2c<%E}F^ZZ*~g9{R7XwgtIcxc|T!h1m6UuBq>(DD;4C zI*+SONkvJHl91b#1wVd?wHx%B*?w{=&h7arWOX2cJ)GBc4&4VH_5QLw>48jwW+ydC zyYcpjf5f^Kf)ve)k$->0KX4Dp`-MHZ+p4VML{GhcraxOo=5TTZIW8CbTaOf!8(&7K zWp8%L#EKCpLrKq>R)eOOHsBdv>U+xPD(L!8^0q*hYIM}oYwIFoct1k?GxAltv**azAsa)F+ssB};Q5-isjkMd^Q!|KQ;81Rho1+{jE?Cvev^Bz+osSQt z^>xWt>S?E+tItTaxkf3E^=tkXk|>aUI6lNzC$#_gZnC4rM_xS$3jJj*nDoX9Ei zb8UMZ|9%UT2>uzh#zy~c?dEfB!v( zZ`p^9m=BF#6w=QyUjaY0q6hd{(St zRah?7w3TJ>QhNz_rJPi3-(}PFio;%3jbA%f=Z`?LK8(H({ngOMoRXsB>f$m#J8S>t zYYLl%iC*NF9*QeiGN2Lr;pV#hf=|GDU?+2rVL2ruSta$3zTs8C@L!5Cww<@W+wY3- zC&76vS>iU+)2%*EF_H~?&ARWe92J!tlHu0UDsX>X1lSg8f{i7;_|oek1zO>@$l#T( zk#I{17}*pOV}Jic^4lzq9s7qchup+K^O}eZ82M@^_+Q{_66GOeg{0%;v!-g-@l5+@ zOj9feT3){StH;4-fFXtR6+hUOtZK)>B5r-#9I;|;2Ri{l+z5mGh16#?aV*uSu%csL zK$psN4N{1ViDO`R{Nlpqi(e9JdC<$u^xEFZ=kHV$Wa@OWE_U_}Hm|A*Zu?7q-c7m= zd&TFwI_Oh1j-b~8GB0HFh;ud5arWxu`}9t3+(pO0uD+pCgK;}2s>s)_7se07TOOJN zjOWON{66d;8jN>~juE}>n@%C4-(b>`pB zuZ68_Q9?bveLI1IF)dA(`+>C5unkG{t_;Dk;XyEcszABO^^UMToyYiKz+qOOi~8ww z((mrX*O@V5WYcG9r8K#z|*Gk(u zLc;C`e)fVqp09kd{~opzDS_{MA?Mk3=eFgST>Q*zT!*dq zZ4=AXqjKRh=!6~aDp`t58SD@@G?_}DuRr@>#pr2~8`)QZ4^5>UF8eZx>@(O|JylD~|)vMil=moaG=|a@GQC`Ljd5&ebZci2TB=EWl+MpchCp~l@L$go3k?$ zQ@faS4fo;m!3kdhwc93GaV6#$C}GYAPFA)?=L0u27!~6Hm#=%dxuzLSTO~}kM`_RZ zb0di4;H7E0`0W4d5wU~2LQ^4KXblqRkckFh- z?vH3Uf2(&Q9>x9*X5=}UolCBnXMPU7yG4idkM-h0y92XpAk7!UQ%$^&fx+XR_RrfF zg(5^IUpx8@0TzcFl3D-N)!xZfFuw~zxwLY0Z&zWApsR}o4>+HuWVqgjSE1!q5{xsJ zTFehM8hXYJ=B_hqKVJ6@-8Tuoz}Y_?4?J7yvn9_wO&#{q({sn8S-r zyw;SowuVU*5c9fC&y@=Rn#;9Xb%tK}Pj}XdRuORl!~%dnDS4lKy51v!qoae9qk~S> z88>73ous{Gx}qkXB265Qf0g`pq&5CJSnR9q{tdHPH?4oTw11WW6Dcrm8+7VbncaZz z$FCSaNi=2q27Yk*1hnwGK>KegXMCY6@r>&=d2MpAw2gy^)i?QW1HhsO>@1gNi#u1* z3`JiML_a_PY9nDC3C(iA}QC@0b+yz(ubNvB2aWnAzj2nRP6x^wz(FlXk$ z!Ad#<=8ncIKN`_1WBOR9!?Sd!z-_`0bZ@9{1HY?Ot>o}sgnX_lOKWj}JqF!!eT6(_ z^D!N00N1i;@(~ZK5F)0e~HB5vNQM4B<1GIv@q2G!zLw#1hpp z-OWwTO<6EU4=GQv<>*s@Dku{jEjj%sB=K2#^uuF2lfU;xcX1jB?lfuo|8FipjPlgL zacIv+0Gy%O`dmeh4*LJRfQqQ-cnv4!+~xiv75;EYeOcL{sIGEetmh1Qt6ufF{)aJUp)FY)GnceiiCegV+?4WmF++^tDmj)+s zfwkP$(k0CzMHBOU>ek=#?jO=q^$w{?-PGR!gnDftQ{M1VkWvyMI(#EMy8S>b^)TIU zlI8TtKA@P2>PTxA_NOY$Pdlb&tx*2xa@1B6_7hUT?{K6{R=bVv zrv}QHAg}Mx0MrEo6FGSU3JlI-_~j+WQ6|+U92Bt46#6A_GNwAW^rClf-DakWK%Ka) zk&ky_xn4$Yymj}!XS9hOK=_s2pEVBkt*cPvh!M21ub-U-Z#-6lXH)`;4L@;$`f8+J zqr*d~+w<@|p7Fwxv5{)BXjWxoZFsmTDx(u}2owq5>+UaDYYsG}BFwyx%(V2>Z!5Ll z`dcO3_t0W6Vr1ZNT~pem7-TGs#qnF_S*E$kY0*GaD)v3S$bc3cnnXH2y8H*^Ce9-hPygB(b8pPp;(MMi4Zo@&T;B3EybQ1k_arf&L}10_SCh4^^K*pSIP~Z z4>-{&aq0jN(86{yV&(b4k|iZX(^xggW&MShl>Uo2Yf}||Z!5OMDYlZO%Ue79KkJS9 zHfsAdzvrhVVyF$!as5~n)@>G!^KgdW>M*})a8Cda1iRYyp`3z|l%BuS8RET}?WMfN zIijnQO6)R}ucs^VxfqMnt4;RfcKH%;AmP2Ted~-Gh|DPe*=-gLTO_M5#vUjt-4_K~ zf&5&xHyvdG7SLI4*}q@)z;WD!w1Jx21N5ZFdCPv=c?9m#{mgpmjq}m&txC}bcH8Gp zuVYl<@3Lu(UN3)?@cl@PEu~&8d7chn9GZl^dzs5MG<4wkYv6n_sOn-!5CTQVzOg`c z-uHEs_@)%8(#ef5NQ7m4Gn@_SHQL`ZA%r}+g*(Bc5srRih>_|U{|WPiPJ~!@2cGjO ztO!MX!Bx3HfYOcdGisRLfM-Z1v>@#4o{BSBCI^*}w_J=`@(S0<`K`RP;e6B=IncZM zZ|~;Z-gb@ViI}+^*ur7;uKoSSJ~`%vPr1QWlW)=b-w%GzPBI!5?Q3l^?%AEc<64ab z0hGfboXx2ur`02lPgQULoml_UwXlBAUdRZ%DXzw&#}6vBVP}RQU}o3eWg`7T3N zUe51!&(@1Sbs0Sa=mhPWHUZZn3DxGTyBX7wI=nt*Qmj7GbD3$`y*-w%0=ks zx((kTEuAer8kU(9bFkuhCRt|p$l(&Ethiuq@6Y+ar`CSO_2E^UULIo-C?^6&-|Vxb zyg^(3Z(1W$qHneJEvYUgOYl%z^8F{?d*s-An%q3#uQB_=ii?wqqSZ$oj3YHXJF8XA zP0k+g@?L=KvlG7k1wN8O4c}@L1`1%(fCq-a)2+;8Xc(C&h);8c2!YqNrb9U^wt#=1 zk6$JhXZ2>UAiL~%MrC*f*Y0_B9f(9P!R|N2WLTllFWyWUf&{o&&jW8`&AZp=X|3u{ zp)((wL~)5Saj)ruI}wp!sqTz!ZQlO5prb0pUTBqc7NQ{EuQPg`{jEX)k0Dw~GMEaW zPBdR9y|7y8$&{4zS{@@j0sb>-c^^BlagYFvR=8rk2MBzuK|ktndqrgYR>MmM;13F# z&B}goAo~EDPbim;$XVH0&MzSWY}Jm-3!3Ur26flL+5~rT_Q7zgJX{0as6gvETnMEL zwvP11&#u2=QMlmF#GF#b8}3}O4Hb{Hm6I%z4s)Tg?sEv@WiL`?s3_c;YO~0s{Urt{T>Di&0sM{SH*tiS9~-(7xKPo&gPq38k|5Q6GvR(b`hoXUvMQ%*7!g1_5d zFT}N72PL<^3nGRuhE`8){c5xTzMm@L;;88B^Y+?WJZFq28tA{A8*Nlh%2I`nXzF=^ zx3KZPT-OZ)^SishTFmlk$V$gYYi= zTDXd~4pX&en>+R!-ZNN_VE7M>$>9U$HV%-Ahe^l$wdpe32#eoIC zqoM5ynp-DR$H(+wQs7%*Mad@PBL#ve6AjAHAN^~{=0(?zfQffT`RMoMER9j!Ib~eF zOrPDLe^XJ--k9cxQu6!i94I6$l`GsdhYzmtC##j7n_P(?BNFNvJk)glEA z*YiV5^YstF-Pw||?hR7{$F15_vhfq}t!t)6|q z=&WnC5WA?!F;6)-0M*INE~Rj`guKm?%D`Gw@VI!QjzKnE1CMuv*2kym^Qt5%9VV6} zLMVBmvtD^J{Cs0%f&)6zX>7qE2nw!G`Nk$TQ@hKrf9tV=OhA5f>_&izAg%(Qdpw(s zJBO7I$^>s|5AzF(1{9#kw6zN+&pusam(Q68!H;AdxSTENMQgG-4{Z^f`xSKW3VL9< z?5y0{6D*1McZ@L7EM3h{%F*vxr^Uy`oGtiGr48nc%7t}hZ;$JNdrCmJc_jHJLvwji z6H3@!wrj5=!u52;_vXC<@cqQ%?K&Hm|1J{@%bc&+cMbgTVHgQpH!)fF!%RtzCC2*X zce%j@TwJ1SUkx+;B+*SLfV*>GXPsiE+w@pRbidtW1Q-lh;$>cTB>BtVBZg)mA_c(0 zipY5Eo+tFNPSy~X*$e#`vN7}exsa@MI9|oe0HA6u>_`M*SGsrW#ciA9gujD{;Yp0#n6SJKmK4(bw3o)6%oQ zto(s_4srZ?JO}dlC$~4N^|gO9&w3UU6H41K&)!Vwyt+zbC3Emo#dU;Qps(Ik7k=7G ztN{fCiZ0ea9gJKCI99Vs+#W+tf8qA!o7qnrtT#H!GkgStbO!Bbt4^lRJ6d46s=P;X zCF;GAb3r`S}ZmgiT#47H}TwTY)89BDP%_0oIx13i^`0U}*olw*LHFQ>Juo)A0ni zV`Nw2rb^Fc!P~#W75|8^(Vn@k{ZcE|QJv4GNQcfpYY}HYFFABlpmcYgrzKaJY!A=l z7K4#Pm$&%ZEKA7ymESwk_wb2yjX3rMat@}IA%^H;RJc0;nX{=OZ&i( z-_n=raGm$XyB*WfVh*hY(t{-be)U2hjD1p#CUx9Kx=G_H8&EQ)aH_vlgYn}xpPj9N zc6sr@$2WowHj^FAt7>X=u=&geGinEK>S%Ju!#)@-n15aVz3uK>wPEY4THJ3 z$KI4O*vIi6lsNsPOL{HsHRRwbl=XWVJNm&%wI&Fc~ z08@?-e^%DBaIzLtCD4Ge^DlIu9yBqaotTmA#Z5v%-_W_BxSkVSQxXdUo;c=9P4cVP zB*w`TAW4vlRh}wTEmkRTRZbq67upebEJo8PDJaQl2{o=GX5_E9NI4hN5A3|f58j0F zEb=P|{pxW~9N0cM(1+Ny)a5LEepX+`u}<0h(NgM*jjoZ5v{n-sXU4}bX+L99OGH-t z5rs4*-m8>dZQ&q|iMuKynrCgT)#$|dxe<#f^Ad07hISLf0r%>cY;P)b-VlaA6wZMo zgWL^+tV^k^GjA!nd|Gi2A1}BD;)oCSQxj9mD(pAb%!T{MX5XEE&5HqqGUqE|SWv9z zvugG`4H>ZTL06k9IPkM_*@*fj#l{+_BE1uNKE}{JCHN~#)#h~VXeN=8S2sElz1_HU zuB(jH^qGx;XHz`Rf1JCa((!DqL@!u=);FZ*A*pghn5bf8W@IJjP0_KF2`lv3yOoe2 zopUTkhEjoO%`2W@{2NPJ%RSkAdqKMl_#~(XA9=CJP;4Fio=E)T0Ky8KMBiMQ&8p`u zn8PySt@VjA0vi*#8rg$4aQ9K%yXk7Rb3iI}s_9c%DC+E5+7ibqAs&ZumAc3Vl9Ey_ z3-_0(d(}R_65roL6UK*pl{b9VE`*xRgqEd|Jzq%#VFGg%l`|Na6k;x7D0ijtraMs5 z=X7f_bVCJgjLpNQDCRI{xXEavFU55AcTAYWfN{;KiW=nZ(SU27&sgp7>>|)0uAbox8H5CoKN=HwZ>D9(p z;nSLKuFXz4xZql264dO z@NdN^E7u0zk>iaEuhAg(Xu|vmD2&+a3IrQ2OwH;Dtue-jLK$M zwCtgmY07!JP#F3)5}zP zYN7*yL9LAdF4mN+(w2LmurgCr+;y+1x3pGZ;VeNA=ac!erNS>WZ7q5bb@VY!WR-d&3=G4$A=n zGF*SiSXFk7tObKH-g4L&M}C!@_7O{3rzw&{macZ;`~Bg(UL=Pfx^avcy944)@AUQV z$iliNs!fjSOb-^SZ}=)v_@{XHQ0-ikQ%yf zD-20G6@(tPc15}vsYlTwVG8>F&|%n*KQy&%;RyLqN1TiHAW2P7vrAD^LrFts98a50 zsI|gXbN=O~BJX+(NBhlA24tYtPrfecXvQbL6NUHfz z>6TxNaaC|VRfCA)4TX)5G!hQ~_qfm2F~<>FgYKOSt^(~ot?B2yu!5)rI#bhlQ|V}3 z<=nXV+3!$D;bQyRY>nzDr|PnL{6X^g`SPjx^<9;VB3b_Nq`gB?(2Bg@+k4p5_FC{$ zlDwRaFg3pQdbg_jZ}Ui*hIA>57#8h`QiT1qWW`<5r3#idXFasR3K-3;N_NJI9W$I> zl;w++w_PqbK5q}u^}4yl;}-vvzS>CRpi8GBmFrj~ONuotuAHn%mMHbfs7RM0`cKal zQgrQJuH}+VNHmvOp&aXc|Hut^r!dKFOSMMc&wUzo|5=5mBqg)+(U!50&}q%4Ob4yM3z@hmH)16?{j_-+eKJp3FM7P?ST!*8loS^$8QUeZp6 z*j;!O{jpK>%X=#WUC}@EzW{&wymw5!@gJcRF=SYO3_AoSkzmSrz%pTP$Yd!qb$$}n zCGGrFr`IHwRc)bd=3I{$ZY#U5J=LU+!bs0IkMziahfENx45dFp9YTMxh14o5b@9tR z`F+Lv4?|0g$vr?43SPRrFq%n=0^?2vo$Hx zsnA%rEyWIZIoHuZw39Nh%yQ*er~aYT74`AOEs2ZP3u|}1GbR)@#+}j(vLPRpqRlH6 zC1q0njkL2Rn%DS+bQ{E!yj)pYdNnBz8QMri^=9Pnfe0GEF)s#>n~w3rhW@hMTF45} zO-BvQDbf%MPF$Of)28zxx9ko`w*r>1k596Tcy*c3wv zHyrlkG4oH+Z4%Be93NN!#~!2u(;~{Lp8Q_06EL(A;s3Ss#r?l_zA69Jh=SdH|9||q z{?@ARV(5|iqjG47hkIE=goHN{RUceBUp*_^jJ1teyOsN{_gm*h@$J%5nw=h@_Hzf? z$73^TRhz4PTghkW43Gx*)$dyWkjc9_owmJRdTd)w$6HRQh`R|}L1dp-_4+N7C!5J( zG#;_*+05t)1GeHBja+?Ver`3aQ2ql0Bo1@3vRc}NY76%E_I~>G2`Z8tI`d1p0jK#f zgqQ{8bZT{iCHaP{jIm@+<07z^vUM8UJENaM0GX$E)+*Uclf25#jNr4YOK-CPu(7#W z<#IeH=mSxtvDnI5BSj!1BQqLKV7xzDeL9WAaK^LUPVor0ozVyu3+A%dUn$mBc;ADX zDOrPUCoSb!V=|jbjc#Y-zKjRl@Y~Dhn(i>@wBQ4KW9j%fI0uJ^f8v@AJ6T3XM^DGn z`Gww|*#VnH2L`?~Q)B$V!gt-3Rr)Uq1Pcg2!PvU*15I`nV=?xGLK|XU~zeO&wWu-%n)*7MMvK5vRun&9VvZ#v8#1gi}GP2V&$h1-5z4d`iJRMhRF zr(uBg_kDqI)-xJ#W@KdG1pVoWxoO}9Q){*aAU7vR6IrMc+YM)*?QC?8h`=cc+WJVO z#lpsBrTewr5j<$AzMcz+i%a0HXGD;LyFZ5lA_H>pmZO^C{~HtuLbd2*OS;DUl8CCt`V`hOWpjKP8eEzm$kS|)_5rpmU+7;LtojmgW)i%u-q>ht7k zjQ`Vu0OiJIL>3k5zkVE(xsNaR>+q;}wcx11kDRoqwYG)UO^7;^x*+TdOJ;LiQe2;#eIEh>F!&SO}V7rQt@%J{ueuKtM*ewxNG-KpOzNFfcOewYW0I4z4uV6G!$hH#wOL3xhf! zHj}Q!d^zDF6(!~F#=n5e%geyPKsY!!AIObX<+mSob+^>3?}daqR0`zt^7D;w=9--L z7i-MDJUxAE^t!jRw?BV_l~v{B@w}nY zBDuEoM))Btywn74EbxX&N=QHh@iKL_rLy|1#{Chu$MYrjzcDZ|Z|>LqGI^cMRvI1Z znm!=~8qJkzftRK@`$b$l9en=rYS-`eky_h)CT3!O8Vb zjP;!-&EtBnOs642l{-+B)n=jc_iqFkn1Jp6NORNweh3Atn8`!@cj>ma4TcTl70}k==>4{mZ`q%R{IQ-ZD{gi3f zK?>8~iP)?(va+#NgY$emUjg2Zx`YHQfPmN0lb(;Sxv-Fihi74WT7?Ev zK81t!b8CuEMsYQ`N|CXdz!3+)qvz-6w}VT1zFZFl&KQK6{s_XSxaB_dKqe!D( zu3J<-0|@iIYj!&?SGJ0YCYN+Qnn48QGrSWk7MGWG7_eYE!3mhfKR!Gh%@iUAzza{! z%zWF;Pf1C6g&bELaf17pk)9sLpVNA6**kL7j9I@G2LSQ<QJBBG%Imi`bD z5>}H$OU*2o99i4x66gCG&gYU-l9P*lO3(h`nTD9%l#@b5`d6Isuei|*XVJEcL;{!kYY>nW-H+Ej67lCUqUfc5rb~gH5tlJpBp-qOmaeP9s~} zQ4_X2W!gjH3IEbs5OF=+Pft&cjf@-}9gE@0&CJbXVfp!6AS|I zd;Qy&1G{ubD=lv5|4o_a%|TYN>SJQZN}I52`1V70FnY;pO7|uT9!KF`XjJ*d&!0aJ z4i1=W;eS{#L}~ivMIry2o}RuxS1_45mSToS zY*N&x^RZvQ#AP>&%DDous_^+1Y~=f^q2WFe|Bo;SFo1d$&t};5>w16u2?8nCt~&yk zBe)ymhIc`{1yKj6bl8?)TT1z&pkRN#*5gt-oY8;f2p>JbzQlwI_ol6bB01OXF!V+1Xk_3OlFH0C}g&Sum>XkpeIw} z)3SZX%emj%w1trM9K%nN1ZIPbq@+S^ya2<6hs$k+jL$_MdJRoY>p)0H#pn7J92|Q& zj{--|#|OFA?E^>O>Un$A6^J}NH8s=VkNesEA`lsy16n*E{1yuHNkIR2eG!F*spMjpO^RL_31p*@Duc8=eJLfn~RfvlgZ7tdYod4ALK%E z6D`x56#$?}RhzS?OnZO=$A+r~v~9#zu`w}@Z?7*mH_lwCtDs@8GVVV*JoG7a(9&82 zF%|@%U!sQK{enXyC=>+dNf;PT24kroA0KU(>j^onR8bE_C_YL{Ng)6{yu6kaQOcz6 z5zf{-h_SIXKum6Su|`Lav|DXfTdw~5cVi@(9pnQM5fLi+vPzi=pc%fqy_J)f#|rID zNJs#w*-`o3Qcghu_}15lGo|shl&>!D!{sft0<+}&0Qc0L<)I_;Xi?6nXSy~QdStFt zEw_9hw2y{{o+FmRMyp{DC{+?O7*eIFY%Y{@KEdODcv$o0Wpegz1^NQ*htoABW#Qth z$yz$9M&bW=aF75}Alsp-_SA4~PC+>sOiYdrRgqL~X=!n`+F(%nKL-TQyW*9Q@QIC$ zkCu#%b<4v@!pu(Jx9a@m+xpW#0IWoGJD)4kh_qj6;YT|I8^e_Nc0};K`@gnoFdCuT zlf#QS8hHgJt)lNUA0U>Rx&t)}0|z}nyQU;X#))AC;Qdb$-542VkCs91`CqoWgR@H` z=s0+`@R#XxVE{%3xhF!Nk6W`(MpZXC`*Vo@8`jn~4d#$Wk$sL)?BKuK;kYokv9~~w z6AF_Q@5_0*eo-}YHxUK@j)W$D9BkQbDbvwvZ02d?<99wj`eyb2&~z10S#8^T3j#_R zARQthtso#FA=07J-AI>ohX{y(2#82acXxv*4bmMV-AXra;h6l?W;HNF?kGj_yqoz$&X#DQTiI@V5fidLc#i2VE^x59?#Rt zmg$0b{V-LVBPYg?4SAZlFw>(g?Hc6cFBCf7Xx9JT;dnov__T8TjK8D=7)o=l! zhx)@@iny~UU$%~c#@kFMsNKbJc8EB;P!W90SDM07@RT#Z%>U{g&Cq-OqWO255q%Gd z91ez40Ru};c&J=g*(Q!os@|+|;Yr$y#@|w}pj;J32eBd~L{Bt1@WdYLvk2 zOa0oH$@-jJD~>nZt6oClY^iMl-ZCGNzv2Jzt6uff!8_RKQzxgC7i3kZ6O{Mgzwd0D zlD~=Ln~=Z%Q`$K9cqSNKRU_EM48&-ogbZ>;?Q(4Gazzj1;y8cLLFY9eAh}~4f`k1& z_`M`6GuVXhgjgzCx)A)m2P<8tmE%b3qZa9>uO(H|G z5n$MFyEk`tp%+KK$R&J$9?+D!;zv z^Y37oy}A1?b9DYs+uJwE@^9|S1mje5mu?J{Fh!1kWorD_TYbm)TSr;D8ddi|ft_Pj zHW+6(|4W&3Q!09d#V69M{|;AZ!_N;fg}K=<2UJgPrI@ouJoqB9HVMVJukGLIqncM+ zkCBC0t2ahicZ|IzvQpyxqS<_(LKiCQ73+59_{)#!0I-6*30vI}>kD(l?Y~zqa;{H% zfn<7Yp!UPog`cp?(%Xk@T+97gc_TKV&`Y<jw1X2I?rpeHe?T2avpN@=D0ApWZ*g?A0xGT5jv{N z_d#BhrN@6CmPAnyr?a!T;3oreas z&1l1cc3zf5@NmZb#y(kP5dk>?7$u$TrIJ|!6@r5S(|5&(# zkL9P2>M`|U!xCf9iIlhZ?3tX$-j)A8_&Sqx1m)6;!`J6;TR2f=AKtrp?fX(hSoFlh zShnOa2g~Ru##*nh-#{P;XFE}H1-0w5wX=ta4K3O#Zp@gs7!`jBzKH3YHc#(N(>3cp zp9pv(m9)BSQs>3F-tb&4q*zPT#Sg|q7O66Zb>L&t>fF8Ry={Bt1D<{$)PoG4qy%&h zb)N7a$f5XIsqzqOPL<1t1ir*tcwWu@e$NsYD{JyMfdtT8g{FG%Sg zYFncxkfxQ-5>A@mnYo^$dM{GKkc3_C{`@?FR^8u1+y|-*#};%!N6&Xb+yge|ry1tC zYIm>w=J%KnnqEl}VGL7bee;svNCH5%H+NTi7as&PeSKVhU58VOHsPEr>h8OFl@-YC zWF_ezR(ta~dGe`lUHg&!6x_vx@4sh1+v3E_+WB#BSy$BZz4FI$)~FN_vj>5;yjTA^X3-#z7!T3n%>G^&0T}gE)bMr3pg( z!X1>m4hmTx+wtemsP@9#jWWj-u^8%K)%qVzmc7aOku<>MrNay~fjmEV7Vr2LMI1Py zRTr4>OkR&Np1ooxB745@d=(#4Ou$pZzO>ACC+G%Qo&3+lUu)kU{2H4dTN7Nt4^))O zx8``nnM;{6jT*R@!RFcJee0R)V;LQ#R2|s?w0l*;8`r5m!OeMnd^ebI^Zh~M&8>FV zv_e}|8T7OGt81nP-mkft=^~zI^i^v1c(M3=`t|ehW#`Byt_uOgpQ-(BL64Rdq>=C) zDCK3+?^-`k`x=yXI}rQ+pKaTw8m{dF5gi9KhZ}c}!rF_g70{X=@@rWRG0fkMXCC== zV&*+)PIF5xP$GAYfC+2hC+V8KgN0ybqDK6L!X27nSweK*{PTnfUE$vEa)jbnlk*|k zpdlKhLDvm1hPLr7^OfD)NDF2uaOOgV|Hbi}1jIKG?Ph)_3$@A7a>X0}&ZYO^_YtJG zWmsOxZoi+LF_OKu$0Vxh|u z)sdoNMw^E#f;l6hL>GVKJ4G+ndnq%K9LPz9rCpx0|%*d z<5ts%<|>y%{>x74i$5v&EM7j~=ii8lm84_+QL_w_4@+sL`OUHal<~ zO}H=kh}4*Ml;-Ap$afsK(?_5@zJ%3D9IbVxJ35#dxnKrC@SmmY*~ic%Y@!+^o*b|u zy9f3m*Q>rJ$X1;HH(GP(*QsnkVdDV5X3hKpp&M?j7d)lIpL<~+cm2gutkn5tE{3mQ z+0FSEVf`0HwhJGLN-$LE^ zX2|}WV$#`giZgv76(1`^N*H=NZvNq?Hb9PmXG+3~xu%p?#REKX_(!_{23jTuya2OoXc}Bej$a zu{snew0x{t5;w=FId}^q?VDzUR^C_@jWVOb+oYN^O$aVB`A9|^Q}EZA5}SwGJ*VzFg|Ny z*x_l4>dOi;9v6@9bg#jKe5+$!qW!4~)X;UUk%t5LWmE!x_kz(#4RlgD%Bd2?pe&BS zc+A--Fi<8!^O;$#Y;DkmlB^X9;}=3f&C``e)7clYt%@2)Tuf4(jM$cdj8D+aC@UM4 zJz3lGCH#5eR|7J#9VhF;B%k6T`we(F#yQi*2;>hZf|O< z+7pTg92~pb3!Me>Gy9ZB&1H6c;>#Cjq>btrguZEmxry0d@7Zw$Tm~mf2k$BK*{B`_uwYDh_^%~R;&l4+dAp|Vy zyGw~)T(6>s8%IW*j(1m{P*@AiY@S!(a%|erHGKWv=OHimS}lrItG2FgV&bzQ#Q4k0>-VAS?-fAV}uHv!V?26afoV0y@NK2Yi zp1o)MX6D$uBEiJW3>^(kHikp8tcQb4AdwQFW8g80RNeplj0{?O{<2KGA=sUN?cG5l z?zx9({UZv#LT#ObtZg-E*I^cs+%b#%kj}Qr+Is+K1In2*9BU($m>WA~_eQ{XJUM}Ctf{Ff zMJz-<>Hc;RKnnbB`^&2)8D8}h_UwZqZTE-Gf<$sP+RQ$R|D0Z2yaPmv$H{)yIHAko zM#W6JOfCfI>|gePi#0vza!k?+fVqO6k@H-b7wt54>K zVqa`;2Pti|y-2d#n@VDqw~d|8S=1; zEPS*P?b(hZp7fs*J1cm|60+TY*;Ebjo2KiYm+)vR@!Y)~ShP3D^7W~$q7$(a7Deb| zj$Twys}JR$qMomhm1OTM6%`fPQ>HDN)ZOA;V1t6e3J*&lhwb9`T2^6<(0feKpAt7xrH=Lq3%NBm&d6KZR z;r3xL{KSbBgZ7<+krAD@nnEHv+*%BwBNu^;k?*~xTY(+#?tQHf!-VuK>=(yf`gDl( z6w4hhPH6)bGT7Z!pNH1dwcDTOvK^lh10lg4Ed$(=rKM$<1pcjCVG$7q#>OLQ(u9)4 zaCQg@qoSj;l9Kcc4CdzNX<7r{zkd(hrJ|yuA&^RdL@m}^RG~8y#>w~g-JM_W@W@hHQckgtIRQX+ZO$O3KQe{j{SE3&1*xK2Zm6c6RP6FP&z%2(~BP@&( z)mc_jvIh9$BOLelcTG%9BXh>DFBCfe{cS#yzcJI`_4|7u&>tTzDGY!_CB7ez78wAz z__L?yv-^*xrrEYIYPbulUksI+n=zc`A1DQTW=~~wUStfQqM|}1%WjLw`~j67gqXu=c`7k>a70zk*`g5H%~z$Ee@u+H*)w3@E1vR&wa z1F+t}O=h(|S|lDyVL6l?sh^V~%f-rFrbTcFA_90HL;OU3jS~3W14hxwd;;2 ztL!ZHf9febp%641%mfm!4DdRi42c&nbP9EwfFGb0U*Fhx5y#z^6SexRjf=0i7;76# z8pb=DD>M(&`F9X!E;LwAFi9q=PMDymsPjDbDO$<3WC%@XxP zXJurB`ye9V3>3O6!8!-9wf_yu9{nP09Gn1b;^dT+o!RC9c+dqJ6|A(hX+BqO{Y>CL zIoWCm5=jPr8u*pE;$n6hnt1?CVV79X)LV)*S(6G|SmY)qQu;PQHw@KYO*m{H;2Hx% zLTGPZyIhL0vhI!!OdOm#hc#sx85t1~k!RD7>b)+efjwGZw=gg;u(r1L@HjL6nLtZR zi$nDA7hL$|emd?byNVekb@9aJd^<#Z$oK8?N`JtOef2u~7~i_c{wGI21NXXPA1f#* zz~d);pe>Uo|3jY*dE#|WrmzYavBgyN9k&;TMXJsIIBvif0=^v{9*&KTZKxptOACt{ zDDoSax~ZwDB45}(G3>88KC@`Jdw7tON5+@ttGYuDSukU>qXJuyAEH(_3pizfymB8;- z=&PotMnORVaf1)Oy!e%LgtBaVu1-UO$UVQeZ?Va^3$wGO#KkcX6ciMF1A{EZ)ysWT z_X}v_bD)%gg0frcsdKA1Uk-+++S$|NXk!CsuW{4wC4C&1s>Nj6?ysF`1pzi3sW^tsEywP@o3>?D-dM&|NOa$>4#I)8w{u4 zf{PD0&Ea8!fabmxd!*9+h@H#k_37d3KzYIt@(as-De$DIZwG#nkBW+-@@kwT;HnAMBC%uL|=QN3?biY0SPI?28T zl(56B)_Quk2ni1sItj;&Xwf)0InCFPX~SUj%?nSE12Cu4L z(!Vv^D3^Syj}v|_?dj>c@uH}r%~eH8a`-*7R&}Y@#W|)!;h3(Wp`w}^!yda7uGEH; z^w)tEn%jY`a}b#!&XM_QiAvbnv3-QPcjp4XC%k|tcY=Ysw%_aZn<8|en?}#84-XGXC;@`Au<7mW zlrF#3{%IWgn%KtHMW37;CWMBTR&Od~)&2aG7?3}QqU#gYjdOohuht)AxGEkb9H4rZB! zF38`Il%fR!Q&GYm#GhBBMP+3_ggp|tU!$y@D2m^`N#Aea@CpATRS^D zAY2YHpbX#uKa=}eCCgRw7Y`G6&#t6;Lqu1`~O-10k{2i=r&y=M=p5Ca+(!#Xp|`l5lT(D2i5MzIkSb(ycX-`}OY-(#M5V>gX`etnHSa-44WB zh7vKxrwR&q8d|OpN$)TzLurTz!Lbn%Y6P|v(FMuL5NeMuPWaCy=xU`|4Qd=WK)HdA zfbvGQ+=7_vfC(QHge?mzD^W!|JsnBsuXMX*g2)8 zJE7i{OA&(%1U0go+X<+B$jRfOfE-aR4M5X!IXf;(PhTv4)miB4>gxK~YO;4|2*xZ@ zKDHu@fBu-Ay?dJB$hQtLr-38`1Ii_lL^_JqIK$RBt#q=fD&X zVI3^@p>{seg%ZZfidAjS`OmMXAi+Z}OU}T+Kp!8U@^Z&S0gp@7puRqhLj5&jRkiX! z%o>~3wsao>{?f8@!K}}DWk;uD26OjV3$%GVt>9p;tDD{Oc6u(Z30I~ zd^{9Kz)%;mxU(SC)YO#!{$%;=T3A>>nE_AS+}s>AMDK%wG%Bn%wzeqA$)`XZK;jq0 ztoD0pNj&N0s0CMf+O-s@&k_Z_68N0*v$C>;?$R?dvU6}i<)N;qI0%ekY`LpcDinnR z0s`_;X?WM(N?&3N&M>VPFQ6G}LOFVTP=rBNgN}MoH4YZdZgr3W4-bzu3bGZgO8D~( zL9g>u5R^gYbbdlX`g?7nV(Ui`A@#j`F9jOVh3}el#qpl%{YmPW9Lat2Cpp-7^0yO1 zQIq(7o}7{HO{7t2(W~vlm^jVaXyp;p&iD5q=}$YL46vRk$61@;3eV1FWRKR|BIAwi zhWRZHENfZ0xr6=vX3mFxr>E}gdx8)4iUNzw^gFd`_jOm-Wg+P8S)rI+a~IH|#~&u8 zQl`hpy!xhEKU4oNmKQ@;E@(BNhTh*$Eq9-t_xFIn%!-|UE9OWr$%n_Y>;Et-6B1)K$w~;6DY+~C;$4_I%E1|8XCcpfI08@NN~E~BSaIXssqXb6 z37X{kgpSXudQoim?~6PBvEp2U0FCJCrxrh1<|gau6?)L{mFCf-6370yMU>&YN@epp zMm;FvQ1AXwSy>64C>d@>oREn_-_+F6eq9ta zVB|cushCqANy8-G(weSq=67^vs`uzz$H5Avx_upYUwJf{UnL4e1RB|^*Da|GhMO@i z(nSYAU<@ZZG20_7V0U*HYH(77E+mqIQJbf?%=8(?K8iQA!_wVUUl-qr++!}tH+CW% z+SLE@1SZ95R@*bMvOf2-YU>6$(QQ1uK#^~wast`=qCejK&_{Gjc;dWTvXn%zZmlUF z^QwwOg6QXeI2<(#i)GtKDjB+go|e-CQ)#kyE9O0_Z$7E4ah%^df*!Y3-{mp&8(&}O?648w*ULDfA|IB#!YgzgelMypsl}IW zY^YOt-00Pg+iEqZuI79|S)>c_i_=!X!U7@FKqA3y(yPc@;l9Gx(v$)oSO|#9N{qTe zYZhKdty%U?BpK>r=s^L1R8mq}=`FjLkgz%WG%HCj)pk+8Z40aX{g2;(8M3OEX2RNY z7JtbhU07I{ot=f`{#;fT&3j6Nfawu|x|X5Qg2URQy03+wMDBsCCxn83{M$F-cw-c9 zvgi8xzmIm7E{KuWo-P+(q{>%0Z~L{P&)T}MC5BIndb*s<4%D}URq+#wS~> zu9|eV3FZkDW{*Ch(4k7#{~hk`|MULsYiKhG^xy9yCh`p3uX|Itlq_(&NAVDOeP7GG zZc@{bpM>tR5~1&90a^`yFL@gNQTdDxHYDx_OEU#}S^tRXeBkPD!CUttMec=5>;)`a z+#Pi#XNgHq*z{r@Ohw6&p^8AfMedzrJfN11kQz|zqIqA=g@Yg&Z5jjmch4WOHO_IG zDfB1!E?G~zoUfRhg-p%ID87%)@ilgeqL|_Z^||~wi%I)sxIXv2d5r{i4Ku;cQONDJ zlh9`B6t6}9$81(ZWhJPXVV7@b#zYNhzIBbnz21AKa^oE0EBSh!tG(Uk%4~m+@SZf$ zr3&NSXLQ*Jb?qiycG5`v!^5V%_uJxBrI|yQGJ-3}sJ)J)sb4m^9L*(>B_jPF3CMwZ zerwHEi7#2=QcdyhvwxBg1%giynKgb(^vxw{R?0E1R({31)ViCy(Zlk8Ka}SHl0eCm zBr|dR>z6EKUw>gq5#k-(71y#J6nOS2H}emIdF7JOGBGW!D1(jw#pjQ-aLlCG=e!AH ziz%=1IN1Y=dgbTmwqyT@EEb$f1l*D z`*#%+d7vZSZaLeVn8TG9Kb022j~kGaKmKU({Po4_%5Cxg?t*2=1Me(k@TKv@~dh*cw{=8AP&)R&-^ zB_>{d5z9S)T%~oi66*V4BbvvdGAzk+uYUh@p+O zZO7+H7vbcXGB8Y4+2yDur2$um_ca}n^yW}nM4bc;Ca>E>$;Fm!X!T$aufN2Cjq~i* zZ+iUOky67+=3$aFEG*dsvI_0(NKvlts|9$iuv9q3y0m)?Y_+O!1}pSG+N9Qf&h zp>%oR5@OP2Z(n$|i|S$J9d zyxR9kmzzva!;bO`-Id9VG&~i{h#M0#3?0IE_(k0Wv%gQ*)%1jg))q=#pHZGTC z7(=HdE*>OvPhW2^C!1T7RR{a`wk zgHjjsaSpvQ2HCjXpW5uYm$%uhSsgY9wTEDeTx$BoGXJRH?&og=ne>7kS zh{;1u?cIMrZhwt8rwmHFP|}6Du_-j^Lkwhvcns96x%zhXwGG2XzTdWW9(R%j>ldl| zL~D3m$Mj3h6Fo`0!hjpqIs2z@S2)~P^^T$v77rZX_p-P0vLsENb<(L|-j>*$eiswY zy|AyDWWg|Y?R|IOa5BU9nBI#h@^!x7#0t*Q&>G`XSO)m8Jxu% z0m%}rcyP$Bc<3JrzWxlA(4KIz)!Vrf(X!DGmk(Ed@g)`D(+}SsRL>1!Pk_~Cg~2>c zjnyG{?#Qr){xno(Q&W19@arN}g}q8s0G_X*@@p3u#6vf{_6dfT!3G&fUF|IcUCQTb zMR-j4vMHwWWu+V|;~dpoAz~@|b?Ujnp9v!2znhy6)wAstzf$R4BySDCU3XafohYNM zMC<#*$WBekqN^gC@wp3{Z})8R*d%NquGr2+n@%PoB_*=lm|RA#*zP>TLK+YEToqhI zk&#%MFX+|kcWv@ti8M2YwG5+Y<;m@=*q$7lS0Vf|IcWqN~w(YI$Ynj>(^AgDRo*kMKZ{2f_q$kf5Ls{ia`ko~5#j3}Jz@!DOFy+e8b6JvwYTh5wmWPH_Mc7GM0r&2 zr%Y2kp7nELN4uLtWj<47dcL*(INRY{_It^j|Jn=No9C>!i2sexI=mhM2bR|6v!hMw z9|+gdg;?SsS6xHmo`ye&Y>@-n$ho zV_O(ymwY3jS+9g|6#FCZ|Na(ab5|NCyWR@*ew=7qzBP^5+~18%t~L8#?xLP^!$nF< zOs0saYbuzJyGx{jVTGdTPE@AA0c(t|$laSPA-QoV$U|%Mej@5f>|v-vZzNWFuAWXo zV%c388Y-y?8q7b29=wPxf)4G!RHaLB*%n7+r>u_szp3Q_M>>dXB1Fj@%_ z8X1-^qkjKS+-zcMT+k_D@;Y*Wak07$zZtHja-e3LlN;mo_wK!WcmSuj7No zL>lL=L#3hUt!l?z`-t!vtCknho!EB^EwulN|CE4(ct{`m5w>mU|J}qNXLrQJ-72fp zT8;XCerAFrf*iUxWjtU=tWPC7J?RL1M(Pd^ZC;J!+JCA0*WaVwWxbEvnEnU7E`r57 zuemjim-eBt5c7BCAmO}U@kiXz^RT8ey#M`m&o#QQF?oq2{nQvN4Ge7X~bh;DJ%(KTA@j>Gv zLRv*a!#$6NXkHt?$9WP}6n0)#Qq$EfQ%{#F-b%~0D1wtJ={o#3aHhZh)g6L%9?SP> zUTEGsD#8oW5y8!~!Pt8n7F~R$*7sXSu6Q@kdb~T1iP!L^jgIuq92G&@x;pq?)2Rwz z;bV?{uZ*lQ7q5ySFj~TI@wim%;$6GC7X|ZR&VbZ!Z{uYo<=3iC6E#;MbX!>8Hp3jX z*gOfy2^bc6%>ypHxKVF4&N!=ma{%s&r z>psKD(Q9tpmmU#eX~3I(>ZR^x;Q#cy{@;o%9$x!AZGPSnC4XTmoIfiR_WO=zuF4}l zxVDKoKJ@are!_iFlGL2@#>SjKqe55773`J3+IRfCrhF(%ESNbf6&9saYxy$^^d1J$ zq@?Xj1zG3PjvOs0VG=}KZRVZ+RSlos?2JJ{>`38tGFu}2nV6jCbzK+%L|noi;s%RZ z8qrt5O1NrcXc~?fTJ`7GI`L*C1jJgsfhJ5$dCW#puyqRgaL@HQwD>fzg7 z=*Z*iTTUb>gp}%985P^M4^}-A?|l61T{o+6%6%eEjz7*%wD*6aM~hrVs=R)9UDted z98vb;VuiLMs6&uAVAm{+9X}*!A#iI_5GYeV05iN%MxcL zwZVmy&$VMU`8PZAwajoE8I*>b61vh~~?95WUclg=f! zFfJ}3EDSIigDaDa5F*kX+R(W6Ixs#e%= zL8H*5f+J3tqBeoUZZ4k5?UniP$lF~igU=7YD8bSI!U^DZDJJD}Y+zl0ZUa^upu+CB z2ETmS9H;n~jpYVkg~&2|>M<9QwA-)9ulKX@SMu&Su5`wKql3N@jpvy1ZC0ESnv zBoUh)OcE+9DjEZ0C@@`;G~s4iw=w-fDFw9~JkOw8Pw$$}%*o;G%@C)ygkw#vUxxD zHOl_#LWyF@(w#uj;oN~gP$EELZ_xC9>M9BqH@EX>;jx=n$0bq@Y$#!%$o(4_rvjED zT0g$)7fo0XB+uOj&k;~lrnpH3pj$LF9;AZ>MJYSBDbGg6>rK!1Wp}WGq_8lhb4JOkxw>1jE$0r3FBmbobxhUJ zkW9x7@j4TP0X83$j?S~@#udJkVDZTiG@baa5bFDgEsA0o+}%@?LL z;Cz;qlIkBEBzxjq2zVnNMJOQ!MM7jG5Jsk!mU(7QF>JsgJb(T?eP9Jxx0huPWYHqp z-Qm1CI_~!-JqrmAR^0e->V6(mebDRo^RKDe*b%$7!Efq<*cgtZ;P~pGJ%AShk-c%r z?RBf}f!IMl?WQ7fms?c}uzHpezV-c)XR&wV$g7^Y{ zXQnn}L(ul=GXh+F4c1r^2g1uYp}f4F!$mP~;|vo4rR@|j{WMi1d9{e6UH+RFAS-fD zPSN6xKf1v7kjoGM?r_roEHijdC<-LgmRHSkK{r55g{-VLfjX(HtpzrQjGR0}CVCsfh14#veSsZ{56p)IF`c!QGT_~fL)bq^DPNO0*| z1&gKC6tCnJx`?BOi?agYnSrB&83sgL7NbBCdOCcg;RAB7KMlCL=KN-e%tdYaEdDPN zVR-Aw#nDnC@-aO8(z*&3P+;sN#$#2PTj-iW^kVVq)g^HV2F~BVb=-Pfo=gJ)3x0D8 zOG^miXQ!vh$)BnRTtRgN%LR{0zlfWjJ`*11@bEBb4WjVrSy-lE%mJwOHrCh4$jDlP ziS57u2Fqn@yA8WQOG^tJxSl5)6~HkmDly!3to=4tZ+azCIAO6NP>I$U{q~Q3#pb&} zg`Vwi#1<560lfP>@tpW+dvEW}lQ~$k2s+tp&j6r{9{|S=zBLdqaoQA|fq)0*4^WR0 zeoRO>KRwz3DGn%>-1NrsW8OUD& zuk+eecX(iM8vy^qr3bUbWkzjpZ~LHPcpm`5OD0nV=;rc zd&%A{-$T5Wt{`w>pw+r}{S*fQcWzQr5{OeUuU}6ZAjNM3D|-g`U_VXDuJc|+L*w^2 zE(LiBW|m(^dso+!*T3KT`Zj~a1~}!wtU_O3)G&#w2yVb5kou86J{N3J27*&Wf#ieXZBOq{j-x#=Ls--4j$xJly@LcL#c3PU7=RmxwN=A!( zd|Ukf-8&*SeGy5?z*KhtzG0h!mmY z_q$U5hZNtvl!*Y3`vvwVmgD|Pk6sztlkpZ3g0F1#v41)_I=Gub%V@vU6Mu0y7Ye^z zQ(cWBBqP%Wx0I8e?Rv0DPE6cx$o`0(9Yz&gLBv0hK>=g@j2U(dDV7l4&6}_-!7mQR zT5D5NIE*~z!v$!HpJZRZ{tIFkOiWA=?-ql)P%_beC|haiXCgSrfx!jUmJHnvWF$DY zu(`5uEbrcRfi9QcCwZZpyw}6o_XzBmG zZ}R_IfM6h$!M86bC#S8g?c(Axbf@;!OGU*}v!QI*M&Kg{2^6V$DFS-KH>8iPZeDu} z7rZb(|1i1>^q9OnJdvM2;}WyeKxm+9X$>JIBqg25*Qx`c0oyJZe% z&2N-AIA!^ooPiIfq)dcMT5YHX_86vqaB*?L4s10-#49Yu-jGAu`2%A*_@6uh-UwKE;Hn?ljX#i6QgQ(O9~?wbQz+Sj{%QrJ zqzH_q@RyC{(!=2A=TCg%qHk@jqpDg7Ixrx@+sjYkIAYwOKpNpd7bR4J=^jE|NpUfJ z#GRWrAKLs9@>9HmHsd&bp!?D>ea$A3v=@|CY5|3IT0HN2b}BIi%Lsnq@`ie<5Kx%Q0N&Lp!)8CZRg?P zL5l$+5_;mG@PJ^@97B6qkDb6_2l)h4ATywlfuA+h07<0)7dEJAMRV`xXJk}BJq6nj z*6~uzyJvocx_-t89KM6Sz5C8$NcsMS_;Z~KEAoi;!I2TK?fL8b?`2Z@;4I(29SAZO zB7A&^e<&aW8T&i}q3?DeR6P#6%W~`QKo1GUJ#-xx5HNuY!v*!%%IaPYxG$eOYQCZ! zDCIW~lK^3A(yCN^WaJ$R3S0zoaltL?Yik)cRg;jXI=D#h+=+~d!9*Y?iSJL3w+u7Mc_5S$rqbr7Gkr^*H_aM=ura=})#%v${ zV*k7Nn4o-6*2?;!{};3wh%Ou;&TU_rSS}nl5=bXZ6U+h4utvB@7t9V(FvA8}2q-#z zlda!&+zJ$dZJi6_M{M?1m5Nkll3tpXioxDl-?$XIS~&9iCa4;~Gsx!`vqqr|Nb16ApTnzqnvvw3WYH%tK!f|_=T_=WUIs5ulRC3 zYTZ;`bc8~ch(NHmd;L1=@2zw`(3F_P&0XmCr%G~u(2f7n?I$C5Dy_yI+Jj9<=r=Uc>=OV6W#X4>3R>QiFJ5(g)7Pnlh+)+IvO;# z(1S*q_R*Ewh31{JHXv9rjE$TkfGL(|YE+CP(f5IYl9sT^NzL`p z;k-0MS!mTEo{f!-6?f~~d5FL(_ga)OViV;o9;Z68Jj9TZl{F{)OxD%j9%e~UgWuQJ z2UBNq{@#L$GEfBK>!sY=>C@MGQlFuFz|=DnbiQ+^V=IOY4oOexV)>G_2R#_$gm6hp zKHb!p%jX3uIOG>>T}~2T!*k7O{xu4I*TKqHd*z?szJ2RTsSy|k(FAm#(o>)lM!p^o zrgb#U+8E`9aBC)P&nb_H4jezt<@D;%eOM@HW`f~=Io{`w^PWWu9K+W4N{L?9yx9%E z3>1}>-*hn|Fp4ZvFQRinzn}&5Jn%=plm6k$4EM*vKx1BgGd>gPO*?_^U7h|c67SK| zXbw>WUH0hFNp-NV3k$y)WbX(b9~TMDOD(&Kfg#QE)hf)?ScbS9Q0lp*Ep%rmm9hs& z$4nD_)fn{pWyZIP&i&TML=Q7wf0Rgtlso9@jC{>12_IP5g=t91$;soCdP)d_ho=_J z3u!V;0!Vg*9&Q>#RQ&1^^U6@5!%Dc^b{BHk21qW=XiCz~m*US()w80!vz3DK&!7kcB|gJo6Wy$mv%SuPip($HucQ#*u8urYDCuGS zRfM{>w?|USkijbMkaZT*K0RPsXs)S#Gy425yQ z{Opr=$dFIUbl9!;dKPIjFOHO zO<7r~=>WQhm4jL~w6RJVfP&ET;_LuQBS;TQ%E~YmW(^($2?+_r@O*(n6AKGS-m1*QP`(R7;Sb_W3keVK z2|$YgwJ!9`4S=~oDg-+Zw38AN@cPorDRIMpEt- zy+}(Ay6n+#yX(+_!G^pP*TPj#f?p!<1+2>D#lhvm(ypzqKQU~(3DR&F^Z`}VJot}j zXvhRTH93b)0zec5jSUQ>!-2vGOg{Pf`Jne#$=8&WmWJ_PAO;<>^S#WG0nM;tuke>9TGBD(vlPgV^*kRj_@8mhsXo19JS_xnVF>K<$Dm1nmUi5t~a(QJ+59 zuk_POzN3c%8RqgZ=y5H=sIdDtuYrXKq_()&*xzx1Bd;p~lDh0U%*?WBG;whQNCn)v%eQJJ!LtS{8wVL4iW4YIY3+8QGZKK<5>lc5 zgrWhmH>?n-S-~0rTL(ZgRPX%!O~ChHc+q9o1uWr2NTj|L6Y(ZMjI7qR>AzXVkpF+#+Ta9I=`H(G%_+%V*CR99Oa*IAa9*Q zrS;fi^lNGLAXFS<;ur(lI}q>wzsw{IpaeAD zs0bj>u#5>H00poDZiuGFT~<~W6$NFLeD6GTKvCc0U{i}VN+Zey-cS+{5FifSPe1@6 z0YS`{FQ9L8c5#u^;sL0311vcA>FCMFV#7H}ioY0~V}fsN-P7p}WA$w{OV+KS35KlO7)%`_bA8g>eeJB&NGt zws@B)pcyt)S3KVV>>KE6K*t6@34$;fZKWV_2hoKoZBUMX%B9Ky&F+4hbQB6IDunu& z*w~!3v>_M~1>q<{LSB0fO5^3(S?G_Rru0GINXIh`?GaqwkCqmHKfgN!1pOu)#>PwV z8^hBy{qBFax4)4*(~`D7Jv+NE^W_hya{=oHF|op8yi|%&J`%vF)_dGf08~MY-?MFehZLq=*5&gJ>i_z z!wf$dLzO1*8g7P^!{numOt6p7mEopuHa0eg(0jlcgt|{dQ!_UsGaMv5SkbYdVwt$_e*B&v z?0TDO4Wy`kk@E+gv!8AXUs+@AE`5_YJRmjS=g%0ZKf@mMZJwpkKcnXJTJT5hP^-ZB z=hPQwgkRe1kH6n@df_RJgHtQ9dojU$gQCuMZvPR`yv=SZOih%ZhOxkLnQF7JIEQEa zrroeV=G8!i!5bb?>#6Ef^PTVTe?bEBQ}deKb4KES@E?_5!;9&w-U{L`wyn|M(mNBD5C@gPNJf{PCM7@Za@+Xl$E5q@UIk@L8| zTtyoU7gB^OSm$nUZ=<a1#<9r0x@y)e_|tEEd#6ZA)Vyi#@8`ul zCgNNm@zxj>R27deEykm&WlH|iA&>Q@dax0gRQ5&2XfBTv4fNWoT_(gM_&K6=D_V-z z7Gon{#a3gMay|l~seTdBM14|%ZoQgDmI}ohi7w$0up`-ysn0#NTO7A_#MWzI@uEQ}L66bu*K6~%A_MCIgC6M-!gX3*9nO_y|)*c)bWo+sGB-i`XUeC6ZXbHH? zgiL1p6Ifl)b6InN(I8kzy0G5?(-7dAp?@Z^K>SD=+Iok(JN2u-Xz(XY3iK5)SU{{z zRyQdNglyF;UfV5N4l$tP;`#0j0)$4$Q^+;s=?6AafS$p%{PyJDqb(FD4R!~IVzTdJ zk#KZz@364op>MZ6%_Z%1$?=WJWROl?>cmg5__1EUj_M&*>WI`TGYKlHauFE)bGYb9 z$4wy>j}6#tU{NxZ)ZCG^MGDlW`aEPXEh&}~(NU8UFnUs?n6dqd5Cm|SklbKT;ZLxNk(}0ihgF?)LRjQ&Nga&9bEq@q&U(uyF#Y zO+%A|CZ0`BL>DvA`F{BsEdLrnNB}rf{L)3q&%n?S)SVmx6+b9_KNF~6Eg$VdIp1M0 z-RCR&!Ub400EOw|=8XG*B%!9h-s|pi;CIZS&jR)Cldt$lru^e#Gbnrw`2HOPw)U>B zg4v8z0|SHqN?(+Z=F0lJWnD4CpE2s6emq({g!R#xF4YBrH}7Su%0Dm&yhVGEjbvtm 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 + AliECS Environment + + +## Tasks + The basic unit of scheduling in AliECS is a **task**. A task generally corresponds to a process. Sometimes this is a process that can receive and respond to OCC-compatible control messages (also called a **stateful task**), and other times this is simply a shell script or command line tool invocation (also called a **stateless task** or **basic task**). +## Workflows, roles and environments + All AliECS **workflows** are collections of tasks, which together form a coherent data processing chain. -Tasks are the leaves in a tree of roles. A **role** is a runtime subdivision of the complete system, it represents a kind of operation along with its resources (but less than a complete data processing chain). Each task implements one or more roles. Roles allow binding tasks or groups of tasks to specific host attributes, detectors and configuration values. Each role represents either a single task, or a group of child roles. If tasks are leaves, roles are all the other nodes in the control tree of an environment. +Tasks are the leaves in a tree of roles. +A **role** is a runtime subdivision of the complete system, it represents a kind of operation along with its resources (but less than a complete data processing chain). +Each task implements one or more roles. +Roles allow binding tasks or groups of tasks to specific host attributes, detectors and configuration values. +Each role represents either a single task, or a group of child roles. While tasks are leaves, roles are all the other nodes in the control tree of an environment. + +These novel, more flexible and more easily deployable abstractions represent the evolution of Run 2 abstractions such as ECS partitions. +In memory, a tree of O² roles, along with their tasks and their configuration is a **workflow**. +A workflow aggregates the collective state of its constituent O2 roles. +A running workflow, along with associated detectors and other hardware and software resources required for experiment operation constitutes an **environment**. + +## Activities and runs + +**Activity** and (data-taking) **run** are used interchangeably in AliECS. +Run is a term present in ALICE from the beginning, while activity was introduced in the early days of the O2 project when it was not clear how the idea of a run would evolve. + +A run is a period of data taking, which is defined by a start and end time, typically lasting several hours at most. +It is identified by a run number, which is a monotonically increasing integer. +It is also associated with a set of configuration parameters, which are used to configure the data processing chain. -These novel, more flexible and more easily deployable abstractions represent the evolution of Run 2 abstractions such as ECS partitions. In memory, a tree of O² roles, along with their tasks and their configuration is a **workflow**. A workflow aggregates the collective state of its constituent O2 roles. A running workflow, along with associated detectors and other hardware and software resources required for experiment operation constitutes an **environment**. +Run has also a second meaning at CERN, which is understood as a period of LHC operations, lasting a few years and separated by Long Shutdowns. +These operational runs are not to be mistaken with data-taking runs. diff --git a/docs/handbook/configuration.md b/docs/handbook/configuration.md index cf0024fe..f0953e17 100644 --- a/docs/handbook/configuration.md +++ b/docs/handbook/configuration.md @@ -167,21 +167,6 @@ roles: In the absence of an explicit `critical` trait for a given task role, the assumed default value is `critical: true`. -#### State machine callbacks moments - -The underlying state machine library allows us to add callbacks upon entering and leaving states as well as before and after events (transitions). -This is the order of callback execution upon a state transition: -1. `before_` - called before event named `` -2. `before_event` - called before all events -3. `leave_` - called before leaving `` -4. `leave_state` - called before leaving all states -5. `enter_`, `` - called after entering `` -6. `enter_state` - called after entering all states -7. `after_`, `` - called after event named `` -8. `after_event` - called after all events - -Callback execution is further refined with integer indexes, with the syntax `±index`, e.g. `before_CONFIGURE+2`, `enter_CONFIGURED-666`. An expression with no index is assumed to be indexed `+0`. These indexes do not correspond to timestamps, they are discrete labels that allow more granularity in callbacks, ensuring a strict ordering of callback opportunities within a given callback moment. Thus, `before_CONFIGURE+2` will complete execution strictly after `before_CONFIGURE` runs, but strictly before `enter_CONFIGURED-666` is executed. - ### Call roles Call roles represent calls to integrated services. They must contain a `call` @@ -212,7 +197,7 @@ for examples of call roles that reference a variety of integration plugins. The state machine callback moments are exposed to the AliECS workflow template interface and can be used as triggers or synchronization points for integration plugin function calls. The `call` block can be used for this purpose, with similar syntax to the `task` block used for controllable tasks. Its fields are as follows. * `func` - mandatory, it parses as an [`antonmedv/expr`](https://github.com/antonmedv/expr) expression that corresponds to a call to a function that belongs to an integration plugin object (e.g. `bookkeeping.StartOfRun()`, `dcs.EndOfRun()`, etc.). -* `trigger` - mandatory, the expression at `func` will be executed once the state machine reaches this moment. +* `trigger` - mandatory, the expression at `func` will be executed once the state machine reaches this moment. For possible values, see [State machine triggers](/docs/handbook/operation_order.md#state-machine-triggers) * `await` - optional, if absent it defaults to the same as `trigger`, the expression at `func` needs to finish by this moment, and the state machine will block until `func` completes. * `timeout` - optional, Go `time.Duration` expression, defaults to `30s`, the maximum time that `func` should take. The value is provided to the plugin via `varStack["__call_timeout"]` and the plugin should implement a timeout mechanism. The ECS will not abort the call upon reaching the timeout value! * `critical` - optional, it defaults to `true`, if `true` then a failure or timeout for `func` will send the environment state machine to `ERROR`. diff --git a/docs/handbook/operation_order.md b/docs/handbook/operation_order.md index fdf05fa7..de36c9f5 100644 --- a/docs/handbook/operation_order.md +++ b/docs/handbook/operation_order.md @@ -4,7 +4,22 @@ This chapter attempts to document the order of important operations done during Since AliECS is an evolving system, the information presented here might be out-of-date, thus please refer to event handling in [core/environment/environment.go](https://github.com/AliceO2Group/Control/blob/master/core/environment/environment.go) and plugin calls in [ControlWorkflows/workflows/readout-dataflow.yaml](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml) for the ultimate source of truth. Also, please report to the ECS developers any inaccuracies. -[State Machine Callbacks](configuration.md#State-machine-callbacks) documents the order of callbacks that can be associated with state machine transitions. +## State machine triggers + +The underlying state machine library allows us to add callbacks upon entering and leaving states as well as before and after events (transitions). +This is the order of callback execution upon a state transition: +1. `before_` - called before event named `` +2. `before_event` - called before all events +3. `leave_` - called before leaving `` +4. `leave_state` - called before leaving all states +5. `enter_`, `` - called after entering `` +6. `enter_state` - called after entering all states +7. `after_`, `` - called after event named `` +8. `after_event` - called after all events + +Callback execution is further refined with integer indexes, with the syntax `±index`, e.g. `before_CONFIGURE+2`, `enter_CONFIGURED-666`. +An expression with no index is assumed to be indexed `+0`. These indexes do not correspond to timestamps, they are discrete labels that allow more granularity in callbacks, ensuring a strict ordering of callback opportunities within a given callback moment. +Thus, `before_CONFIGURE+2` will complete execution strictly after `before_CONFIGURE` runs, but strictly before `enter_CONFIGURED-666` is executed. ## START_ACTIVITY (Start Of Run) @@ -83,116 +98,4 @@ This is the order of actions happening at a healthy end of run. - `"run_end_completion_time_ms"` is set using current time. It is considered as the EOEOR timestamp. - `after_STOP_ACTIVITY` hooks with positive weights (incl. 0) are executed: - `ccdb.RunStop()` at `0` - - `bookkeeping.UpdateRunStop()`, `bookkeeping.UpdateEnv()` at `+100` - -# Integrated service operations - -## DCS - -### DCS operations - -The DCS integration plugin exposes to the workflow template (WFT) context the -following operations. Their associated transitions in this table refer -to the [readout-dataflow](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml) workflow template. - -| **DCS operation** | **WFT call** | **Call timing** | **Critical** | **Contingent on detector state** | -|-----------------------|---------------------|---------------------------|--------------|----------------------------------| -| Prepare For Run (PFR) | `dcs.PrepareForRun` | during `CONFIGURE` | `false` | yes | -| Start Of Run (SOR) | `dcs.StartOfRun` | early in `START_ACTIVITY` | `true` | yes | -| End Of Run (EOR) | `dcs.EndOfRun` | late in `STOP_ACTIVITY` | `true` | no | - -The DCS integration plugin subscribes to the [DCS service](https://github.com/AliceO2Group/Control/blob/master/core/integration/dcs/protos/dcs.proto) and continually -receives information on operation-state compatibility for all -detectors. -When a given environment reaches a DCS call, the relevant DCS operation -will be called only if the DCS service reports that all detectors in that -environment are compatible with this operation, except EOR, which is -always called. - -### DCS PrepareForRun behaviour - -Unlike SOR and EOR, which are mandatory if `dcs_enabled` is set to `true`, -an impossibility to run PFR or a PFR failure will not prevent the -environment from transitioning forward. - -#### DCS PFR incompatibility - -When `dcs.PrepareForRun` is called, if at least one detector is in a -state that is incompatible with PFR as reported by the DCS service, -a grace period of 10 seconds is given for the detector(s) to become -compatible with PFR, with 1Hz polling frequency. As soon as all -detectors become compatible with PFR, the PFR operation is requested -to the DCS service. - -If the grace period ends and at least one detector -included in the environment is still incompatible with PFR, the PFR -operation will be performed for the PFR-compatible detectors. - -Despite some detectors not having performed PFR, the environment -can still transition forward towards the `RUNNING` state, and any DCS -activities that would have taken place in PFR will instead happen -during SOR. Only at that point, if at least one detector is not -compatible with SOR (or if it is but SOR fails), will the environment -declare a failure. - -#### DCS PFR failure - -When `dcs.PrepareForRun` is called, if all detectors are compatible -with PFR as reported by the DCS service (or become compatible during -the grace period), the PFR operation is immediately requested to the -DCS service. - -`dcs.PrepareForRun` call fails if no detectors are PFR-compatible -or PFR fails for all those which were PFR-compatible, -but since it is non-critical the environment may still reach the -`CONFIGURED` state and transition forward towards `RUNNING`. - -As in the case of an impossibility to run PFR, any DCS activities that -would have taken place in PFR will instead be done during SOR. - -### DCS StartOfRun behaviour - -The SOR operation is mandatory if `dcs_enabled` is set to `true` -(AliECS GUI "DCS" switched on). - -#### DCS SOR incompatibility - -When `dcs.StartOfRun` is called, if at least one detector is in a -state that is incompatible with SOR as reported by the DCS service, -or if after a grace period of 10 seconds at least one detector is -still incompatible with SOR, the SOR operation **will not run for any -detector**. - -The environment will then declare a **failure**, the -`START_ACTIVITY` transition will be blocked and the environment -will move to `ERROR`. - -#### DCS SOR failure - -When `dcs.StartOfRun` is called, if all detectors are compatible -with SOR as reported by the DCS service (or become compatible during -the grace period), the SOR operation is immediately requested to the -DCS service. - -If this operation fails for one or more detectors, the -`dcs.StartOfRun` call as a whole is considered to have failed. - -The environment will then declare a **failure**, the -`START_ACTIVITY` transition will be blocked and the environment -will move to `ERROR` - -### DCS EndOfRun behaviour - -The EOR operation is mandatory if `dcs_enabled` is set to `true` -(AliECS GUI "DCS" switched on). However, unlike with PFR and SOR, there -is **no check for compatibility** with the EOR operation. The EOR -request will always be sent to the DCS service during `STOP_ACTIVITY`. - -#### DCS EOR failure - -If this operation fails for one or more detectors, the -`dcs.EndOfRun` call as a whole is considered to have failed. - -The environment will then declare a **failure**, the -`STOP_ACTIVITY` transition will be blocked and the environment -will move to `ERROR`. + - `bookkeeping.UpdateRunStop()`, `bookkeeping.UpdateEnv()` at `+100` \ No newline at end of file diff --git a/docs/handbook/overview.md b/docs/handbook/overview.md index 2d3bcb48..d1cf1f9a 100644 --- a/docs/handbook/overview.md +++ b/docs/handbook/overview.md @@ -1,6 +1,6 @@ # Design Overview -AliECS in a distributed application, using Apache Mesos as toolkit. It integrates a task scheduler component, a purpose-built distributed state machine system, a multi-source stateful process configuration mechanism, and a control plugin and library compatible with any data-driven O2 process. +AliECS is a distributed application, using Apache Mesos as toolkit. It integrates a task scheduler component, a purpose-built distributed state machine system, a multi-source stateful process configuration mechanism, and a control plugin and library compatible with any data-driven O2 process. ## AliECS Structure @@ -18,14 +18,13 @@ AliECS in a distributed application, using Apache Mesos as toolkit. It integrate | AliECS GUI | Instance of the user-facing web interface for AliECS (`cog`), running on the head node. This is the main entry point for regular users. | | AliECS CLI | The `coconut` command, provided by the package with the same name. This is the reference client for advanced users and developers. | - ## Resource Management Apache Mesos is a cluster resource management system. It greatly streamlines distributed application development by providing a unified distributed execution environment. Mesos facilitates the management of O²/FLP components, resources and tasks inside the O²/FLP facility, effectively enabling the developer to program against the datacenter (i.e., the O²/FLP facility at LHC Point 2) as if it was a single pool of resources. For AliECS, Mesos acts as an authoritative source of knowledge on the state of the cluster, as well as providing transport facilities for communication between the AliECS core and the executor. -You can view the state of the cluster as presented by Mesos via the Mesos web interface, served on port `5050` of your head node when deployed via the [O²/FLP Suite setup tool](../../installation/). +You can view the state of the cluster as presented by Mesos via the Mesos web interface, served on port `5050` of your head node when deployed via the O²/FLP Suite setup tool. ## FairMQ @@ -40,4 +39,4 @@ The main state machine of AliECS is the environment state machine, which represe ![](AliECS-envsm.svg) -While FairMQ devices use their own, FairMQ-specific state machine, non-FairMQ tasks based on the [OCC library](https://alice-flp-suite.docs.cern.ch/aliecs/occ/) use the same state machine as the AliECS environment state machine, the only difference being that the `START_ACTIVITY` transition is simply `START`, and the `STOP_ACTIVITY` transition is simply `STOP`. +While FairMQ devices use their own, FairMQ-specific state machine, non-FairMQ tasks based on the [OCC library](/occ/README.md) use the same state machine as the AliECS environment state machine, the only difference being that the `START_ACTIVITY` transition is simply `START`, and the `STOP_ACTIVITY` transition is simply `STOP`. diff --git a/docs/kafka.md b/docs/kafka.md index 53d3bd56..0a5ab281 100644 --- a/docs/kafka.md +++ b/docs/kafka.md @@ -24,7 +24,7 @@ Once the topics exist, no further messages can be lost and no action is necessar ### Currently available topics -See [events.proto](../common/protos/events.proto) for the protobuf definitions of the messages. +See [events.proto](/common/protos/events.proto) for the protobuf definitions of the messages. * `aliecs.core` - core events that don't concern a specific environment or task * `aliecs.environment` - events that concern an environment, e.g. environment state changes @@ -38,7 +38,7 @@ See [events.proto](../common/protos/events.proto) for the protobuf definitions o ### Decoding the messages -Messages are encoded with protobuf, with the aforementioned [events.proto](../common/protos/events.proto) file defining the schema. +Messages are encoded with protobuf, with the aforementioned [events.proto](/common/protos/events.proto) file defining the schema. Integraed service messages include a payload portion that is usually JSON-encoded, and has no predefined schema. To generate the precompiled protobuf interface, run `make fdset`. @@ -79,7 +79,7 @@ As for today, AliECS publishes on the following types of topics: ### Decoding the messages -Messages are encoded with protobuf. Please use [this](../core/integration/kafka/protos/kafka.proto) proto file to generate code which deserializes the messages. +Messages are encoded with protobuf. Please use [this](/core/integration/kafka/protos/kafka.proto) proto file to generate code which deserializes the messages. ### Getting Start of Run and End of Run notifications diff --git a/docs/running.md b/docs/running.md index 840c89aa..53d80afa 100644 --- a/docs/running.md +++ b/docs/running.md @@ -1,14 +1,14 @@ # Running AliECS as a developer - -> **WARNING**: The running instructions described in this page are **for development purposes only**. Users interested in deploying, running and controlling O²/FLP software or their own software with AliECS should refer to the [O²/FLP Suite instructions](https://alice-flp-suite.docs.cern.ch/installation/) instead. - +> **WARNING**: The running instructions described in this page are **for development purposes only**. Users interested in deploying, running and controlling O²/FLP software or their own software with AliECS should refer to the O²/FLP Suite instructions instead. ## Running the AliECS core This part assumes you have already set up the Go environment, fetched the sources and built all AliECS Go components. -The recommended way to set up a Mesos cluster is by performing a complete deployment of the O²/FLP Suite with `o2-flp-setup`. The AliECS core on the head node should be stopped (`systemctl stop o2-aliecs-core`) and your own AliECS core should be made to point to the head node. +The recommended way to set up a Mesos cluster is by performing a complete deployment of the O²/FLP Suite with `o2-flp-setup`. +The AliECS core on the head node should be stopped (`systemctl stop o2-aliecs-core`) and your own AliECS core should be made to point to the head node. +Typically, it can be done by replacing the AliECS core binary on the head node with your own and restarting the `o2-aliecs-core` systemd service. The following example flags assume a remote head node `centosvmtest`, the use of the default `settings.yaml` file, very verbose output, verbose workflow dumps on every workflow deployment, and the executor having been copied (`scp`) to `/opt/o2control-executor` on all controlled nodes: @@ -26,7 +26,7 @@ http://centosvmtest:5050/api/v1/scheduler --dumpWorkflows ``` -See [Using `coconut`](./coconut/README.md) for instructions on the O² Control core command line interface. +See [Using `coconut`](/coconut/README.md) for instructions on the O² Control core command line interface. # Running AliECS in production diff --git a/docs/using_grpcc_occ.md b/docs/using_grpcc_occ.md index 68e535ae..8a26c5b1 100644 --- a/docs/using_grpcc_occ.md +++ b/docs/using_grpcc_occ.md @@ -8,7 +8,7 @@ installation with `npm` is straightforward. ```bash $ sudo yum install http-parser nodejs npm -$ npm install -g grpcc +$ npm install -g grpcc # it can take a few minutes due to grpc build ``` In a new terminal, we go to the `occ` directory (not the `build` dir) and connect via gRPC: diff --git a/hacking/COG.md b/hacking/COG.md index 0bb5aad7..1733f3cb 100644 --- a/hacking/COG.md +++ b/hacking/COG.md @@ -1,4 +1,4 @@ -# AliECS GUI +# AliECS GUI overview If you are using the [Single node O²/FLP software deployment instructions](https://gitlab.cern.ch/AliceO2Group/system-configuration/blob/master/ansible/docs/O2_INSTALL_FLP_STANDALONE.md), the AliECS GUI is automatically installed along with the full O²/FLP suite. @@ -44,11 +44,11 @@ In production, AliECS will manage and push all configuration to active tasks, bu Every task still has their own configuration file, with paths such as `/etc/flp.d/qc/*.json` for QualityControl and `/home/flp/readout.cfg` for Readout. These paths can be edited by the user, and any changes affect all newly launched instances of the task. -All configuration file paths used by tasks can be found in the task descriptors of the workflow configuration repository in use. For more information on workflow configuration repositories, see [the `coconut repository` reference](https://github.com/AliceO2Group/Control/blob/doc/coconut/doc/coconut_repository.md). The default workflow configuration repository which comes pre-loaded with AliECS is accessible at [AliceO2Group/ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) (all task descriptor files are found in the `tasks` directory). +All configuration file paths used by tasks can be found in the task descriptors of the workflow configuration repository in use. For more information on workflow configuration repositories, see [the `coconut repository` reference](/coconut/doc/coconut_repository.md). The default workflow configuration repository which comes pre-loaded with AliECS is accessible at [AliceO2Group/ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) (all task descriptor files are found in the `tasks` directory). * **modify an existing workflow or task?** -You are free to keep as many workflow configuration repositories as you wish in your AliECS instance. For more information on workflow configuration repositories, see [the `coconut repository` reference](https://github.com/AliceO2Group/Control/blob/doc/coconut/doc/coconut_repository.md). +You are free to keep as many workflow configuration repositories as you wish in your AliECS instance. For more information on workflow configuration repositories, see [the `coconut repository` reference](/coconut/doc/coconut_repository.md). Changes to a configuration repository are immediately available after running `coconut repo refresh`. There is no support in the AliECS GUI at this time. diff --git a/occ/README.md b/occ/README.md index 330a370e..6afa0dcb 100644 --- a/occ/README.md +++ b/occ/README.md @@ -55,4 +55,4 @@ See [`peanut` Overview](peanut/README.md). ## OCC API debugging with `grpcc` -See [OCC API debugging with `grpcc`](../docs/using_grpcc_occ.md). \ No newline at end of file +See [OCC API debugging with `grpcc`](/docs/using_grpcc_occ.md). \ No newline at end of file diff --git a/occ/occlib/examples/dummy-process/README.md b/occ/occlib/examples/dummy-process/README.md index 905a427d..516bcf2e 100644 --- a/occ/occlib/examples/dummy-process/README.md +++ b/occ/occlib/examples/dummy-process/README.md @@ -2,7 +2,7 @@ This example is built from the top-level CMakeLists.txt when `BUILD_EXAMPLES` is true. -For instructions on running it, see [Run example](../../../README.md#run-example). +For instructions on running it, see [Run example](/occ/README.md#run-example). ## Standalone build diff --git a/occ/peanut/README.md b/occ/peanut/README.md index b6d488e3..61f72815 100644 --- a/occ/peanut/README.md +++ b/occ/peanut/README.md @@ -1,4 +1,4 @@ -# `peanut` +# Process control and execution utility overview `peanut` is the **p**rocess **e**xecution **a**nd co**n**trol **ut**ility for OCClib-based O² processes. Its purpose is to be a debugging and development aid for non-FairMQ O² devices, where FairMQ's interactive From 12ac5edf3aac4f10fbadf95a06889229bec8025c Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Fri, 16 May 2025 14:34:39 +0200 Subject: [PATCH 5/7] [doc] formatting fixes for mkdocs --- README.md | 1 + apricot/README.md | 1 + coconut/README.md | 1 + coconut/doc/coconut_environment_create.md | 1 + coconut/doc/coconut_repository.md | 1 + coconut/doc/coconut_repository_add.md | 1 + coconut/doc/coconut_role_query.md | 1 + coconut/doc/coconut_template_list.md | 3 ++- core/integration/README.md | 1 + docs/handbook/configuration.md | 5 +++++ docs/handbook/{index.md => introduction.md} | 0 docs/handbook/operation_order.md | 1 + docs/kafka.md | 7 +++++-- 13 files changed, 21 insertions(+), 3 deletions(-) rename docs/handbook/{index.md => introduction.md} (100%) diff --git a/README.md b/README.md index 17d77d23..79d3ca6f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ There are two ways of interacting with AliECS: ### I want my application to send requests to AliECS :scroll: See the API docs of AliECS components: + - [core gRPC server](/docs/apidocs_aliecs.md) - [apricot gRPC server](/docs/apidocs_apricot.md) - [apricot HTTP server](/apricot/docs/apricot_http_service.md) diff --git a/apricot/README.md b/apricot/README.md index c32dce70..f3a63b6e 100644 --- a/apricot/README.md +++ b/apricot/README.md @@ -4,6 +4,7 @@ It adds templating, load balancing and caching on top of the configuration store. See also: + * [apricot HTTP service](docs/apricot_http_service.md) - make essential cluster information available via a web server * Protofile: [apricot.proto](protos/apricot.proto) * [Command reference](docs/apricot.md) diff --git a/coconut/README.md b/coconut/README.md index c6cb97a6..086de07f 100644 --- a/coconut/README.md +++ b/coconut/README.md @@ -98,6 +98,7 @@ A valid workflow template (sometimes called simply "workflow" for brevity) must Workflows and tasks are managed with a git based configuration system, so the workflow template may be provided simply by name or with repository and branch/tag/hash constraints. Examples: + * `coconut env create -w myworkflow` - loads workflow `myworkflow` from default configuration repository at HEAD of master branch * `coconut env create -w github.com/AliceO2Group/MyConfRepo/myworkflow` - loads a workflow from a specific git repository, HEAD of master branch * `coconut env create -w myworkflow@rev` - loads a workflow from default repository, on branch, tag or revision `rev` diff --git a/coconut/doc/coconut_environment_create.md b/coconut/doc/coconut_environment_create.md index ce39b734..62ec3047 100644 --- a/coconut/doc/coconut_environment_create.md +++ b/coconut/doc/coconut_environment_create.md @@ -13,6 +13,7 @@ A valid workflow template (sometimes called simply "workflow" for brevity) must Workflows and tasks are managed with a git based configuration system, so the workflow template may be provided simply by name or with repository and branch/tag/hash constraints. Examples: + * `coconut env create -w myworkflow` - loads workflow `myworkflow` from default configuration repository at HEAD of master branch * `coconut env create -w github.com/AliceO2Group/MyConfRepo/myworkflow` - loads a workflow from a specific git repository, HEAD of master branch * `coconut env create -w myworkflow@rev` - loads a workflow from default repository, on branch, tag or revision `rev` diff --git a/coconut/doc/coconut_repository.md b/coconut/doc/coconut_repository.md index 32156d56..7ba96cc1 100644 --- a/coconut/doc/coconut_repository.md +++ b/coconut/doc/coconut_repository.md @@ -9,6 +9,7 @@ The repository command performs operations on the repositories used for task and A valid workflow configuration repository must contain the directories `tasks` and `workflows` in its `master` branch. When referencing a repository, the clone method should never be prepended. Supported repo backends and their expected format are: + - https: [hostname]/[repo_path] - ssh: [hostname]:[repo_path] - local [repo_path] (local repo entries are ephemeral and will not survive a core restart) diff --git a/coconut/doc/coconut_repository_add.md b/coconut/doc/coconut_repository_add.md index 90db22e4..7b24c4f8 100644 --- a/coconut/doc/coconut_repository_add.md +++ b/coconut/doc/coconut_repository_add.md @@ -16,6 +16,7 @@ the ensuing list is followed until a valid revision has been identified: Exhaustion of the aforementioned list results in a repo add failure. `coconut repo add` can be called with + 1) a repository identifier 2) a repository identifier coupled with the `--default-revision` flag (see examples below) diff --git a/coconut/doc/coconut_role_query.md b/coconut/doc/coconut_role_query.md index 6d562679..ee375927 100644 --- a/coconut/doc/coconut_role_query.md +++ b/coconut/doc/coconut_role_query.md @@ -17,6 +17,7 @@ walk through the role tree of the given environment, starting from the root role per https://github.com/gobwas/glob syntax. Examples: + * `coconut role query 2rE9AV3m1HL readout-dataflow` - queries the role `readout-dataflow` in environment `2rE9AV3m1HL`, prints the full tree, along with the variables defined in the root role * `coconut role query 2rE9AV3m1HL readout-dataflow.host-aido2-bld4-lab102` - queries the role `readout-dataflow.host-aido2-bld4-lab102`, prints the subtree of that role, along with the variables defined in it * `coconut role query 2rE9AV3m1HL readout-dataflow.host-aido2-bld4-lab102.data-distribution.stfs` - queries the role at the given path, it is a task role so there is no subtree, prints the variables defined in that role diff --git a/coconut/doc/coconut_template_list.md b/coconut/doc/coconut_template_list.md index 47853a11..7d2fdda2 100644 --- a/coconut/doc/coconut_template_list.md +++ b/coconut/doc/coconut_template_list.md @@ -7,7 +7,8 @@ list available workflow templates The template list command shows a list of available workflow templates. These workflow templates can then be loaded to create an environment. -`coconut templ list` can be called with +`coconut templ list` can be called with + 1) a combination of the `--repo` , `--revision` , `--all-branches` , `--all-tags` , `--all-workflows` flags, or with 2) an argument in the form of [repo-pattern]@[revision-pattern], where the patterns are globbing. diff --git a/core/integration/README.md b/core/integration/README.md index 3095650c..369be93b 100644 --- a/core/integration/README.md +++ b/core/integration/README.md @@ -9,6 +9,7 @@ All plugins should implement the [`Plugin`](/core/integration/plugin.go) interfa See the existing plugins for examples. In order to have the plugin loaded by the AliECS, one has to: + - add `RegisterPlugin` to the `init()` function in [AliECS core main source](https://github.com/AliceO2Group/Control/blob/master/cmd/o2-aliecs-core/main.go) - add plugin name in the `integrationPlugins` list and set the endpoint in the AliECS configuration file (typically at `/o2/components/aliecs/ANY/any/settings` in the configuration store) diff --git a/docs/handbook/configuration.md b/docs/handbook/configuration.md index f0953e17..a6da1f08 100644 --- a/docs/handbook/configuration.md +++ b/docs/handbook/configuration.md @@ -12,6 +12,7 @@ while still being powerful enough to express complex workflows. To instantiate a data taking activity, or environment, two kinds of files are needed: + * workflow templates * task templates @@ -196,6 +197,7 @@ for examples of call roles that reference a variety of integration plugins. #### Workflow hook call structure The state machine callback moments are exposed to the AliECS workflow template interface and can be used as triggers or synchronization points for integration plugin function calls. The `call` block can be used for this purpose, with similar syntax to the `task` block used for controllable tasks. Its fields are as follows. + * `func` - mandatory, it parses as an [`antonmedv/expr`](https://github.com/antonmedv/expr) expression that corresponds to a call to a function that belongs to an integration plugin object (e.g. `bookkeeping.StartOfRun()`, `dcs.EndOfRun()`, etc.). * `trigger` - mandatory, the expression at `func` will be executed once the state machine reaches this moment. For possible values, see [State machine triggers](/docs/handbook/operation_order.md#state-machine-triggers) * `await` - optional, if absent it defaults to the same as `trigger`, the expression at `func` needs to finish by this moment, and the state machine will block until `func` completes. @@ -410,10 +412,12 @@ Variables whose availability to tasks is handled in some way by AliECS include * variables delivered to tasks explicitly via task templates. The latter can be + * sourced from Apricot with a query from the task template iself (e.g. `config.Get`), or * sourced from the variables available to the current AliECS environment, as defined in the workflow template (e.g. readout-dataflow.yaml) Depending on the specification in the task template (`command.env`, `command.arguments` or `properties`), the push to the given task can happen + * as system environment variables on task startup, * as command line parameters on task startup, or * as (FairMQ) key-values during `CONFIGURE`. @@ -441,6 +445,7 @@ In addition to the above, which varies depending on the configuration of the env * `pdp_override_run_start_time` The following values are pushed by AliECS during `STOP_ACTIVITY`: + * `run_end_time_ms` FairMQ task implementors should expect that these values are written to the FairMQ properties map right before the `RUN` and `STOP` transitions via `SetProperty` calls. diff --git a/docs/handbook/index.md b/docs/handbook/introduction.md similarity index 100% rename from docs/handbook/index.md rename to docs/handbook/introduction.md diff --git a/docs/handbook/operation_order.md b/docs/handbook/operation_order.md index de36c9f5..f27172f1 100644 --- a/docs/handbook/operation_order.md +++ b/docs/handbook/operation_order.md @@ -8,6 +8,7 @@ Also, please report to the ECS developers any inaccuracies. The underlying state machine library allows us to add callbacks upon entering and leaving states as well as before and after events (transitions). This is the order of callback execution upon a state transition: + 1. `before_` - called before event named `` 2. `before_event` - called before all events 3. `leave_` - called before leaving `` diff --git a/docs/kafka.md b/docs/kafka.md index 0a5ab281..29290081 100644 --- a/docs/kafka.md +++ b/docs/kafka.md @@ -6,7 +6,8 @@ As of 2024 the AliECS core integrates Kafka producer functionality independent o ### Making sure that AliECS sends messages -To enable the plugin, one should make sure that the following points are fullfiled. +To enable the plugin, one should make sure that the following points are fulfilled. + * The consul instance includes coordinates to the list of kafka brokers. Navigate to `o2/components/aliecs/ANY/any/settings` and make sure the following key value pairs are there: ``` @@ -57,7 +58,8 @@ The messages are encoded with protobuf. ### Making sure that AliECS sends messages -To enable the plugin, one should make sure that the following points are fullfiled. +To enable the plugin, one should make sure that the following points are fulfilled. + * The consul instance includes coordinates to your kafka broker and enables the plugin. Navigate to `o2/components/aliecs/ANY/any/settings` and make sure the following key value pairs are there: ``` @@ -84,6 +86,7 @@ Messages are encoded with protobuf. Please use [this](/core/integration/kafka/pr ### Getting Start of Run and End of Run notifications To get SOR and EOR notifications, please subscribe to the two corresponding topics: + * `aliecs.env_state.RUNNING` for Start of Run * `aliecs.env_leave_state.RUNNING` for End of Run From f967a08ef448a3861fe2e497327cb27f27a91988 Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Mon, 19 May 2025 14:00:32 +0200 Subject: [PATCH 6/7] [doc] reorganization and link fixes for mkdocs --- core/integration/README.md | 2 +- docs/CONTRIBUTING.md | 16 +++++- docs/building.md | 2 +- docs/development.md | 2 +- docs/handbook/configuration.md | 2 +- docs/handbook/operation_order.md | 2 +- docs/metrics.md | 12 ++--- hacking/COG.md | 4 +- mkdocs-dev.yml | 12 +++++ mkdocs.yml | 54 ++++++++++----------- occ/README.md | 8 +-- occ/occlib/examples/dummy-process/README.md | 4 +- 12 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 mkdocs-dev.yml diff --git a/core/integration/README.md b/core/integration/README.md index 369be93b..d8662223 100644 --- a/core/integration/README.md +++ b/core/integration/README.md @@ -5,7 +5,7 @@ A plugin can register a set of callback which can be invoked upon defined enviro ## Plugin system overview -All plugins should implement the [`Plugin`](/core/integration/plugin.go) interface. +All plugins should implement the [`Plugin`](https://github.com/AliceO2Group/Control/blob/master/core/integration/plugin.go) interface. See the existing plugins for examples. In order to have the plugin loaded by the AliECS, one has to: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0bd697dc..54611fd4 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -46,4 +46,18 @@ Gomega/Ginkgo tests are preferred, but other style of tests are also welcome. - Add documentation for new features. -- Your contribution will be reviewed by the project maintainers once the PR is marked as ready for review. \ No newline at end of file +- Your contribution will be reviewed by the project maintainers once the PR is marked as ready for review. + +## Documentation guidelines + +The markdown documentation is aimed to be browsed on GitHub, but it also on the aggregated [FLP documentation](https://alice-flp.docs.cern.ch) based on [MkDocs](https://www.mkdocs.org/). +Consequently, any changes in the documentation structure should be reflected in the Table of Contents in the main README.md, as well as `mkdocs.yml` and `mkdocs.yml`. + +The AliECS MkDocs documentation is split into two aforementioned files to follow the split between "Products" and "Developers" tabs in the FLP documentation. +The `mkdocs-dev.yml` uses a symlink `aliecs-dev` to `aliecs` directory to avoid complaints about duplicated site names. + +Because of the dual target of the documentation, the points below are important to keep in mind: + +- Absolute paths in links to other files do not always work, they should be avoided. +- When referencing source files in the repository, use full URIs to GitHub. +- In MkDocs layouts, one cannot reference specific sections within markdown files. Only links to entire markdown files are possible. \ No newline at end of file diff --git a/docs/building.md b/docs/building.md index 62489880..0a4243ec 100644 --- a/docs/building.md +++ b/docs/building.md @@ -84,6 +84,6 @@ You should find several executables including `o2control-core`, `o2control-execu For subsequent builds (after the first one), plain `make` (instead of `make all`) is sufficient. See the [Makefile reference](makefile_reference.md) for more information. -If you wish to also build the process control library and/or plugin, see [the OCC readme](/occ/README.md). +If you wish to also build the process control library and/or plugin, see [the OCC readme](../occ/README.md). This build of AliECS can be run locally and connected to an existing O²/FLP Suite cluster by passing a `--mesosUrl` parameter. If you do this, remember to `systemctl stop o2-aliecs-core` on the head node, in order to stop the core that came with the O²/FLP Suite and use your own. diff --git a/docs/development.md b/docs/development.md index 0c94e2fc..d0373f09 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,7 +4,7 @@ Generated API documentation is available on [pkg.go.dev](https:///pkg.go.dev/git The release log is managed via [GitHub](https://github.com/AliceO2Group/Control/releases/). -Bugs go to [JIRA](https://alice.its.cern.ch/jira/browse/OCTRL). +Bugs go to [JIRA](https://its.cern.ch/jira/projects/OCTRL/issues). ## Release Procedure diff --git a/docs/handbook/configuration.md b/docs/handbook/configuration.md index a6da1f08..9f937652 100644 --- a/docs/handbook/configuration.md +++ b/docs/handbook/configuration.md @@ -199,7 +199,7 @@ for examples of call roles that reference a variety of integration plugins. The state machine callback moments are exposed to the AliECS workflow template interface and can be used as triggers or synchronization points for integration plugin function calls. The `call` block can be used for this purpose, with similar syntax to the `task` block used for controllable tasks. Its fields are as follows. * `func` - mandatory, it parses as an [`antonmedv/expr`](https://github.com/antonmedv/expr) expression that corresponds to a call to a function that belongs to an integration plugin object (e.g. `bookkeeping.StartOfRun()`, `dcs.EndOfRun()`, etc.). -* `trigger` - mandatory, the expression at `func` will be executed once the state machine reaches this moment. For possible values, see [State machine triggers](/docs/handbook/operation_order.md#state-machine-triggers) +* `trigger` - mandatory, the expression at `func` will be executed once the state machine reaches this moment. For possible values, see [State machine triggers](operation_order.md#state-machine-triggers) * `await` - optional, if absent it defaults to the same as `trigger`, the expression at `func` needs to finish by this moment, and the state machine will block until `func` completes. * `timeout` - optional, Go `time.Duration` expression, defaults to `30s`, the maximum time that `func` should take. The value is provided to the plugin via `varStack["__call_timeout"]` and the plugin should implement a timeout mechanism. The ECS will not abort the call upon reaching the timeout value! * `critical` - optional, it defaults to `true`, if `true` then a failure or timeout for `func` will send the environment state machine to `ERROR`. diff --git a/docs/handbook/operation_order.md b/docs/handbook/operation_order.md index f27172f1..50dac714 100644 --- a/docs/handbook/operation_order.md +++ b/docs/handbook/operation_order.md @@ -1,7 +1,7 @@ # Environment operation order This chapter attempts to document the order of important operations done during environment transitions. -Since AliECS is an evolving system, the information presented here might be out-of-date, thus please refer to event handling in [core/environment/environment.go](https://github.com/AliceO2Group/Control/blob/master/core/environment/environment.go) and plugin calls in [ControlWorkflows/workflows/readout-dataflow.yaml](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml) for the ultimate source of truth. +Since AliECS is an evolving system, the information presented here might be out-of-date, thus please refer to event handling in [environment.go][https://github.com/AliceO2Group/Control/blob/master/core/environment/environment.go) and plugin calls in [ControlWorkflows/workflows/readout-dataflow.yaml](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml) for the ultimate source of truth. Also, please report to the ECS developers any inaccuracies. ## State machine triggers diff --git a/docs/metrics.md b/docs/metrics.md index a925513f..799b88a6 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -59,7 +59,7 @@ ECS and aliecs.run are values. 6) space - divides fields and timestamp 7) timestamp - (optional) int64 value of unix timestamp in ns -In order to provide support for this format we introduced Metric structure in [common/monitoring/metric.go](../common/monitoring/metric.go). +In order to provide support for this format we introduced Metric structure in [common/monitoring/metric.go](https://github.com/AliceO2Group/Control/blob/master/common/monitoring/metric.go). Following code shows how to create a Metric with `measurement` as measurement name, one tag `tag1=val1` and field `field1=42u`: @@ -70,7 +70,7 @@ m.SetFieldUInt64("field1", 42) ``` However we also need to be able to store metrics, so these can be scraped correctly. -This mechanism is implemented in [common/monitoring/monitoring.go](../common/monitoring/monitoring.go). +This mechanism is implemented in [common/monitoring/monitoring.go](https://github.com/AliceO2Group/Control/blob/master/common/monitoring/monitoring.go). Metrics endpoint is run by calling `Run(port, endpointName)`. As this method is blocking it is advised to call it from `goroutine`. After this method is called we can than send metrics via methods `Send` and `SendHistogrammable`. If you want to send simple metrics @@ -92,7 +92,7 @@ Example for this use-case is duration of some function, eg. measure sending batch of messages. If we want the best coverage of metrics possible we can combine both of these to measure amount of messages send per batch and also measurement duration of the send. For example in code you can take a look actual -actual code in [writer.go](../common/event/writer.go) where we are sending multiple +actual code in [writer.go](https://github.com/AliceO2Group/Control/blob/master/common/event/writer.go) where we are sending multiple fields per metric and demonstrate full potential of these metrics. Previous code example will result in following metrics to be reported: @@ -203,7 +203,7 @@ if different points, but creating statistical report as mentioned in previous pa ### Event loop In order to send metrics from unlimited amount of goroutines, we need to have -robust and thread-safe mechanism. It is implemented in [common/monitoring/monitoring.go](../common/monitoring/monitoring.go) +robust and thread-safe mechanism. It is implemented in [common/monitoring/monitoring.go](https://github.com/AliceO2Group/Control/blob/master/common/monitoring/monitoring.go) as event loop (`eventLoop`) that reads data from two buffered channels (`metricsChannel` and `metricsHistosChannel`) with one goroutine. Apart from reading messages from these two channels event loop also handles scraping requests from `http.Server` endpoint. As the http endpoint is called by a @@ -219,8 +219,8 @@ which are consumed by event loop. In order to correctly implement behaviour described in the part about Aggregation we use the same implementation in two container aggregating objects `MetricsAggregate`, `MetricsReservoirSampling` implemented in files -[common/monitoring/metricsaggregate.go](../common/monitoring/metricsaggregate.go) -and [metricsreservoirsampling.go](../common/monitoring/metricsreservoirsampling.go) +[common/monitoring/metricsaggregate.go](https://github.com/AliceO2Group/Control/blob/master/common/monitoring/metricsaggregate.go) +and [metricsreservoirsampling.go](https://github.com/AliceO2Group/Control/blob/master/common/monitoring/metricsreservoirsampling.go) in the same directory. The implementation is done as different buckets in map with distinct keys (`metricsBuckets`). These keys need to be unique according to the timestamp and tags. We use struct `key` composed diff --git a/hacking/COG.md b/hacking/COG.md index 1733f3cb..21ca826e 100644 --- a/hacking/COG.md +++ b/hacking/COG.md @@ -44,11 +44,11 @@ In production, AliECS will manage and push all configuration to active tasks, bu Every task still has their own configuration file, with paths such as `/etc/flp.d/qc/*.json` for QualityControl and `/home/flp/readout.cfg` for Readout. These paths can be edited by the user, and any changes affect all newly launched instances of the task. -All configuration file paths used by tasks can be found in the task descriptors of the workflow configuration repository in use. For more information on workflow configuration repositories, see [the `coconut repository` reference](/coconut/doc/coconut_repository.md). The default workflow configuration repository which comes pre-loaded with AliECS is accessible at [AliceO2Group/ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) (all task descriptor files are found in the `tasks` directory). +All configuration file paths used by tasks can be found in the task descriptors of the workflow configuration repository in use. For more information on workflow configuration repositories, see [the `coconut repository` reference](../coconut/doc/coconut_repository.md). The default workflow configuration repository which comes pre-loaded with AliECS is accessible at [AliceO2Group/ControlWorkflows](https://github.com/AliceO2Group/ControlWorkflows) (all task descriptor files are found in the `tasks` directory). * **modify an existing workflow or task?** -You are free to keep as many workflow configuration repositories as you wish in your AliECS instance. For more information on workflow configuration repositories, see [the `coconut repository` reference](/coconut/doc/coconut_repository.md). +You are free to keep as many workflow configuration repositories as you wish in your AliECS instance. For more information on workflow configuration repositories, see [the `coconut repository` reference](../coconut/doc/coconut_repository.md). Changes to a configuration repository are immediately available after running `coconut repo refresh`. There is no support in the AliECS GUI at this time. diff --git a/mkdocs-dev.yml b/mkdocs-dev.yml new file mode 100644 index 00000000..6f5db4ae --- /dev/null +++ b/mkdocs-dev.yml @@ -0,0 +1,12 @@ +site_name: aliecs-dev + +nav: + - 'Contributing': '/docs/CONTRIBUTING.md' + - 'Development': '/docs/development.md' + - 'Package pkg.go.dev': 'https://pkg.go.dev/github.com/AliceO2Group/Control' + - 'Building': '/docs/building.md' + - 'Makefile reference': '/docs/makefile_reference.md' + - 'Component Configuration': '/docs/handbook/appconfiguration.md' + - 'Running': 'docs/running.md' + - 'Metrics': '/docs/metrics.md' + - 'OCC API debugging': '/docs/using_grpcc_occ.md' diff --git a/mkdocs.yml b/mkdocs.yml index edcf58f5..35ae83ae 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,31 +2,29 @@ site_name: aliecs nav: - 'Handbook': - - Introduction: handbook/index.md - - Overview: handbook/overview.md - - Basic Concepts: handbook/concepts.md - - Environment Operation Order: handbook/operation_order.md - - Configuration: - - Workflow Configuration: handbook/configuration.md - - Apricot Usage: './apricot/docs/apricot.md' - - Apricot HTTP API: './apricot/docs/apricot_http_service.md' - - Interfaces: - - AliECS gRPC API: apidocs_aliecs.md - - Apricot gRPC API: apidocs_apricot.md - - OCC gRPC API (Protobuf based): apidocs_occ.md - - Kafka: kafka.md - - Workflow Variables: '../controlworkflows/README.md' - - 'Command Reference': - - coconut: - - Overview: './coconut/README.md' - - coconut: './coconut/doc/coconut.md' - - coconut about: './coconut/doc/coconut_about.md' - - coconut configuration: './coconut/doc/coconut_configuration.md' - - coconut environment: './coconut/doc/coconut_environment.md' - - coconut info: './coconut/doc/coconut_info.md' - - coconut repository: './coconut/doc/coconut_repository.md' - - coconut role: './coconut/doc/coconut_role.md' - - coconut task: './coconut/doc/coconut_task.md' - - coconut template: './coconut/doc/coconut_template.md' - - peanut: './occ/peanut/README.md' - - 'FAQ': faq.md + - 'Introduction': 'docs/handbook/introduction.md' + - 'Basic Concepts': 'docs/handbook/concepts.md' + - 'Design Overview': 'docs/handbook/overview.md' + - 'Workflow and Task Config.': 'docs/handbook/configuration.md' + - 'Environment Operation Order': 'docs/handbook/operation_order.md' + - 'Workflow Variables': '../controlworkflows' + - 'Component reference': + - 'AliECS GUI': 'hacking/COG.md' + - 'AliECS core': + - 'Integrated Services': 'core/integration/README.md' + - 'Protocol': 'docs/apidocs_aliecs.md' + - 'coconut': + - 'Overview': 'coconut/README.md' + - 'Command Ref.': 'coconut/doc/coconut.md' + - 'apricot': + - 'Overview': 'apricot/README.md' + - 'HTTP Service': 'apricot/docs/apricot_http_service.md' + - 'Protocol': 'docs/apidocs_apricot.md' + - 'Command Ref.': 'apricot/docs/apricot.md' + - 'occ': + - 'Overview': 'occ/README.md' + - 'Example': 'occ/occlib/examples/dummy-process/README.md' + - 'Protocol': 'docs/apidocs_occ.md' + - 'peanut': + - 'Overview': 'occ/peanut/README.md' + - 'Event Service': 'docs/kafka.md' diff --git a/occ/README.md b/occ/README.md index 6afa0dcb..8e173441 100644 --- a/occ/README.md +++ b/occ/README.md @@ -9,9 +9,9 @@ For stateful tasks that do not use FairMQ, the OCC interface is implemented by t ## Developer quick start instructions for OCClib 1. Build & install the OCC library either manually or via aliBuild (`Control-OCCPlugin`); -2. check out [the dummy process example](occlib/examples/dummy-process) and [its entry point](occlib/examples/dummy-process/main.cxx) and to see how to instantiate OCC; -3. implement interface at [`occlib/RuntimeControlledObject.h`](occlib/RuntimeControlledObject.h), -4. link your non-FairMQ O² process against the target `AliceO2::Occ` as described in [the dummy process README](occlib/examples/dummy-process/README.md#standalone-build). +2. check out [the dummy process example](https://github.com/AliceO2Group/Control/blob/master/occ/occlib/examples/dummy-process) and [its entry point](https://github.com/AliceO2Group/Control/blob/master/occ/occlib/examples/dummy-process/main.cxx) and to see how to instantiate OCC; +3. implement interface at [`occlib/RuntimeControlledObject.h`](https://github.com/AliceO2Group/Control/blob/master/occ/occlib/RuntimeControlledObject.h), +4. link your non-FairMQ O² process against the target `AliceO2::Occ` as described in [the dummy process README](https://github.com/AliceO2Group/Control/blob/master/occ/occlib/examples/dummy-process/README.md#standalone-build). ## Manual build instructions Starting from the `occ` directory. @@ -55,4 +55,4 @@ See [`peanut` Overview](peanut/README.md). ## OCC API debugging with `grpcc` -See [OCC API debugging with `grpcc`](/docs/using_grpcc_occ.md). \ No newline at end of file +See [OCC API debugging with `grpcc`](../docs/using_grpcc_occ.md). \ No newline at end of file diff --git a/occ/occlib/examples/dummy-process/README.md b/occ/occlib/examples/dummy-process/README.md index 516bcf2e..9324c791 100644 --- a/occ/occlib/examples/dummy-process/README.md +++ b/occ/occlib/examples/dummy-process/README.md @@ -2,11 +2,11 @@ This example is built from the top-level CMakeLists.txt when `BUILD_EXAMPLES` is true. -For instructions on running it, see [Run example](/occ/README.md#run-example). +For instructions on running it, see [Run example](../../../README.md#run-example). ## Standalone build -For guidelines on building the example as a standalone project, see [CMakeLists.txt.example](CMakeLists.txt.example). +For guidelines on building the example as a standalone project, see [CMakeLists.txt.example](https://github.com/AliceO2Group/Control/blob/master/occ/occlib/examples/dummy-process/CMakeLists.txt.example). Dependencies in aliBuild: From dac20d02c867708391d5c9d871d11b8d7ba9a8a9 Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Mon, 19 May 2025 14:35:21 +0200 Subject: [PATCH 7/7] [doc] linkspector should check only changed documentation lines Unfortunately this change will prevent us from discovering broken links in files which have a reference to the affected ones. However, checking too many links causes sometimes "Too Many Requests" error, so we would be getting some false positives. --- .github/workflows/linkspector.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linkspector.yml b/.github/workflows/linkspector.yml index 20999edf..75d76699 100644 --- a/.github/workflows/linkspector.yml +++ b/.github/workflows/linkspector.yml @@ -12,4 +12,4 @@ jobs: github_token: ${{ secrets.github_token }} reporter: github-pr-check fail_level: any - filter_mode: nofilter + filter_mode: added